/* =============================================================
   Site public — Cuisinaffaires
   -------------------------------------------------------------
   Fichier distinct de admin.css, et cela doit le rester : le
   back-office est dense et fonctionnel, le site public est
   editorial et aere. Les faire cohabiter obligerait a prefixer
   chaque regle et rendrait toute evolution risquee des deux cotes.

   NOTE SUR L'ACCENT — a lire avant de « corriger » la couleur.
   La palette impose #F16411. Cet orange donne 3,20:1 sur blanc ET
   blanc sur lui donne aussi 3,20:1 : les DEUX echouent au seuil AA
   de 4,5:1 du texte courant. --orange sert donc uniquement aux
   aplats, aux fonds de bouton, aux grands titres (>= 24px, ou
   >= 18,66px gras), aux icones et aux bordures — jamais au texte
   courant, jamais en texte blanc sur lui-meme.

   Un bouton orange porte du texte --encre (5,55:1), jamais blanc :
   ce projet a deja livre un bouton a 1,00:1 (voir admin.css) et ne
   tolere pas un huitieme manquement de ce type.

   Le petit texte orange (liens, prix, libelles < 24px ou < 18,66px
   gras) emploie --orange-texte (#C25408, 4,60:1) — MAIS uniquement
   pose sur --surface ou --fond, tous deux BLANCS aujourd'hui. Sur
   --orange-clair il tombe a 4,16:1 et echoue. La regle appliquee dans ce
   fichier : tout texte orange sur --orange-clair passe a --encre, jamais a
   --orange-texte. (Cette interdiction a aussi vise --fond chaque fois qu'il
   a ete teinte : 4,40:1 en #FAFAF9, 4,11:1 en #F1F2F5.)
   Les liens generiques (<a> sans classe) heritent donc de --encre
   par defaut, avec le seul soulignement au survol comme retour
   visuel — leur fond reel (surface ou fond) n'est jamais garanti a
   l'echelle du selecteur global.

   Survoler un bouton plein orange ASSOMBRIT --orange vers le noir :
   comme --encre est deja tres sombre, assombrir REDUIT le contraste
   texte/fond au lieu de l'ameliorer (verifie : -6% de luminosite
   suffit a repasser sous 4,5:1). --orange-survol ECLAIRCIT donc
   l'aplat au lieu de l'assombrir (6,33:1 avec --encre, verifie).

   RATIOS DE CONTRASTE VERIFIES (calcules, pas estimes) :
     --orange        / --surface         :  3,20:1  <- grands textes/composants SEULEMENT
     blanc           / --orange          :  3,20:1  <- jamais de texte, echoue pareil
     --orange        / --fond            :  3,20:1  <- identique a --surface, --fond etant blanc. ATTENTION : ce chiffre
                                                        depend entierement de cette valeur -- il tombait a 2,85:1 quand le
                                                        fond etait teinte en gris, sous meme le seuil 3:1.
     --orange        / --orange-clair    :  2,89:1  <- ECHOUE meme le seuil 3:1 composant : jamais de bordure --orange sur --orange-clair
     --orange-texte  / --surface         :  4,60:1  <- petit texte, UNIQUEMENT sur --surface
     --orange-texte  / --fond            :  4,60:1  <- passe, --fond etant blanc. Meme avertissement : 4,40:1 en #FAFAF9,
                                                        4,11:1 en #F1F2F5 -- toute teinte du fond fait echouer ce couple.
     --orange-texte  / --orange-clair    :  4,16:1  <- ECHOUE le seuil texte 4,5:1 (idem) MAIS PASSE le seuil composant 3:1 :
                                                        c'est le choix retenu pour une bordure --orange posee sur --orange-clair
                                                        (ex. .btn--fantome:hover) -- jamais pour du texte.
     --bordure       / --surface         :  1,26:1  <- separateur decoratif (cartes, panneaux, pagination) : fond/ombre/
                                                        espacement portent deja l'information ailleurs, non concerne.
                                                        RELECTURE FINALE, CORRIGE : cette exemption etait affirmee "n'est
                                                        JAMAIS le seul indicateur d'une limite de composant" -- FAUX. Un
                                                        .champ__saisie pose dans une .carte a un remplissage IDENTIQUE au
                                                        fond (1,00:1, ni ombre ni espacement distinctif) : --bordure y
                                                        etait bien le seul repere, sous le seuil composant WCAG de 3:1.
                                                        Voir --bordure-controle ci-dessous, introduit pour ces cas la
                                                        exclusivement (champs de saisie, panier__quantite/produit__quantite
                                                        inclus). Meme constat qu'avant ce commit (1,24:1) pour les usages
                                                        reellement decoratifs, qui restent inchanges.
     --bordure-controle / --surface      :  3,48:1  <- NOUVEAU jeton (relecture finale) : limite d'un champ de saisie --
                                                        les cas ou --bordure etait de facto le seul repere. --texte-doux
                                                        (6,38:1) aurait ete visuellement trop lourd pour une bordure de
                                                        1-2px.
     --bordure-controle / --fond         :  3,48:1  <- identique a --surface, les deux etant #FFFFFF
     --orange-survol / --encre           :  6,33:1  <- survol/pression des boutons pleins
     --encre         / --surface         : 17,74:1
     --encre         / --fond            : 17,74:1  <- identique a --surface
     --encre         / --orange          :  5,55:1  <- texte de bouton orange
     --encre         / --orange-clair    : 16,05:1
     --orange        / --encre-profonde  :  5,89:1  <- icones/texte orange sur le PIED de page (fond sombre).
                                                          L'en-tete ne figure plus ici : il est passe sur --surface
                                                          (voir la note de ".entete"), ses couples sont ceux du
                                                          fond clair listes plus bas.
     blanc           / --encre-profonde  : 18,83:1
     #434343         / --surface         :  9,89:1  <- le mot du logo fourni (LOGO-SITE.png, deux teintes :
                                                          #434343 et #E85725). C'est CE ratio qui a decide du
                                                          passage de l'en-tete en clair : sur --encre-profonde,
                                                          le meme #434343 ne donne que 1,90:1 -- invisible.
     #E85725         / --surface         :  3,61:1  <- l'accent du logo. Sous 4,5:1, et c'est sans consequence :
                                                          il ne porte aucune information, le mot voisin a 9,89:1
                                                          dit tout. Le nom accessible du lien vient de son "alt".
     --encre         / --surface         : 17,74:1  <- liens de l'en-tete clair (menu, panier, bascule) --
                                                          meme paire que le corps de page, deja listee plus haut.
     --orange-texte  / --surface         :  4,60:1  <- leur survol, et l'etat actif du menu. C'est --orange-texte
                                                          et non --orange (3,20:1 sur --surface, sous le seuil
                                                          texte) : la regle s'inverse par rapport au fond sombre,
                                                          ou c'etait --orange qui convenait.
     --orange        / --encre-elevee    :  4,87:1
     blanc           / --encre-elevee    : 15,57:1  <- .agrandissement__fermer (icone de fermeture)
     --texte-doux    / --surface         :  6,38:1  (deja valide, ne pas eclaircir)
     --texte-doux    / --fond            :  6,38:1  <- identique a --surface
     --texte-doux    / --fond-doux       :  5,60:1  <- pastille de marque, cadre de vignette, survol de ligne
     --fond-doux     / --surface         :  1,14:1  <- remplissage discret, decoratif : presque le triple de l'ecart
                                                        que --fond/#FAFAF9 donnait (1,04:1), donc mieux visible qu'avant
     badge promo/rupture                 :  6,36:1  (--danger sur #FEF2F2, inchange)
     badge nouveau/stock                 :  5,21:1  (--succes sur #ECFDF5, inchange)
     badge commande                      :  4,84:1  (--alerte sur #FFFBEB, inchange)
     pied de page (texte)                : 15,27:1  (--sombre-texte sur --encre-profonde)
     pied de page (survol)               :  5,89:1  (--orange sur --encre-profonde -- refonte tache 2 :
                                                          remplace le blanc-sur-survol d'origine, 18,83:1,
                                                          par un vrai accent de couleur au lieu d'un simple
                                                          eclaircissement, cf. brief "survol en --orange")
     --orange-texte  / --orange           :  1,44:1  <- PIEGE DE SPECIFICITE CORRIGE : sans ".pagination__lien--actif:hover",
                                                          ".pagination__lien:hover" (0,2,0) ecrasait ".pagination__lien--actif"
                                                          (0,1,0) et affichait ce couple sur le lien de page courante survole.
     --danger        / #FEF2F2 / #ECFDF5 / #FFFBEB (opacity .6 composee) : 2,44 a 3,07:1 selon la variante d'alerte, ECHOUE le
                                                          seuil 3:1 -- .alerte__fermer est un bouton fonctionnel, pas un controle
                                                          inactif exempte. Corrige : couleur pleine au repos (4,84 a 6,36:1, voir
                                                          ci-dessus), --encre au survol au lieu de l'opacite (>= 16:1, non liste
                                                          separement, encre est deja documente comme toujours sur).
     PIEGE DE SPECIFICITE (pas un echec de ratio, mais une couleur d'etat qui disparait) : sans
       ".champ__saisie--erreur:focus", ".champ__saisie:focus" (0,2,0) ecrasait la bordure --danger de
       ".champ__saisie--erreur" (0,1,0) par --orange des que le champ recevait le focus. Corrige : la
       bordure --danger est conservee au focus, seul l'anneau change de teinte (--danger-halo).
     --sombre-texte      / --encre-profonde : 15,27:1  <- texte/liens du pied de page et du tiroir du panier.
                                                            L'en-tete partageait ce couple jusqu'a son passage
                                                            en clair ; il ne le porte plus.
     --sombre-texte-doux / --encre-profonde : 12,68:1  <- meta/texte secondaire du pied (sa ligne de bas). Le
                                                            placeholder du champ de recherche, second usage cite
                                                            ici jusqu'au retrait de ce champ, n'existe plus.
     --danger        / --surface         :  6,96:1  <- .btn--danger au repos (texte + bordure) -- refonte tache 3
     blanc           / --danger          :  6,96:1  <- .btn--danger au survol (fond plein) -- meme paire, ratio symetrique
     --texte-doux    / --bordure         :  5,08:1  <- .btn:disabled / [aria-disabled] -- couleur EXPLICITE, jamais opacity
                                                          (deja au-dessus du seuil texte alors qu'un controle desactive en
                                                          est exempt par WCAG 1.4.3) -- refonte tache 3
     --encre         / --bordure         : 14,13:1  <- .alerte__fermer:hover (fond+icone) -- refonte tache 3
     PIEGE DE SPECIFICITE CORRIGE (refonte en-tete/pied, tache 2) : en mobile, ".nav__element:hover >
       .megamenu { display:none; }" (deux classes + un pseudo-classe, 0,3,0) ecrasait TOUJOURS
       ".megamenu--ouvert { display:block; }" (une classe, 0,1,0) quel que soit l'ordre des regles --
       3 > 1 dans tous les cas. Sur WebKit/Safari mobile, un tap sur le declencheur ne lui donne pas le
       focus (:focus-within ne s'applique donc pas) mais laisse un :hover "colle" sur .nav__element : le
       clic ouvrait bien .megamenu--ouvert cote JS (aria-expanded="true"), mais cette regle de survol,
       plus specifique, forcait quand meme display:none -- le panneau restait invisible. Chromium/Firefox
       masquaient le defaut car un vrai pointeur en survol y coexiste rarement avec un focus manquant.
       Cette regle de fermeture au survol n'avait de toute facon aucun effet utile en mobile : le panneau
       est deja masque par defaut via ".megamenu { display:none; }" (meme specificite que
       ".megamenu--ouvert", tranchee par l'ordre des regles) -- son seul effet observable etait donc de
       COMBATTRE l'etat ouvert pilote par le clic, jamais de fermer quoi que ce soit d'ouvert par le
       survol lui-meme (aucune regle CSS n'ouvre le panneau au survol en dessous de 1024px). Corrige en
       la retirant purement et simplement : sous 1024px, la visibilite du panneau ne depend plus que de
       ".megamenu--ouvert" (clic, pilote par front.js) et de ":focus-within" (clavier, contrat n°5 --
       jamais retiree). Plus aucune regle de survol dans ce bloc, donc plus aucun conflit de specificite
       possible avec l'etat pose par le script.

   LE BLOC PRIX EST POSE SUR --surface, et il doit le rester : son texte
   utilise --orange-texte, qui passe a 4,60:1 sur du blanc mais echoue des
   que le fond porte la moindre teinte (4,40:1 en #FAFAF9, 4,11:1 en
   #F1F2F5). Le fond de page etant blanc aujourd'hui, la contrainte est
   sans effet -- elle redeviendrait mordante au premier reteintage.
   Le prix de la carte est passe a 20px gras, donc au-dessus du seuil des
   18,66px gras qui autoriserait --orange direct a 3,20:1 — on garde
   neanmoins --orange-texte et sa marge (voir .carte-produit__prix-actuel).
   Le prix de la fiche produit (30px gras) emploie --orange directement.
   ============================================================= */

@font-face {
    font-family: 'Inter';
    src: url('/assets/fonts/inter-400.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Inter';
    src: url('/assets/fonts/inter-500.woff2') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Inter';
    src: url('/assets/fonts/inter-600.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Sora';
    src: url('/assets/fonts/sora-600.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Sora';
    src: url('/assets/fonts/sora-700.woff2') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
}

:root {
    /* Couleurs — voir le tableau de contraste en tete de fichier. */
    --orange:        #F16411;   /* aplats, fonds de bouton, grands titres, icones, bordures */
    --orange-texte:  #C25408;   /* petit texte orange — UNIQUEMENT sur --surface, voir note */
    --orange-survol: #F3772E;   /* survol/pression des boutons pleins (eclaircit, n'assombrit pas) */
    --orange-clair:  #FFF1E8;   /* fonds de badge, survols discrets — non-texte */
    --orange-halo:   rgba(241, 100, 17, .18); /* halo de focus translucide — non-texte */
    --danger-halo:   rgba(176, 27, 27, .18); /* halo de focus d'un champ en erreur — non-texte */

    --encre:              #111827;  /* texte courant, texte sur bouton orange */
    --encre-profonde:     #0B1120;  /* en-tete, pied de page, tiroir */
    --encre-elevee:       #1B2434;  /* cartes/elements poses sur fond sombre */
    --encre-profonde-rgb: 11, 17, 32;

    /* Texte/liens sur fond sombre (en-tete ET pied, tous deux --encre-profonde
       depuis la refonte tache 2) -- voir le tableau de contraste en tete de
       fichier. Memes valeurs que le pied de page utilisait deja en dur avant
       ce commit, desormais nommees et partagees avec l'en-tete. */
    --sombre-texte:      #E2E8F0;  /* 15,27:1 sur --encre-profonde */
    --sombre-texte-doux: #CBD5E1;  /* 12,68:1 sur --encre-profonde */

    /* LE FOND DE PAGE EST BLANC, et c'est une decision arretee du
       proprietaire, pas un defaut a corriger. Trois valeurs ont ete
       essayees et deux refusees :
         #FAFAF9 -- blanc casse CHAUD, juge terne.
         #F1F2F5 -- gris froid, qui detachait bien les cartes (1,12:1) mais
                    a ete refuse net : « je ne veux pas de gris dans le fond
                    du site ».
         #FFFFFF -- retenu.
       Consequence assumee : une carte blanche ne se distingue plus du fond
       par sa couleur. Elle le fait par son OMBRE et sa BORDURE -- verifier
       que les deux sont bien la avant d'ajouter un bloc. Et pour un panneau
       qui doit vraiment se detacher, la teinte a employer est --orange-clair
       (voir .panneau-texte), jamais un gris.

       CE JETON COMMANDE DEUX INTERDICTIONS. Tant qu'il vaut du blanc,
       --orange-texte y passe (4,60:1) ; toute teinte le fait echouer
       (4,11:1 en #F1F2F5, 4,40:1 en #FAFAF9). Le reteindre un jour obligera
       donc a relire chaque texte orange pose a nu sur la page. */
    --fond:        #FFFFFF;
    --surface:     #FFFFFF;
    /* Remplissage discret POSE SUR UNE CARTE : pastille de marque, cadre de
       vignette du tiroir, survol de ligne de tableau. Il ne peut pas valoir
       --fond, qui est blanc : ces elements disparaitraient. Teinte CHAUDE,
       accordee au reste de la palette (--bordure #E7E5E4, --orange-clair
       #FFF1E8) plutot qu'un gris froid. 1,14:1 sur du blanc, --texte-doux
       5,60:1, --encre 15,58:1. */
    --fond-doux:   #F1F0EE;
    --texte-doux:  #556070;   /* 6,38:1 sur --surface ET sur --fond, tous deux blancs */
    --bordure:     #E7E5E4;
    /* Relecture finale : limite d'un champ de saisie/zone de depot/piste de
       bascule -- 3,48:1 sur --surface comme sur --fond, les deux valant
       desormais #FFFFFF (c'etait 3,33:1 sur l'ancien --fond, voir le tableau en
       tete de fichier). --bordure seule n'y suffit pas (1,26:1) : ce n'est
       jamais le seul indicateur pour un separateur decoratif, mais ca
       L'ETAIT pour un champ pose sans ombre ni espacement distinctif. */
    --bordure-controle: #7C8B9D;

    --succes:      #047857;
    --alerte:      #B45309;
    --danger:      #B01B1B;

    /* Echelle typographique fluide : clamp() entre ~400px et ~1280px,
       aucun saut de palier a un breakpoint. Corps de texte fixe a
       16px (seuil mobile), 65-75 caracteres de mesure sur le texte
       courant via --mesure. */
    --texte-xs:   .75rem;
    --texte-sm:   .875rem;
    --texte-base: 1rem;
    --texte-lg:   1.125rem;
    --texte-xl:   clamp(1.25rem, 1.16rem + .4vw, 1.5rem);
    --texte-2xl:  clamp(1.5rem, 1.3rem + .9vw, 1.875rem);
    --texte-3xl:  clamp(1.75rem, 1.35rem + 1.9vw, 2.375rem);
    --texte-4xl:  clamp(2rem, 1.4rem + 2.7vw, 2.75rem);
    --mesure:     70ch;

    /* Espacement : echelle en jetons, base 4px — pas de valeurs
       magiques dispersees dans les regles. */
    --espace-1: .25rem;
    --espace-2: .5rem;
    --espace-3: .75rem;
    --espace-4: 1rem;
    --espace-5: 1.5rem;
    --espace-6: 2rem;
    --espace-7: 3rem;
    --espace-8: 4rem;

    --r-s: 8px; --r-m: 12px; --r-l: 16px; --r-plein: 999px;

    /* Elevation nette et dense : ombres courtes et contrastees,
       pas de grand flou pale. Trois niveaux. */
    --ombre-s: 0 1px 2px rgba(17, 24, 39, .14), 0 1px 1px rgba(17, 24, 39, .08);
    --ombre-m: 0 3px 6px rgba(17, 24, 39, .16), 0 2px 4px rgba(17, 24, 39, .10);
    --ombre-l: 0 10px 20px rgba(17, 24, 39, .22), 0 4px 8px rgba(17, 24, 39, .14);

    /* Mouvement : deux durees, deux courbes. Rapide+elastique pour
       les interactions (survol, pression), normale+sortie pour les
       entrees (menus, tiroirs, agrandissements). */
    --duree-rapide:     160ms;
    --duree-normale:    280ms;
    --easing-sortie:    cubic-bezier(.16, 1, .3, 1);
    --easing-elastique: cubic-bezier(.34, 1.56, .64, 1);
    --transition:       var(--duree-rapide) var(--easing-elastique);

    /* Focus clavier unifie : anneau --orange, decale de la bordure.
       Verifie sur fond clair (3,20:1 sur --surface) ET sur fond
       sombre (5,89:1 sur --encre-profonde, en-tete/pied) — les deux
       au-dessus du seuil 3:1 des indicateurs de focus. */
    --focus-couleur:   var(--orange);
    --focus-epaisseur: 3px;
    --focus-decalage:  2px;

    /* z-index : echelle fermee, aucune valeur magique ailleurs. */
    --z-sticky:    10;
    --z-menu:      20;
    --z-evitement: 30;
    --z-survol:    50;

    /* Voiles semi-transparents des superpositions (agrandissement,
       tiroir du panier), derives de --encre-profonde. */
    --voile:       rgba(var(--encre-profonde-rgb), .92);
    --voile-leger: rgba(var(--encre-profonde-rgb), .5);

    --largeur: 1280px;
}

*, *::before, *::after { box-sizing: border-box; }

/* Defilement fluide (liens d'ancre : fil d'Ariane, evitement, "Voir tout"
   d'un panneau replie...) -- neutralise par le bloc prefers-reduced-motion
   en fin de fichier ("scroll-behavior: auto !important" sur "*", deja en
   place avant cette tache : elle anticipait deja cette regle). */
html { scroll-behavior: smooth; }

body {
    margin: 0;
    background: var(--fond);
    color: var(--encre);
    font-family: 'Inter', system-ui, sans-serif;
    font-size: 16px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { font-family: 'Sora', system-ui, sans-serif; font-weight: 700; line-height: 1.2; margin: 0; }
h1 { font-size: var(--texte-4xl); }
h2 { font-size: var(--texte-3xl); }
h3 { font-size: var(--texte-xl); font-weight: 600; }
h4 { font-size: var(--texte-lg); }

/* Lien generique (sans classe) : encre par defaut. Le fond reel derriere un
   <a> nu n'est jamais garanti -- --surface, --fond, --orange-clair ou une
   carte, selon le contexte -- et --orange-texte echoue sur --orange-clair
   (4,16:1). Le soulignement au survol reste donc le seul retour visuel ici,
   toujours sur et jamais teste au cas par cas. (Cette regle visait aussi
   --fond du temps ou il etait teinte ; il est blanc aujourd'hui, mais
   --orange-clair suffit a justifier la regle.)
   Les composants nommes plus bas, dont le fond est audite, peuvent
   se permettre --orange-texte. */
a { color: var(--encre); text-decoration: none; }
a:hover { text-decoration: underline; }

img { max-width: 100%; height: auto; display: block; }

:focus-visible {
    outline: var(--focus-epaisseur) solid var(--focus-couleur);
    outline-offset: var(--focus-decalage);
    border-radius: 2px;
}

.conteneur { width: 100%; max-width: var(--largeur); margin: 0 auto; padding: 0 var(--espace-4); }

/* Le lien d'evitement : premier element focusable de la page, il permet a un
   utilisateur au clavier de sauter le mega-menu. */
.evitement {
    position: absolute; left: -9999px;
    background: var(--surface); color: var(--encre);
    padding: var(--espace-3) var(--espace-4); border-radius: var(--r-s); z-index: var(--z-evitement);
}
.evitement:focus { left: var(--espace-4); top: var(--espace-4); }

/* ------------------------------------------------------ boutons */

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--espace-2);
    padding: .6875rem 1.25rem;
    border: 1px solid transparent; border-radius: var(--r-s);
    font: inherit; font-weight: 600; line-height: 1;
    cursor: pointer; text-decoration: none;
    min-height: 44px;
    transition: background-color var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition);
}
.btn:hover { text-decoration: none; }
/* Bouton plein : texte --encre, jamais blanc — blanc sur --orange
   n'atteint que 3,20:1 (voir note en tete de fichier). L'ombre au survol
   (pas un agrandissement : le referentiel proscrit les transformations
   d'echelle qui decalent la mise en page) donne le retour "tactile" que
   le brief demande sans deplacer un seul pixel voisin. */
.btn--primaire { background: var(--orange); color: var(--encre); }
.btn--primaire:hover { background: var(--orange-survol); color: var(--encre); box-shadow: var(--ombre-s); }
.btn--primaire:active { background: var(--orange); box-shadow: none; }
.btn--fantome { background: transparent; border-color: var(--bordure); color: var(--encre); }
/* La bordure ne peut pas rester --orange ici : sur --orange-clair (son
   propre fond au survol), --orange/--orange-clair ne donne que 2,89:1,
   sous le seuil 3:1 des composants. --orange-texte y donne 4,16:1. */
.btn--fantome:hover { background: var(--orange-clair); border-color: var(--orange-texte); color: var(--encre); }
/* Danger : reserve aux actions destructrices (vider le panier, retirer une
   ligne). Fantome au repos -- --danger/--surface, 6,96:1 (calcule, voir le
   rapport de tache) -- puis plein au survol : blanc sur --danger donne le
   MEME ratio 6,96:1 (la formule de contraste est symetrique). --danger
   n'est jamais assombri ni eclairci : il n'a, contrairement a --orange,
   qu'un seul palier a verifier. */
.btn--danger { background: transparent; border-color: var(--danger); color: var(--danger); }
.btn--danger:hover { background: var(--danger); border-color: var(--danger); color: #FFFFFF; box-shadow: var(--ombre-s); }
.btn--danger:active { box-shadow: none; }
/* Etat desactive : couleur EXPLICITE, jamais opacity (voir la note du
   brief -- une opacite composee avec le fond a deja fait tomber
   .alerte__fermer sous le seuil 3:1, voir le tableau en tete de fichier).
   --texte-doux/--bordure = 5,08:1 (calcule), confortablement au-dessus du
   seuil texte alors meme qu'un controle reellement disabled en est exempt
   (WCAG 1.4.3). aria-disabled couvre le meme motif qu'un <a>/<span> qui
   IMITE un bouton desactive sans l'attribut HTML natif. */
.btn:disabled, .btn[aria-disabled="true"] {
    background: var(--bordure); border-color: var(--bordure); color: var(--texte-doux);
    cursor: not-allowed; pointer-events: none; box-shadow: none;
}
.btn--bloc { width: 100%; }

/* ------------------------------------------------------ badges */

.badge {
    display: inline-block; padding: var(--espace-1) .625rem;
    border-radius: var(--r-plein);
    font-size: var(--texte-xs); font-weight: 600; line-height: 1.4;
    /* Ombre courte : lisibilite quand le badge est pose sur une photo (carte
       produit). Decorative uniquement -- le fond du badge reste OPAQUE
       (#FEF2F2/#ECFDF5/#FFFBEB), donc le contraste texte/fond deja calcule
       en tete de fichier n'est pas affecte par ce qu'il y a dessous. */
    box-shadow: 0 1px 2px rgba(17, 24, 39, .12);
}
.badge--promo    { background: #FEF2F2; color: var(--danger); }
.badge--nouveau  { background: #ECFDF5; color: var(--succes); }
/* Le destockage prend l'indigo, seule famille encore libre : le rouge est
   pris par « Promo » et le vert par « Nouveau », et un produit peut porter
   les trois a la fois. Les distinguer d'un coup d'oeil demande trois teintes
   franchement differentes.
   #3730A3 sur #EEF2FF : 8,88:1, calcule -- tres au-dessus du seuil de 4,5:1
   du petit texte. */
.badge--destockage { background: #EEF2FF; color: #3730A3; }
.badge--stock    { background: #ECFDF5; color: var(--succes); }
.badge--commande { background: #FFFBEB; color: var(--alerte); }
.badge--rupture  { background: #FEF2F2; color: var(--danger); }

/* ------------------------------------------------ en-tete */

/* Icones de l'en-tete et de la navigation : jeu coherent, dessine a la main,
   viewBox="0 0 24 24", stroke="currentColor" -- jamais d'emoji, jamais de
   ressource externe (CSP : img-src 'self' data: blob:, aucune icone
   distante). currentColor signifie que le contraste de chaque icone est
   EXACTEMENT celui du texte qui l'accompagne (deja calcule ci-dessous) :
   aucune paire supplementaire a verifier pour l'icone elle-meme. */
.entete svg[aria-hidden], .nav svg[aria-hidden], .megamenu svg[aria-hidden] {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex-shrink: 0;
}

/* EN-TETE CLAIR. Il etait sur --encre-profonde, comme le pied de page ; il
   est passe sur --surface, et la raison est mesuree, pas esthetique : le
   logo fourni par le proprietaire est un PNG a fond transparent en deux
   teintes, #434343 pour le mot et #E85725 pour l'accent. Sur
   --encre-profonde, #434343 ne donne que 1,90:1 -- le mot serait quasi
   invisible (seul l'accent orange serait passe, a 5,22:1). Sur --surface, le
   meme logo donne 9,89:1 sans retoucher le fichier.

   Le pied de page RESTE sombre : la page est ainsi cadree entre deux
   ancrages, et --sombre-texte / --encre-profonde y garde tout son sens.
   Aucun jeton n'a change de valeur -- seuls les couples employes ici ont
   change de cote (voir le tableau en tete de fichier).

   Bordure basse au repos, la ou l'en-tete sombre n'en avait pas : le fond
   de l'en-tete et celui de la page sont l'un et l'autre blancs, donc
   rigoureusement indistinguables sans un trait. INDISPENSABLE, pas
   decoratif -- la retirer effacerait la limite de l'en-tete. L'ombre de
   l'etat "defile" s'y ajoute. */
/* CADRE : l'en-tete ne court plus d'un bord a l'autre de la fenetre, il est
   borne a --largeur et centre, comme le contenu de la page.

   "max-width + margin auto" sans "width: calc(100% - N)", et c'est le seul
   reglage qui garde l'alignement exact avec le contenu :
     - au-dela de 1280px, le cadre commence a (100vw - 1280)/2 et son
       .conteneur interne ajoute son propre padding de 1rem -- le logo tombe
       donc exactement sur la meme verticale que le contenu de la page ;
     - en dessous, le cadre occupe toute la largeur et son .conteneur donne
       le meme 1rem que partout ailleurs.
   Une largeur en calc() aurait ajoute une marge laterale AU CADRE en plus du
   padding du .conteneur, decalant le logo de 16px par rapport au contenu sur
   tous les ecrans sous 1280px.

   Pas d'"overflow: hidden" pour arrondir : .megamenu est en position absolue
   et deborde SOUS l'en-tete -- il serait purement et simplement coupe. Le
   fond arrondi de .entete suffit, .nav n'ayant pas de fond propre. */
.entete {
    position: sticky; top: 0; z-index: var(--z-sticky);
    max-width: var(--largeur);
    margin: 0 auto;
    border-radius: 0 0 var(--r-l) var(--r-l);
    background: var(--surface);
    border-bottom: 1px solid var(--bordure);
    /* Une ombre DES LE REPOS, pas seulement une fois defile. Le bloc est
       arrondi en bas et pose sur un fond blanc : sans elle il ne se
       detachait de la page que par un filet de 1px, ce qui ne se voit
       pratiquement pas sur un telephone. Elle reste tres douce -- c'est un
       decollement, pas un relief -- et .entete--defile la renforce quand du
       contenu passe dessous. */
    box-shadow: 0 2px 12px -6px rgba(17, 24, 39, .22);
    transition: box-shadow var(--transition);
}
/* position: relative -- contexte de positionnement du mega-menu. Ce role
   revenait au ".conteneur" interne de .nav ; celle-ci vit desormais DANS
   cette barre, et y imbriquer un second .conteneur aurait double le padding
   lateral, decalant le menu de 16px par rapport au reste de la page. Les
   panneaux gardent donc une position horizontale unique, alignee sur le
   contenu -- exactement la propriete qui les empeche de deborder par la
   droite quand le declencheur est proche du bord (voir le piege 2/2 de
   .megamenu). */
.entete__barre {
    position: relative;
    display: flex; align-items: center; gap: var(--espace-4);
    min-height: 76px;
    transition: min-height var(--transition);
}
/* CENTRAGE REEL DU MENU. .entete__marque et .entete__actions partagent le
   MEME "flex: 1 1 0" : ils occupent donc exactement la meme largeur de part
   et d'autre, et la navigation -- a sa largeur naturelle -- tombe pile au
   milieu de la barre.
   Une premiere version donnait "flex: 1" a .nav seule : elle absorbait bien
   l'espace libre, mais son centre etait celui de l'espace RESTANT entre le
   logo et les actions, deux blocs de largeurs differentes. Mesure en
   navigateur : 42px de decalage a 1440px de large. Le centrage optique ne
   vient pas de la navigation, il vient de l'egalite de ses deux voisins.

   min-width sur .entete__marque volontairement laisse a "auto" (le defaut) :
   c'est ce qui empeche le logo d'etre compresse par une barre de navigation
   chargee -- c'est alors .nav, qui porte min-width: 0, qui cede. */
.entete__marque { flex: 1 1 0; display: flex; align-items: center; }
/* Groupe panier + bascule : ils restent solidaires a droite quand la
   navigation disparait sous 1024px, ou plus rien ne pousserait le panier. */
.entete__actions { flex: 1 1 0; display: flex; align-items: center; justify-content: flex-end; gap: var(--espace-2); }
/* Etat "defile" : classe posee par front.js sur window.scroll (voir son
   bloc "en-tete au defilement"). SANS JavaScript, .entete ne porte jamais
   cette classe -- l'en-tete reste simplement dans son etat de repos, deja
   complet et fonctionnel (contrainte du brief : "sans effet si le script ne
   tourne pas"). Plus dense (barre plus basse) et une ombre apparait pour se
   detacher du contenu qui defile dessous. */
.entete--defile .entete__barre { min-height: 58px; }
.entete--defile { box-shadow: var(--ombre-m); }

/* Le logo est desormais une IMAGE, plus un texte : les declarations de
   police, de couleur et de white-space qui vivaient ici n'avaient plus
   aucun effet et ont ete retirees. min-height:44px reste -- c'est la cible
   tactile du lien de retour a l'accueil, exigee par le brief.
   flex-shrink: 0 : le logo ne doit jamais etre compresse par le groupe
   panier/menu sur un ecran etroit ; c'est la barre qui doit se resserrer. */
.entete__logo {
    display: inline-flex; align-items: center; min-height: 44px;
    flex-shrink: 0;
    transition: opacity var(--transition);
}
/* display:block sur l'image : sans lui, elle reste "inline" et herite de la
   ligne de base du texte, qui ajoute ~4px sous elle -- le logo paraitrait
   decale vers le haut dans une barre par ailleurs centree.
   height fixe + width auto : le fichier fait 182x48, le ratio est conserve
   quelle que soit la hauteur choisie. */
.entete__logo-image { display: block; height: 34px; width: auto; }
/* Le survol ne peut plus se jouer en "color" (l'image ne l'herite pas) :
   opacite, la seule des deux voies que le brief autorise pour un survol
   ("couleur ou opacite, jamais une transformation"). .88 reste tres
   au-dessus de tout seuil de lisibilite -- ce n'est pas un indicateur
   d'etat, seulement un retour de survol, le lien restant identifiable par
   sa position et le curseur. */
.entete__logo:hover { opacity: .88; text-decoration: none; }

/* Le bloc ".recherche" (champ, icone, bouton) a ete SUPPRIME de ce fichier
   en meme temps que le formulaire d'entete.php : plus aucun selecteur ne le
   portait, le garder aurait fait 35 lignes de regles mortes. La note sur
   "min-width: 0" qui l'accompagnait disparait avec lui -- elle documentait
   un debordement horizontal a 375px cause par le bouton "Rechercher", qui
   ne peut plus se produire.

   C'est desormais .entete__panier qui porte "margin-left: auto" : sans lui,
   la barre n'aurait plus rien pour pousser le panier et le menu a droite, et
   les trois controles se colleraient au logo. */

/* Lien vers /panier (jamais un fragment, voir entete.php) : sans JavaScript
   il navigue reellement, avec JavaScript front.js l'intercepte pour ouvrir
   le tiroir a la place. Pas de bordure ici (voir la note du brief : le
   survol se joue en couleur/opacite, pas en transformation ni en cadre) --
   l'icone, le libelle et la pastille suffisent a signaler un objet cliquable.
   EN-TETE CLAIR : --encre / --surface, 17,74:1 (etait --sombre-texte /
   --encre-profonde, 15,27:1). Survol : --orange-texte / --surface, 4,60:1 --
   c'est bien --orange-texte et non --orange, qui ne donne que 3,20:1 sur
   fond clair, sous le seuil 4,5:1 d'un texte. La regle inverse s'appliquait
   sur fond sombre, ou c'etait --orange qui convenait. */
/* NOUVEAU MODELE : un disque a icone porte une pastille chiffree, au lieu de
   la ligne « icone + "Panier" + pastille "N article" ». La navigation ayant
   rejoint cette meme barre, un libelle en toutes lettres y prenait une place
   qui manque au menu -- et un panier a pastille se reconnait sans mot.
   Le libelle n'a pas disparu pour autant : il est passe en .evitement dans la
   vue, donc toujours lu par les lecteurs d'ecran (voir la note d'entete.php). */
.entete__panier {
    display: inline-flex; align-items: center;
    padding: 0;
    color: var(--encre);
    transition: color var(--transition);
}
.entete__panier:hover { text-decoration: none; }

/* 44px pleins : c'est CE disque qui porte la cible tactile, le lien
   n'ayant plus de libelle visible pour l'elargir. --fond-doux au repos
   (1,14:1, decoratif -- l'icone en --encre a 15,58:1 dessus porte seule
   l'information), --orange au survol (icone --encre : 5,55:1). */
.entete__panier-disque {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px;
    border-radius: var(--r-plein);
    background: var(--fond-doux);
    transition: background var(--transition), transform var(--transition);
}
.entete__panier:hover .entete__panier-disque { background: var(--orange); }
.entete__panier:active .entete__panier-disque { transform: scale(.94); }
.entete__panier-icone { width: 21px; height: 21px; }

/* Pastille chiffree en debord du disque. Texte --encre sur --orange :
   5,55:1 -- jamais de blanc sur orange (3,20:1, echoue), voir la note en
   tete de fichier. La bordure --surface la detache du disque quand celui-ci
   passe lui-meme en --orange au survol, sans quoi les deux aplats
   fusionneraient.
   Elle porte [data-panier-compte] et NON [data-panier-nombre] : ce dernier
   reste la phrase accordee (« 1 article »), invisible mais lue -- un « 1 »
   nu ne porte pas l'accord singulier/pluriel. front.js met les deux a jour. */
.entete__panier-pastille {
    position: absolute;
    top: -2px; right: -2px;
    min-width: 20px; height: 20px;
    display: flex; align-items: center; justify-content: center;
    padding: 0 5px;
    border: 2px solid var(--surface);
    border-radius: var(--r-plein);
    background: var(--orange); color: var(--encre);
    font-size: var(--texte-xs); font-weight: 700; line-height: 1;
}
/* Le "hidden" du HTML (panier vide) doit gagner : une regle d'auteur
   "display: flex" bat le "display: none" de la feuille du navigateur, quelle
   que soit sa specificite. Meme piege que .galerie[hidden] dans admin.css. */
.entete__panier-pastille[hidden] { display: none; }

/* Un vrai lien (<a href="#navigation-principale">), pas un <button> : sans
   JavaScript, le clic navigue vers l'ancre et ouvre le menu par :target
   (voir plus bas). front.js n'ajoute que la prevention du saut de
   defilement et la synchronisation de aria-expanded. Meme traitement que
   .entete__panier : pas de bordure, couleur + icone seules signalent le
   controle (--encre au repos, --orange-texte au survol, memes couples que
   son voisin depuis le passage de l'en-tete en clair). */
.entete__bascule {
    display: none;
    align-items: center; gap: var(--espace-2);
    padding: var(--espace-2) var(--espace-1);
    min-height: 44px;
    background: transparent; color: var(--encre);
    font: inherit; font-weight: 600; cursor: pointer;
    transition: color var(--transition);
}
.entete__bascule-icone { width: 22px; height: 22px; }
/* .entete__bascule:hover doit l'emporter sur la regle generique a:hover
   (elle-meme plus specifique que .entete__bascule seule) pour ne pas
   afficher un soulignement sur ce qui doit rester un bouton. */
.entete__bascule:hover { color: var(--orange-texte); text-decoration: none; }

/* ------------------------------------------------ navigation et mega-menu */

/* La navigation vit DANS la barre du logo, plus sur une seconde rangee. Le
   separateur "border-top" qui marquait la limite entre les deux rangees n'a
   donc plus d'objet en bureau ; il ne subsiste que sur mobile, ou le menu
   redescend sous la barre (voir le bloc max-width: 1023px).

   La navigation garde sa largeur NATURELLE (flex: 0 1 auto) : c'est
   l'egalite de .entete__marque et .entete__actions qui la centre dans la
   barre, pas une propriete qui lui serait propre (voir leur note).

   min-width: 0 : sans lui, un item flex a pour largeur minimale celle de son
   CONTENU, et une barre de navigation chargee (beaucoup de categories, plus
   la rubrique Marques) refuserait de se comprimer et pousserait la page en
   defilement horizontal. Meme piege que .admin__zone et l'ancien .recherche.
   C'est aussi ce qui fait ceder la navigation AVANT le logo quand la place
   manque : elle seule autorise une largeur inferieure a son contenu. */
.nav { flex: 0 1 auto; min-width: 0; display: flex; justify-content: center; }
/* Le contexte de positionnement du mega-menu est desormais .entete__barre
   (voir sa propre note) : ".nav > .conteneur", qui le portait, n'existe plus
   dans le balisage. Tous les panneaux partagent toujours la MEME position
   horizontale, alignee sur le contenu de la page -- la propriete qui les
   empeche de deborder par la droite (piege 2/2 de .megamenu). */
.nav__liste { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--espace-1); margin: 0; padding: 0; list-style: none; }

/* Lien de fermeture du menu mobile, en tete du panneau deploye. Necessaire
   car le bouton hamburger (lien vers #navigation-principale) ne peut que
   OUVRIR sans JavaScript : re-cliquer sur une ancre deja active ne
   provoque aucune navigation, donc aucune fermeture. Ce lien pointe vers
   un fragment different (#) pour desactiver :target. Masque hors du
   contexte mobile, ou le mecanisme ne s'applique pas. */
.nav__fermer {
    display: none; align-items: center; gap: var(--espace-2);
    min-height: 44px;
    color: var(--encre);
}
.nav__fermer-icone { width: 18px; height: 18px; }
.nav__fermer:hover { color: var(--orange-texte); text-decoration: none; }

.nav__lien {
    position: relative;
    display: flex; align-items: center; gap: .375rem;
    padding: .875rem var(--espace-4);
    color: var(--encre); font-weight: 500;
    /* Soulignement anime en fond degrade, jamais une transformation
       d'echelle (le brief l'exclut explicitement : une mise a l'echelle
       decalerait les elements voisins). background-size ne deplace ni ne
       redimensionne rien autour de lui. */
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: left calc(100% - 6px);
    background-size: 0 2px;
    transition: background-size var(--transition), color var(--transition), opacity var(--transition);
}
.nav__chevron { width: 14px; height: 14px; margin-left: -.125rem; }
/* CONSTAT DE RELECTURE FINALE : ".nav__liste { gap: var(--espace-1) }"
   (4px) < 5px de portee de l'anneau de focus global (outline 3px,
   outline-offset 2px) -- l'anneau d'un lien focalise recouvrait de 1px son
   voisin, meme defaut et meme remede deja appliques a TROIS listes
   equivalentes d'admin.css (.sidebar__lien, .pagination__lien,
   .onglets__bouton), jamais reportes ici. Anneau INTERIEUR
   (outline-offset negatif) plutot qu'agrandir l'ecart : garde la densite
   de la barre de navigation intacte. */
.nav__lien:focus-visible { outline-offset: -2px; }
/* --encre / --surface (repos, 17,74:1) et --orange-texte / --surface
   (survol/actif/ouvert, 4,60:1). L'en-tete etant repasse en clair, c'est
   --orange-texte qui reprend sa place : --orange ne donne que 3,20:1 sur
   --surface, sous le seuil 4,5:1 d'un texte. Le soulignement anime herite de
   currentColor, il vaut donc lui aussi --orange-texte -- au-dessus du seuil
   3:1 des indicateurs non-textuels, sans avoir a le declarer separement.
   Le declenchement par ".nav__element:hover" plutot que ".nav__lien:hover"
   seul permet au liseré de rester visible tant que la souris est sur le
   panneau du mega-menu ouvert, pas seulement sur le lien declencheur. */
.nav__element:hover > .nav__lien,
.nav__element:focus-within > .nav__lien,
.nav__lien--actif {
    color: var(--orange-texte);
    background-size: 100% 2px;
    text-decoration: none;
}

/* Le deploiement se fait en CSS : sans JavaScript, le menu reste utilisable.
   Le script n'ajoute que le pilotage clavier et le menu mobile. Panneau
   large et multi-colonnes (voir .megamenu__liste) plutot que l'ancienne
   liste etroite -- c'est ce qui doit "frapper" selon le brief. Le liseré
   --orange en border-top est le meme couple deja documente pour
   .btn--primaire (--orange / --surface, 3,20:1, seuil 3:1 des composants).

   PIEGE CORRIGE EN RELECTURE (1/2, colonnes) : une premiere version posait
   seulement min-width/max-width sur ce panneau en position:absolute, sans
   largeur explicite. Dans un conteneur a taille INTRINSEQUE (aucun "width"
   resolu), "grid-template-columns: repeat(auto-fill, minmax(...))" ne peut
   pas calculer combien de pistes tiennent -- Chromium et Firefox retombent
   tous les deux sur UNE seule colonne, quel que soit le nombre d'elements,
   et max-width restait donc du code mort, jamais atteint (verifie en
   navigateur avec 2 ET 10 sous-categories : meme panneau a 320px dans les
   deux cas). Corrige en donnant un "width" EXPLICITE (donc un contexte de
   distribution defini) par palier de nombre de sous-categories, choisi cote
   PHP (voir entete.php) plutot que par une fonction CSS intrinseque
   (fit-content()/max-content auraient reproduit exactement le meme piege,
   la taille du conteneur redevenant circulaire avec celle de sa grille).

   PIEGE CORRIGE EN RELECTURE (2/2, debordement horizontal) : elargir le
   panneau (correctif 1/2 ci-dessus, jusqu'a 680px) a mecaniquement AGGRAVE
   un debordement deja possible avant, jamais traite jusqu'ici -- ce panneau
   etait ancre en left:0 sur .nav__element (le <li> du declencheur), sans
   aucune protection de bord. Un declencheur proche du bord droit de la
   barre (le dernier, ou n'importe lequel si le nombre de categories de
   premier niveau grandit -- aucune limite cote back-office) pousse le
   panneau hors ecran des que declencheur_x + largeur_panneau depasse la
   largeur de la fenetre. Mesure avant correctif : jusqu'a 505px hors champ
   a 1024px de large avec 8 categories et 12 sous-categories sur la
   derniere. Corrige "par construction" (options du relecteur) plutot que
   par un cas particulier sur le dernier element (qui n'aurait pas couvert
   un declencheur intermediaire trop a droite avec assez de categories) : le
   panneau n'est plus ancre sur son propre declencheur .nav__element (qui
   perd son "position:relative", desormais inutile) mais sur ".nav >
   .conteneur" ci-dessus -- deja centre et borne a --largeur (1280px) comme
   le reste de la page. Tous les panneaux, quel que soit leur declencheur,
   partagent donc la MEME position horizontale (alignee au bord gauche du
   contenu de la page), jamais celle du declencheur -- un panneau de 680px
   au plus large tient toujours sous la largeur de bureau la plus etroite
   (1024px, juste avant la bascule mobile) avec une marge confortable. */
/* ANCRAGE : le panneau est centre SOUS SON DECLENCHEUR.
   Il etait ancre sur la barre entiere, tous les panneaux partageant la meme
   position horizontale -- un remede contre le debordement par la droite qui
   passait inapercu tant que la navigation commencait au bord gauche. Depuis
   qu'elle est CENTREE dans la barre, ce meme ancrage ouvrait les panneaux a
   l'extreme gauche, a plusieurs centaines de pixels de la rubrique cliquee :
   le menu paraissait casse.
   --decalage-x : la part horizontale de la translation, isolee en variable
   pour que les regles d'ouverture/fermeture n'aient pas a la redupliquer.
   --recadrage : pose par front.js quand un panneau deborderait du cadre (voir
   son bloc « recadrage des panneaux »). 0px et non 0 : calc() refuse
   d'additionner un nombre sans unite et une longueur. */
.megamenu {
    --decalage-x: -50%;
    --recadrage: 0px;
    position: absolute; top: 100%; left: 50%; z-index: var(--z-menu);
    padding: var(--espace-5);
    background: var(--surface);
    border: 1px solid var(--bordure); border-top: 3px solid var(--orange);
    border-radius: var(--r-l);
    box-shadow: var(--ombre-l);
    opacity: 0; visibility: hidden;
    transform: translate(calc(var(--decalage-x) + var(--recadrage)), -6px);
    /* Sortie plus rapide que l'entree. Au survol rapide de la barre (on
       quitte un declencheur pour un autre avant la fin de son animation),
       deux panneaux sont en fondu en meme temps et leurs contenus se
       superposent visiblement la ou ils se recouvrent.
       Ce recouvrement etait TOTAL, pixel pour pixel, du temps ou tous les
       panneaux partageaient la meme ancre ; depuis leur reancrage sous leur
       propre declencheur il n'est plus que PARTIEL -- le defaut est donc
       moindre qu'avant, mais la parade reste la meme et garde tout son sens. Un navigateur applique les proprietes de
       transition de l'etat VERS LEQUEL l'element bascule (verifie en
       navigateur reel, voir le rapport de tache) : cette regle-ci s'applique
       donc a la FERMETURE (retour vers ".megamenu" simple), la regle
       ouverte ci-dessous garde --duree-normale pour l'entree.
       REGRESSION DE FINITION CORRIGEE (relecture finale) : .01ms supprimait
       TOUTE animation de fermeture -- le menu se refermait d'un coup sec,
       le geste que le proprietaire teste en premier. 130ms (dans la
       fourchette 120-140ms demandee) rend un mouvement de sortie percu tout
       en restant nettement plus rapide que les 280ms d'entree -- assez
       court pour qu'un survol rapide d'un declencheur a l'autre laisse peu
       de temps de recouvrement. Verifie en navigateur reel (voir le rapport
       de correction) : le fondu croise ne reapparait pas -- la fenetre ou
       les DEUX panneaux depassent 50% d'opacite simultanement reste quasi
       nulle, la duree de fermeture plus courte que l'entree faisant chuter
       l'ancien panneau sous ce seuil avant que le nouveau ne l'atteigne. */
    transition: opacity 130ms var(--easing-sortie),
                transform 130ms var(--easing-sortie),
                visibility 130ms;
    /* Filet de securite pour une categorie a beaucoup de sous-categories :
       sans lui, un panneau plus haut que la fenetre (sous un en-tete
       COLLANT) rendrait ses derniers liens inatteignables -- ni la souris
       (le panneau continuerait sous le bas de l'ecran) ni le clavier (Tab
       les visiterait mais rien ne les ramenerait a l'ecran sans defilement
       du panneau lui-meme) n'y donneraient acces. ~8rem retranches a la
       hauteur de la fenetre couvre l'en-tete (barre + rangee de nav, ~9rem
       au repos) avec une marge -- mesure en navigateur avec 12
       sous-categories, voir le rapport de tache. */
    max-height: calc(100vh - 8rem);
    overflow-y: auto;
}
.nav__element:hover > .megamenu,
.nav__element:focus-within > .megamenu,
.megamenu--ouvert {
    opacity: 1; visibility: visible; transform: translate(calc(var(--decalage-x) + var(--recadrage)), 0);
    /* L'ENTREE reste pleinement animee (inchangee) : seule la sortie,
       ci-dessus, a ete raccourcie -- voir sa note. */
    transition: opacity var(--duree-normale) var(--easing-sortie),
                transform var(--duree-normale) var(--easing-sortie),
                visibility var(--duree-normale);
}

/* Largeur explicite par palier (voir la note de piege ci-dessus), posee
   cote PHP via une classe .megamenu--col-1/2/3 selon le nombre reel de
   sous-categories (entete.php) : 1 colonne jusqu'a 4 elements, 2 jusqu'a 8,
   3 au-dela -- aucune colonne vide, aucune ambiguite de dimensionnement
   CSS intrinseque. */
.megamenu--col-1 { width: 320px; }
.megamenu--col-2 { width: 520px; }
.megamenu--col-3 { width: 680px; }

/* .nav__element redevient l'ancre de son propre panneau. Il l'avait ete, puis
   avait perdu son "position: relative" quand les panneaux ont ete regroupes
   sur la barre pour eviter le debordement -- la centrage du menu a rendu ce
   regroupement intenable (voir la note d'ancrage de .megamenu). */
.nav__element { position: relative; }

/* GARDE SANS JAVASCRIPT contre le debordement lateral. Le premier et le
   dernier declencheur sont, par construction, les plus proches des bords :
   leur panneau s'aligne sur le bord correspondant plutot que de se centrer,
   ce qui suffit a couvrir le cas courant meme si le script ne tourne pas.
   Les declencheurs intermediaires, eux, sont recadres par front.js quand le
   nombre de categories rend leur panneau debordant -- un cas qui demande a la
   fois beaucoup de rubriques ET un panneau large. */
.nav__element:first-child > .megamenu { --decalage-x: 0px; left: 0; }
.nav__element:last-child > .megamenu { --decalage-x: 0px; left: auto; right: 0; }

/* Pose et retiree par front.js le temps de mesurer les panneaux (voir son
   bloc « recadrage des panneaux de menu »). Sans elle, la remise a zero qui
   precede la mesure serait ANIMEE, et les positions lues seraient celles
   d'une transition en vol -- donc fausses, avec pour consequence un panneau
   qui repart deborder. C'est le defaut qui a fait echouer une premiere
   version du recadrage.
   !important : la regle qu'elle doit neutraliser porte sur .megamenu (0,1,0)
   et sur trois selecteurs d'ouverture plus specifiques (0,3,0) -- aucun
   selecteur descendant raisonnable ne les battrait tous. */
.sans-transition-menu .megamenu { transition: none !important; }

.megamenu__liste { margin: 0; padding: 0; list-style: none; display: grid; gap: .125rem var(--espace-5); }
.megamenu__liste--col-1 { grid-template-columns: 1fr; }
.megamenu__liste--col-2 { grid-template-columns: repeat(2, 1fr); }
.megamenu__liste--col-3 { grid-template-columns: repeat(3, 1fr); }

/* Cible tactile 44px (contrainte globale du brief) : min-height plutot
   qu'une simple dependance au line-height + padding -- 41,6px mesures avant
   ce commit (padding .5rem + line-height hors "min-height" explicite),
   sous le seuil, dans le panneau de bureau ET dans l'accordeon mobile
   (meme classe, aucune regle de largeur mobile ne le concerne). Defaut
   preexistant (non introduit par la refonte), corrige ici puisque ce
   fichier est deja rouvert et que c'est le lien que le visiteur mobile doit
   atteindre. */
.megamenu__lien {
    display: flex; align-items: center;
    min-height: 44px;
    padding: var(--espace-2) var(--espace-3);
    border-radius: var(--r-s);
    color: var(--encre);
    transition: background-color var(--transition), color var(--transition);
}
/* Le fond de survol est --orange-clair et non --fond : celui-ci est blanc,
   un survol en --fond serait donc rigoureusement invisible.
   --encre sur --orange-clair donne 16,05:1 — tres large
   marge, et le rendu est plus marque que l'ancien survol gris. */
.megamenu__lien:hover { background: var(--orange-clair); color: var(--encre); text-decoration: none; }
/* CONSTAT DE RELECTURE FINALE : ".megamenu__liste { gap: .125rem ... }"
   (2px de rangee) < 5px de portee de l'anneau de focus global -- l'anneau
   d'un lien focalise recouvrait 3px de son voisin de rangee. Meme defaut,
   meme remede (anneau interieur) que ".nav__lien" ci-dessus et que les
   trois listes deja corrigees dans admin.css. Couvre aussi
   ".megamenu__lien--voir-tout" (memes deux classes sur le meme <a>, pas de
   regle separee necessaire). */
.megamenu__lien:focus-visible { outline-offset: -2px; }
.megamenu__vide { padding: var(--espace-2) var(--espace-3); color: var(--texte-doux); font-size: var(--texte-sm); }

/* Le lien "Voir tout" (contrat n°4, jamais retire) : toujours le premier de
   la liste, mis en avant sur toute la largeur du panneau plutot que noye
   dans la grille des sous-categories. --orange-texte / --surface, 4,60:1,
   deja documente (petit texte, sur --surface uniquement). */
/* PLEINE LARGEUR : la regle porte sur le <li>, seul enfant de la grille.
   Elle vivait sur le <a> -- ou « grid-column » ne veut rien dire, le lien
   n'etant pas un element de grille. Le « Voir tout » occupait donc UNE
   cellule sur deux, la premiere sous-categorie se rangeait a sa droite sur
   la meme rangee, et le trait de separation ne courait que sur la moitie du
   panneau. L'intention etait pourtant ecrite depuis le debut, dans le
   commentaire de entete.php comme ici. */
.megamenu__element--tout { grid-column: 1 / -1; }

.megamenu__lien--voir-tout {
    justify-content: space-between; gap: var(--espace-2);
    margin-bottom: var(--espace-2); padding-bottom: .875rem;
    border-bottom: 1px solid var(--bordure);
    color: var(--orange-texte); font-weight: 600;
}
/* Meme specificite que ".megamenu__lien:hover" (une classe + :hover,
   0,2,0 de part et d'autre) : place APRES elle dans le fichier, cette regle
   l'emporte a l'ordre des regles pour les elements qui portent les deux
   classes. --encre / --surface, 17,74:1, deja documente. */
.megamenu__lien--voir-tout:hover { background: transparent; color: var(--encre); }
.megamenu__fleche { width: 16px; height: 16px; }

/* --------------------------------------- bandeau de page (catalogue) */

/* Meme famille visuelle que la banniere d'accueil -- fond --encre-profonde et
   halo orange -- mais SANS photo : un aplat, donc des contrastes fixes et
   verifiables une fois pour toutes, la ou la banniere a demande une mesure
   bande par bande. Blanc sur --encre-profonde : 18,83:1.

   Il sert les QUATRE pages de cette vue (catalogue, categorie,
   sous-categorie, marque, recherche) : c'est le controleur qui fournit
   l'intitule, le chapeau et le visuel, jamais la vue qui devine. */
/* PHOTOGRAPHIE EN ARRIERE-PLAN, sous un voile, comme la banniere de
   l'accueil -- meme construction, meme ordre de couches, pour que les deux
   bandeaux du site se ressemblent.

   L'ORDRE COMPTE, et c'est un piege deja rencontre : dans une propriete
   "background" multi-couches, la PREMIERE listee est peinte AU-DESSUS. Le
   degrade vient donc en premier (c'est lui qui assombrit), l'image ensuite,
   et --encre-profonde en dernier -- ce dernier reste le fond si l'image ne
   charge pas : le bandeau est alors uni, jamais clair, et le texte blanc
   reste lisible.

   Le voile est plus dense a GAUCHE (.93) qu'a droite (.50) : le titre et le
   chapeau vivent a gauche, les appareils eclaires de la photo a droite.

   MESURE, PAS ESTIMEE. Le bandeau rendu a ete capture en production puis
   decoupe en 24 colonnes, et le rapport du blanc calcule sur le PIRE pixel
   de chaque colonne -- pas sur sa moyenne : un reflet metallique de
   quelques pixels suffit a rendre une lettre illisible, et une moyenne le
   noierait. Sous la zone de texte, le pire vaut 4,81:1, au-dessus du seuil
   de 4,5:1 du petit texte ; la majeure partie est a 18:1.

   Deux pieges rencontres en mesurant, notes ici pour la prochaine fois :
   il faut MASQUER le texte avant de capturer (sinon on mesure les lettres
   blanches elles-memes, et tout ressort a 1,00:1), et ECARTER les bords
   (la capture d'un element a coins arrondis laisse voir la page blanche
   dans les coins, ce qui donne un faux 1,00:1). */
.entete-catalogue {
    position: relative;
    overflow: hidden;
    margin: var(--espace-4) 0 var(--espace-6);
    padding: clamp(1.75rem, 4vw, 2.75rem);
    border-radius: var(--r-l);
    background:
        linear-gradient(100deg,
            rgba(11, 17, 32, .93) 0%,
            rgba(11, 17, 32, .86) 45%,
            rgba(11, 17, 32, .62) 78%,
            rgba(11, 17, 32, .50) 100%),
        url('../img/bandeau.webp') center / cover no-repeat,
        var(--encre-profonde);
    color: #FFFFFF;
}
/* Halo decoratif, cale a droite comme celui de la banniere. Aucun texte n'y
   est pose : le contenu est borne a gauche par son propre max-width. */
.entete-catalogue::before {
    content: '';
    position: absolute;
    right: -6%;
    top: 50%;
    transform: translateY(-50%);
    width: 26rem;
    aspect-ratio: 1;
    background: radial-gradient(circle,
        rgba(241, 100, 17, .42) 0%,
        rgba(241, 100, 17, .14) 45%,
        rgba(241, 100, 17, 0) 70%);
    pointer-events: none;
}
/* Au-dessus du halo : cree apres dans l'ordre du document, il se peindrait
   sinon par-dessus le texte. */
.entete-catalogue__contenu {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--espace-5);
    flex-wrap: wrap;
}

/* TUILE BLANCHE sous le visuel, et ce n'est pas un ornement : les logos de
   marque sont dessines pour du papier blanc, et ce bandeau est sombre. Sans
   elle, un logo a encre foncee y serait illisible -- exactement le defaut
   mesure a 1,90:1 sur le logo du site avant le passage de l'en-tete en clair.
   Taille FIXE : les logos ont des proportions quelconques, object-fit les
   loge sans en deformer aucun. */
.entete-catalogue__visuel {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 96px;
    height: 96px;
    padding: var(--espace-3);
    border-radius: var(--r-m);
    background: var(--surface);
    box-shadow: var(--ombre-m);
}
.entete-catalogue__visuel img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }

/* min-width: 0 -- sans lui, un intitule long empeche l'item flex de
   retrecir sous sa largeur intrinseque et pousse la page en debordement. */
.entete-catalogue__texte { min-width: 0; flex: 1 1 18rem; }
/* AUCUN font-size ici : c'est un <h1>, il prend donc celui de h1
   (--texte-4xl) comme tous les autres titres de page du site. Il portait
   --texte-3xl -- la taille des h2 -- et etait le SEUL h1 du site a ne pas
   suivre la charte, ce qui se voyait en passant de l'accueil au catalogue. */
.entete-catalogue__titre { margin: 0; color: #FFFFFF; }
/* --sombre-texte sur --encre-profonde : 15,27:1. */
.entete-catalogue__chapeau {
    margin: var(--espace-3) 0 0;
    max-width: var(--mesure);
    color: var(--sombre-texte);
    font-size: var(--texte-lg);
}
/* Pastille de compte. --orange sur --encre-profonde : 5,89:1, et le fond
   translucide qu'elle porte ne compose que de l'orange sur du sombre --
   il ECLAIRCIT donc a peine, sans jamais descendre sous ce ratio. */
.entete-catalogue__compte {
    display: inline-block;
    margin: var(--espace-4) 0 0;
    padding: .25rem var(--espace-3);
    border-radius: var(--r-plein);
    background: rgba(241, 100, 17, .16);
    color: var(--orange);
    font-size: var(--texte-sm);
    font-weight: 700;
}

/* ------------------------------------------------ fil d'Ariane */

/* Piste de pastilles a chevrons. Le fil etait une suite de mots separes par
   des barres obliques : lisible, mais rien n'y designait l'etape courante
   autrement qu'une graisse un peu plus forte, et les liens mesuraient 17px
   de haut -- tres sous le seuil tactile de 44px, un constat deja releve en
   relecture et laisse en l'etat faute de place. Les pastilles resolvent les
   deux : elles donnent une vraie cible et distinguent l'etape courante par
   sa couleur de fond, pas par un detail de graisse. */
.ariane { padding: var(--espace-3) 0; font-size: var(--texte-sm); }
.ariane__liste {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Gouttiere de 2px seulement : ce sont les pastilles qui portent
       l'espacement, par leur propre padding. Un gap large les detacherait
       les unes des autres et casserait la lecture en « piste ». */
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.ariane__element { display: flex; align-items: center; }

/* min-height 44px : la cible tactile exigee par le brief, enfin atteinte
   ici. Le fond reste transparent au repos -- une piste de pastilles toutes
   pleines ferait concurrence au titre de la page juste en dessous. */
.ariane__lien {
    display: inline-flex;
    align-items: center;
    gap: var(--espace-2);
    min-height: 44px;
    padding: 0 var(--espace-3);
    border-radius: var(--r-plein);
    /* --texte-doux sur --surface : 6,38:1. */
    color: var(--texte-doux);
    transition: background var(--transition), color var(--transition);
}
/* Au survol, la pastille se remplit : --encre sur --fond-doux, 15,58:1.
   text-decoration: none assume -- le changement de FOND est un indicateur
   non textuel autrement plus visible qu'un soulignement, et il ne repose
   pas sur la seule couleur. */
a.ariane__lien:hover { background: var(--fond-doux); color: var(--encre); text-decoration: none; }
/* Anneau INTERIEUR : la gouttiere ne fait que 2px, un anneau exterieur de
   5px de portee recouvrirait la pastille voisine. Meme remede que
   .nav__liste et .megamenu__liste. */
a.ariane__lien:focus-visible { outline-offset: -2px; }

/* L'etape courante : pastille pleine en --orange-clair, texte --encre,
   16,05:1. Surtout PAS --orange-texte dessus, qui n'y donne que 4,16:1 --
   sous le seuil d'un texte de cette taille (voir la note en tete). */
.ariane__lien--courant {
    background: var(--orange-clair);
    color: var(--encre);
    font-weight: 600;
    cursor: default;
}

.ariane__icone { width: 15px; height: 15px; flex-shrink: 0; }

/* Chevron dessine en bordures plutot qu'avec un caractere « › » : son
   rendu ne depend alors d'aucune police, et il reste net a toute taille.
   --bordure-controle (3,48:1 sur --surface) et non --bordure (1,26:1) :
   c'est un separateur decoratif, mais autant qu'il se voie.
   flex-shrink: 0 -- sans lui, un libelle long ecrase le chevron a zero et
   les deux etapes se collent. */
.ariane__element:not(:last-child)::after {
    content: '';
    flex-shrink: 0;
    width: 6px;
    height: 6px;
    margin: 0 2px;
    border-top: 2px solid var(--bordure-controle);
    border-right: 2px solid var(--bordure-controle);
    transform: rotate(45deg);
}

/* ------------------------------------------------ icones du corps de page */

/* Meme jeu de regles que .entete/.nav/.megamenu (voir plus haut) : dessinees
   a la main, viewBox="0 0 24 24", stroke="currentColor" -- jamais d'emoji,
   jamais de ressource externe. currentColor signifie que le contraste de
   chaque icone est EXACTEMENT celui du texte qui l'accompagne (deja
   calcule dans le tableau en tete de fichier) : aucune paire supplementaire
   a verifier pour l'icone seule. */
.filtres svg[aria-hidden],
.jeton svg[aria-hidden],
[data-galerie] svg[aria-hidden],
.agrandissement svg[aria-hidden],
.tiroir-panier svg[aria-hidden],
.alerte svg[aria-hidden],
.vide svg[aria-hidden] {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex-shrink: 0;
}

/* ------------------------------------------------ grille et carte produit */

.grille-produits {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--espace-4);
}

/* --r-l (16px), pas --r-m : la carte est l'element le plus repete du site,
   c'est elle qui porte l'impression generale (brief tache 3) -- elle merite
   le meme rayon prononce que le panneau de prix de la fiche produit.
   L'elevation au survol reste translateY (compositor pur, ne deplace ni ne
   redimensionne aucun element voisin -- pas la transformation d'echelle que
   le referentiel proscrit), inchangee depuis la tache 1. */
/* position: relative -- ancre de la zone cliquable qui couvre desormais TOUTE
   la carte (voir .carte-produit__nom a::after plus bas). */
.carte-produit {
    position: relative;
    display: flex; flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-l);
    overflow: hidden;
    box-shadow: var(--ombre-s);
    transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
}
/* La bordure passe a --orange au survol (elle valait --bordure, donc ne
   changeait pas du tout). --orange sur --surface : 3,20:1, au-dessus du
   seuil 3:1 des indicateurs non-textuels. */
.carte-produit:hover { transform: translateY(-4px); box-shadow: var(--ombre-l); border-color: var(--orange); }

/* Le survol "se sent" (brief) : un zoom LEGER de la photo, CONTENU par
   overflow:hidden sur son propre cadre a ratio fixe -- il ne deplace, ne
   redimensionne et ne recouvre aucun element voisin (le cadre lui-meme ne
   bouge pas), donc ce n'est pas la transformation d'echelle que le
   referentiel proscrit (celle-ci vise les elements qui decalent la mise en
   page en grandissant). */
/* Degrade tres doux plutot qu'un aplat --fond : une photo de produit sur
   fond blanc (le cas courant en electromenager) flottait sans limite visible
   dans son cadre. Le degrade lui donne une assise sans introduire de bordure
   supplementaire. --surface -> --fond, deux jetons voisins : rien de
   perceptible comme un aplat de couleur, aucun seuil de contraste en jeu. */
.carte-produit__media {
    position: relative;
    aspect-ratio: 4 / 3;
    background: linear-gradient(155deg, var(--surface) 0%, var(--fond-doux) 100%);
    overflow: hidden;
}
/* « contain » et NON « cover ». « cover » remplit la vignette en rognant ce
   qui depasse : sur une photo d'evier ou de four, cadree serre par le
   fabricant, cela coupait le haut et le bas de l'appareil. Un catalogue
   vend l'objet -- le montrer ENTIER passe avant l'uniformite des tuiles.

   Le prix a payer est une marge variable autour des images qui ne sont pas
   en 4/3 : elle est prise en charge par le degrade de .carte-produit__media,
   deja la, plutot que par des bandes blanches franches.

   padding : sans lui l'objet touche les aretes de la vignette, ce qui le
   fait paraitre a l'etroit -- et coupe visuellement l'ombre portee que
   portent la plupart des photos de produit. */
.carte-produit__image {
    width: 100%;
    height: 100%;
    padding: var(--espace-3);
    object-fit: contain;
    transition: transform var(--duree-normale) var(--easing-sortie);
}
/* Agrandissement plus discret qu'avec « cover » : en « contain » l'image ne
   deborde plus de son cadre, un zoom trop franc la ferait sortir du fond. */
.carte-produit:hover .carte-produit__image { transform: scale(1.03); }
.carte-produit__sans-image {
    display: flex; align-items: center; justify-content: center;
    height: 100%;
    color: var(--texte-doux); font-size: .8125rem; text-align: center; padding: var(--espace-4);
}
.carte-produit__badges { position: absolute; top: .625rem; left: .625rem; display: flex; flex-wrap: wrap; gap: .375rem; }

.carte-produit__corps { display: flex; flex-direction: column; gap: var(--espace-2); padding: var(--espace-4); flex: 1; }
/* La marque devient une pastille plutot qu'une ligne de petites capitales
   grises, qui se lisait comme une legende oubliee. align-self: flex-start
   pour qu'elle epouse son texte au lieu de s'etirer sur toute la carte (le
   corps est un conteneur flex en colonne, donc etire ses enfants par
   defaut). Le remplissage est --fond-doux et non --fond : celui-ci est
   blanc, la pastille disparaitrait sur une carte blanche. --texte-doux sur
   --fond-doux : 5,60:1. */
.carte-produit__marque {
    align-self: flex-start;
    padding: .1875rem .5rem;
    border-radius: var(--r-plein);
    background: var(--fond-doux);
    color: var(--texte-doux);
    font-size: var(--texte-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
}

/* Variante LOGO : la pastille disparait. Un logo pose dans une pastille
   grise donnerait deux cadres imbriques, et les logos a fond blanc y
   feraient une tache claire dans un rectangle gris.
   line-height: 0 -- le paragraphe ne contient plus que l'image ; sans lui,
   font-size (--texte-xs) fixerait encore une hauteur de ligne et le logo
   flotterait dans sa boite. */
.carte-produit__marque--logo {
    padding: 0;
    background: transparent;
    line-height: 0;
}
/* Hauteur BORNEE et largeur libre, comme sur la fiche produit : les logos
   ont des proportions quelconques et n'imposer que la hauteur les aligne
   tous sur la meme ligne optique sans en deformer aucun.
   30px et non 44 comme sur la fiche : ici la marque est une mention
   secondaire au-dessus du nom, pas un lien -- elle ne porte aucune cible
   tactile. Premiere valeur essayee : 22px, qui mesurait 14px de LARGE sur un
   logo carre -- moins lisible que le texte qu'il remplace, donc un recul.
   max-width empeche a l'inverse un logo tres large de manger la carte. */
.carte-produit__marque--logo img {
    display: block;
    height: 30px;
    width: auto;
    max-width: 62%;
    object-fit: contain;
    object-position: left center;
}
/* CONSTAT DE RELECTURE FINALE : ce lien est le SEUL chemin entre une carte
   et sa fiche produit (l'element le plus repete du site, sur le chemin de
   conversion) -- mesure a 19px de haut, tres sous le seuil tactile de
   44px, alors que la carte entiere "se comporte" comme cliquable au
   survol (elevation, ombre, zoom de la photo). Correction demandee :
   agrandir la cible du TITRE, sans restructurer la carte entiere en un
   seul grand lien (decision reservee au proprietaire). position:relative
   ici donne un point d'ancrage STABLE (boite de bloc unique, meme si le
   nom s'enroule sur deux lignes) au ::after ci-dessous. */
/* PAS de position: relative ici, et c'est le point qui fait marcher tout le
   reste. Il y en avait un du temps ou le calque cliquable ne couvrait que le
   titre : tant qu'il subsiste, c'est CE titre qui sert de conteneur de bloc
   au ::after ci-dessous, et son "inset: 0" ne couvre que lui -- le calque
   s'arrete au texte, la photo et le prix ne sont pas cliquables. Le defaut
   est invisible a la lecture du CSS (les deux regles se lisent bien
   separement) et ne se voit qu'en cliquant : mesure au navigateur,
   elementFromPoint au centre de la photo rendait IMG et non le lien. */
.carte-produit__nom { font-size: .9375rem; font-weight: 600; line-height: 1.35; }
.carte-produit__nom a { color: var(--encre); }
/* La carte ENTIERE devient la cible du lien. Ce ::after mesurait 44px de
   haut et ne couvrait que le titre -- un correctif minimal apporte quand la
   restructuration de la carte etait hors perimetre. Elle ne l'est plus : le
   proprietaire demande une carte moderne, et une carte produit dont seul le
   titre est cliquable alors qu'elle s'eleve, s'ombre et zoome au survol
   ment sur ce qu'elle est.

   inset: 0 sur .carte-produit (position: relative plus haut), et non sur
   .carte-produit__nom : c'est ce changement d'ancre qui etend la zone.
   Le formulaire d'ajout est remonte au-dessus par z-index (regle suivante),
   sinon la couche l'intercepterait et "Ajouter au panier" ne repondrait
   plus. L'anneau de focus clavier continue de n'entourer que le texte reel
   du titre -- un ::after n'est jamais focusable, donc rien ne change pour
   la navigation au clavier. */
.carte-produit__nom a::after { content: ''; position: absolute; inset: 0; }
/* .carte-produit repose toujours sur --surface : --orange-texte y est sur. */
.carte-produit__nom a:hover { color: var(--orange-texte); text-decoration: none; }
.carte-produit__prix { display: flex; align-items: baseline; gap: var(--espace-2); margin-top: auto; padding-top: var(--espace-2); }
/* Passe de 17 a 20px : c'est l'information commerciale de la carte, elle
   doit peser. A 20px GRAS, le prix devient un "texte large" au sens WCAG
   (seuil 18,66px gras), ce qui autoriserait --orange direct a 3,20:1 -- on
   garde neanmoins --orange-texte et ses 4,60:1. La vivacite vient de la
   TAILLE, pas d'une baisse de contraste sur le seul chiffre que le visiteur
   doit lire sans effort. L'orange vif reste sur ce qui ne porte pas
   d'information : barre d'accent, bordure au survol, bouton plein. */
.carte-produit__prix-actuel {
    font-family: 'Sora', sans-serif;
    font-weight: 700;
    font-size: 1.25rem;
    letter-spacing: -.015em;
    color: var(--orange-texte);
}
.carte-produit__prix-barre { color: var(--texte-doux); font-size: var(--texte-sm); text-decoration: line-through; }

/* z-index: 1 -- INDISPENSABLE depuis que la couche cliquable couvre toute la
   carte : sans lui, elle passerait par-dessus ce formulaire et le bouton
   "Ajouter au panier" enverrait vers la fiche produit au lieu d'ajouter.
   position: relative parce qu'un z-index n'a aucun effet sur un element
   statique. */
.carte-produit__ajout { position: relative; z-index: 1; margin-top: .625rem; }
/* min-height:44px herite de .btn est conserve : cible tactile minimale,
   meme sur ce bouton compact (voir contraintes globales du brief). */
.carte-produit__ajout .btn { padding: var(--espace-2) var(--espace-3); font-size: .8125rem; }
/* Le bouton s'allume quand la CARTE est survolee, pas seulement lui-meme :
   c'est ce qui fait "vivre" la carte. --encre sur --orange : 5,55:1.
   (0,3,0) de specificite, comme la regle de survol du bouton juste apres --
   c'est donc l'ordre qui tranche, et le survol direct du bouton doit
   l'emporter, d'ou sa position en second. */
.carte-produit:hover .carte-produit__ajout .btn {
    background: var(--orange);
    border-color: var(--orange);
    color: var(--encre);
}
/* --encre sur --orange-survol : 6,33:1. --orange-survol ECLAIRCIT --orange,
   il ne l'assombrit pas (voir la note en tete de fichier). */
.carte-produit__ajout .btn:hover {
    background: var(--orange-survol);
    border-color: var(--orange-survol);
    color: var(--encre);
}
/* --texte-doux sur --surface (fond de la carte) : 6,38:1, voir la note de
   contraste en tete de fichier -- pas de nouvelle couleur a verifier. */
.carte-produit__rupture { margin: .625rem 0 0; color: var(--texte-doux); font-size: .8125rem; font-weight: 500; }

/* ------------------------------------------------ barre de filtres */

/* minmax(0, 1fr), pas 1fr nu (relecture finale, 1/7) : convention deja
   appliquee dans admin.css (.formulaire-grille, .repetable__champs) mais
   jamais reportee ici. "1fr" seul equivaut a "minmax(auto, 1fr)" -- son
   minimum implicite reste le CONTENU (min-content), pas zero : un contenu
   suffisamment large (table, formulaire, chaine non cassable) peut forcer
   la piste, et avec elle la page, en debordement horizontal -- exactement
   la famille de bug deja rencontree trois fois sur ce projet (.recherche,
   .admin__zone, .panier). "minmax(0, 1fr)" autorise la piste a retrecir
   jusqu'a 0 avant de deborder ; le contenu continue de s'enrouler/deborder
   dans SA propre boite plutot que d'entrainer toute la mise en page.
   Correctif purement defensif : aucun changement de rendu au repos. */
.catalogue { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--espace-5); align-items: start; }

/* --r-l : cohérent avec la carte produit et le panneau de prix, mêmes
   éléments "de premier plan" du site. Le titre porte une icône entonnoir
   (jeu d'icônes de page, voir plus haut) pour que le panneau se lise comme
   un vrai objet d'interface -- pas un formulaire administratif (brief). */
/* Le panneau devient un objet a part entiere : un chapeau sombre qui le
   coiffe, un corps blanc, et il SUIT le defilement sur les grands ecrans
   (voir le bloc min-width: 1024px). Sur une liste de vingt-quatre produits,
   un panneau qui reste en haut oblige a remonter pour chaque changement de
   critere.
   overflow: hidden -- le chapeau sombre doit epouser les coins arrondis ;
   sans lui, ses angles depasseraient du panneau. */
.filtres {
    background: var(--surface);
    border: 1px solid var(--bordure);
    border-radius: var(--r-l);
    overflow: hidden;
    box-shadow: var(--ombre-s);
}
/* Chapeau plein --encre-profonde : le meme aplat que le bandeau de page et
   le pied, pour que les deux se repondent. Blanc dessus : 18,83:1.
   Il porte aussi le padding lateral du panneau, d'ou la reprise ci-dessous
   sur .filtres__groupe et .filtres__actions plutot qu'un padding global. */
.filtres__titre {
    display: flex; align-items: center; gap: var(--espace-2);
    margin: 0;
    padding: var(--espace-4) var(--espace-5);
    background: var(--encre-profonde);
    color: #FFFFFF;
    font-size: var(--texte-base);
}
/* --orange sur --encre-profonde : 5,89:1. Sur le fond blanc d'avant, cette
   meme icone ne donnait que 3,20:1 -- elle passait au titre de composant,
   elle est desormais bien au-dela. */
.filtres__titre-icone { width: 18px; height: 18px; color: var(--orange); }
/* Le padding lateral vit ICI et non sur .filtres : le chapeau sombre doit
   aller d'un bord a l'autre du panneau, un padding global l'aurait encadre
   de blanc. */
.filtres__groupe { padding: var(--espace-4) var(--espace-5); border-top: 1px solid var(--bordure); }
.filtres__groupe:first-of-type { border-top: 0; }
.filtres__legende {
    display: flex; align-items: center; gap: .375rem;
    font-weight: 600; font-size: var(--texte-sm); margin-bottom: var(--espace-3);
}
.filtres__legende-icone { width: 16px; height: 16px; color: var(--texte-doux); }
/* Cible tactile 44px (contrainte globale du brief) : padding vertical seul
   mesurait 41,6px (--espace-2 x2 + corps de texte 16px/1,6) -- corrige par
   un min-height explicite, meme motif que les autres controles du fichier. */
/* --bordure-controle, pas --bordure : meme defaut et meme remede que
   .champ__saisie (voir sa note et le tableau en tete de fichier) -- ce
   champ vit dans .filtres, elle-meme sur --surface. */
.filtres__champ {
    width: 100%; min-height: 44px; padding: var(--espace-2) .8125rem;
    border: 1px solid var(--bordure-controle); border-radius: var(--r-s);
    font: inherit; color: var(--encre); background: var(--surface);
    transition: border-color var(--transition), box-shadow var(--transition);
}
/* Chevron dessine en fond plutot que la fleche native du systeme : celle-ci
   varie d'un navigateur a l'autre et jure avec le reste des controles.
   appearance: none la retire ; le padding droit reserve la place du chevron,
   sans quoi une valeur longue passerait dessous.
   Uniquement sur les <select> -- les champs de prix sont des <input number>
   et n'ont pas de fleche a remplacer. */
select.filtres__champ {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 2.25rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23556070' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right .75rem center;
    background-size: 16px 16px;
}
.filtres__champ:hover { border-color: var(--texte-doux); }
.filtres__champ:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px var(--orange-halo); }
.filtres__paire { display: flex; align-items: center; gap: var(--espace-2); }
/* Separateur purement decoratif entre les deux bornes de prix : --texte-doux
   sur --surface, 6,38:1, deja documente -- aucune paire nouvelle. */
.filtres__paire-tiret { color: var(--texte-doux); flex-shrink: 0; }
/* Pied du panneau : fond --fond-doux pour le detacher des groupes de
   criteres au-dessus, et padding lateral repris ici comme sur
   .filtres__groupe (le panneau n'en a pas globalement, son chapeau devant
   aller d'un bord a l'autre). --texte-doux y donne 5,60:1, --encre 15,58:1
   -- rien de plus clair qu'un bouton n'y est pose de toute facon.
   position: sticky : sur un panneau qui defile dans sa propre boite (voir
   .catalogue > aside), le bouton « Appliquer » doit rester atteignable sans
   descendre jusqu'au bout des criteres. */
.filtres__actions {
    position: sticky;
    bottom: 0;
    display: flex; flex-direction: column; gap: var(--espace-2);
    margin-top: 0;
    padding: var(--espace-4) var(--espace-5);
    background: var(--fond-doux);
    border-top: 1px solid var(--bordure);
}

/* Barre-outil au-dessus de la grille : compte de resultats + tri, posee sur
   sa propre carte plutot que flottant a nu sur --fond -- un vrai objet
   d'interface, pas une simple ligne de texte (brief : "aeree"). */
.barre-tri {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: var(--espace-3); margin-bottom: var(--espace-4);
    padding: var(--espace-3) var(--espace-4);
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-m);
}
.barre-tri__compte { color: var(--texte-doux); font-size: var(--texte-sm); }
.barre-tri form { display: flex; align-items: center; gap: var(--espace-2); }
/* --bordure-controle, meme raison que .champ__saisie ci-dessus. */
.barre-tri__champ {
    min-height: 44px; padding: var(--espace-2) .8125rem;
    border: 1px solid var(--bordure-controle); border-radius: var(--r-s);
    font: inherit; color: var(--encre); background: var(--surface);
    transition: border-color var(--transition), box-shadow var(--transition);
}
.barre-tri__champ:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px var(--orange-halo); }

/* Jetons : deux emplois distincts, meme forme de base. ".jeton" seul sert de
   lien de navigation (sous-categories, marques -- accueil et catalogue).
   ".jeton--actif" (nouveau, tache 3) recapitule un filtre CHOISI par le
   visiteur et le retire au clic -- CatalogueFiltre::chaineRequete() est deja
   documentee pour exactement cet usage ("c'est ainsi que la vue construit un
   lien retirer ce filtre"), jamais consommee jusqu'ici. Cible tactile 44px :
   min-height explicite, la pilule mesurait ~30,8px sans lui (padding
   .3125rem x2 + corps 13px/1,6). */
.jetons { display: flex; flex-wrap: wrap; align-items: center; gap: var(--espace-2); margin-bottom: var(--espace-4); }
.jeton {
    display: inline-flex; align-items: center; gap: .375rem;
    min-height: 44px; padding: .375rem var(--espace-3);
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-plein);
    font-size: .8125rem; font-weight: 500; color: var(--encre);
    transition: border-color var(--transition), color var(--transition), background-color var(--transition);
}
/* .jeton repose sur --surface : --orange-texte y est sur. */
.jeton:hover { border-color: var(--orange); color: var(--orange-texte); text-decoration: none; }
/* --encre/--orange-clair = 16,05:1 (repos), --orange-texte/--orange-clair =
   4,16:1 (bordure, seuil composant 3:1), --encre/--orange = 5,55:1 (survol) --
   les trois deja documentes en tete de fichier, aucune paire nouvelle. Jamais
   --orange en bordure sur --orange-clair (2,89:1, echoue -- piege deja
   corrige ailleurs dans ce fichier, voir la note en tete). */
.jeton--actif { background: var(--orange-clair); border-color: var(--orange-texte); color: var(--encre); font-weight: 600; }
.jeton--actif:hover { background: var(--orange); border-color: var(--orange); color: var(--encre); }
.jeton__croix { width: 14px; height: 14px; }

/* ------------------------------------------------ pagination */

.pagination { display: flex; flex-wrap: wrap; gap: var(--espace-2); justify-content: center; margin-top: var(--espace-6); }
/* Cible tactile 44px (contrainte globale du brief) : le lien mesurait
   ~38,4px sans min-height explicite (padding --espace-2 x2 + corps de
   texte 14px/1,6) -- corrige, meme motif que .filtres__champ ci-dessus.
   border-radius: --r-plein (pilule) plutot que --r-s : coherent avec les
   jetons et les boutons fantome, un peu plus "objet d'interface" qu'une
   simple case carree. */
.pagination__lien {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; padding: var(--espace-2) var(--espace-3);
    border: 1px solid var(--bordure); border-radius: var(--r-plein);
    background: var(--surface); color: var(--encre);
    text-align: center; font-size: var(--texte-sm);
    transition: border-color var(--transition), color var(--transition), background-color var(--transition);
}
/* .pagination repose sur --surface : --orange-texte y est sur. */
.pagination__lien:hover { border-color: var(--orange); color: var(--orange-texte); text-decoration: none; }
/* Bouton plein : texte --encre, jamais blanc (voir note en tete). */
.pagination__lien--actif { background: var(--orange); border-color: var(--orange); color: var(--encre); font-weight: 600; }
/* Sans cette regle, ".pagination__lien:hover" (classe + pseudo-classe,
   0,2,0) l'emporte sur ".pagination__lien--actif" (une classe, 0,1,0)
   quel que soit l'ordre dans le fichier : le lien de la page courante
   (un <a aria-current="page"> reellement survolable) affichait alors
   --orange-texte sur son propre fond --orange -- 1,44:1, quasiment
   illisible. admin.css evite deja ce piege avec la meme regle. */
.pagination__lien--actif:hover { background: var(--orange); color: var(--encre); }
/* <span aria-disabled="true">, pas un lien : pas d'opacity necessaire pour
   "griser" quelque chose qui n'est deja plus interactif ni activable au
   clavier. --texte-doux seul donne 6,38:1 sur --surface (fond de
   .pagination__lien) -- confortablement au-dessus du seuil AA de 4,5:1, la
   ou l'ancienne opacity:.55 le faisait tomber a 2,38:1. */
.pagination__lien--inactif { color: var(--texte-doux); cursor: default; }
/* Sans cette regle, ".pagination__lien:hover" (0,2,0) l'emporterait sur
   ".pagination__lien--inactif" (0,1,0) : ce <span aria-disabled> non
   interactif prendrait une couleur d'aspect actif au simple survol de
   la souris (:hover se declenche independamment de l'interactivite
   reelle). Pas un echec de contraste ici (le fond reste --surface),
   mais une incoherence d'etat du meme patron que les cas ci-dessus. */
.pagination__lien--inactif:hover { color: var(--texte-doux); border-color: var(--bordure); }

/* ------------------------------------------------ fiche produit */

/* minmax(0, 1fr) (relecture finale, 2/7) : voir la note de convention sur
   .catalogue plus haut. */
.produit { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--espace-5); }

.galerie__principale {
    position: relative;
    aspect-ratio: 4 / 3; background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-l);
    overflow: hidden;
}
.galerie__principale img { width: 100%; height: 100%; object-fit: contain; }
.galerie__miniatures { display: flex; flex-wrap: wrap; gap: var(--espace-2); margin-top: var(--espace-3); }
.galerie__miniature {
    width: 72px; height: 72px; padding: 0;
    border: 2px solid var(--bordure); border-radius: var(--r-s);
    background: var(--surface); cursor: pointer; overflow: hidden;
    transition: border-color var(--transition);
}
/* « contain », comme la vignette du catalogue : une miniature rognee ne
   montre pas la meme chose que l'image qu'elle represente, et on choisit sur
   quoi cliquer d'apres elle. */
.galerie__miniature img { width: 100%; height: 100%; padding: 2px; object-fit: contain; }
/* --orange-texte/--surface = 4,60:1, deja documente -- une bordure de survol
   discrete sur les vignettes non actives (le seuil composant n'est que
   3:1, la marge est large). */
.galerie__miniature:hover { border-color: var(--orange-texte); }
.galerie__miniature--active,
.galerie__miniature--active:hover { border-color: var(--orange); }

/* Le declencheur d'agrandissement enveloppe l'image principale : un <button>
   plein cadre, sans decoration propre, pour rester focusable et actionnable
   au clavier sans qu'aucun style ne trahisse qu'il s'agit d'un bouton plutot
   que de l'image elle-meme. */
.galerie__zoom-declencheur {
    display: block; width: 100%; height: 100%; padding: 0;
    border: 0; background: transparent; cursor: zoom-in;
}
/* Pastille d'affordance : sans elle, le curseur "zoom-in" (souris
   uniquement) est le SEUL indice qu'un clic agrandit la photo -- invisible
   au tactile. Purement decorative (aria-hidden, pointer-events:none : le
   vrai bouton est .galerie__zoom-declencheur en dessous, plein cadre).
   --encre/--surface = 17,74:1, deja documente. */
.galerie__zoom-indice {
    position: absolute; right: var(--espace-3); bottom: var(--espace-3);
    display: flex; align-items: center; justify-content: center;
    width: 36px; height: 36px;
    background: var(--surface); color: var(--encre);
    border: 1px solid var(--bordure); border-radius: var(--r-plein);
    box-shadow: var(--ombre-s);
    pointer-events: none;
}
.galerie__zoom-indice svg { width: 18px; height: 18px; }

/* Le bloc de prix repose sur ce conteneur, pas a nu sur --fond : voir la
   note de contraste en tete de fichier. */
/* --r-l (16px) plutot que --r-m : le panneau de prix est le bloc le plus
   important de la fiche produit, il merite le rayon le plus prononce du
   fichier -- --r-l etait declare mais sans consommateur avant ce commit. */
.produit__panneau {
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-l);
    padding: var(--espace-5);
}

.produit__marque { color: var(--texte-doux); font-size: var(--texte-sm); text-transform: uppercase; letter-spacing: .04em; }

/* Marque affichee en LOGO. Le modificateur neutralise les reglages
   typographiques ci-dessus -- text-transform et letter-spacing n'ont aucun
   effet sur une image, mais font-size, lui, en a un : il fixerait la
   hauteur de ligne de la boite du <p> et l'image flotterait dedans. */
.produit__marque--logo { line-height: 0; }
/* Hauteur bornee, largeur libre : les logos envoyes par le proprietaire ont
   des proportions quelconques (un carre, une banniere large). En n'imposant
   que la hauteur, tous s'alignent sur la meme ligne optique sans qu'aucun
   soit deforme -- ce que ferait un width/height fixe.
   max-width : un logo tres large ne doit pas depasser le panneau, meme si
   cela lui coute quelques pixels de hauteur. */
/* 44px, la meme valeur que la cible tactile du lien : le logo remplit alors
   exactement sa zone cliquable, sans bande morte au-dessus ni en dessous.
   Mesure a 34px d'abord -- correct, mais chetif face au titre de la fiche,
   qui fait 44px et plus. */
.produit__marque-logo {
    display: block;
    height: 44px;
    width: auto;
    max-width: 100%;
    object-fit: contain;
    object-position: left center;
}
/* Le lien qui porte le logo : sa cible tactile serait celle de l'image, soit
   34px de haut -- sous le seuil de 44px. inline-flex + min-height l'etend
   sans deplacer quoi que ce soit, meme idiome que .caracteristiques a et
   .pied a. */
.produit__marque--logo a { display: inline-flex; align-items: center; min-height: 44px; }
.produit__marque--logo a:hover { opacity: .85; text-decoration: none; }
.produit__resume { color: var(--texte-doux); margin: var(--espace-3) 0 0; max-width: var(--mesure); }
.produit__prix { display: flex; align-items: baseline; gap: var(--espace-3); margin: 1.25rem 0; }
/* 30px gras : au-dessus du seuil des 18,66px gras, --orange direct
   est valide (3,20:1 sur --surface, voir note en tete). */
.produit__prix-actuel { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 1.875rem; color: var(--orange); }
.produit__prix-barre { color: var(--texte-doux); font-size: var(--texte-lg); text-decoration: line-through; }

.produit__ajout { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--espace-4); margin-top: var(--espace-5); }
.produit__quantite { display: flex; flex-direction: column; gap: .375rem; }
.produit__quantite label { font-size: .8125rem; font-weight: 600; color: var(--texte-doux); }
/* Finition reportee par la tache 3 : ses trois champs cousins
   (.filtres__champ, .barre-tri__champ, .panier__quantite input) avaient
   deja recu l'anneau de focus orange -- celui-ci ne l'avait jamais recu,
   alors qu'il est sur le CHEMIN D'ACHAT PRINCIPAL (fiche produit, ajout au
   panier). Meme regle exactement que .panier__quantite input:focus
   ci-dessous dans ce fichier -- --orange-halo, deja documente, aucune
   nouvelle paire de contraste. */
/* --bordure-controle, meme raison que .champ__saisie -- ce champ est sur
   le CHEMIN D'ACHAT PRINCIPAL (fiche produit, ajout au panier). */
.produit__quantite input {
    width: 5rem; padding: .625rem var(--espace-3);
    border: 1px solid var(--bordure-controle); border-radius: var(--r-s);
    font: inherit; color: var(--encre); background: var(--surface);
    transition: border-color var(--transition), box-shadow var(--transition);
}
.produit__quantite input:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px var(--orange-halo); }
.produit__indisponible { margin-top: var(--espace-5); color: var(--texte-doux); font-weight: 500; }

/* Panneau de texte long : description produit, et tout bloc redactionnel qui
   viendrait ensuite. Meme carte que le reste du site, mais avec une
   typographie de LECTURE et non d'interface.

   max-width en "ch" et non en pixels : l'unite suit la police reellement
   rendue, donc la mesure reste de 70 caracteres quelle que soit la taille
   choisie par le visiteur dans son navigateur. Une largeur en pixels
   donnerait 90 caracteres par ligne a qui lit en petit, et 40 a qui lit en
   grand -- les deux fatigants.

   line-height 1.75 contre 1.6 ailleurs : sur un paragraphe de plusieurs
   lignes, l'oeil a besoin de plus d'air pour retrouver le debut de la ligne
   suivante. Ce n'est pas un gout, c'est ce qui distingue un texte a lire
   d'un libelle a survoler. */
/* Fond --orange-clair et non --surface : la page etant BLANCHE, un panneau
   blanc ne se detacherait pas -- or c'est precisement ce qu'on lui demande.
   Et pas un gris non plus, refuse pour le fond de page comme pour le reste.
   --orange-clair (#FFF1E8) donne 1,11:1 contre le blanc : assez pour poser
   le bloc, assez peu pour ne pas crier. --encre dessus : 16,05:1.

   ATTENTION si un lien apparait un jour dans ce panneau : --orange-texte n'y
   donne que 4,16:1, sous le seuil. Il faudra --encre, comme partout ailleurs
   sur --orange-clair (voir la note en tete de fichier). */
.panneau-texte {
    background: var(--orange-clair);
    border: 1px solid var(--bordure);
    border-radius: var(--r-l);
    padding: clamp(1.25rem, 3vw, 2rem);
}
.panneau-texte p {
    margin: 0;
    max-width: var(--mesure);
    font-size: var(--texte-lg);
    line-height: 1.75;
    /* --encre et non --texte-doux : c'est le contenu principal du bloc, pas
       une note de bas de page. 17,74:1 sur --surface. */
    color: var(--encre);
}
.panneau-texte p + p { margin-top: var(--espace-4); }

.caracteristiques { width: 100%; border-collapse: collapse; }
.caracteristiques th, .caracteristiques td {
    padding: .625rem var(--espace-3); text-align: left;
    border-bottom: 1px solid var(--bordure); font-size: .9375rem;
}
.caracteristiques th { width: 40%; color: var(--texte-doux); font-weight: 500; }
/* CONSTAT DE RELECTURE FINALE : les liens tel:/mailto: de la page contact
   (seul usage de <a> a l'interieur de .caracteristiques) mesuraient 19px
   de haut, sous le seuil tactile de 44px -- et ce sont, contrairement au
   fil d'Ariane (liens en ligne dans un paragraphe, defendable en l'etat),
   les POINTS D'APPEL du site. inline-flex + min-height, meme idiome que
   .pied a plus bas dans ce fichier. */
.caracteristiques a { display: inline-flex; align-items: center; min-height: 44px; color: var(--encre); }

/* margin-top/-bottom SEPAREMENT, jamais le raccourci "margin: 2.5rem 0".
   Ce raccourci ecrasait les marges HORIZONTALES en plus des verticales, et
   ".section" est presque toujours porte par le MEME element que ".conteneur"
   (accueil, contact, reservation, page de remerciement). A specificite egale
   (0,1,0), c'est l'ordre du fichier qui tranche -- ".section" etant plus bas,
   son "0" horizontal remplacait le "margin: 0 auto" de ".conteneur" ligne 316.
   Resultat : la section gardait bien ses 1280px de large mais restait COLLEE
   A GAUCHE de la fenetre au lieu d'etre centree, alors que les sections
   voisines sans ".section" l'etaient. Le defaut existait sur les quatre pages
   citees ; il n'a saute aux yeux qu'une fois l'en-tete cadre, qui a donne un
   repere visuel a l'alignement. */
.section { margin-top: 2.5rem; margin-bottom: 2.5rem; }
/* align-items passe de "baseline" a "center" : le lien "Tout voir" est
   devenu une pilule de 44px de haut, et un alignement sur la ligne de base
   du texte l'aurait fait depasser sous le titre. */
.section__entete { display: flex; align-items: center; justify-content: space-between; gap: var(--espace-4); margin-bottom: 1.25rem; }

/* Barre d'accent devant chaque titre de section. Scopee aux h2 : le
   ".section__entete" du catalogue porte un h1 (l'intitule de la page), qui
   n'a pas a recevoir la meme signature qu'un titre de rubrique.
   Purement decorative -- le titre reste lisible et complet sans elle, aucun
   seuil de contraste ne s'y applique. */
.section__entete h2 { position: relative; padding-left: 1.0625rem; }
.section__entete h2::before {
    content: '';
    position: absolute;
    left: 0; top: .18em; bottom: .18em;
    width: 5px;
    border-radius: var(--r-plein);
    background: linear-gradient(var(--orange), var(--orange-survol));
}

/* Lien de section en pilule. --encre sur --orange-clair (16,05:1) au repos
   et sur --orange (5,55:1) au survol : deux couples deja documentes en tete
   de fichier. --orange-texte, qu'on aurait pu croire naturel ici, ne donne
   que 4,16:1 sur --orange-clair -- sous le seuil 4,5:1 d'un texte de cette
   taille, et sans soulignement pour compenser puisque c'est une pilule. */
.section__lien {
    display: inline-flex;
    align-items: center;
    gap: var(--espace-2);
    flex-shrink: 0;
    min-height: 44px;
    padding: var(--espace-2) var(--espace-4);
    border-radius: var(--r-plein);
    background: var(--orange-clair);
    color: var(--encre);
    font-size: var(--texte-sm);
    font-weight: 600;
    white-space: nowrap;
    transition: background var(--transition), color var(--transition);
}
.section__lien:hover { background: var(--orange); color: var(--encre); text-decoration: none; }
.section__lien svg {
    width: 16px; height: 16px;
    fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
    transition: transform var(--transition);
}
/* La fleche avance de 3px : un mouvement de 3px ne deplace aucun voisin (la
   pilule garde sa largeur, la fleche glisse a l'interieur) -- ce n'est pas
   la transformation d'echelle que le referentiel proscrit. */
.section__lien:hover svg { transform: translateX(3px); }

/* ------------------------------------------------------ banniere (accueil)
   Cadree comme l'en-tete (bornee a --largeur, centree, coins arrondis) : une
   banniere pleine largeur sous un en-tete cadre donnerait deux alignements
   differents sur la meme page.

   overflow: hidden est ici SANS DANGER, contrairement a .entete : cette
   section ne contient aucun element en position absolue qui doive deborder
   d'elle. Il sert a couper le halo au ras des coins arrondis. */
/* PHOTO DE FOND ET SON VOILE.
   Le voile n'est pas un effet, c'est ce qui rend le texte lisible. Il a ete
   dimensionne PAR MESURE sur l'image reelle, bande de 5% par bande de 5%,
   et non choisi a l'oeil -- une premiere version, calee sur la moyenne, a
   ete prise en defaut par une seule bande.

   L'image est composee pour cet usage : sa moitie gauche est un mur presque
   noir (pixel le plus clair rgb(26,31,37), soit 16,58:1 pour du blanc SANS
   aucun voile), et la cuisine eclairee occupe la droite. Le piege est la
   TRANSITION : entre 45% et 50%, l'image saute du noir a rgb(242,242,242).
   Un degrade qui s'allegeait progressivement depuis la gauche y tombait a
   3,16:1 -- pile sous le texte, sur les ecrans ou le titre s'etale le plus.

   D'ou un PLATEAU a .82 sur toute la zone ou le texte peut aller (jusqu'a
   62% : 34rem de titre sur une banniere de 1024px, le cas le plus etale),
   puis une chute franche. Pire cas mesure dans la zone du texte : 9,23:1
   pour le paragraphe, 11,38:1 pour le titre. Le tiers droit, lui, ne porte
   aucun texte et laisse voir la photo presque sans voile.

   "left center" et non "center" : sous 768px, la banniere devient plus haute
   que large et "cover" rogne les COTES. Centre, elle montrerait la cuisine
   eclairee sous un texte qui occupe alors toute la largeur ; calee a gauche,
   c'est le mur sombre qui reste visible.

   L'ordre des couches compte : dans un "background" multiple, la PREMIERE
   listee est celle du dessus -- le degrade doit donc preceder l'image.
   var(--encre-profonde) en derniere couche reste le fond si l'image ne
   charge pas : la banniere est alors unie, jamais claire. */
.banniere {
    position: relative;
    overflow: hidden;
    max-width: var(--largeur);
    margin: var(--espace-5) auto var(--espace-6);
    border-radius: var(--r-l);
    background:
        linear-gradient(90deg,
            rgba(11, 17, 32, .82) 0%,
            rgba(11, 17, 32, .82) 62%,
            rgba(11, 17, 32, 0) 98%),
        url('../img/banniere.webp') left center / cover no-repeat,
        var(--encre-profonde);
    color: #FFFFFF;
    padding: clamp(2.5rem, 7vw, 5rem) 0;
}

/* Halo purement decoratif. Son opacite maximale a ete choisie APRES calcul,
   pas a l'oeil : compose sur --encre-profonde, .45 donne #733619, sur lequel
   le blanc tient 9,25:1 -- et meme .60 tiendrait 6,79:1. Le texte peut donc
   passer n'importe ou dans la banniere sans jamais approcher le seuil 4,5:1,
   ce qui evite d'avoir a le contraindre a rester hors du halo (une regle que
   le premier changement de taille d'ecran aurait de toute facon violee).

   aspect-ratio + une seule dimension : un disque reste un disque, et la
   taille suit la largeur de la banniere sans media query. */
.banniere::before {
    content: '';
    position: absolute;
    right: -12%;
    top: 50%;
    transform: translateY(-50%);
    width: 46rem;
    aspect-ratio: 1;
    background: radial-gradient(circle,
        rgba(241, 100, 17, .45) 0%,
        rgba(241, 100, 17, .16) 42%,
        rgba(241, 100, 17, 0) 70%);
    pointer-events: none;
}

/* Au-dessus du halo, qui n'a pas de z-index : sans cette regle le pseudo
   element, cree apres dans l'ordre du document, se peindrait par-dessus le
   texte.

   AUCUN max-width ici, et c'est deliberé : cet element porte aussi
   ".conteneur", donc "margin: 0 auto". Le brider a 34rem aurait CENTRE un
   bloc de 544px au milieu des 1280px de la banniere, au lieu de l'aligner a
   gauche sur le reste de la page -- et l'aurait pousse droit dans le halo.
   La largeur de ligne est bornee sur les elements de TEXTE eux-memes, plus
   bas, qui n'heritent d'aucun centrage. */
.banniere__contenu { position: relative; z-index: 1; }

/* LE SURTITRE N'EST PLUS ORANGE, et c'est la photo de fond qui l'a decide.
   --orange donnait 5,89:1 sur l'aplat --encre-profonde d'origine. Sur le
   pire composite de la zone du texte (#353A46, la bande claire de l'image
   sous le voile), il tombe a 3,56:1. Ce chiffre passerait pour un GRAND
   texte ou un composant, seuil 3:1 -- mais ce surtitre fait 14px gras, donc
   sous les 18,66px gras qui donnent droit a cette exemption. Il echoue.

   La verification a ete refaite apres avoir remplace l'image par une bien
   plus claire, en esperant pouvoir rendre l'orange : la reponse est restee
   non. La mesure a tranche deux fois contre l'intuition.

   Blanc a la place : 11,38:1 dans le meme pire cas. L'accent orange n'est
   pas perdu -- il vit dans le bouton d'appel, un aplat plein que le fond
   n'atteint pas, et dans le halo decoratif. */
.banniere__surtitre {
    margin: 0 0 var(--espace-3);
    color: #FFFFFF;
    font-size: var(--texte-sm);
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    /* Une opacite legere le distingue du titre sans toucher a sa couleur :
       .82 de blanc sur le pire composite (#68615F) reste a 4,66:1, au-dessus
       du seuil. Ne pas descendre plus bas. */
    opacity: .82;
}
/* blanc sur --encre-profonde : 18,83:1. max-width en ch (et non en rem) :
   la mesure suit la taille reelle du texte, elle-meme fluide via clamp()
   dans --texte-4xl. Borne le titre AVANT qu'il n'atteigne le halo, sans
   centrer quoi que ce soit. */
.banniere__titre { margin: 0 0 var(--espace-4); max-width: 18ch; font-size: var(--texte-4xl); line-height: 1.15; color: #FFFFFF; }
/* --sombre-texte sur --encre-profonde : 15,27:1. */
.banniere__texte { margin: 0 0 var(--espace-6); max-width: 46ch; color: var(--sombre-texte); font-size: var(--texte-lg); }
.banniere__actions { display: flex; flex-wrap: wrap; gap: var(--espace-3); }

/* Bouton secondaire POUR FOND SOMBRE. .btn--fantome ne convient pas ici :
   il porte --bordure et --encre, deux valeurs pensees pour --surface. Sur
   --encre-profonde sa bordure passerait (15,00:1) mais son TEXTE tomberait a
   1,06:1 -- deux tons proches du noir l'un sur l'autre, illisible. Blanc sur
   --encre-profonde : 18,83:1, pour le texte comme pour la bordure. */
.btn--clair {
    background: transparent;
    border-color: #FFFFFF;
    color: #FFFFFF;
}
/* Au survol, le blanc devient le FOND et --encre le texte : meme couple,
   ratio identique par symetrie (18,83:1). Aucun nouveau palier a verifier. */
.btn--clair:hover { background: #FFFFFF; border-color: #FFFFFF; color: var(--encre); }

/* ------------------------------------------------------ arguments (accueil)
   Deuxieme modele. Le premier posait trois bandeaux plats en --orange-clair,
   icone a gauche et texte a droite : lisible, mais sans relief ni mouvement.
   Celui-ci empile numero, icone, titre, texte sur une carte blanche, et fait
   travailler trois choses au survol -- le liseré du haut, l'icone, la carte
   elle-meme. */
.arguments {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
    gap: var(--espace-4);
    list-style: none;
    margin: 0 0 var(--espace-7);
    padding: 0;
}

/* isolation: isolate -- cree un contexte d'empilement propre a la carte, pour
   que le liseré du ::before ne puisse jamais passer sous une carte voisine
   quelles que soient les regles ajoutees plus tard. overflow: hidden pour que
   ce meme liseré epouse les coins arrondis. */
.argument {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--espace-3);
    padding: var(--espace-5);
    background: var(--surface);
    border: 1px solid var(--bordure);
    border-radius: var(--r-l);
    box-shadow: var(--ombre-s);
    transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
}
.argument:hover { transform: translateY(-4px); box-shadow: var(--ombre-l); border-color: var(--orange); }

/* Liseré haut qui se deploie de la gauche vers la droite. scaleX sur un
   element ABSOLU, jamais sur la carte : il ne repousse ni ne redimensionne
   aucun voisin -- ce n'est pas la mise a l'echelle que le referentiel
   proscrit. Decoratif, aucun seuil de contraste. */
.argument::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 4px;
    background: linear-gradient(90deg, var(--orange), var(--orange-survol));
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--duree-normale) var(--easing-sortie);
}
.argument:hover::before { transform: scaleX(1); }

/* Le numero en filigrane, dans le coin. --bordure sur --surface : 1,26:1 --
   volontairement a peine visible, c'est un ornement. Il porte
   aria-hidden dans la vue : un lecteur d'ecran n'annonce pas "zero un". */
.argument__numero {
    position: absolute;
    top: var(--espace-3); right: var(--espace-4);
    font-family: 'Sora', sans-serif;
    font-size: 2.5rem;
    font-weight: 700;
    line-height: 1;
    color: var(--bordure);
    transition: color var(--transition);
    pointer-events: none;
}
.argument:hover .argument__numero { color: var(--orange-clair); }

/* Tuile pleine plutot que pastille ronde : --orange en fond, icone --encre
   par-dessus (5,55:1) -- jamais de blanc sur orange, qui ne donne que
   3,20:1. Au survol elle bascule legerement et s'agrandit ; contenue dans
   sa propre boite, elle ne deplace rien autour d'elle. */
.argument__icone {
    flex: 0 0 auto;
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r-m);
    background: linear-gradient(140deg, var(--orange), var(--orange-survol));
    color: var(--encre);
    box-shadow: var(--ombre-s);
    transition: transform var(--duree-normale) var(--easing-elastique);
}
.argument:hover .argument__icone { transform: rotate(-6deg) scale(1.08); }
.argument__icone svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* padding-right : reserve la place du numero en filigrane, sinon un titre
   long passerait dessous. min-width: 0 sur le texte -- sans lui, un mot long
   empeche la piste de retrecir sous sa largeur intrinseque et fait deborder
   la grille. */
.argument__titre {
    padding-right: 3rem;
    font-family: 'Sora', sans-serif;
    font-weight: 700;
    color: var(--encre);
    font-size: var(--texte-lg);
    line-height: 1.25;
}
.argument__texte { min-width: 0; font-size: var(--texte-sm); color: var(--texte-doux); line-height: 1.55; }
/* --orange-texte sur --surface : 4,60:1 (la carte est blanche, plus
   --orange-clair). Le soulignement herite de la regle generique des liens :
   la couleur n'est jamais le seul indicateur. */
.argument__texte a { color: var(--orange-texte); font-weight: 600; }

/* ------------------------------------------------- mur de marques (accueil)
   La page d'accueil ne presente plus que les marques : ce bloc EST la page.
   Il remplace les pastilles de texte (.jetons), qui donnaient a l'accueil
   l'aspect d'une sortie de debogage. */
/* scroll-margin-top : la rubrique « Marques » du menu pointe vers #marques.
   Sans cette reserve, l'en-tete COLLANT (76px au repos, 58px une fois
   defile) recouvrirait le titre de la section a l'arrivee sur l'ancre -- le
   visiteur atterrirait sur la grille sans savoir ou il est. 6rem couvre le
   cas le plus haut, marge comprise. */
.marques { scroll-margin-top: 6rem; }
/* .marques__entete / .marques__titre : SUPPRIMES. Ils centraient le titre
   « Nos marques » et lui donnaient --texte-3xl sans barre d'accent, quand
   « Nos produits » -- un h2 de meme rang, sur la meme page -- est aligne a
   gauche avec sa barre orange. Le bloc utilise desormais .section__entete
   comme toutes les autres sections, il n'y a donc plus qu'une seule maniere
   de titrer une rubrique. Seule l'accroche garde une classe a elle. */
.marques__intro {
    max-width: 46ch;
    margin: -.5rem 0 var(--espace-6);
    color: var(--texte-doux);
    font-size: var(--texte-lg);
}

/* Flex plutot que grid, et c'est un choix mesure, pas une preference :
   "repeat(auto-fill, minmax(180px, 1fr))" produit des pistes qui remplissent
   TOUTE la rangee, donc une seule marque -- l'etat reel du catalogue
   aujourd'hui -- donnerait une tuile collee a gauche avec 1100px de vide a
   sa droite. Ici "flex: 1 1 180px" + "max-width: 260px" + "justify-content:
   center" centre le peu qu'il y a et empeche une tuile isolee de s'etirer,
   tout en donnant exactement le meme pavage qu'une grille des que les
   marques se multiplient. min-width: 0 pour la meme raison que
   "minmax(0, 1fr)" en grille : sans lui, un nom long empecherait le
   retrecissement sous sa largeur intrinseque et deborderait de l'ecran. */
.grille-marques {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--espace-4);
    list-style: none;
    margin: 0;
    padding: 0;
}
.grille-marques__element { flex: 1 1 180px; min-width: 0; max-width: 260px; }

.marque-tuile {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Ratio fixe : les logos envoyes par le proprietaire auront des
       proportions quelconques (un carre, une banniere large). Une boite au
       ratio impose + object-fit: contain donne un mur regulier sans retoucher
       une seule image, et supprime tout decalage de mise en page au
       chargement, sans avoir a connaitre les dimensions de chaque fichier. */
    aspect-ratio: 3 / 2;
    padding: var(--espace-5);
    background: var(--surface);
    border: 1px solid var(--bordure);
    border-radius: var(--r-l);
    text-decoration: none;
    transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition);
}
.marque-tuile:hover {
    border-color: var(--orange);
    box-shadow: var(--ombre-m);
    transform: translateY(-3px);
    text-decoration: none;
}
.marque-tuile:active { transform: translateY(-1px); }

/* max-height en pourcentage plutot que "height: 100%" : le logo doit tenir
   DANS le rembourrage, jamais le remplir bord a bord -- un logo colle aux
   bords de sa tuile se lit comme une erreur de mise en page. */
.marque-tuile__logo {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
}

/* Repli sans logo : le nom en toutes lettres. Sora comme les titres, pour
   qu'une marque sans visuel ait quand meme l'air d'un logotype et non d'un
   texte oublie. --encre sur --surface : 17,74:1. */
.marque-tuile__mot {
    font-family: 'Sora', sans-serif;
    font-weight: 700;
    font-size: var(--texte-xl);
    line-height: 1.15;
    letter-spacing: -.02em;
    color: var(--encre);
    text-align: center;
    /* Un nom trop long pour la tuile se coupe entre mots plutot que de
       deborder ; overflow-wrap traite le cas d'un seul mot tres long. */
    overflow-wrap: anywhere;
}
.marque-tuile:hover .marque-tuile__mot { color: var(--orange-texte); }

/* ------------------------------------------------ agrandissement d'image */

/* Meme mecanisme que .megamenu / .megamenu--ouvert : masque par defaut via
   opacity/visibility (pas display, pour permettre une transition), un
   modificateur --ouvert le fait apparaitre. z-index au-dessus de l'en-tete
   collant. La transition respecte prefers-reduced-motion via la regle
   generique en fin de fichier, qui cible deja "*". */
.agrandissement {
    position: fixed; inset: 0; z-index: var(--z-survol);
    display: flex; align-items: center; justify-content: center;
    padding: var(--espace-6);
    background: var(--voile);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity var(--duree-normale) var(--easing-sortie), visibility var(--duree-normale);
}
.agrandissement--ouvert { opacity: 1; visibility: visible; pointer-events: auto; }
.agrandissement__image { max-width: 100%; max-height: 100%; object-fit: contain; }
/* Rond solide en --encre-elevee (element pose sur le fond sombre --voile,
   pas une carte, mais le meme role : se detacher d'un fond sombre) plutot
   que la superposition translucide precedente -- plus net, coherent avec
   le style "dense" vise. blanc/--encre-elevee : 15,57:1. Au survol, le
   glyphe de fermeture est traite comme une icone (voir note en tete) :
   fond --orange, texte --encre (5,55:1), jamais blanc sur orange. */
.agrandissement__fermer {
    position: absolute; top: var(--espace-4); right: var(--espace-4);
    width: 44px; height: 44px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid rgba(255, 255, 255, .5); border-radius: var(--r-plein);
    background: var(--encre-elevee); color: #FFFFFF;
    cursor: pointer;
    transition: background-color var(--transition), color var(--transition);
}
.agrandissement__fermer svg { width: 20px; height: 20px; }
.agrandissement__fermer:hover { background: var(--orange); color: var(--encre); }

/* ------------------------------------------------ page panier */

/* Sous 480px (5 colonnes : Produit/Prix/Quantite/Total/Actions), la table ne
   tient jamais dans un ecran de telephone -- sans ce conteneur, l'algorithme
   de mise en page automatique des tables (table-layout:auto herite) traite
   "width:100%" comme un MINIMUM, pas un plafond, et laisse la table
   s'elargir jusqu'a 734px pour loger son contenu, entrainant TOUTE LA PAGE
   en defilement horizontal -- exactement l'interdit du brief. Meme
   mecanisme et meme remede que ".table-conteneur" dans admin.css (une table
   large defile dans son PROPRE conteneur, jamais la page) -- sans le
   plafond de hauteur ici, aucun en-tete collant a preserver sur cette page.
   Verifie en navigateur reel a 375px : scrollWidth de la page repasse a
   375px (aucun depassement), le conteneur lui-meme defile horizontalement
   (scrollWidth 734 / clientWidth 343). */
.panier-conteneur { overflow-x: auto; }
.panier { width: 100%; border-collapse: collapse; margin-top: var(--espace-5); }
.panier th, .panier td { padding: .875rem; text-align: left; border-bottom: 1px solid var(--bordure); vertical-align: middle; }
.panier th { font-size: .8125rem; text-transform: uppercase; letter-spacing: .04em; color: var(--texte-doux); }
/* Survol de ligne : pur confort visuel (--fond, deja documente comme fond
   de page -- aucun texte ne change de couleur ici, seul l'arriere-plan
   bouge), aucune paire de contraste supplementaire. */
.panier tbody tr { transition: background-color var(--transition); }
.panier tbody tr:hover { background: var(--fond-doux); }
.panier__produit { display: flex; align-items: center; gap: var(--espace-3); }
/* 56px de cote : « cover » y rognait l'appareil au point qu'on ne
   reconnaissait plus ce qu'on avait mis dans son panier. */
.panier__media { width: 56px; height: 56px; object-fit: contain; border-radius: var(--r-s); flex-shrink: 0; background: var(--fond-doux); }
.panier__quantite { display: flex; align-items: center; gap: var(--espace-2); }
/* Cible tactile 44px : padding --espace-2 x2 + corps 16px/1,6 mesurait
   41,6px sans min-height explicite, meme motif que les autres champs de ce
   fichier. */
/* --bordure-controle, meme raison que .champ__saisie ci-dessus. */
.panier__quantite input {
    width: 4rem; min-height: 44px; padding: var(--espace-2) .625rem;
    border: 1px solid var(--bordure-controle); border-radius: var(--r-s);
    font: inherit; color: var(--encre); background: var(--surface);
    transition: border-color var(--transition), box-shadow var(--transition);
}
.panier__quantite input:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px var(--orange-halo); }

/* Le total repose sur --surface, pas a nu sur --fond : meme regle que
   .produit__panneau, voir la note de contraste en tete de fichier. */
.panier__recap {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--espace-4);
    margin-top: var(--espace-5); padding: 1.25rem;
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-l);
}
.panier__total { font-size: var(--texte-lg); margin: 0; }
/* 18px gras, sous le seuil des 18,66px gras : --orange-texte, sur
   --surface (.panier__recap), donne 4,60:1. */
.panier__total strong { font-family: 'Sora', sans-serif; color: var(--orange-texte); }
.panier__actions { display: flex; gap: var(--espace-3); flex-wrap: wrap; }

/* ------------------------------------------------ tiroir du panier */

/* Meme mecanisme que .agrandissement : masque par defaut via
   opacity/visibility (pas display, pour permettre une transition). SANS
   JavaScript ce panneau n'est jamais ouvert -- aucune classe --ouvert n'est
   jamais posee sans lui -- ce qui est acceptable puisque /panier offre
   exactement le meme contenu (voir tiroir-panier.php). */
.tiroir-panier {
    position: fixed; inset: 0; z-index: var(--z-survol);
    display: flex; justify-content: flex-end;
    background: var(--voile-leger);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity var(--duree-normale) var(--easing-sortie), visibility var(--duree-normale);
}
.tiroir-panier--ouvert { opacity: 1; visibility: visible; pointer-events: auto; }
/* Entree par glissement (brief tache 3) : le panneau part ENTIEREMENT hors
   ecran (translateX(100%), pas le simple nudge de 16px d'avant ce commit)
   et glisse jusqu'a sa position -- combine au fondu du voile (.tiroir-panier
   ci-dessus), c'est un vrai tiroir qui glisse, pas un panneau qui
   apparait sur place. */
.tiroir-panier__panneau {
    width: 100%; max-width: 380px; height: 100%;
    display: flex; flex-direction: column; gap: var(--espace-4);
    padding: var(--espace-5); overflow-y: auto;
    background: var(--surface);
    box-shadow: var(--ombre-l);
    transform: translateX(100%);
    transition: transform var(--duree-normale) var(--easing-sortie);
}
.tiroir-panier--ouvert .tiroir-panier__panneau { transform: translateX(0); }
.tiroir-panier__entete { display: flex; align-items: center; justify-content: space-between; gap: var(--espace-3); }
.tiroir-panier__entete h2 { display: flex; align-items: center; gap: var(--espace-2); font-size: 1.25rem; }
.tiroir-panier__entete-icone { width: 22px; height: 22px; color: var(--orange); }
.tiroir-panier__fermer {
    width: 44px; height: 44px; padding: 0; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--bordure); border-radius: var(--r-plein);
    background: transparent; color: var(--encre);
    cursor: pointer;
    transition: border-color var(--transition), color var(--transition);
}
.tiroir-panier__fermer svg { width: 18px; height: 18px; }
/* Glyphe de fermeture traite comme une icone : --orange plein est
   l'usage explicitement prevu pour les icones (voir note en tete). */
.tiroir-panier__fermer:hover { border-color: var(--orange); color: var(--orange); }
.tiroir-panier__nombre { margin: 0; color: var(--texte-doux); font-size: var(--texte-sm); }
/* Liste de lignes reconstruite en entier par front.js (avec createElement,
   jamais innerHTML). C'etait un bloc de texte en "white-space: pre-line"
   jusqu'a l'ajout des vignettes : une image ne tient pas dans du texte.
   overflow-y : un panier de vingt articles ne doit pas pousser le total et
   les deux boutons hors de l'ecran -- ce sont eux qui portent l'action. */
.tiroir-panier__lignes {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--espace-3);
    overflow-y: auto;
    color: var(--encre);
    font-size: .9375rem;
}
.tiroir-panier__vide { color: var(--texte-doux); }

.tiroir-panier__ligne { display: flex; align-items: center; gap: var(--espace-3); }

/* Taille FIXE (flex: 0 0 56px) : une photo de produit peut etre large ou
   haute, et sans plancher ni plafond la premiere ligne dicterait la largeur
   de toutes les autres, decalant les noms les uns par rapport aux autres.
   Le fond --fond et la bordure rendent la boite visible meme sans image --
   c'est ce qui garde les noms alignes quand un produit n'a pas de photo. */
.tiroir-panier__media {
    flex: 0 0 56px;
    width: 56px;
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: var(--fond-doux);
    border: 1px solid var(--bordure);
    border-radius: var(--r-s);
}
.tiroir-panier__media img { width: 100%; height: 100%; object-fit: contain; }

/* min-width: 0 -- indispensable et non cosmetique : un element flex a pour
   largeur minimale celle de son CONTENU. Sans lui, un nom de produit long et
   insecable elargirait la ligne et ferait deborder le tiroir. */
.tiroir-panier__detail { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tiroir-panier__nom { font-weight: 600; line-height: 1.35; }
.tiroir-panier__meta { color: var(--texte-doux); font-size: var(--texte-sm); }
.tiroir-panier__total { margin: auto 0 0; padding-top: var(--espace-4); border-top: 1px solid var(--bordure); font-size: 1.0625rem; }
/* 17px gras, sous le seuil des 18,66px gras : --orange-texte, sur
   --surface (.tiroir-panier__panneau), donne 4,60:1. */
.tiroir-panier__total strong { font-family: 'Sora', sans-serif; color: var(--orange-texte); }
.tiroir-panier__actions { display: flex; flex-direction: column; gap: .625rem; }

/* ------------------------------------------------ champs de formulaire */

/* Memes noms de classe qu'admin.css (.champ, .champ__label, ...) : deux
   fichiers distincts par choix assume (voir la note en tete de ce fichier),
   mais rien n'empeche de reprendre un vocabulaire de classes qui a deja fait
   ses preuves cote back-office. */
.champ { margin-bottom: 1.125rem; }
.champ__label { display: block; font-weight: 600; font-size: var(--texte-sm); margin-bottom: .375rem; color: var(--encre); }
.champ__label span { color: var(--danger); }
/* --bordure-controle (relecture finale), pas --bordure : ce champ, pose
   dans une .carte ou a nu sur --fond, n'a ni ombre ni espacement
   distinctif -- la bordure est son seul repere visuel, et --bordure seule
   n'y donne que 1,00 a 1,26:1 selon le fond, sous le seuil composant 3:1.
   Voir la note en tete de fichier. */
.champ__saisie {
    width: 100%;
    padding: .625rem .8125rem;
    border: 1px solid var(--bordure-controle);
    border-radius: var(--r-s);
    font: inherit;
    color: var(--encre);
    background: var(--surface);
    transition: border-color var(--transition), box-shadow var(--transition);
}
.champ__saisie:hover { border-color: var(--texte-doux); }
.champ__saisie:focus {
    outline: none;
    border-color: var(--orange);
    box-shadow: 0 0 0 3px var(--orange-halo);
}
.champ__saisie--erreur { border-color: var(--danger); }
/* Sans cette regle, ".champ__saisie:focus" (classe + pseudo-classe,
   0,2,0) l'emporte sur ".champ__saisie--erreur" (une classe, 0,1,0) :
   un champ invalide qui recoit le focus repassait de la bordure --danger
   a --orange, la couleur redevenant alors le seul indicateur de l'etat
   fautif par accident. Le champ garde sa bordure --danger, seul l'anneau
   de focus change de teinte pour rester distinct. */
.champ__saisie--erreur:focus { border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-halo); }
/* PIEGE DE SPECIFICITE INTRODUIT PAR CETTE TACHE, CORRIGE AVANT LIVRAISON :
   ".champ__saisie:hover" (ci-dessus, classe + pseudo-classe, 0,2,0) que
   cette tache vient d'ajouter l'emporterait sur ".champ__saisie--erreur"
   seule (une classe, 0,1,0) -- un champ invalide survole (sans le focus)
   perdrait sa bordure --danger au profit du gris de survol neutre, la
   encore un etat fautif qui disparaitrait par accident. Meme remede que
   :focus ci-dessus. */
.champ__saisie--erreur:hover { border-color: var(--danger); }
.champ__erreur { color: var(--danger); font-size: .8125rem; margin-top: .3125rem; }
textarea.champ__saisie { min-height: 120px; resize: vertical; }

/* minmax(0, 1fr) (relecture finale, 3/7) : voir la note de convention sur
   .catalogue plus haut. */
.champ__rangee { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--espace-4); }

/* ------------------------------------------------ leurre anti-robot */

/* Hors ecran, jamais display:none NI visibility:hidden : certains robots
   ignorent ce qui est masque par ces deux proprietes et remplissent quand
   meme le champ, ce qui ruinerait la detection (voir ReservationController::
   leurreRempli()). aria-hidden retire le champ des technologies d'assistance
   -- un humain, voyant ou non, ne le rencontre jamais. */
.leurre {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ------------------------------------------------ page de contact */

.contact-formulaire {
    max-width: var(--mesure);
    margin-top: var(--espace-6);
    padding-top: var(--espace-5);
    border-top: 1px solid var(--bordure);
}

.contact-formulaire h2 { margin-bottom: .375rem; }

/* ------------------------------------------------ page de reservation */

/* minmax(0, 1fr) (relecture finale, 4/7) : voir la note de convention sur
   .catalogue plus haut. */
.reservation { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--espace-6); margin-top: var(--espace-5); }

.reservation__recap {
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-l);
    padding: 1.25rem;
    height: fit-content;
}
.reservation__recap h2 { font-size: 1.0625rem; margin-bottom: var(--espace-4); }
.reservation__ligne {
    display: flex; justify-content: space-between; gap: var(--espace-4);
    padding: .625rem 0; border-bottom: 1px solid var(--bordure);
    font-size: .9375rem;
}
.reservation__ligne:last-of-type { border-bottom: 0; }
.reservation__total {
    display: flex; justify-content: space-between; gap: var(--espace-4);
    margin-top: var(--espace-4); padding-top: var(--espace-4); border-top: 2px solid var(--bordure);
    font-size: 1.0625rem;
}
/* 17px gras, sous le seuil des 18,66px gras : --orange-texte, sur
   --surface (.reservation__recap), donne 4,60:1. */
.reservation__total strong { font-family: 'Sora', sans-serif; color: var(--orange-texte); }

/* ------------------------------------------------ confirmation de reservation */

.confirmation__reference {
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-m);
    padding: var(--espace-5); margin: var(--espace-5) 0;
    text-align: center;
}
.confirmation__reference span { display: block; color: var(--texte-doux); font-size: var(--texte-sm); margin-bottom: .375rem; }
/* --texte-2xl (24-30px fluide) plutot que 1.5rem fixe -- --texte-2xl etait
   declare mais sans consommateur avant ce commit. Sa borne basse (24px)
   reste au seuil des grands textes, --orange direct est valide sur toute
   la plage fluide (3,20:1 sur --surface). */
.confirmation__reference strong { font-family: 'Sora', sans-serif; font-size: var(--texte-2xl); color: var(--orange); }

/* ------------------------------------------------ messages flash */

/* --succes / --danger sur ces fonds : memes couples deja verifies pour
   .badge--stock (5,21:1) et .badge--rupture (6,36:1) en tete de fichier. */
.alerte {
    display: flex; align-items: center; gap: var(--espace-3);
    padding: .875rem 1.125rem; margin: var(--espace-4) 0;
    border-radius: var(--r-m); border: 1px solid transparent;
    font-size: .9375rem;
}
.alerte--succes { background: #ECFDF5; border-color: #A7F3D0; color: var(--succes); }
.alerte--erreur { background: #FEF2F2; border-color: #FECACA; color: var(--danger); }
/* --alerte sur #FFFBEB : 4,84:1, meme couple deja verifie pour
   .badge--commande en tete de fichier. Utilisee sur reservation-merci.php
   quand emails_sent vaut 0 : un avertissement honnete, ni une erreur (la
   demande EST enregistree) ni un succes complet (l'accuse n'est pas parti). */
.alerte--avertissement { background: #FFFBEB; border-color: #FDE68A; color: var(--alerte); }
/* Icone de type, currentColor : herite EXACTEMENT la couleur deja validee
   de sa variante (succes/erreur/avertissement, voir tableau en tete de
   fichier) -- aucune paire supplementaire. */
.alerte__icone { width: 20px; height: 20px; flex-shrink: 0; }
.alerte__texte { flex: 1; }
/* Cible tactile 44px (contrainte globale du brief) : le glyphe texte seul,
   sans plancher explicite, mesurait ~28,8px (police --texte-lg/1,6) --
   corrige par un cadre carre explicite, meme motif que .tiroir-panier__fermer.
   C'est un bouton FONCTIONNEL (cible de front.js), pas un controle inactif :
   l'exemption WCAG 1.4.3 sur les controles desactives ne s'applique pas.
   opacity:.6 composait avec le fond de l'alerte pour donner 2,44:1 a 3,27:1
   selon la variante -- sous le seuil 3:1 des objets graphiques, un piege
   deja rencontre sur la pagination du back-office (voir admin.css). Couleur
   explicite plutot qu'opacite : au repos, la couleur heritee de la variante
   d'alerte est deja validee (4,84 a 6,36:1, voir le tableau en tete de
   fichier) ; au survol, --encre remplace l'opacite -- toujours >= 16:1 sur
   les trois fonds pales, quelle que soit la variante. */
.alerte__fermer {
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 44px; height: 44px; min-width: 44px;
    background: none; border: 0; border-radius: var(--r-s); cursor: pointer;
    color: inherit;
    transition: background-color var(--transition), color var(--transition);
}
.alerte__fermer svg { width: 18px; height: 18px; }
.alerte__fermer:hover { background: var(--bordure); color: var(--encre); }

/* ------------------------------------------------ pied de page */

.pied svg[aria-hidden] {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex-shrink: 0;
}

.pied {
    margin-top: var(--espace-8); padding: 2.5rem 0 var(--espace-5);
    background: var(--encre-profonde); color: var(--sombre-texte);
}
/* --sombre-texte / --encre-profonde au repos (15,27:1), --orange /
   --encre-profonde au survol (5,89:1) -- memes deux couples que l'en-tete,
   voir le tableau en tete de fichier. Meme soulignement anime qu'en
   navigation (fond degrade, pas de transform) plutot que le simple
   eclaircissement vers blanc d'avant ce commit.

   min-height:44px + inline-flex : cible tactile (contrainte globale du
   brief), sur TOUS les liens du pied (".pied a" est plus specifique que
   ".pied__liste"/".pied__reseau-lien" seules -- tag + classe, (0,1,1) contre
   (0,1,0) -- donc s'applique meme sans etre repete par chaque variante).
   Sans elle, un lien de simple texte (".pied__liste a", aucun padding avant
   ce commit) mesurait ~24px de haut (font-size 15px * line-height 1,6
   heritee du corps), et ".pied__reseau-lien" ~30px -- tous deux sous le
   seuil. Un lien de 44px de haut adjacent au suivant, sans espace
   supplementaire entre eux, reste conforme (WCAG 2.5.8 borne la TAILLE de
   la cible, pas l'espacement entre cibles adjacentes a ce niveau).

   RELECTURE FINALE, MESURE ET LAISSE EN L'ETAT : les libelles courts
   ("LG" 19,3px, "Beko" 35,5px, "Froid" 35,6px -- mesures en navigateur
   reel) restent sous 44px de LARGEUR malgre les 44px de hauteur ci-dessus.
   Un min-width:44px cache un cout visuel reel : le soulignement anime au
   survol (background-size, ci-dessous) est proportionnel a la boite, pas
   au texte -- il deborderait alors visiblement au-dela du mot pour tout
   libelle plus court que 44px. Le decoupler demanderait un <span> interne
   dedie a l'encadrement (restructuration du gabarit, hors perimetre de ce
   correctif). Laisse tel quel : signale au proprietaire plutot que
   corrige au prix d'un artefact visuel silencieux. */
.pied a {
    display: inline-flex; align-items: center;
    min-height: 44px;
    color: var(--sombre-texte);
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: left calc(100% - 2px);
    background-size: 0 1px;
    transition: background-size var(--transition), color var(--transition);
}
.pied a:hover { color: var(--orange); background-size: 100% 1px; text-decoration: none; }
/* auto-fit plutot que des paliers fixes (repeat(2, 1fr) puis repeat(4, 1fr))
   : le nombre de colonnes reellement rendues varie de 2 a 6 selon les
   reglages (Contact et Reseaux sont chacun conditionnels, voir pied.php) --
   un palier fixe laisserait des rangees a moitie vides selon la
   combinaison. minmax(200px, 1fr) replie deja tout en une seule colonne
   sous ~440px de large sans media query dediee. */
.pied__grille {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--espace-6) var(--espace-5);
}
.pied__titre { font-size: var(--texte-sm); text-transform: uppercase; letter-spacing: .06em; color: #FFFFFF; margin-bottom: .875rem; }
.pied__liste { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--espace-2); font-size: .9375rem; }

/* Colonne "Reseaux" : icone + libelle disposes en ligne (pas une liste
   verticale, mieux adaptee a deux courts libelles cote a cote). Memes
   couples currentColor que .pied a ci-dessus -- aucune paire nouvelle. */
.pied__reseaux { display: flex; flex-wrap: wrap; gap: var(--espace-4); margin: 0; padding: 0; list-style: none; }
/* display/align-items repetes de ".pied a" (deja appliques, voir plus haut)
   -- seul "gap" (icone/texte) est propre a cette variante. min-height:44px
   herite de ".pied a" suffit desormais, sans padding vertical dedie. */
.pied__reseau-lien { gap: var(--espace-2); }
.pied__reseau-icone { width: 20px; height: 20px; }

.pied__bas {
    margin-top: var(--espace-6); padding-top: 1.25rem;
    border-top: 1px solid rgba(255, 255, 255, .12);
    font-size: var(--texte-sm); color: var(--sombre-texte-doux);
}

/* ------------------------------------------------ etats vides */

/* "Au premier jour, c'est ce que le proprietaire verra" (brief) : un etat
   vide soigne, pas un simple paragraphe centre -- une icone posee sur un
   disque discret, un titre, un texte, parfois un lien/bouton (marge
   confortable au-dessus grace au disque, deja prevue par --espace-7). */
.vide { display: flex; flex-direction: column; align-items: center; padding: var(--espace-7) var(--espace-4); text-align: center; color: var(--texte-doux); }
/* --surface (pas --fond, meme si .vide repose lui-meme sur --fond dans ses
   quatre emplois) : le disque doit se detacher du fond de page, pas s'y
   fondre -- --texte-doux/--surface = 6,38:1 (deja documente), decoratif,
   aria-hidden sur l'icone elle-meme. */
.vide__disque {
    display: flex; align-items: center; justify-content: center;
    width: 72px; height: 72px; margin-bottom: var(--espace-4);
    background: var(--surface);
    border: 1px solid var(--bordure); border-radius: var(--r-plein);
    color: var(--texte-doux);
}
.vide__icone { width: 32px; height: 32px; }
.vide__titre { font-family: 'Sora', sans-serif; font-size: var(--texte-lg); color: var(--encre); margin: 0 0 var(--espace-2); }
.vide p { margin: 0 0 var(--espace-2); max-width: var(--mesure); }
.vide p:last-child { margin-bottom: 0; }

/* Trace de debogage sur la page 500 : n'apparait que si app.debug vaut vrai.
   var(--sombre-texte), pas le litteral #E2E8F0 (relecture finale) : ce
   fichier definit deja ce jeton pour EXACTEMENT cette valeur/ce fond. */
.erreur__trace {
    text-align: left;
    background: var(--encre-profonde);
    color: var(--sombre-texte);
    padding: var(--espace-4);
    border-radius: var(--r-s);
    font-size: var(--texte-xs);
    overflow-x: auto;
    margin-top: 1.75rem;
}

/* ------------------------------------------------ revelation au defilement */

/* Pilotee entierement par front.js (IntersectionObserver) : la classe
   ".avant-revelation" n'est JAMAIS ecrite dans le HTML rendu par le serveur,
   seul le script l'ajoute -- SANS JavaScript, ou si IntersectionObserver
   n'existe pas, aucun element ne la porte jamais et tout reste dans son etat
   par defaut ci-dessous (visible). Contrainte du brief, deja payee d'un
   correctif ailleurs sur ce projet : jamais de contenu invisible sans
   JavaScript.
   transform/opacity uniquement, jamais display : l'element cache garde sa
   place dans la mise en page (aucun saut de contenu autour de lui pendant
   la revelation). 1.25rem ne correspond a AUCUN palier --espace-* (entre
   --espace-4/1rem et --espace-5/1.5rem) -- deliberement, pour ne pas
   emprunter un jeton d'espacement a un usage qui n'est pas de l'espacement. */
[data-reveal] {
    transition: opacity var(--duree-normale) var(--easing-sortie),
                transform var(--duree-normale) var(--easing-sortie);
}
[data-reveal].avant-revelation { opacity: 0; transform: translateY(1.25rem); }

/* ------------------------------------------------ points de rupture */

/* Les blocs qui suivent sont ordonnes par seuil croissant (480, 767, 768,
   1023, 1024), quel que soit le sens de la requete (max-width ou min-width) :
   le fichier se lit une seule fois, du plus etroit au plus large, sans
   jamais revenir en arriere. Une precedente version melangeait les deux sens
   dans un ordre non croissant (768, 1024, 1023, 767) -- sans consequence sur
   la cascade ici, mais symptomatique d'une lecture "bureau par defaut". */

/* Deux tuiles par rangee sur telephone. Sans ce palier, la base de 180px ne
   tient qu'une seule fois dans les 343px utiles d'un ecran de 375px : le mur
   de marques deviendrait une colonne de gros carres, un par ecran. 130px de
   base + 16px de gouttiere en font entrer deux, qui s'etirent ensuite a
   163px chacune. */
@media (max-width: 479px) {
    .grille-marques__element { flex-basis: 130px; }
}

@media (min-width: 480px) {
    .tiroir-panier__panneau { max-width: 400px; }
}

/* "flex-wrap: wrap" a ete retire avec le champ de recherche : il n'existait
   que pour laisser ce champ passer sur une deuxieme ligne (flex-basis: 100%,
   order: 3). Les trois controles restants -- logo, panier, menu -- tiennent
   sur une seule ligne des 320px. */
@media (max-width: 767px) {
    /* CADRAGE DE LA BANNIERE SUR TELEPHONE.
       En bureau le bloc est large et bas, et l'image est calee a gauche :
       le texte se pose sur sa moitie sombre et vide, les appareils eclaires
       restent visibles a droite. Sur telephone le bloc devient haut et
       etroit -- "cover" ne garde alors qu'une bande VERTICALE de l'image, et
       "left center" choisit precisement celle qui est vide. Mesure a 430px :
       background-position valait 0% 50%, on ne voyait donc que du noir.

       Deux corrections, pas une : le cadrage passe a droite pour montrer les
       appareils, ET le voile devient VERTICAL. Un voile horizontal n'a plus
       de sens quand le texte n'est plus a cote de l'image mais dessus : il
       doit assombrir le haut, la ou le titre se trouve, et laisser le bas
       respirer. */
    .banniere {
        background:
            linear-gradient(180deg,
                rgba(11, 17, 32, .92) 0%,
                rgba(11, 17, 32, .88) 55%,
                rgba(11, 17, 32, .74) 100%),
            url('../img/banniere.webp') 72% center / cover no-repeat,
            var(--encre-profonde);
    }

    /* padding-block et NON le raccourci "padding". La barre porte aussi la
       classe .conteneur, qui lui donne son retrait lateral ; ecrire
       "padding: var(--espace-3) 0" remettait ce retrait a ZERO. Mesure sur
       un iPhone 14 Pro Max : le logo commencait a 0px du bord gauche et le
       bouton « Menu » finissait a 0px du bord droit, tous deux colles aux
       aretes de la barre. */
    .entete__barre { min-height: 0; padding-block: var(--espace-3); }
}

@media (min-width: 768px) {
    /* Les tailles de h1/h2 sont deja fluides via clamp() (voir :root) :
       aucun saut de palier ici, contrairement a l'ancienne version qui
       fixait une seconde valeur en dur a ce breakpoint. */
    .grille-produits { grid-template-columns: repeat(3, 1fr); gap: 1.25rem; }
    /* minmax(0, 1fr) x2 (relecture finale, 5/7) : voir la note de convention
       sur .catalogue plus haut. */
    .produit { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--espace-6); }
    .champ__rangee { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
}

/* Sous 1024px, la navigation devient un menu deroulant. Au-dessus, elle est
   toujours visible et le mega-menu se deploie au survol -- donc utilisable
   sans JavaScript des cette largeur. En dessous, l'ouverture SANS
   JavaScript passe par :target (voir .nav:target ci-dessous) : le bouton
   hamburger est un lien vers #navigation-principale, pas un <button>.
   front.js, quand il s'execute, prend le relais via .nav--ouverte et
   n'utilise plus jamais le fragment d'URL -- les deux mecanismes ne se
   chevauchent donc qu'au tout premier rendu. */
@media (max-width: 1023px) {
    /* display:flex (pas display:block) : .entete__bascule porte desormais une
       icone en plus de son texte (gap, alignement vertical) -- sous
       display:block ces deux proprietes flex deviennent des no-op et
       l'icone/le texte retombent sur un simple alignement de ligne de base,
       moins net. min-height:44px (regle non scopee) s'applique de toute
       facon quel que soit le display. */
    .entete__bascule { display: flex; }
    /* CONSTAT DE RELECTURE FINALE (CRITIQUE) : ".entete" est position:sticky;
       top:0 -- un en-tete plus haut que la fenetre (menu mobile deploye,
       accordeon + sous-categories) reste COLLE a top:0 a TOUTE position de
       defilement de la page. La page ne peut donc JAMAIS faire defiler son
       propre debordement : mesure en navigateur reel, accordeon ouvert,
       375x667 -- le rectangle de ".entete" (enteteTop/enteteBottom) est
       IDENTIQUE a scrollY 0, 200, 400, 800, 1226, 1670 et au maximum. Avec 8
       categories de premier niveau (rien ne les borne cote back-office),
       jusqu'a 3 liens restaient definitivement hors ecran -- souris ET
       clavier -- dont "Petit electromenager". Meme remede exactement que
       ".sidebar" cote back-office (admin.css, meme forme de bug : sticky +
       contenu plus grand que la fenetre) : borner LE CONTENEUR STICKY
       LUI-MEME a 100vh et le rendre defilant, plutot qu'une soustraction
       approximative ("100vh - Xrem") accrochee a la hauteur -- elle-meme
       variable -- de la barre au-dessus (qui change deja de hauteur entre
       le repos/defile et le retour a la ligne de la recherche sous 767px) :
       un plafond mal calcule aurait simplement deplace le meme bug d'un
       cran plutot que de le fermer. Verifie a 375x667 ET 360x640, 4/6/8
       categories, accordeon ouvert et ferme : plus aucun lien hors ecran a
       la position de defilement maximale (voir le rapport de correction). */
    /* 100dvh apres 100vh, jamais a la place : les navigateurs qui ignorent
       l'unite dynamique gardent le plafond statique. Sur mobile, "100vh"
       resout le viewport LARGE (barre d'URL escamotee) -- barre visible, le
       bas de cet en-tete plafonne mais colle a top:0 passait sous la ligne
       de flottaison, et le geste portant ici sur une zone defilante
       n'escamote pas la barre. "dvh" suit la hauteur reellement visible. */
    .entete { max-height: 100vh; max-height: 100dvh; overflow-y: auto; }
    /* La navigation vit dans la barre du logo depuis la refonte de l'en-tete.
       Deployee sur mobile, elle doit REDESCENDRE sous cette barre plutot que
       de s'y intercaler : flex-wrap sur la barre, flex-basis 100% et order 3
       sur elle. Sans ces trois declarations, le menu ouvert se glisserait
       entre le logo et le panier, sur une seule ligne devenue illisible.
       C'est exactement le mecanisme qui servait au champ de recherche avant
       son retrait. */
    .entete__barre { flex-wrap: wrap; }
    .nav { display: none; }
    /* :target est le filet de secours SANS JavaScript, pas une redondance
       du script : si celui-ci ne s'execute pas (bloque, en echec, ou
       simplement pas encore charge), le clic sur le lien hamburger navigue
       vers l'ancre et cette seule regle affiche le menu. Ne pas supprimer
       en la prenant pour un doublon de .nav--ouverte. */
    .nav--ouverte,
    .nav:target {
        display: block;
        order: 3;
        flex-basis: 100%;
        /* Le separateur entre la barre et le menu deploye. En bureau les deux
           sont sur la meme ligne et ce trait n'aurait aucun sens ; ici le menu
           forme bien une seconde rangee. */
        border-top: 1px solid var(--bordure);
        padding-bottom: var(--espace-2);
    }
    .nav__liste { flex-direction: column; flex-wrap: nowrap; justify-content: flex-start; }
    /* display/margin uniquement ici : la couleur reste celle de la regle
       non-scopee plus haut -- aujourd'hui --encre au repos et --orange-texte
       au survol, l'en-tete etant repasse sur --surface.

       Ne JAMAIS redeclarer de couleur dans ce bloc, et l'historique explique
       pourquoi : quand l'en-tete est passe du clair au sombre, cette regle
       portait encore un "color: var(--encre)" cable pour le fond clair. A
       specificite egale (0,1,0 de part et d'autre) elle gagnait la cascade
       par sa position plus basse dans le fichier -- le menu mobile s'est
       donc retrouve en --encre sur --encre-profonde, deux tons proches du
       noir, sans que rien ne le signale. Une couleur dupliquee ici ne suit
       pas les changements de fond ; une couleur heritee, si. */
    .nav__fermer {
        display: block;
        margin: var(--espace-3) var(--espace-4) var(--espace-1);
        font-weight: 600;
    }
    /* --decalage-x et --recadrage remis a zero, et c'est INDISPENSABLE, pas
       une precaution. "transform: none" ci-dessous ne suffit pas : les trois
       regles d'ouverture (survol, focus-within, .megamenu--ouvert) sont
       declarees hors de ce bloc media et posent leur propre transform, dont
       deux a une specificite (0,3,0) qu'aucune regle raisonnable d'ici ne
       battrait. Elles calculent "translate(calc(--decalage-x + --recadrage))"
       -- en mettant les deux variables a zero, ce calcul devient l'identite.
       Sans cela, le panneau d'accordeon, pourtant en position: static, restait
       translate de -50% de sa largeur : mesure a gauche = -187px sur un ecran
       de 375px, soit la moitie du menu hors de l'ecran. */
    .megamenu {
        --decalage-x: 0px;
        --recadrage: 0px;
        position: static; opacity: 1; visibility: visible; transform: none;
        box-shadow: none; border: 0; border-radius: 0;
        padding: 0 0 var(--espace-2) var(--espace-4);
        display: none;
    }
    /* Neutralise les largeurs et le plafond de hauteur du panneau flottant
       de bureau (.megamenu--col-1/2/3, plus haut) : en accordeon mobile,
       position:static (ci-dessus) fait deja flotter le panneau dans le flux
       normal de la page, a la largeur de son parent -- une largeur fixee en
       pixels y deborderait sur un petit ecran.
       CONSTAT DE RELECTURE FINALE (CRITIQUE), CORRIGE : la version
       precedente de ce commentaire affirmait qu'un max-height/overflow-y
       etait ici inutile "parce que c'est la PAGE qui defile, pas le
       panneau" -- FAUX, et le navigateur le prouve (voir la regle
       ".entete" ci-dessus dans ce meme bloc media pour la preuve et le
       correctif). Le plafond individuel de ce panneau reste neutralise
       (width/max-height/overflow-y), mais desormais pour la bonne raison :
       ".entete" porte lui-meme le defilement borne, un second plafond ici
       ne ferait qu'imbriquer deux zones de defilement l'une dans l'autre
       sans aucun benefice. Meme specificite (0,1,0) que les regles de
       largeur/hauteur ci-dessus, tranchee par l'ordre des regles (cette
       regle est plus bas). */
    .megamenu--col-1, .megamenu--col-2, .megamenu--col-3 {
        width: auto; max-height: none; overflow-y: visible;
    }
    /* Colonne unique en accordeon mobile : la grille multi-colonnes du
       panneau flottant de bureau (.megamenu__liste--col-1/2/3, plus haut)
       n'a de sens que sur un panneau large -- ici le panneau est un simple
       prolongement vertical, en retrait, de la liste de navigation. */
    .megamenu__liste--col-1, .megamenu__liste--col-2, .megamenu__liste--col-3 {
        grid-template-columns: 1fr;
    }
    /* Aucune regle de survol ici : voir la note de specificite en tete de
       fichier ("PIEGE DE SPECIFICITE CORRIGE (refonte en-tete/pied,
       tache 2)"). La visibilite du panneau sous 1024px ne depend plus que
       de ".megamenu--ouvert" (clic, front.js) et ":focus-within" (clavier,
       contrat n°5, sans JavaScript) -- jamais d'un ":hover" qui n'ouvrait
       rien mais pouvait fermer ce que le clic venait d'ouvrir. */
    .megamenu--ouvert { display: block; }
    .nav__element:focus-within > .megamenu { display: block; }
}

@media (min-width: 1024px) {
    /* TROIS cartes par rangee sur grand ecran, et non quatre : chacune gagne
       environ un tiers de largeur, donc une photo plus grande et un nom de
       produit qui tient sur moins de lignes. Les noms sont longs sur ce
       catalogue (« Four multifonction et systeme HydroClean en 60 cm »), et
       a quatre colonnes ils passaient systematiquement sur trois lignes.
       Espacement d'un palier au-dessus du 1,25rem herite du seuil 768px : le
       grand ecran a la place. */
    .grille-produits { grid-template-columns: repeat(3, 1fr); gap: var(--espace-5); }
    /* minmax(0, 1fr) (relecture finale, 6/7) : voir la note de convention
       sur .catalogue plus haut. */
    .catalogue { grid-template-columns: 280px minmax(0, 1fr); gap: var(--espace-6); }
    /* Le panneau SUIT le defilement. Sur une liste de vingt-quatre produits,
       un panneau fige en haut oblige a remonter pour chaque changement de
       critere.
       top : la hauteur de l'en-tete collant (76px, 58px une fois defile) plus
       une marge -- sans elle, le panneau se glisserait dessous.

       PLUS DE max-height NI D'overflow-y. La version precedente bornait la
       hauteur du panneau et lui donnait sa propre barre de defilement, pour
       qu'un panneau plus haut que la fenetre garde ses derniers criteres
       atteignables. Le remede se voyait : une barre grise a l'interieur du
       panneau, sur un ecran ou il ne manquait qu'une centaine de pixels.

       La cause etait ailleurs -- un panneau colle NE PEUT PAS depasser la
       fenetre. On ne le colle donc que lorsqu'il y a la place : le panneau
       mesure 631px, plus 76px d'en-tete et deux marges, soit environ 770px.
       En dessous de 800px de hauteur de fenetre il redevient un bloc normal,
       qui defile avec la page : rien n'est inatteignable, et il n'y a plus
       de barre nulle part. */
    .catalogue > aside { align-self: start; }

    @media (min-height: 800px) {
        .catalogue > aside {
            position: sticky;
            top: calc(76px + var(--espace-4));
        }
    }
    /* Formulaire a gauche, recapitulatif du panier a droite -- le recap
       garde une largeur fixe, comme .formulaire-grille__lateral cote
       back-office (admin.css). */
    /* minmax(0, 1fr) (relecture finale, 7/7) : voir la note de convention
       sur .catalogue plus haut. */
    .reservation { grid-template-columns: minmax(0, 1fr) 340px; align-items: start; }
}

/* prefers-reduced-motion : bloc unique, en fin de fichier, qui neutralise
   TOUTES les durees d'animation/transition du fichier (le selecteur "*"
   couvre les transitions de transform au meme titre que celles de couleur
   ou d'opacite). Volontairement PAS de "transform: none" general ici :
   plusieurs transforms de ce fichier codent un ETAT (ex. la position
   ouverte du tiroir du panier), pas seulement une animation -- les figer
   a "none" romprait leur affichage final au lieu de simplement supprimer
   le mouvement. Mettre la duree a .01ms conserve l'etat final correct
   tout en rendant la transition imperceptible. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
        /* CONSTAT DE RELECTURE FINALE : sans ceci, un delai pose en ligne par
           front.js (le decalage progressif de la revelation au defilement,
           jusqu'a 360ms sur la 6e carte d'une grille) survit a la reduction
           de duree ci-dessus -- un utilisateur ayant demande moins de
           mouvement attendait quand meme jusqu'a 360ms par carte avant
           qu'elle n'apparaisse. !important l'emporte sur le style en ligne
           non important pose par element.style.transitionDelay. */
        transition-delay: 0s !important;
        scroll-behavior: auto !important;
    }
}

/* ------------------------------------------------ temoignages (accueil) */

/* BANDE SOMBRE PLEINE LARGEUR, comme la banniere du haut : la page d'accueil
   commence sur --encre-profonde et se referme dessus, les produits et les
   marques restant sur blanc entre les deux. Le bloc sort du .conteneur (c'est
   son contenu interieur qui le porte) -- meme construction que .banniere.

   TOUS les rapports cites plus bas sont CALCULES sur le pire cas : le fond le
   plus clair possible, c'est-a-dire les deux halos oranges superposes a leur
   maximum. Ailleurs sur la bande, sans halo, ils sont meilleurs. Fonds de
   reference : rgb(98,49,26) pour la bande, rgb(107,61,40) pour une carte,
   rgb(117,74,53) pour une carte survolee. */
.temoignages {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    margin: var(--espace-8) 0 0;
    padding: clamp(3.5rem, 7vw, 6rem) 0;
    background: var(--encre-profonde);
    color: #FFFFFF;
}

/* Couche de decor SEPAREE du fond de la section, et c'est ce qui rend le
   parallaxe possible : on translate cette couche sans jamais toucher au fond
   uni, donc aucune bande vide ne peut apparaitre en haut ou en bas.
   inset: -20% 0 -- elle deborde volontairement de 20% de part et d'autre pour
   absorber la translation. z-index: -1 avec isolation: isolate sur le parent :
   elle passe au-dessus du fond de la section et sous son contenu. */
.temoignages__decor {
    position: absolute;
    inset: -20% 0;
    z-index: -1;
    background:
        radial-gradient(70% 65% at 6% 12%, rgba(241, 100, 17, .26), transparent 70%),
        radial-gradient(60% 60% at 94% 88%, rgba(241, 100, 17, .16), transparent 70%);
    will-change: transform;
}

/* Le titre reprend .section__entete -- meme balise, meme barre d'accent que
   « Nos produits » et « Nos marques ». Seule la couleur change, la bande
   etant sombre : blanc pur, 10,64:1 sur le halo le plus dense. */
.temoignages .section__entete h2 { color: #FFFFFF; }

.temoignages__intro {
    max-width: 52ch;
    margin: -.25rem 0 var(--espace-6);
    /* Blanc a 75% sur la bande : 6,78:1. */
    color: rgba(255, 255, 255, .75);
    font-size: var(--texte-lg);
}

/* La piste defile NATIVEMENT : overflow-x + scroll-snap. Aucun calcul de
   position en JavaScript, donc rien ne se desynchronise ; le doigt et la
   molette fonctionnent sans script ; le clavier la traverse grace au
   tabindex de la vue ; et le navigateur gere l'inertie et l'accrochage bien
   mieux qu'un calcul maison. */
.temoignages__cadre { position: relative; }

.temoignages__piste {
    display: flex;
    gap: var(--espace-4);
    margin: 0;
    /* Le retrait vertical laisse la place a l'ombre portee et surtout au
       soulevement de 8px au survol, qu'overflow rognerait sinon. */
    padding: var(--espace-3) 0 var(--espace-5);
    list-style: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* PAS de scroll-padding : la piste n'a aucun retrait horizontal, donc un
       scroll-padding-left decalait le point d'accrochage de 8px vers la
       gauche. Mesure : apres un clic sur « suivant », la carte s'arretait a
       +8px du bord et laissait depasser un lisere de la precedente, alors que
       la grille de produits plus haut commence a 0. */
    scroll-behavior: smooth;
    /* La barre de defilement est masquee : les fleches et le geste tactile
       suffisent, et une barre grise sous des cartes casse le bloc. Le
       defilement lui-meme reste possible -- on cache la barre, jamais la
       fonction. */
    scrollbar-width: none;
}
.temoignages__piste::-webkit-scrollbar { display: none; }

.temoignages__piste:focus-visible { outline-offset: -3px; border-radius: var(--r-l); }

/* flex-basis en pourcentage : trois cartes visibles sur grand ecran, une
   seule sur telephone (paliers juste en dessous). flex-shrink: 0 --
   sans lui, les cartes se compressent pour tenir toutes dans le cadre et
   plus rien ne defile. */
.temoignages__element { flex: 0 0 100%; scroll-snap-align: start; min-width: 0; }

/* LES PALIERS SONT ICI, et non dans les blocs @media generaux plus haut dans
   ce fichier : ils y etaient, et ils ne servaient a rien. La regle de base
   ci-dessus utilise le raccourci "flex", qui ecrit aussi flex-basis ; a
   specificite egale (0,1,0), c'est la DERNIERE regle du fichier qui l'emporte,
   et elle etait plus bas. Resultat mesure a 1440px : une seule carte de 1248px
   au lieu de trois. Un media query ne bat pas l'ordre de la cascade.
   Le calc retire la gouttiere : sans lui, trois cartes a 33,333% plus les deux
   gaps depassent du cadre, et l'accrochage cale sur une carte coupee. */
@media (min-width: 768px) {
    .temoignages__element { flex-basis: calc(50% - var(--espace-4) / 2); }
}

@media (min-width: 1024px) {
    .temoignages__element { flex-basis: calc(33.333% - var(--espace-4) * 2 / 3); }
}

/* CARTE DE VERRE. Le voile blanc a 6% sur la bande sombre donne rgb(93,56,42)
   au pire (sous le halo) : blanc pur a 9,04:1, et 7,53:1 une fois la carte
   survolee et eclaircie a 12%. La bordure et l'ombre sont decoratives -- ce
   qui delimite le temoignage pour un lecteur d'ecran, c'est <figure>, pas un
   lisere.
   height: 100% : les temoignages n'ont pas la meme longueur, sans cela les
   cartes d'une meme rangee auraient des hauteurs differentes et l'accrochage
   les ferait sautiller. */
.temoignage {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
    margin: 0;
    padding: var(--espace-6) var(--espace-5) var(--espace-5);
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .14);
    /* --r-l, PAS --r-xl : ce jeton n'existe pas (:root ne declare que
       --r-s / --r-m / --r-l / --r-plein). Ecrit ainsi, border-radius etait
       invalide et les cartes avaient des angles droits -- vu au rendu, pas
       dans le code. */
    border-radius: var(--r-l);
    box-shadow: 0 20px 45px -28px rgba(0, 0, 0, .95);
    transition: transform var(--duree-normale) var(--easing-sortie),
                background var(--duree-normale) var(--easing-sortie),
                border-color var(--duree-normale) var(--easing-sortie),
                box-shadow var(--duree-normale) var(--easing-sortie);
}

/* Le survol porte sur la CARTE et non sur l'element de liste : declenche
   depuis le <li>, le soulevement partirait des que le curseur entre dans la
   gouttiere, et la carte bougerait sans qu'on la survole vraiment. */
.temoignage:hover {
    transform: translateY(-8px);
    background: rgba(255, 255, 255, .12);
    border-color: rgba(241, 100, 17, .55);
    box-shadow: 0 28px 55px -26px rgba(0, 0, 0, 1);
}

/* Grand guillemet decoratif. inset-inline-start et non "left" : sur un
   temoignage arabe (dir="rtl") il doit passer a droite, la ou commence la
   lecture. aria-hidden dans la vue, donc aucun seuil de contraste ne s'y
   applique -- c'est une texture, pas une information. */
.temoignage__guillemet {
    position: absolute;
    top: .1rem;
    inset-inline-start: var(--espace-5);
    color: var(--orange);
    font-size: 4.5rem;
    font-weight: 700;
    line-height: 1;
    opacity: .5;
    transition: opacity var(--duree-normale) var(--easing-sortie),
                transform var(--duree-normale) var(--easing-sortie);
}
.temoignage:hover .temoignage__guillemet { opacity: .9; transform: scale(1.1); }

/* Blanc pur : 9,04:1 au repos, 7,53:1 sur une carte survolee. */
.temoignage__texte {
    margin: 0 0 var(--espace-5);
    padding: 0;
    border: 0;
    color: #FFFFFF;
    font-size: var(--texte-lg);
    line-height: 1.65;
}

/* margin-top: auto pousse la signature en bas de la carte quelle que soit la
   longueur du texte : les cinq signatures s'alignent alors sur une meme ligne
   malgre des temoignages de tailles differentes. */
.temoignage__auteur {
    display: flex;
    align-items: center;
    gap: var(--espace-3);
    margin-top: auto;
}

/* Pastille a l'initiale : une identite visuelle sans photo inventee. Lettre
   --encre sur --orange, 5,55:1. Le degrade va vers --orange-survol, plus
   clair, ce qui ne fait qu'ameliorer le rapport sur sa moitie. */
.temoignage__initiale {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 42px; height: 42px;
    border-radius: var(--r-plein);
    background: linear-gradient(135deg, var(--orange), var(--orange-survol));
    color: var(--encre);
    font-size: var(--texte-lg);
    font-weight: 700;
    line-height: 1;
}

/* Blanc a 75% : 5,92:1 au repos, 5,09:1 sur une carte survolee -- au-dessus
   du seuil de 4,5:1 dans les deux etats. */
.temoignage__nom {
    color: rgba(255, 255, 255, .75);
    font-size: var(--texte-sm);
    font-weight: 600;
}

.temoignages__commandes { display: flex; gap: var(--espace-2); justify-content: flex-end; margin-top: var(--espace-2); }
/* Le serveur pose [hidden] sur les commandes ; front.js le retire quand il les
   branche. SANS CETTE REGLE le masquage ne prendrait pas : le display: none que
   le navigateur attache a [hidden] vient de sa feuille par defaut, et le
   "display: flex" ci-dessus, ecrit par l'auteur, l'emporte sur elle. Piege deja
   rencontre deux fois dans ce fichier. */
.temoignages__commandes[hidden] { display: none; }

/* 44px : cible tactile, comme tous les controles du site. Bordure blanche a
   45%, soit 3,48:1 sur la bande -- au-dessus du seuil de 3:1 des composants.
   Une bordure a 40% n'aurait donne que 2,90:1 : calcule, pas estime. */
.temoignages__bouton {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px;
    border: 1px solid rgba(255, 255, 255, .45);
    border-radius: var(--r-plein);
    background: transparent;
    color: #FFFFFF;
    cursor: pointer;
    transition: background var(--duree-rapide) var(--easing-sortie),
                border-color var(--duree-rapide) var(--easing-sortie),
                transform var(--duree-rapide) var(--easing-sortie);
}
.temoignages__bouton:hover { background: var(--orange); border-color: var(--orange); color: var(--encre); transform: scale(1.08); }
.temoignages__bouton[disabled] { opacity: .35; cursor: default; }
.temoignages__bouton[disabled]:hover { background: transparent; border-color: rgba(255, 255, 255, .45); color: #FFFFFF; transform: none; }
.temoignages__bouton svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* MOUVEMENT REDUIT. Le bloc @media general plus haut neutralise deja les
   transitions et le defilement doux ; il reste a couper ce qui est propre a ce
   bloc : le parallaxe (front.js ne le branche pas non plus -- ceci n'est qu'une
   seconde barriere, au cas ou la preference changerait apres le chargement) et
   les agrandissements au survol. */
@media (prefers-reduced-motion: reduce) {
    .temoignages__decor { transform: none !important; }
    .temoignage:hover { transform: none; }
    .temoignage:hover .temoignage__guillemet { transform: none; }
    .temoignages__bouton:hover { transform: none; }
}

/* ===================================================================
   BOITES DE DIALOGUE ET BANDEAUX (voir public/assets/js/dialogue.js)

   L'equivalent de SweetAlert, ecrit ici parce que la politique de
   securite du site interdit tout chargement externe : une feuille
   servie par un CDN ne se chargerait pas, et les boites s'afficheraient
   sans le moindre style au milieu de la page.

   CONTRASTES CALCULES (jamais estimes) :
     titre --encre sur --surface           17,74:1
     texte --texte-doux sur --surface       6,38:1
     pastille --danger sur son fond pale    6,36:1
     pastille --succes sur son fond pale    5,21:1
     pastille --alerte sur #FFFBEB          4,84:1
     pastille d'information : --encre sur --orange-clair, 16,05:1
       -- et surtout PAS --orange sur --orange-clair, qui ne donne que
       2,89:1 et echoue meme le seuil de 3:1 des composants. Ce couple
       est deja signale comme interdit en tete des deux feuilles.
     voile rgba(11,17,32,.55) : la boite blanche s'en detache a 4,18:1
   =================================================================== */

.dlg {
    position: fixed;
    inset: 0;
    z-index: var(--z-survol);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--espace-4);
    background: rgba(11, 17, 32, .55);
    opacity: 0;
    transition: opacity 180ms ease-out;
}
.dlg--entre { opacity: 1; }
.dlg--sort { opacity: 0; }

/* overflow-y: auto sur la BOITE et non sur le voile : sur un telephone
   en paysage, un message long depassait l'ecran sans qu'aucun
   defilement ne soit possible -- les deux boutons devenaient
   inatteignables. */
.dlg__boite {
    width: 100%;
    max-width: 26rem;
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    padding: var(--espace-6) var(--espace-5) var(--espace-5);
    border-radius: var(--r-l);
    background: var(--surface);
    box-shadow: 0 24px 60px -20px rgba(11, 17, 32, .45);
    text-align: center;
    transform: scale(.94) translateY(8px);
    transition: transform 200ms cubic-bezier(.16, 1, .3, 1);
}
.dlg--entre .dlg__boite { transform: none; }
.dlg--sort .dlg__boite { transform: scale(.96); }

/* La pastille est la seule chose coloree de la boite : c'est elle qui
   dit en un coup d'oeil s'il s'agit d'une question anodine ou d'une
   suppression. Le titre, lui, reste toujours en --encre -- un titre
   rouge sur une boite deja rouge ne se lit pas mieux, il crie. */
.dlg__icone {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    margin: 0 auto var(--espace-4);
    border-radius: var(--r-plein);
}
.dlg__icone svg { width: 28px; height: 28px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.dlg__icone--danger { background: var(--danger-pale, #FEF2F2); color: var(--danger); }
.dlg__icone--info { background: var(--orange-clair); color: var(--encre); }

.dlg__titre { margin: 0 0 var(--espace-2); font-size: var(--texte-lg); line-height: 1.3; }
.dlg__texte { margin: 0; color: var(--texte-doux); font-size: var(--texte-sm); line-height: 1.6; }

/* L'ordre du DOM est deja le bon : « Annuler » d'abord -- donc premier
   au clavier et premier a recevoir le focus, pour qu'une touche Entree
   pressee par reflexe devant une suppression definitive ne la declenche
   pas -- puis l'action, qui se retrouve naturellement A DROITE comme
   partout ailleurs sur le site.
   Une premiere version ajoutait « flex-direction: row-reverse » en
   croyant remettre l'action a droite : elle l'a mise a GAUCHE. Mesure
   au navigateur, en comparant les positions reelles des deux boutons. */
.dlg__actions {
    display: flex;
    justify-content: center;
    gap: var(--espace-2);
    margin-top: var(--espace-5);
}
.dlg__actions .btn { min-width: 7rem; }

/* --------------------------------------------------- bandeaux */

/* Ancree en haut a droite, sous la barre haute de l'administration.
   pointer-events: none sur la PILE, rétabli sur chaque bandeau : sans
   cela, la colonne invisible interceptait les clics sur toute la
   hauteur du cote droit de l'ecran. */
.notifs {
    position: fixed;
    top: var(--espace-4);
    right: var(--espace-4);
    z-index: var(--z-survol);
    display: flex;
    flex-direction: column;
    gap: var(--espace-2);
    width: min(24rem, calc(100vw - 2rem));
    pointer-events: none;
}

.notif {
    display: flex;
    align-items: flex-start;
    gap: var(--espace-3);
    padding: var(--espace-3) var(--espace-4);
    border: 1px solid var(--bordure);
    border-left-width: 4px;
    border-radius: var(--r-m);
    background: var(--surface);
    box-shadow: 0 12px 28px -14px rgba(11, 17, 32, .35);
    pointer-events: auto;
    opacity: 0;
    transform: translateX(12px);
    transition: opacity 200ms ease-out, transform 200ms cubic-bezier(.16, 1, .3, 1);
}
.notif--entre { opacity: 1; transform: none; }

.notif__icone { display: grid; place-items: center; flex-shrink: 0; width: 20px; height: 20px; margin-top: 1px; }
.notif__icone svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.notif__texte { margin: 0; flex: 1; font-size: var(--texte-sm); line-height: 1.5; color: var(--encre); }

.notif__fermer {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 24px; height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--r-s);
    background: transparent;
    color: var(--texte-doux);
    cursor: pointer;
}
.notif__fermer:hover { color: var(--encre); }
.notif__fermer svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }

/* Le lisere gauche porte la couleur ; le texte reste en --encre. Un
   bandeau entierement colore rend son message moins lisible, pas plus. */
.notif--succes { border-left-color: var(--succes); }
.notif--succes .notif__icone { color: var(--succes); }
.notif--erreur, .notif--danger { border-left-color: var(--danger); }
.notif--erreur .notif__icone, .notif--danger .notif__icone { color: var(--danger); }
.notif--avertissement { border-left-color: var(--alerte); }
.notif--avertissement .notif__icone { color: var(--alerte); }
.notif--info { border-left-color: var(--orange); }
.notif--info .notif__icone { color: var(--encre); }

@media (max-width: 600px) {
    /* Sur telephone la pile occupe toute la largeur et vient d'en haut :
       collee a droite, elle masquait la moitie de la barre haute. */
    .notifs { left: var(--espace-3); right: var(--espace-3); width: auto; }
}

@media (prefers-reduced-motion: reduce) {
    .dlg, .dlg__boite, .notif { transition: none; }
    .dlg__boite { transform: none; }
    .notif { transform: none; }
}

/* ------------------------------------------- promotion a duree limitee */

/* Le prix barre : <s> dans les vues, donc l'element DIT deja que ce prix ne
   s'applique plus -- un lecteur d'ecran l'annonce, la ligne n'est que le
   pendant visuel. --texte-doux : 6,38:1 sur --surface, 5,60:1 sur
   --fond-doux, au-dessus du seuil de 4,5:1 dans les deux cas. */
.carte-produit__prix-barre,
.produit__prix-barre {
    color: var(--texte-doux);
    font-size: var(--texte-sm);
    font-weight: 500;
    text-decoration: line-through;
}

/* Etiquette de remise : blanc sur --danger, 6,96:1. Le rouge est reserve a
   CE role sur le site public -- il n'y sert a rien d'autre, donc il ne peut
   pas etre confondu avec un message d'erreur. */
.produit__remise {
    display: inline-flex;
    align-items: center;
    padding: .125rem .4375rem;
    border-radius: var(--r-s);
    background: var(--danger);
    color: #FFFFFF;
    font-size: var(--texte-sm);
    font-weight: 700;
    white-space: nowrap;
}

/* Le compte a rebours. --danger sur son fond pale : 6,36:1.
   font-variant-numeric: tabular-nums -- SANS lui, la largeur de l'horloge
   change a chaque seconde (le 1 est plus etroit que le 8 dans Inter) et
   tout le bloc tremble une fois par seconde, juste sous le prix. */
.promo-fin {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .375rem;
    margin: var(--espace-2) 0 0;
    padding: .1875rem .5rem;
    border-radius: var(--r-s);
    background: #FEF2F2;
    color: var(--danger);
    font-size: var(--texte-xs);
    font-weight: 600;
}
.promo-fin time { font-weight: 500; }

/* Quand le script prend la main, il ajoute .promo-fin--vivant et un
   <span class="promo-fin__horloge"> : la date en clair rendue par le
   serveur passe alors en retrait, sans disparaitre -- elle reste
   l'information exacte, l'horloge n'en est que la lecture urgente. */
.promo-fin--vivant time { opacity: .75; font-size: .9em; }
.promo-fin__horloge {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    letter-spacing: .01em;
}

/* Sur la fiche, le bloc respire davantage : c'est la que se prend la
   decision d'acheter. */
.promo-fin--fiche {
    margin-top: var(--espace-3);
    padding: var(--espace-2) var(--espace-3);
    font-size: var(--texte-sm);
}
.promo-fin__intitule { font-weight: 600; }
.promo-fin--fiche .promo-fin__horloge { font-size: var(--texte-lg); }

/* Le prix retenu dans le panier, avec l'ancien barre en dessous. */
.panier__prix-barre { color: var(--texte-doux); font-size: var(--texte-sm); }

/* Rubrique « Promotions » de la barre de navigation.

   Le libelle reste --encre comme les autres rubriques : le rouge est deja
   porte par la pastille, le repeter sur le mot ferait crier une entree de
   menu au milieu de ses voisines. C'est la pastille qui attire l'oeil, et
   elle porte l'information utile -- combien.

   Blanc sur --danger : 6,96:1. */
.nav__pastille {
    display: inline-grid;
    place-items: center;
    min-width: 1.125rem;
    height: 1.125rem;
    margin-inline-start: .25rem;
    padding: 0 .25rem;
    border-radius: var(--r-plein);
    background: var(--danger);
    color: #FFFFFF;
    font-size: .6875rem;
    font-weight: 700;
    line-height: 1;
    /* Les chiffres ne doivent pas changer la largeur de la pastille entre
       « 1 » et « 8 » : sur une barre de navigation, un element qui respire
       decale tous ses voisins. */
    font-variant-numeric: tabular-nums;
}

/* La rubrique reste alignee sur ses voisines : c'est un lien de menu, pas
   un bouton. Seule la pastille le distingue. */
/* Les deux rubriques a pastille alignent leur compteur sur le libelle.
   Une seule regle pour les deux : elles ne different que par leur nom. */
.nav__lien--promo,
.nav__lien--destockage { display: inline-flex; align-items: center; }

/* ------------------------------------ colonne de resultats en cours de chargement */

/* Les filtres s'appliquent sans recharger la page (voir front.js). Pendant
   la requete, la colonne s'attenue et cesse de repondre au clic : sans ce
   retour, un reseau lent donne l'impression que le filtre n'a rien fait, et
   on reclique.
   Pas de sablier ni de squelette : les resultats precedents restent
   lisibles, ce qui vaut mieux qu'un vide le temps de l'aller-retour.
   aria-busy est pose en meme temps par le script -- c'est lui qui porte
   l'information pour un lecteur d'ecran, l'opacite ne dit rien. */
[data-resultats] {
    transition: opacity var(--duree-rapide) var(--easing-sortie);
}
.resultats--charge {
    opacity: .5;
    pointer-events: none;
}

/* --------------------------------- controles remplaces par l'automatisme */

/* front.js pose [hidden] sur ces deux controles quand il branche
   l'application automatique des filtres. SANS CES DEUX REGLES le masquage
   ne prend pas : le display: none que le navigateur attache a [hidden] vient
   de SA feuille par defaut, et un "display: flex" ecrit par l'auteur
   l'emporte sur elle.

   Ce n'est pas une precaution : c'est un defaut mesure. Les deux boutons
   portaient bien hidden=true et occupaient toujours 77px et 44px de hauteur
   a l'ecran. C'est la TROISIEME fois que ce piege se referme dans ce
   fichier -- voir .entete__panier-pastille et .temoignages__commandes. */
.filtres__actions[hidden] { display: none; }
.barre-tri [hidden] { display: none; }

/* ------------------------------------------- groupes de sous-categorie */

/* Le catalogue est regroupe par sous-categorie (voir
   CatalogueFiltre::ordre()). Chaque groupe est une <section> avec son
   titre, et sa propre grille -- les cartes d'un groupe ne doivent pas
   s'aligner sur celles du groupe suivant, sinon la coupure ne se voit plus.

   Le premier groupe n'a pas de marge haute : il touche deja la barre de tri
   au-dessus de lui. */
.groupe-produits + .groupe-produits { margin-top: var(--espace-7); }

/* Meme signature que les titres de section du site -- barre d'accent
   orange, aligne a gauche. Le trait sous le titre separe visuellement deux
   groupes successifs sans ajouter de cadre.
   --encre sur --fond : 16,86:1. */
.groupe-produits__titre {
    position: relative;
    margin: 0 0 var(--espace-4);
    padding: 0 0 var(--espace-3) 1.0625rem;
    border-bottom: 1px solid var(--bordure);
    color: var(--encre);
    font-size: var(--texte-xl);
}
.groupe-produits__titre::before {
    content: '';
    position: absolute;
    left: 0; top: .18em;
    height: 1em;
    width: 5px;
    border-radius: var(--r-plein);
    background: linear-gradient(var(--orange), var(--orange-survol));
}

/* ---------------------------------------- reference produit dans le panier */

/* La reference se lit sous le nom, en retrait : c'est une information de
   verification -- on la dicte au telephone, on la recopie sur un bon de
   commande -- pas ce qu'on lit en premier.
   Chasse tabulaire : deux references l'une sous l'autre s'alignent chiffre
   par chiffre, ce qui rend une difference immediatement visible.
   --texte-doux sur --surface : 6,38:1, deja documente. */
.panier__identite { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.panier__reference,
.reservation__reference,
.tiroir-panier__reference {
    display: block;
    color: var(--texte-doux);
    font-size: var(--texte-xs);
    font-variant-numeric: tabular-nums;
}
