/*
Theme Name: VSA Codepromo
Theme URI: https://codepromo.voyagesansagence.com/
Description: Theme autonome de codepromo.voyagesansagence.com, ecrit pour remplacer ClipMyDeals 8.6.13. Il ne reprend que les fonctions reellement utilisees : les offres, les boutiques, les categories, leur administration et leur affichage. Charte du site : terracotta #C7501E, creme #fff8f2, police Assistant, aucune animation. Le contenu est rendu cote serveur, dans sa forme finale, donc lisible par les moteurs des la premiere lecture.
Author: Voyage sans agence
Version: 1.20.0
Requires at least: 6.0
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: vsa-codepromo
*/

/* =========================================================================
   CHANGELOG
     1.20.0 LES ANCIENNES ADRESSES D'OFFRE REDIRIGENT, ET LA DECISION D9 EST
            CORRIGEE. Elle posait qu'aucune redirection n'etait necessaire, ces
            adresses n'ayant jamais servi en ligne. Les journaux disent autre
            chose : Bingbot a demande /coupons/offre-halloween-30/.
            L'explication tient a nos propres donnees structurees. Jusqu'au
            18/08, la liste ItemList publiait une url en /coupons/{slug}/ pour
            CHAQUE offre de chaque page : les moteurs les ont relevees pendant
            des mois. Elles ont bel et bien vecu, annoncees par le site lui-meme.
            Elles rejoignent donc /cpn/, /str/ et /prd/ : 301 vers la fiche de la
            boutique avec l'ancre de l'offre, et seulement si l'offre existe.
     1.19.0 DEUX POINTS DE REFERENCEMENT RELEVES A L'EXPLORATION.
            - DUPLICATION PAR PERMUTATION DE PARAMETRES sur /site/. Les quatre
              liens de filtre etaient composes chacun a leur maniere : tantot
              « ?actives=1&famille=transport », tantot l'inverse. Deux adresses
              pour un contenu identique, soit huit paires de doublons et
              vingt-trois adresses la ou sept suffisent. L'adresse canonique
              protegeait de l'indexation, pas du gaspillage d'exploration.
              L'ordre est desormais impose par une fonction unique : la famille
              d'abord, l'etat des offres ensuite.
            - LE POIDS DES PAGES DE CATEGORIE. Le champ « visuel de l'offre »
              contient souvent l'adresse du fichier PLEINE TAILLE : une image de
              1 600 px pour une vignette rendue a 82 px. La page
              /bons-plans/transport/ pesait 3 Mo. Le theme sert desormais la
              taille « medium » quand le fichier appartient a la mediatheque.
              Une adresse externe est rendue telle quelle : on ne peut rien pour
              elle. La recherche est faite UNE FOIS par adresse et par requete,
              pour ne pas remplacer un probleme de poids par un probleme de
              lectures en base.
     1.18.0 - RETOUR A LA GRILLE A COLONNES FIXES sur la page des categories.
              La version de la 1.17.0, qui etirait les tuiles pour combler la
              rangee, est ecartee : deux tuiles larges comme des banderoles
              disaient moins bien ce qu'elles etaient. Une famille de deux
              sous-categories montre donc deux tuiles de taille normale, et la
              rangee n'est pas remplie.
            - NOUVELLE ZONE « Colonne, encart d'annonce », affichee a droite du
              modele « Toutes les categories ». Elle ne contient QUE ce qu'on y
              pose : ni categories, ni boutiques voisines, donc aucun doublon
              avec le contenu de la page.
              SA LARGEUR EST FIXE, 300 px, avec 250 px de hauteur reservee. Un
              encart arrive souvent apres le reste de la page, depuis un autre
              domaine : sans espace pris d'avance, la page se reorganiserait
              sous les yeux du visiteur au moment ou il apparait. 300 px est la
              largeur des formats courants, du 300x250 au 300x600.
              Sous 992 px, l'encart passe sous le contenu, centre.
     1.17.1 ERREUR FATALE A L'ENREGISTREMENT D'UNE BOUTIQUE OU D'UNE CATEGORIE.
            L'action « edited_{taxonomie} » de WordPress passe ( term_id, tt_id,
            args ) : le troisieme argument est un TABLEAU d'arguments, pas le
            nom de la taxonomie. Seule l'action generique « edited_term » passe
            le nom en troisieme position. J'avais suppose l'inverse.
            Sous PHP 8, chercher une cle de tableau avec un tableau pour index
            arrete l'execution : l'ecran d'administration tombait en erreur
            fatale des qu'on enregistrait un terme. Le defaut dormait depuis le
            lot 3 et ne s'est reveille qu'au premier enregistrement en
            production, le 19/08/2026.
            La taxonomie est desormais demandee au TERME lui-meme, qui la
            connait toujours, et le parametre n'est plus qu'un raccourci quand
            il se trouve etre une chaine.
     1.17.0 LA PAGE DES CATEGORIES PARAISSAIT A MOITIE VIDE. La grille a cinq
            colonnes fixes convient a une famille qui en a cinq ; avec deux
            sous-categories, elle laissait trois colonnes vides a droite. La
            page semblait incomplete alors qu'elle montrait tout ce qu'elle
            avait. Les tuiles s'etirent desormais pour occuper la rangee : deux
            tuiles font une demi-rangee chacune, six en font trois par ligne.
     1.16.2 UN PARENT DE MENU N'ETAIT PAS CLIQUABLE. Bootstrap pose l'ouverture
            du panneau sur le lien lui-meme : le clic ouvrait le menu et
            n'atteignait jamais la destination. « Bons plans » ne menait donc
            nulle part, alors qu'il pointe vers une vraie page.
            Les deux gestes sont separes : le libelle redevient un lien
            ordinaire, et un bouton chevron a cote ouvre le panneau. Deux cibles
            distinctes, atteignables au clavier, et le bouton porte un intitule
            que seul un lecteur d'ecran entend.
            Une entree dont l'adresse vaut « # » garde l'ancien comportement :
            elle n'est qu'un intitule de regroupement, il n'y a rien a atteindre.
     1.16.1 NOUVEAU SHORTCODE [vsa_categories]. La section des categories etait
            cousue dans le modele « Grille des categories d'offres » : elle
            s'affichait toujours apres le contenu, sans moyen de la remonter, de
            la descendre ou de la poser entre deux paragraphes.
            Le rendu est desormais une fonction, appelee par le shortcode ET par
            le modele : un seul endroit produit ces tuiles, elles ne peuvent pas
            se mettre a diverger selon la page qui les demande.
            Attributs : « max » pour limiter le nombre de tuiles, « titre » pour
            un intitule facultatif. Le modele existant continue de les afficher
            d'office : les pages qui l'utilisent ne changent pas.
     1.16.0 NOUVEAU MODELE DE PAGE « Toutes les categories ». Une section par
            famille, en pleine largeur, avec ses sous-categories en tuiles.
            La grille de l'accueil n'est PAS touchee : les deux usages sont
            opposes, ils ont donc deux modeles.
            - Les categories sans offre sont AFFICHEES, attenuees, avec la
              mention « Bientot ».
            - Ordre alphabetique stable, et non par nombre d'offres.
            - Pas de colonne de droite sur ce modele : le contenu est deja la
              liste exhaustive, la colonne la repeterait mot pour mot.
            - PHRASE SOUS CHAQUE FAMILLE : ni la description du terme, qui est
              une ligne de referencement, ni l'article, qui fait plus de mille
              caracteres, n'y convenaient. La page affiche un decompte factuel,
              calcule, qui ne demande aucune saisie. Un champ « Resume court »
              permet de le remplacer par une phrase, famille par famille.
     1.15.1 LES FILTRES DE L'ANNUAIRE ETAIENT COLLES AU « LIRE LA SUITE ».
            Rien ne disait ou finissait le texte et ou commencaient les
            commandes. Les deux rangees sont reunies dans un bloc annonce par un
            titre de section, « Trouver une boutique », et chaque rangee porte
            son intitule, « Univers » et « Disponibilite ». Une serie de
            pastilles sans titre obligeait a deviner ce qu'on choisit.
            Le bouton de pliage gagne par ailleurs de l'air en dessous : sans
            lui, il se lisait comme le premier element de ce qui suit.
     1.15.0 - L'ESPACE SOUS LA BARRE etait de 140 px : 48 px de « pt-5 » herite
              du balisage d'origine, ajoutes a la hauteur reservee sous la barre
              fixe. Le titre et le premier module de la colonne commencaient donc
              tres bas, sans que rien n'occupe ce vide. Ramene a 20 px, et 12 sur
              mobile.
            - NOUVEAU SHORTCODE [vsa_replier lignes="4"]. Il plie n'importe quel
              passage derriere un « Lire la suite », a l'endroit voulu dans la
              page. LE TEXTE ENTIER RESTE DANS LE DOCUMENT : il est seulement
              borne en hauteur, les moteurs le lisent en totalite. Sans
              JavaScript rien n'est plie, ce qui est le bon repli. Le bouton
              n'apparait que si le texte depasse reellement, et la mesure est
              refaite au redimensionnement.
     1.14.1 LE TITRE DES PAGES ETAIT INVISIBLE. La charte heritee masquait
            .entry-title partout : la regle avait son sens du temps de
            ClipMyDeals, qui affichait par ailleurs une banniere de titre. Le
            theme neuf n'en a pas, et le titre disparaissait donc purement et
            simplement. Constate sur « Toutes les boutiques » le 19/08/2026.
            Le montrer partout ne convenait pas davantage : sur l'accueil, le
            titre de la page est « Accueil » alors que le contenu porte deja le
            sien, ce qui donnerait deux titres et deux h1.
            Le gabarit affiche donc le titre SEULEMENT si le contenu n'en porte
            pas deja un de niveau 1. Le redacteur garde la main sans reglage a
            connaitre. Meme allure que les titres de fiche, 24 px centres.
     1.14.0 LA MIGRATION NE RECOPIAIT PAS TOUT LE DOSSIER.
            Elle se limitait a la liste fermee du theme, celle des champs qu'il
            AFFICHE. Or le dossier d'une boutique en contient d'autres, que le
            PONT ECRIT ENCORE : popular, video, map, et store_color qu'il force
            a chaque ecriture. Sa route de lecture expose d'ailleurs le dossier
            ENTIER au site principal.
            Consequence relevee le 19/08/2026 : le controle de suppression
            comptait 166 champs divergents et restait bloque. Forcer la
            suppression aurait FAIT PERDRE ces donnees.
            La migration recopie desormais toutes les cles du dossier. La liste
            fermee garde son role la ou elle en a un : ce que le theme affiche
            et ce qu'il ecrit. L'etat des lieux compte de meme toutes les cles.
     1.13.1 LES CARTES DE SOURCE DE BOOTSTRAP PRODUISAIENT DES 404.
            Les deux fichiers de Bootstrap se terminent par un commentaire qui
            annonce une carte de source, bootstrap.bundle.min.js.map et
            bootstrap.min.css.map. Ces cartes servent au deboguage et n'ont
            aucune raison d'etre en ligne : elles ne sont pas embarquees, et
            le theme actuel ne les servait pas non plus a cette adresse.
            Resultat, un outil d'audit ou une console ouverte les demandait et
            recoltait une 404. Releve le 19/08/2026 sur un passage de
            Chrome-Lighthouse.
            Les deux commentaires sont retires. Aucune ligne de code de
            Bootstrap n'est touchee, seulement la mention finale : le fichier
            reste valide, verifie.
     1.13.0 LES ANCIENNES ADRESSES DE CLIC REPONDAIENT 404.
            La decision D6 a supprime le detour /cpn/{id}/, et j'en avais conclu
            que ces adresses etaient purement internes. Les journaux du 18 au
            19/08/2026 le dementent : huit appels en treize heures, dont
            plusieurs par des NAVIGATEURS REELS et un par Googlebot. Elles
            avaient donc ete indexees ou mises en favori.
            /cpn/{id}/, /prd/{id}/ et /str/{id}/ redirigent desormais en 301
            vers la fiche de la boutique, avec une ancre vers la carte de
            l'offre. PAS vers le partenaire : cela recreerait un chemin
            d'affiliation suivable par un robot, ce que D6 supprimait.
            Et SEULEMENT si l'identifiant correspond a quelque chose : sinon la
            404 tient. Rediriger en aveugle vers l'accueil est le defaut trouve
            chez l'extension Redirection.
     1.12.1 SUR MOBILE, LA COLONNE REMONTAIT AVANT LA FICHE. La regle ajoutee en
            1.10.0 devait ne faire remonter que le module des categories ; elle
            deplacait en fait la colonne ENTIERE. Sur une fiche boutique, le
            visiteur lisait donc les autres boutiques et la liste des categories
            avant de savoir ou il etait. L'ordre naturel est retabli : la fiche
            d'abord, la colonne ensuite.
     1.12.0 LA BARRE SUR MOBILE.
            - CORRECTIF : sous 783 px, la barre d'administration de WordPress
              n'est plus fixe, elle defile avec la page. Lui reserver 46 px
              ouvrait une bande vide en haut de l'ecran, par laquelle le contenu
              defilait sous la barre.
            - CORRECTIF : la hauteur reservee sous la barre etait ecrite en dur,
              96 px et 128 sous 992. Elle depend du logo, du menu et de la barre
              d'administration : elle est desormais MESUREE. Les valeurs en dur
              restent comme repli sans JavaScript.
            - La barre s'efface pendant la descente et revient des qu'on
              remonte, sous 992 px seulement. Sur un telephone, elle occupait
              plus d'un dixieme de l'ecran en permanence. Aucune transition :
              la charte exclut les animations.
            - Un retour en haut apparait apres trois hauteurs d'ecran. Vrai
              bouton, atteignable au clavier, et retour instantane si la
              personne a demande a reduire les animations.
     1.11.2 LES COMPTEURS DE VOTE RETOMBAIENT A ZERO AU RECHARGEMENT.
            Ils sont ecrits par le SERVEUR, pour rester lisibles sans
            JavaScript. Un cache de pages fige donc le nombre au moment ou la
            page a ete engendree : le vote etait bien enregistre, l'ecran
            d'administration le montrait, mais la page publique servait sa
            version en cache. Chaque vote purge desormais les seules pages
            concernees, la fiche de la boutique et celle de la categorie, jamais
            tout le cache. Sans LiteSpeed, il ne se passe rien.
     1.11.1 LES ANTISLASHS ETAIENT PERDUS A L'ECRITURE DES METADONNEES.
            update_term_meta() applique wp_unslash() a la valeur qu'on lui donne :
            c'est la regle du coeur de WordPress. Un texte contenant un
            antislash, ce qui arrive des qu'une apostrophe est echappee,
            arrivait donc ampute cote metadonnee alors que l'ancienne option le
            conservait. Les deux cotes divergeaient sur un caractere invisible,
            et le controle de suppression de l'ancienne option ne pouvait jamais
            passer. Constate sur la fiche Expedia le 18/08/2026.
            La couche d'acces et l'outil de migration passent desormais par
            wp_slash() avant d'ecrire. Le controle de suppression indique en
            outre la POSITION du premier caractere divergent.
     1.11.0 - LA LIGNE « n codes verifies » PASSE EN FIN D'ARTICLE, dans le meme
              bloc que la presentation longue. Posee sous le titre, elle
              s'inserait entre la presentation et les offres, dans un encadre qui
              n'attendait pas de troisieme ligne.
            - LE LIEN DE SECOURS DU BOUTON DE CODE NE S'AFFICHE PLUS QU'EN
              SECOURS. En 1.10.1 il apparaissait des que le code etait revele et
              offrait un SECOND chemin de depart, celui-la en avant-plan :
              cliquer dessus emmenait chez le partenaire, soit l'inverse du
              resultat cherche. Deux mecanismes pour un meme geste, dont l'un
              defaisait l'autre. Il ne reste que si le navigateur refuse
              d'ouvrir le nouvel onglet.
            - LA RECHERCHE EST ALLEGEE : la loupe passe dans le champ, en gris,
              et ne prend la couleur d'accent qu'au survol. L'aplat terracotta
              pesait autant que le menu, pour une fonction secondaire.
     1.10.2 LE CLIC SUR UN BOUTON DE CODE, ENFIN COMME LA PRODUCTION.
            Je m'etais trompe deux fois en cherchant a ouvrir la boutique en
            arriere-plan. Le site actuel ne fait pas cela : il fait l'INVERSE.
            Releve dans inc/cmd_functions.php, lignes 1683 a 1688 : il ouvre un
            NOUVEL onglet sur la page de codepromo, rechargee avec le code
            revele, et envoie l'onglet COURANT chez le partenaire. Le nouvel
            onglet prend le focus, comme toujours, mais c'est celui qui porte le
            code. Le visiteur reste donc devant son code et la boutique s'est
            ouverte « a cote », sans qu'aucun focus n'ait ete force.
            Le theme reproduit ce tour de main : ?vsa_code={id} revele le code
            COTE SERVEUR, sans attendre le moindre script. Ces adresses sortent
            de l'index, ce sont des duplicatas. Si le navigateur bloque
            l'ouverture, la page ne quitte pas : le visiteur garde son code et
            part par le lien affiche dessous.
     1.10.1 TROIS CORRECTIFS DE RECETTE, releves sur la fiche Abritel.
            - LE BLOC « SANS OFFRE » ETAIT AFFICHE DEUX FOIS. Le module
              Alternatives le rend deja en entier ; le theme en construisait un
              second, avec la meme liste et la meme phrase. Le theme se contente
              desormais de lui indiquer OU ecrire, par une action, comme pour le
              fil d'Ariane. Le module 1.6.0 va avec.
            - LES ALTERNATIVES SERVAIENT TROIS FOIS LE MEME TYPE. A qui
              consultait une location de vacances, le module proposait trois
              hotels : la categorie exacte n'ayant aucune boutique pourvue, tout
              le renfort venait de la meme categorie soeur. Il sert maintenant
              UNE boutique par categorie avant d'en reprendre une seconde.
            - LE BOUTON DE CODE N'OUVRE PLUS RIEN TOUT SEUL. Deux tentatives
              ont echoue : aucun navigateur ne permet d'ouvrir un onglet en
              arriere-plan de facon fiable, et Chrome ramene dessus quoi qu'on
              fasse. Le premier clic revele et copie le code, sans naviguer ;
              un lien « Aller sur le site » apparait dessous et le visiteur part
              quand il le decide. Meme resultat dans tous les navigateurs.
     1.10.0 QUATRE PROPOSITIONS RETENUES, PLUS L'ENCART DU MENU.
            - Proposition 3 : une page de famille commence par ses
              sous-categories, avec leur nombre d'offres, avant d'enumerer.
              Uniquement sur un terme de premier niveau qui a des enfants.
            - Proposition 4 : les offres expirees sont repliees dans un
              <details>. Elles restent dans le document, donc lisibles par les
              moteurs et par qui les cherche, mais ne pesent plus au premier
              coup d'oeil.
            - Proposition 5 : « n codes verifies, dernier controle le … »
              sous le titre des fiches boutique ET des pages categorie. La date
              est CALCULEE a partir des verifications deja saisies : elle ne
              peut pas vieillir sans que personne ne s'en apercoive.
            - Proposition 6 : sur mobile, le module des categories remonte
              avant les offres. Meme balisage, seul l'ordre change.
            - Proposition 2, precisee en recette : une boutique sans code mais
              AVEC un cadre partenaire, une reservation d'activites par
              exemple, ne recoit plus d'alternatives. Trois cas reconnus : un
              cadre dans l'article, un module partenaire affecte, ou le cadre
              GetYourGuide pose par le module Allegements.
            - L'ENCART PASSE DANS LE PANNEAU DU MENU, en quatrieme colonne, et
            n'apparait donc qu'a l'ouverture de « Bons plans ». Il ne peut plus
            deformer la barre, qui garde sa hauteur en toutes circonstances.
            Boite fermee de 240 x 200 px, image plafonnee a 160 px de haut,
            texte a quatre lignes. Format conseille : 240 x 160 px.
     1.9.1  L'ENCART DE FIN DE MENU A DES DIMENSIONS FIXES. Il n'etait borne
            qu'en largeur : une image de 250 px de cote y entrait a 230 px de
            haut et faisait passer la barre de 91 a 263 px, repoussant logo,
            menu et recherche. C'est desormais une boite fermee de 220 x 56 px,
            image plafonnee a 44 px de haut, texte a deux lignes, et ce qui
            depasse est masque. La barre garde sa hauteur quoi que l'on y pose.
            Sous 1200 px l'encart s'efface plutot que de comprimer le menu.
            Format conseille pour un visuel : 300 x 76 px.
     1.9.0  - MENU A TROIS NIVEAUX. Bootstrap 5 ne sait pas ouvrir un menu
              deroulant DANS un menu deroulant : avec une structure
              « Bons plans > Avant le sejour > Vols », le second panneau restait
              referme et ses liens etaient invisibles. Le menu ne produit plus
              qu'UN panneau, ou les niveaux intermediaires deviennent des
              intitules de section. Tout se voit d'un coup d'oeil, sans second
              survol.
            - Le fond pose derriere les listes d'offres en 1.8.0 est retire.
            - YOAST LIT ENFIN L'ARTICLE LONG des boutiques et des categories,
              sans qu'aucune donnee ne bouge : le theme declare une extension
              a l'analyseur de Yoast, qui lui transmet le contenu du champ.
              Le score porte donc sur le texte reellement publie, et non sur
              les quelques lignes de la description courte.
     1.8.1  LE PIED DE PAGE. La structure etait deja la bonne, l'habillage
            manquait : les liens sortaient en terracotta, 16 px, sans casse et
            SOULIGNES, la ou la production sert du gris 59, 14 px, en
            majuscules et sans soulignement. Un lien de pied de page en
            terracotta attire l'oeil autant qu'un bouton d'offre, soit
            l'inverse de ce qu'un pied de page doit faire.
            La ligne de mentions garde en revanche un texte LISIBLE : la
            production la sert en blanc casse sur creme, donc invisible. C'est
            le defaut deja corrige en 1.0.2, et je ne le reproduis pas.
     1.8.0  LE RESULTAT DES VOTES, ET DIX AJUSTEMENTS DE RECETTE.
            - VOTES : les visiteurs votaient, rien ne remontait. La liste des
              offres gagne une colonne « Avis » avec le pourcentage et le
              detail, un tri sur les avis defavorables, et un filtre
              « A verifier » qui isole les offres ayant recu au moins trois
              avis dont une majorite de « non ». Le seuil evite qu'un clic
              malheureux ne fasse remonter une offre saine.
            - FICHE BOUTIQUE : plus de fond creme derriere le logo et le
              bouton, le logo porte deja son cadre blanc.
            - Les listes d'offres sont posees sur une bande creme : les cartes
              blanches s'en detachent sans ombre ni animation, deux couleurs
              deja au catalogue.
            - L'espace entre l'encadre de titre et les offres passe de 48 a
              18 px : ce vide ne portait rien.
            - ARTICLES ET FAQ : une seule echelle. Les questions en h3
              s'affichaient plus GRANDES que le titre de section en h2, ce qui
              inversait la hierarchie. Couleur, taille et graisse unifiees.
            - Les liens du contenu redactionnel ne sont plus soulignes.
            - CODE PROMO : un seul clic revele, copie et ouvre la boutique
              A COTE, sans quitter la page. Le bloc entier est la cible, la ou
              « Copier » ne faisait qu'une dizaine de pixels de haut.
            - RECHERCHE : elle cherche desormais dans les boutiques et les
              categories, la ou les offres sont visibles. Elle ne renvoyait
              plus rien depuis que les offres n'ont plus d'adresse propre.
            - ANNUAIRE : filtre par famille de premier niveau, cumulable avec
              le filtre existant.
            - MENU : une zone de widgets en fin de barre, vide par defaut,
              pour une offre ou une annonce.
     1.7.3  Le logo d'une boutique reprend sa largeur naturelle, plafonnee a
            celle de sa colonne. width:100% l'etirait des que l'ecran devenait
            etroit : 446 px au lieu des 300 px servis par la production.
     1.7.2  Les connexions anticipees ne sont plus annoncees en aveugle. Le
            module « Allegements » sert Font Awesome en LOCAL et neutralise la
            feuille distante en vidant sa source : le theme annoncait donc une
            connexion vers use.fontawesome.com pour une requete qui n'a jamais
            lieu, soit une resolution DNS pour rien. Il verifie desormais que
            la source tient encore, apres le passage du module.
     1.7.1  Notice seule : l'ordre des operations renvoie desormais au bouton de
            controle ajoute au pont 2.7.0, qui prouve l'ecriture double sans
            passer par le site principal ni par une ligne de commande.
     1.7.0  ECRITURE DOUBLE, SUPPRESSION DE L'ANCIENNE OPTION, PERFORMANCE.
            - Les cinq composants sont livres a part, adaptes : le pont ecrit
              desormais des DEUX cotes, les quatre autres lisent la metadonnee
              d'abord et l'ancienne option en repli. Aucun ne depend du theme :
              chacun garde un repli autonome.
            - OUTIL DE SUPPRESSION de l'ancienne option, dans Outils > Champs
              de terme. Il ne se debloque QUE si le controle passe : chaque
              champ non vide de chaque dossier doit etre identique des deux
              cotes, toutes cles confondues et pas seulement celles de la liste
              fermee. Une sauvegarde complete est ecrite avant suppression, et
              un bouton permet de tout remettre en place.
            - PERFORMANCE. Prechargement des termes, des metadonnees d'offre et
              des metadonnees de terme en une fois par liste : sans lui, chaque
              carte declenchait ses propres lectures, soit plusieurs centaines
              d'allers-retours sur une page de cinquante offres. Les trois
              premieres vignettes ne sont plus differees, les suivantes le
              restent. Emojis, generateur et liens de decouverte retires de
              l'en-tete. Connexion annoncee tot aux deux domaines distants.
     1.6.1  - LA CATEGORIE D'UNE OFFRE DEVIENT FACULTATIVE, et par defaut celle
              de sa boutique : le champ store_category porte deja ce classement
              et alimente le fil d'Ariane. La choisir sur l'offre ne sert donc
              qu'a s'en ecarter, au cas par cas. Seule la boutique reste
              obligatoire.
            - LE BOUTON « AJOUTER » N'APPARAISSAIT PAS. Le script qui le pose
              partait dans admin_head : il cherchait le titre de la page avant
              que le navigateur ne l'ait lu, ne trouvait rien, et sortait sans
              rien faire. Il part desormais en pied de page. Le CSS, lui, reste
              en tete, sinon la colonne d'ajout apparaitrait un instant.
            - REGLE DE NETTOYAGE DES OFFRES EXPIREES, ECRITE MAIS DESACTIVEE.
              Boutique par boutique : les deux offres expirees les plus
              recentes sont toujours conservees, et au-dela celles expirees
              depuis plus de deux mois partent A LA CORBEILLE, pas a la
              destruction. Outils > Nettoyage des offres montre a tout moment
              ce qui serait supprime, sans rien supprimer. Rien n'est planifie
              tant que la case n'est pas cochee.
     1.6.0  ADMINISTRATION, sept demandes de recette.
            - BOUTIQUE ET CATEGORIE passent dans le panneau « L'offre », en
              liste deroulante, CHOIX UNIQUE ET OBLIGATOIRE. Les panneaux de
              taxonomie natifs sont retires : la meme information ne doit pas
              etre saisissable a deux endroits, avec des regles differentes.
              Une offre enregistree sans boutique ou sans categorie est RETENUE
              EN BROUILLON, avec un message qui dit pourquoi : sans boutique
              elle n'apparait dans aucune liste et son bouton ne mene nulle
              part, elle serait en ligne sans etre ni visible ni utilisable.
            - LISTE DES OFFRES : deux filtres, par boutique et par categorie.
              La colonne « Etat » signale en plus les offres sans boutique.
            - VISUEL DE L'OFFRE : choix dans la bibliotheque ou televersement,
              avec apercu. La taille « medium » est retenue plutot que
              l'original : la vignette fait 82 px. Une adresse externe reste
              acceptee.
            - PRIORITE D'AFFICHAGE retiree de l'ecran. CORRECTIF AU PASSAGE :
              les listes triaient dessus avec meta_key, ce qui ajoute une
              jointure STRICTE et ecartait toute offre depourvue de cette
              metadonnee. Le tri se fait desormais par date, sans jointure.
              La metadonnee reste ecrite, vide, tant que le pont et les cinq
              composants la lisent.
            - COUPON A IMPRIMER supprime, en tant que concept.
            - BOUTIQUES ET CATEGORIES D'OFFRES : le formulaire d'ajout ne
              comprime plus la liste dans une colonne etroite. La liste occupe
              tout l'ecran, et un bouton « Ajouter » a cote du titre ouvre le
              formulaire seul, en pleine largeur.
     1.5.2  Point d'insertion du fil d'Ariane. Le composant « Donnees
            structurees des offres » rendait son fil en PIED DE PAGE, puis le
            deplacait par un script juste avant le titre : ClipMyDeals
            n'offrait aucun point d'insertion. Le theme declenche desormais
            l'action vsa_theme_fil_ariane a l'endroit exact, dans l'encadre de
            titre. Le fil est ecrit par le serveur, dans sa position finale.
            Le composant garde son ancien comportement si l'action n'existe
            pas : rien ne casse.
     1.5.1  L'acces direct aux offres est ferme SANS REDIRECTION. Ces adresses
            n'ont jamais servi en ligne : il n'y a ni lien entrant ni valeur de
            referencement a transmettre, et une 301 ne ferait qu'entretenir des
            adresses que personne n'a visitees. Le type n'est donc plus
            interrogeable et /coupons/{slug}/ ne repond plus.
            Un filtre remplace toutefois l'adresse rendue par get_permalink()
            par celle de la boutique, suivie d'une ancre vers la carte. Le
            composant « Donnees structurees des offres » publie une url en
            /coupons/{slug}/ dans chaque Offer de sa liste ItemList, verifie sur
            la production : sans ce filtre, il annoncerait aux moteurs des
            centaines d'adresses mortes. Avec, il continue de fonctionner sans
            qu'une seule ligne de son code ne change.
            Les gabarits archive-coupons.php et single-coupons.php sont
            supprimes : ils n'ont plus d'objet.
     1.5.0  Quatre points releves en recette.
            1. UNE OFFRE N'EST PLUS CONSULTABLE A SON ADRESSE PROPRE. On navigue
               par boutique et par categorie, pas au-dela. Redirection
               PERMANENTE vers la fiche de la boutique, et non erreur 404 : ces
               adresses sont indexees, une 404 perdrait leur valeur et enverrait
               les visiteurs dans le vide. Le titre de la carte n'est donc plus
               un lien, l'archive /coupons/ est fermee, et les offres sortent
               des resultats de recherche du site.
            2. LARGEUR DU CONTENEUR. Bootstrap arrete .container-xl a 1140 px ;
               la production en fait 1250, la feuille de 44 Ko du theme actuel
               l'elargissant. Tout le site etait 110 px plus etroit, d'ou le
               cadre du logo plus petit que le panneau d'a cote.
            3. ACCUEIL : trois offres par ligne sur ordinateur, une seule sous
               992 px.
            4. LES DONNEES STRUCTUREES DES ARTICLES REVIENNENT. Le gabarit
               d'origine ecrit l'article d'une boutique sans aucun filtrage ;
               je l'avais passe a wp_kses_post(), ce qui supprimait en silence
               un bloc FAQPage stocke en JSON-LD dans le texte. Le filtrage est
               conserve pour tout le reste, mais les blocs
               application/ld+json sont preserves apres verification que leur
               contenu est du JSON valide.
     1.4.0  LOT 4 : le vote et l'accessibilite.
            - VOTE sur les codes, ecrit a partir des seules briques du noyau :
              une route REST du theme, un jeton verifie cote serveur, les
              compteurs en metadonnees de l'offre. L'anti-rejeu compare une
              EMPREINTE SALEE de l'adresse IP, gardee douze heures ; l'adresse
              n'est jamais stockee ni journalisee. Aucune donnee personnelle,
              donc aucun impact sur le consentement. Les compteurs sont ecrits
              par le serveur : sans JavaScript ils s'affichent, seul le vote
              est inactif.
            - ACCESSIBILITE : .screen-reader-text n'etait defini nulle part,
              les intitules destines aux lecteurs d'ecran s'affichaient donc a
              l'ecran ; le lien « Aller au contenu » restait invisible au
              clavier, donc inatteignable. Les deux sont corriges, et un
              contour de focus net est pose partout.
            - CORRECTIF : la colonne s'affichait sur l'ACCUEIL, alors que la
              production n'en a pas. Le theme actuel l'obtenait par un reglage
              du personnalisateur ; la regle est desormais ecrite dans le
              theme.
     1.3.0  LOT 3, ADMINISTRATION.
            - Ecran d'une offre : UN SEUL panneau rassemble les neuf champs,
              contre onze panneaux separes aujourd'hui. Un nonce, une liste
              fermee, un enregistrement. Les cles ne changent pas (D3), le pont
              lit et ecrit exactement les memes metadonnees.
            - Ecrans d'une boutique et d'une categorie, passant par la couche
              d'acces du lot 1, donc en ECRITURE DOUBLE : metadonnee standard
              et ancienne option, qui ne peuvent plus diverger.
            - Liste des offres : colonne « Etat » (en cours, expiree, sans date
              de fin, code manquant). Listes des boutiques et des categories :
              colonne « Offres en cours », comptees comme le site les affiche
              et non comme la taxonomie les compte.
            - Outil de migration des champs de terme, dans Outils > Champs de
              terme. Idempotent, ne supprime rien, et refuse de s'executer sans
              confirmation explicite que les cinq composants ecrivent des deux
              cotes. L'etat des lieux est consultable a tout moment.
     1.2.2  Annuaire des boutiques : le pied des tuiles portait le fond gris et
            le trait par defaut de Bootstrap. La feuille de 44 Ko du theme
            actuel les neutralisait, elle n'est pas reprise. Mesure faite sur
            la production : fond transparent, aucune bordure.
     1.2.1  Comparaison ecran par ecran avec la production, dans le navigateur.
            1. LES LIBELLES REVENAIENT EN ANGLAIS : « Verified On », « Recently
               Expired Offers », « Did the code work? ». Je les avais laisses
               dans le domaine de traduction « clipmydeals » pour reutiliser sa
               traduction. Or un domaine est charge par le theme ACTIF : celui
               de ClipMyDeals n'etait plus charge du tout. Les libelles sont
               desormais ecrits en francais, repris a l'identique de son
               fichier fr_FR.po, sous le domaine du theme neuf.
            2. L'ACCUEIL affichait trois offres par ligne au lieu de deux
               (cartes de 263 px contre 389 mesurees en production).
     1.2.0  Fiche boutique et page categorie remises a la STRUCTURE REELLE de
            la production, relevee dans le navigateur puis recoupee avec le
            gabarit d'origine. Ma premiere version en avait invente une.
            Trois ecarts corriges :
              - le fil d'Ariane etait affiche DEUX fois. Il est injecte dans la
                description d'archive par un composant ; le poser aussi a la
                main le doublait ;
              - l'article de la boutique ou de la categorie remontait dans
                l'en-tete, ce qui repoussait les offres sous plusieurs ecrans
                de texte. Il revient APRES les offres, dans son div.card ;
              - les offres expirees manquaient sur les pages categorie.
            L'encadre de titre reprend le balisage d'origine
            (.alert.alert-secondary), donc le fond creme deja en place.
     1.1.2  CORRECTIF. Plus aucune offre ne s'affichait, nulle part, alors que
            le reste des pages etait intact. Cause : le plugin « Cartes
            compactes », reste actif. Il cible article.card.cmd-list-layout,
            classe que notre carte portait encore par souci de compatibilite.
            Il y trouvait un a.code-button, remontait au premier ancetre
            .col-12 pour recuperer le « bloc bouton », tombait sur l'enveloppe
            .coupon-box qui contient TOUTE la carte, et la detachait du
            document avant d'echouer en silence dans son try/catch.
            La classe n'est plus posee : le plugin ne trouve plus rien a
            transformer. Il reste a desinstaller, et un message le rappelle
            desormais dans l'administration, mais le theme ne depend plus de
            ce geste pour fonctionner.
     1.1.1  CORRECTIF. La carte d'offre appelait deux fonctions absentes du
            theme, vsa_theme_vote_visible() et vsa_theme_compteur(), oubliees
            a la reprise des gabarits. Toute page affichant une offre tombait
            donc en erreur fatale. Les deux fonctions sont ajoutees.
            Deux garde-fous au passage : la pagination ne suppose plus que la
            requete principale existe, et la grille des categories ne suppose
            plus qu'un composant tiers lui rend bien un tableau.
     1.1.0  LOT 2, affichage. La carte d'offre est rendue par le serveur, dans
            sa forme finale : une seule carte au lieu de deux, plus de
            reagencement en JavaScript, plus de voile anti-clignotement. Le
            bouton pointe DIRECTEMENT vers la destination (decision D6).
            Fiche boutique, page categorie, archive des offres, fiche d'une
            offre, fiche sans offre, grille des categories et pagination
            numerotee. Le plugin « VSA - Cartes compactes » 1.6.0 doit etre
            SUPPRIME en meme temps que l'installation de cette version.
     1.0.2  Trois correctifs apres la premiere recette en preproduction.
            1. Les shortcodes cmd_latest_coupons et cmd_coupons sont repris :
               ils etaient declares par ClipMyDeals, donc perdus avec lui, et
               « [cmd_latest_coupons count=6] » s'affichait en clair sur
               l'accueil. Memes noms, memes attributs, contenu non touche.
            2. Le gabarit page-all-stores.php est repris, sous le meme nom de
               fichier : sans lui la page « Boutiques » ne rendait plus que son
               texte d'introduction. Compteur d'offres EN COURS, singulier
               gere, vitrines vides repoussees et filtrables.
            3. Decalage sous la barre fixe : le menu recouvrait le titre des
               pages, la feuille de 44 Ko du parent le compensait.
     1.0.1  Reprise des regles du CSS additionnel en ligne qui dependent du
            CONTENU et non du theme (voir section 13), et correction du pied
            de page : il portait « bg-dark text-light », or le site ramene
            .bg-dark au creme depuis longtemps. Texte blanc sur creme, donc
            illisible. Le pied porte desormais ses couleurs directement.
     1.0.0  Lot 1, socle. Theme autonome : declaration, structures (type
            « coupons », taxonomies « stores » et « offer_categories »),
            couche d'acces aux champs de terme, chargement des styles,
            charte, en-tete, pied, colonne, menu, recherche. La base d'URL
            des categories est declaree ici, en « bons-plans » : le filtre
            du plugin Outils SEO n'a plus a la reecrire. Bootstrap 5.3.1
            embarque (MIT), en un seul fichier qui contient deja Popper.
            Reprise automatique des reglages de ClipMyDeals a l'activation :
            logo, en-tete et affectation des menus sont conserves.
            L'affichage des offres, la fiche boutique, la page categorie et
            l'annuaire arrivent au lot 2 ; l'administration au lot 3.
   ========================================================================= */


/* =========================================================================
   Cette feuille remplace INTEGRALEMENT le CSS additionnel du personnalisateur.
   Une fois ce theme actif, ce champ doit etre vide : deux sources pour les memes
   regles finiraient par diverger.

   Sommaire
     1.  Variables et typographie
     2.  Boutons
     3.  Carte d'offre (rendue par le theme)
     4.  Offres expirees
     5.  Annuaire des boutiques
     6.  Fiche boutique
     7.  Habillage general
     8.  Menu et recherche
     9.  Accueil : lecture et sections repliables
     10. Grille des categories
     11. Fiche sans offre
     12. Modules de la colonne
   ========================================================================= */

/* 1. VARIABLES ET TYPOGRAPHIE -------------------------------------------- */
:root{
  --vsa-accent:#C7501E;
  --vsa-accent-deep:#9a3d1d;
  --vsa-cream:#fff8f2;
  --vsa-tag:#FAECE7;
  --vsa-border:#efe7df;
  --vsa-ink:#1a1a1a;
  --vsa-ink-2:#5f5e5a;
  --vsa-ink-3:#8b8a87;
  --vsa-green:#1d9e75;
  --cmd-font-primary:#C7501E;
}
body,button,input,select,textarea{font-family:'Assistant',system-ui,-apple-system,sans-serif;color:var(--vsa-ink);}
h1,h2,h3,h4,h5,h6{font-family:'Assistant',system-ui,sans-serif;text-transform:none;letter-spacing:0;}
h1{font-size:30px !important;}
h2{font-size:24px !important;line-height:1.3;}
a{color:var(--vsa-accent);}
/* LE TITRE D'UNE PAGE N'EST PLUS MASQUE. La regle heritee le cachait partout :
   elle avait son sens quand ClipMyDeals affichait par ailleurs une banniere de
   titre, elle n'en avait plus aucun ici. Le gabarit decide desormais de
   l'afficher ou non, selon que le contenu porte deja un titre de niveau 1.
   Meme allure que les titres de fiche, pour ne pas creer une troisieme echelle. */
.entry-title{font-size:24px !important;font-weight:600;text-align:center;margin:0 0 18px;}

/* 2. BOUTONS -------------------------------------------------------------- */
.btn,.code-button,.deal-button,.revealed-button,.copy-button{
  transition:none !important;
  border-radius:9px;
  font-weight:600;
}
.btn-warning,.input-group-text.btn.btn-warning{
  /* Le theme sert la recherche avec l'ancien saumon #CE9079 : un seul accent. */
  background-color:var(--vsa-accent) !important;
  border-color:var(--vsa-accent) !important;
  color:#fff !important;
  box-shadow:none !important;
  transition:none !important;
}
.btn-warning:hover,.btn-warning:focus,.btn-warning:active{
  background-color:var(--vsa-accent) !important;
  border-color:var(--vsa-accent) !important;
  color:#fff !important;
  transform:none !important;
  filter:none !important;
}
.fa-cut,.fa-copy{display:none;}

/* 3. CARTE D'OFFRE -------------------------------------------------------- */
.vsa-carte{
  border:1px solid var(--vsa-border);
  border-radius:14px;
  background:#fff;
  padding:14px;
  height:100%;
  display:flex;
  flex-direction:column;
  box-shadow:0 1px 2px rgba(0,0,0,.03);
}
.vsa-carte-tete{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;}
.vsa-carte-marque{display:flex;gap:9px;align-items:center;min-width:0;}
.vsa-carte-logo{width:34px;height:34px;border:1px solid var(--vsa-border);border-radius:8px;object-fit:contain;background:#fff;flex:0 0 auto;}
.vsa-carte-boutique{font-size:12.5px;font-weight:600;color:var(--vsa-ink);text-decoration:none;display:block;line-height:1.25;}
.vsa-carte-verifie{font-size:11px;color:var(--vsa-green);}
.vsa-carte-badge{font-size:10px;font-weight:700;color:#fff;background:var(--vsa-accent);padding:4px 10px;border-radius:999px;white-space:nowrap;}
.vsa-carte-corps{display:flex;gap:12px;margin-top:10px;}
.vsa-carte-texte{flex:1 1 auto;min-width:0;}
.vsa-carte-titre{font-size:15.5px;font-weight:600;margin:0 0 5px;line-height:1.3;}
.vsa-carte-titre a{color:var(--vsa-ink);text-decoration:none;}
.vsa-carte-desc{font-size:13.5px;color:var(--vsa-ink-2);line-height:1.5;}
.vsa-carte-desc.vsa-clamp{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}
.vsa-carte-desc p{margin:0 0 .45em;}
.vsa-carte-desc p:last-child{margin-bottom:0;}
.vsa-carte-desc strong,.vsa-carte-desc b{font-weight:600;color:var(--vsa-ink);}
.vsa-carte-desc em,.vsa-carte-desc i{font-style:italic;}
.vsa-carte-desc u{text-decoration:underline;}
.vsa-carte-desc ul,.vsa-carte-desc ol{margin:0 0 .45em;padding-left:1.15em;}
.vsa-carte-desc a{color:var(--vsa-accent);text-decoration:underline;}
.vsa-carte-plus{
  display:inline-block;margin-top:5px;font-size:13px;font-weight:600;
  color:var(--vsa-accent);background:none;border:none;padding:0;cursor:pointer;
}
.vsa-carte-plus:hover,.vsa-carte-plus:focus{color:var(--vsa-accent-deep);text-decoration:underline;outline:none;}
.vsa-carte-visuel{width:82px;height:82px;object-fit:cover;border-radius:9px;border:1px solid var(--vsa-border);flex:0 0 auto;background:#fff;}
.vsa-carte-action{margin-top:12px;}
.vsa-carte-action a,.vsa-carte-action .code-button,.vsa-carte-action .deal-button,.vsa-carte-action .revealed-button{
  display:block !important;width:100% !important;text-align:center;
  background:var(--vsa-accent) !important;border-color:var(--vsa-accent) !important;color:#fff !important;
  padding:13px !important;border-radius:9px !important;font-weight:600;font-size:14px;
}
.vsa-carte-action .code-button-parent{padding:0 !important;background:transparent !important;}
.vsa-carte-action .copy-button{font-size:0;}
.vsa-carte-action .copy-button::after{content:'Copier';font-size:13px;}
.vsa-carte-validite{display:inline-block;margin-top:10px;font-size:10.5px;font-weight:700;color:#fff;background:var(--vsa-green);padding:4px 9px;border-radius:6px;}
.vsa-carte-validite.vsa-expire{background:#b9b4ae;}
.vsa-carte-pied{border-top:1px solid var(--vsa-border);margin-top:12px;padding-top:9px;display:flex;justify-content:space-between;align-items:center;font-size:11.5px;color:var(--vsa-ink-3);}
@media(max-width:767px){
  .vsa-carte-visuel{width:62px;height:62px;}
  .vsa-carte-titre{font-size:14.5px;}
}

/* 4. OFFRES EXPIREES ------------------------------------------------------ */
.vsa-carte.vsa-carte-expiree{opacity:.62;}

/* 5. ANNUAIRE DES BOUTIQUES ---------------------------------------------- */
.cmd-taxonomy-card{border:1px solid var(--vsa-border) !important;border-radius:12px !important;box-shadow:none !important;}
.cmd-store-name{color:var(--vsa-ink);font-weight:600;}
.cmd-store-logo-fix-height{height:64px;width:100%;object-fit:contain;background:#fff;}
/* Bootstrap sert .card-footer avec un fond gris et un trait. La feuille de 44 Ko
   du theme actuel les neutralisait ; elle n'est pas reprise, il faut donc le
   faire ici. Mesure sur la production : fond transparent, aucune bordure. */
.cmd-taxonomy-card .card-footer{background-color:transparent;border-top:0;}

/* 6. FICHE BOUTIQUE ------------------------------------------------------- */
/* Pas de fond derriere le logo et le bouton : le logo porte deja son cadre
   blanc, et un second aplat creme cree un encadre dans l'encadre. */
#store-info{background:none;border-radius:0;}

/* 7. HABILLAGE GENERAL ---------------------------------------------------- */
.alert,.widget_media_video,footer#colophon{background-color:var(--vsa-cream);}
footer#colophon{box-shadow:none;border-top:1px solid var(--vsa-border);}
.rounded-4{border-radius:12px !important;}
.coupon-content .rounded-4{background:#fff !important;}

/* 8. MENU ET RECHERCHE ---------------------------------------------------- */
.navbar .nav-link{
  /* Le theme pose une pastille grise translucide sur l'entree courante. */
  background-color:transparent !important;
  color:var(--vsa-ink) !important;
  font-weight:600;
  padding:8px 10px !important;
}
.navbar .current-menu-item > .nav-link,
.navbar .current_page_item > .nav-link,
.navbar .current-menu-parent > .nav-link,
.navbar .nav-item.active > .nav-link,
.navbar .show > .nav-link{color:var(--vsa-accent) !important;background:transparent !important;}
.navbar .dropdown-menu{
  background-color:#fff !important;
  background-image:none !important;
  border:1px solid var(--vsa-border);
  border-radius:10px;
  box-shadow:0 8px 24px rgba(0,0,0,.10);
  padding:6px;margin-top:6px;min-width:210px;
}
.navbar .dropdown-menu li{margin:0 !important;}
.navbar .dropdown-menu .dropdown-item,
.navbar .dropdown-menu .nav-link{
  background:transparent;color:var(--vsa-ink) !important;
  padding:9px 12px;line-height:1.35;border-radius:7px;
  font-size:14px;text-transform:none;letter-spacing:0;transition:none !important;
}
.navbar .dropdown-menu .dropdown-item:hover,
.navbar .dropdown-menu .nav-link:hover,
.navbar .dropdown-menu .dropdown-item:focus{background:var(--vsa-cream) !important;color:var(--vsa-accent) !important;}
.navbar .dropdown-toggle::after{border-top-color:currentColor;margin-left:6px;vertical-align:middle;}
@media(min-width:992px){
  .navbar .dropdown-menu.show{
    display:grid !important;grid-template-columns:repeat(3,minmax(170px,1fr));
    gap:2px;padding:10px !important;min-width:560px !important;
  }
  .navbar .dropdown-menu .dropdown-item{white-space:normal !important;}
  /* Avec des intitules de colonnes (attribut de titre « dropdown-header »),
     chaque intitule ouvre une colonne et emporte les entrees qui le suivent. */
  .navbar .dropdown-menu.show:has(.dropdown-header){
    display:block !important;column-count:3;column-gap:22px;min-width:640px !important;
  }
  .navbar .dropdown-menu .dropdown-header{
    break-before:column;-webkit-column-break-before:column;break-after:avoid;
    padding:4px 12px 6px;margin:0;font-size:11px;font-weight:700;
    text-transform:uppercase;letter-spacing:.05em;color:var(--vsa-ink-3);list-style:none;
  }
  .navbar .dropdown-menu .dropdown-header:first-child{break-before:auto;-webkit-column-break-before:auto;}
  .navbar .dropdown-menu li{break-inside:avoid;-webkit-column-break-inside:avoid;}
}
@media(max-width:991.98px){
  .navbar .dropdown-menu{border:none;box-shadow:none;margin:0 0 4px;padding-left:12px;min-width:0;}
  .navbar .dropdown-menu.show{display:block !important;}
}
.navbar .dropdown-menu .vsa-menu-offre{break-inside:avoid;}
.navbar .dropdown-menu .vsa-menu-offre > .dropdown-item{
  background:var(--vsa-cream) !important;border:1px solid var(--vsa-border);border-radius:9px;
  padding:10px 12px !important;margin-top:4px;font-weight:600;
  color:var(--vsa-accent-deep) !important;white-space:normal !important;
}

/* 9. ACCUEIL : LECTURE ET SECTIONS REPLIABLES ----------------------------- */
body.home .entry-content p,
body.home .entry-content ol,
body.home .entry-content ul{
  max-width:820px;margin-left:auto;margin-right:auto;font-size:16px;line-height:1.65;
}
body.home .entry-content article p,
body.home .entry-content article ol,
body.home .entry-content article ul,
body.home .entry-content .row p,
body.home .entry-content .row ol,
body.home .entry-content .row ul{
  max-width:none;margin-left:0;margin-right:0;font-size:13.5px;line-height:1.5;
}
body.home .wp-block-details{
  max-width:820px;margin:0 auto 10px;border:1px solid var(--vsa-border);border-radius:10px;background:#fff;
}
body.home .wp-block-details > summary{
  list-style:none;cursor:pointer;padding:12px 16px;font-size:16px;font-weight:600;
  color:var(--vsa-ink);display:flex;align-items:center;justify-content:space-between;
}
body.home .wp-block-details > summary::-webkit-details-marker{display:none;}
body.home .wp-block-details > summary::after{content:'+';color:var(--vsa-accent);font-size:20px;font-weight:700;line-height:1;}
body.home .wp-block-details[open] > summary::after{content:'\2212';}
body.home .wp-block-details[open] > summary{border-bottom:1px solid var(--vsa-border);}
body.home .wp-block-details > *:not(summary){padding-left:16px;padding-right:16px;}
body.home .wp-block-details > *:last-child{padding-bottom:12px;}

/* 10. GRILLE DES CATEGORIES ---------------------------------------------- */
.vsa-tuiles{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;}
@media(max-width:991px){.vsa-tuiles{grid-template-columns:repeat(2,1fr);}}
@media(max-width:575px){.vsa-tuiles{grid-template-columns:1fr;}}
.vsa-tuile{
  display:flex;align-items:center;gap:12px;padding:14px 16px;
  border:1px solid var(--vsa-border);border-radius:14px;background:#fff;text-decoration:none;
}
.vsa-tuile:hover{border-color:var(--vsa-accent);}
.vsa-tuile-picto{
  flex:0 0 auto;width:38px;height:38px;border-radius:10px;background:var(--vsa-tag);
  display:flex;align-items:center;justify-content:center;font-size:19px;line-height:1;overflow:hidden;
}
.vsa-tuile-image{width:100%;height:100%;object-fit:contain;padding:4px;box-sizing:border-box;display:block;}
.vsa-tuile-nom{display:block;font-size:15px;font-weight:600;color:var(--vsa-ink);line-height:1.25;}
.vsa-tuile-nb{display:block;font-size:12.5px;color:var(--vsa-ink-3);}

/* 11. FICHE SANS OFFRE ---------------------------------------------------- */
.vsa-vide{max-width:820px;margin:0 auto;text-align:left;}
.vsa-vide-titre{font-size:22px;margin:0 0 8px;}
.vsa-vide-texte{font-size:15px;color:var(--vsa-ink-2);margin:0 0 16px;}
.vsa-vide-widget{margin:0 0 20px;}
.vsa-vide-sstitre{font-size:16px;margin:0 0 10px;}
.vsa-vide-cartes{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;}
@media(max-width:700px){.vsa-vide-cartes{grid-template-columns:1fr;}}
.vsa-vide-carte{
  display:flex;flex-direction:column;align-items:center;gap:5px;padding:12px 10px;
  border:1px solid var(--vsa-border);border-radius:11px;background:#fff;text-decoration:none;
}
.vsa-vide-carte img{max-width:90px;height:32px;object-fit:contain;}
.vsa-vide-nom{font-size:13px;font-weight:600;color:var(--vsa-ink);text-align:center;}
.vsa-vide-nb{font-size:11.5px;color:var(--vsa-accent);}
.vsa-vide-liens{font-size:13.5px;color:var(--vsa-ink-2);margin:16px 0 0;}
.vsa-vide-liens a{color:var(--vsa-accent);}

/* 11 bis. PAGE DES BOUTIQUES --------------------------------------------- */
.vsa-boutiques-filtres{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 16px;}
.vsa-filtre{
  font-size:13px;font-weight:600;padding:6px 14px;border-radius:999px;
  border:1px solid var(--vsa-border);background:#fff;color:var(--vsa-ink-2);text-decoration:none;
}
.vsa-filtre.vsa-actif{background:var(--vsa-tag);border-color:var(--vsa-tag);color:var(--vsa-accent-deep);}
.vsa-boutique-nb{font-size:12.5px;}
.vsa-boutique-nb.vsa-avec{color:#0f6e56;}
.vsa-boutique-nb.vsa-sans{color:#b26b00;font-weight:600;}
.cmd-taxonomy-card.vsa-boutique-vide{opacity:.62;}

/* 12. MODULES DE LA COLONNE ---------------------------------------------- */
#secondary .widget,.sidebar .widget,aside .widget{
  /* Le theme declare ses widgets avec border-0 : ils flottaient sans contour. */
  border:1px solid var(--vsa-border) !important;
  border-radius:14px !important;
  background:#fff;
  box-shadow:0 1px 2px rgba(0,0,0,.03) !important;
  overflow:hidden;
  margin-bottom:16px !important;
}
#secondary .widget .widget-title,.sidebar .widget .widget-title,aside .widget .widget-title{
  background:var(--vsa-cream);border-bottom:1px solid var(--vsa-border);padding:11px 14px;
}
#secondary .widget .widget-title .card-title,.sidebar .widget .widget-title .card-title,aside .widget .widget-title .card-title{
  font-size:15px;font-weight:600;color:var(--vsa-ink);
}

/* =========================================================================
   13. REPRISES DU CSS ADDITIONNEL EN LIGNE
   Ajoute en 1.0.1, apres lecture du CSS additionnel du personnalisateur
   (18 974 octets, md5 9e88abbe0313f129fc9ec780217e1690).

   La charte ci-dessus couvrait deja l'essentiel, mais une comparaison
   selecteur par selecteur a montre 53 regles presentes en ligne et absentes
   ici. La plupart visent des classes de ClipMyDeals (carte d'offre, annuaire,
   grille des categories, fiche boutique) : elles n'ont plus d'objet, puisque
   le lot 2 rend ces zones avec ses propres classes.

   Restent celles qui ne dependent PAS du theme mais du CONTENU publie ou de
   Bootstrap. Elles sont reprises ici : les retirer changerait des pages en
   ligne. C'est notamment le cas de .titre-sans-style, une classe posee a la
   main dans les articles, et du renversement des colonnes de l'accueil.
   ========================================================================= */

/* Typographie de base, telle qu'elle est servie aujourd'hui. */
body,.row,.nav,.nav-link{font-family:'Assistant',system-ui,-apple-system,sans-serif;}
p,.card-text,.blog-description{font-size:15px;color:var(--vsa-ink-2);}
.nav-link{font-size:14px;text-transform:uppercase;}
h1.page-title,h1.page-title.my-0{font-size:24px !important;line-height:1.25 !important;font-weight:600 !important;}
h1,h1.mt-0{text-align:center;}
h2{text-align:center;}

/* Classe posee a la main dans le contenu pour neutraliser un niveau de titre. */
.titre-sans-style{font-weight:normal !important;font-size:1em !important;font-family:'Assistant',system-ui,sans-serif !important;}

.entry-content p{line-height:1.6;}

/* ACCUEIL SUR MOBILE : LES OFFRES AVANT LE TEXTE.
   La page d'accueil est un bloc de colonnes Gutenberg. Sous 781 px, point de
   bascule de Gutenberg, les colonnes s'empilent et le texte de presentation
   passait AVANT les promotions. Vise uniquement l'accueil, et uniquement le
   premier bloc de colonnes. */
@media(max-width:781px){
  body.home .wp-block-columns:first-of-type{display:flex !important;flex-direction:column-reverse !important;}
}

/* Module video de la colonne, servi par le composant Alternatives.
   Sans ces regles, l'iframe est positionnee en absolu dans un conteneur a
   hauteur nulle : la video disparait. */
#storevideo_widget-2{background:transparent !important;box-shadow:none !important;border-radius:0 !important;overflow:visible !important;padding:0 !important;}
#storevideo_widget-2 .embed-responsive{padding-bottom:0 !important;}
#storevideo_widget-2 iframe{position:static !important;height:160px !important;width:100% !important;}

/* Aucune animation, jusque sur les groupes de saisie. */
.input-group-text.btn{transition:none !important;}

/* Bandes creme heritees. .bg-dark n'est plus utilisee par les gabarits du
   theme, mais un contenu ou un widget peut encore la porter : la regle reste,
   accompagnee cette fois d'une couleur de texte lisible. */
div.alert.alert-secondary,div.container-fluid.pb-4.my-4{background-color:var(--vsa-cream);}
.bg-dark{background-color:var(--vsa-cream) !important;}
.bg-dark,.bg-dark .text-light,.bg-dark a{color:var(--vsa-ink) !important;}

/* =========================================================================
   14. DECALAGE SOUS LA BARRE FIXE
   Ajoute en 1.0.2, apres constat en preproduction : le logo et le menu
   recouvraient le titre de la page.

   La barre porte la classe Bootstrap fixed-top, qui la sort du flux : sans
   compensation, le contenu demarre SOUS elle. ClipMyDeals compensait dans sa
   feuille de 44 Ko, non reprise. On la retablit ici, en s'appuyant sur la
   hauteur reelle de la barre plutot que sur une valeur fixe.
   ========================================================================= */
:root{--vsa-barre:96px;}
body{padding-top:var(--vsa-barre);}
@media(max-width:991.98px){:root{--vsa-barre:128px;}}
.navbar.fixed-top{background-color:#fff;border-bottom:1px solid var(--vsa-border);}
.navbar.fixed-top .navbar-brand img,.navbar.fixed-top #site-logo,.navbar.fixed-top .custom-logo{max-height:64px;width:auto;}
/* L'ancre de saut ne doit pas atterrir sous la barre. */
#content{scroll-margin-top:var(--vsa-barre);}
/* La barre d'administration se superpose a la barre fixe pour une personne connectee. */
body.admin-bar .navbar.fixed-top{top:32px;}
/* SOUS 783 PX, LA BARRE D'ADMINISTRATION N'EST PLUS FIXE : WordPress la passe en
   position absolue, elle defile donc avec la page. Lui reserver 46 px laissait
   une bande vide en haut de l'ecran, dans laquelle le contenu venait defiler
   sous la barre. C'est ce que montrait la capture du 18/08/2026. */
@media(max-width:782px){body.admin-bar .navbar.fixed-top{top:0;}}

/* =========================================================================
   15. LA CARTE D'OFFRE
   Ajoute en 1.1.0, lot 2.

   Ces regles viennent du plugin « VSA - Cartes compactes » 1.6.0, qui les
   servait avec un script de reagencement. Le theme rend desormais la carte
   directement : le plugin doit etre SUPPRIME en meme temps que ce lot, sans
   quoi deux mains travailleraient sur le meme bloc.

   Trois regles du plugin ne sont pas reprises, faute d'objet :
     - le voile anti-clignotement html.vsa-attente : plus rien a masquer ;
     - le masquage de la carte grille : il n'y en a plus qu'une ;
     - le masquage de .row.mx-0 : c'etait le balisage d'origine, disparu.
   ========================================================================= */
.vsa-compact{border:1px solid var(--vsa-border) !important;border-radius:16px !important;background:#fff !important;padding:16px 18px !important;box-shadow:none !important;}
.vsa-card{display:flex;flex-direction:column;gap:12px;}
.vsa-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;}
.vsa-store{display:flex;align-items:center;gap:10px;min-width:0;}
.vsa-store-link{display:flex;align-items:center;gap:10px;min-width:0;text-decoration:none !important;color:inherit !important;}
.vsa-store-link:hover .vsa-store-name,.vsa-store-link:focus .vsa-store-name{text-decoration:underline;}
.vsa-store-link:focus-visible{outline:2px solid var(--vsa-accent);outline-offset:2px;border-radius:8px;}
.vsa-logo{width:46px !important;height:46px !important;object-fit:contain !important;border:1px solid var(--vsa-border);border-radius:8px;padding:3px;background:#fff;}
.vsa-store-name{font-weight:600;font-size:14px;color:var(--vsa-ink);line-height:1.2;}
.vsa-verif{font-size:12px;color:var(--vsa-green);}
.vsa-badge{font-size:11px;font-weight:600;color:#fff;background:var(--vsa-accent);padding:4px 11px;border-radius:999px;white-space:nowrap;align-self:flex-start;}
.vsa-title{font-size:19px !important;font-weight:600 !important;margin:0 !important;text-align:left !important;line-height:1.25 !important;}
.vsa-title a{color:var(--vsa-ink) !important;text-decoration:none;}
.vsa-title a:hover{text-decoration:underline;}
.vsa-corps{display:flex;gap:12px;align-items:flex-start;}
.vsa-texte{flex:1 1 auto;min-width:0;}
.vsa-visuel{flex:0 0 auto;width:82px;height:82px;object-fit:cover;border-radius:9px;border:1px solid var(--vsa-border);background:#fff;}
@media(max-width:767px){.vsa-visuel{width:62px;height:62px;}}
.vsa-desc{font-size:14px;color:var(--vsa-ink-2);line-height:1.5;}
.vsa-desc.vsa-clamp{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}
.vsa-desc p{margin:0 0 .45em;}
.vsa-desc p:last-child{margin-bottom:0;}
.vsa-desc strong,.vsa-desc b{font-weight:600;color:var(--vsa-ink);}
.vsa-desc ul,.vsa-desc ol{margin:0 0 .45em;padding-left:1.15em;}
.vsa-desc li{margin:0 0 .15em;}
.vsa-desc a{color:var(--vsa-accent);text-decoration:none;}
.vsa-desc a:hover,.vsa-desc a:focus{text-decoration:none;color:var(--vsa-accent-deep);}
.vsa-plus{display:inline-block;margin-top:5px;font-size:13px;font-weight:600;color:var(--vsa-accent);background:none;border:none;padding:0;cursor:pointer;text-align:left;}
.vsa-plus:hover,.vsa-plus:focus{color:var(--vsa-accent-deep);outline:none;text-decoration:underline;}
.vsa-validity{align-self:flex-start;font-size:12.5px;color:var(--vsa-ink-3);}
.vsa-validity.vsa-expire{color:#b26b00;font-weight:600;}
.vsa-foot{display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--vsa-border);padding-top:10px;font-size:12px;color:var(--vsa-ink-2);}
.vsa-vote-attente{color:var(--vsa-ink-3);}
.expired-coupon{opacity:.62;}

/* Le bouton, identique quel que soit le type d'offre. Sans cadre, sans icone,
   sans effet au survol : la charte ne comporte aucune animation. */
.vsa-btn-block{margin:0;}
.vsa-card a.code-button,.vsa-card a.deal-button,.vsa-card a.coupon-button{
  display:block !important;width:100% !important;padding:13px 20px !important;
  background-color:var(--vsa-accent) !important;background-image:none !important;
  border:none !important;border-radius:10px !important;color:#fff !important;
  font-size:16px !important;font-weight:600 !important;line-height:1.3 !important;
  text-align:center;text-decoration:none;
  box-shadow:none !important;transform:none !important;transition:none !important;
}
.vsa-card a.code-button:hover,.vsa-card a.code-button:focus,.vsa-card a.code-button:active,
.vsa-card a.deal-button:hover,.vsa-card a.deal-button:focus,.vsa-card a.deal-button:active,
.vsa-card a.coupon-button:hover,.vsa-card a.coupon-button:focus,.vsa-card a.coupon-button:active{
  background-color:var(--vsa-accent) !important;color:#fff !important;
  box-shadow:none !important;transform:none !important;filter:none !important;
}

/* Le code revele. Il est deja dans la page des le premier rendu, donc lisible
   par les moteurs ; il n'est que replie. Sans JavaScript, la regle ci-dessous
   ne s'applique jamais et le code reste visible : aucune impasse. */
.vsa-revele{
  display:flex;flex-direction:column;align-items:center;gap:2px;
  margin-top:8px;padding:11px 14px;cursor:pointer;
  border:2px dashed var(--vsa-accent);border-radius:10px;background:var(--vsa-cream);
}
.vsa-revele-code{font-weight:700;font-size:15px;color:var(--vsa-accent-deep);letter-spacing:.06em;}
.vsa-copier-libelle{font-size:10.5px;color:var(--vsa-ink-3);}
.vsa-copie-message{font-size:11.5px;font-weight:600;color:var(--vsa-green);}
/* Lien de secours, affiche par le script uniquement si l'ouverture du nouvel
   onglet a ete refusee par le navigateur. L'attribut hidden le masque le reste
   du temps : un second chemin de depart defaisait le premier. */
.vsa-aller{
  display:block;margin-top:7px;text-align:center;
  font-size:13.5px;font-weight:600;color:var(--vsa-accent);text-decoration:none;
}
.vsa-aller[hidden]{display:none;}
.vsa-aller:hover,.vsa-aller:focus{color:var(--vsa-accent-deep);}
.vsa-copie-message:empty{display:none;}
.js .vsa-code .vsa-revele{display:none;}
.js .vsa-code.vsa-code-revele .vsa-revele{display:flex;}
.js .vsa-code.vsa-code-revele a.code-button{display:none !important;}

/* Deux offres par ligne sur grand ecran, une seule sur mobile. */
@media(min-width:768px){
  .coupon-box.coupon-box-list{flex:0 0 50%;max-width:50%;padding-left:10px;padding-right:10px;}
  .vsa-compact{max-width:560px;}
  /* L'accueil sert DEUX offres par ligne, comme les autres pages : mesure faite
     sur la production, 409 px par carte. Le plugin prevoyait un passage a trois
     par ligne le jour ou les offres sortiraient des colonnes de la mise en page ;
     cette bascule reposait sur la presence d'un ancetre .wp-block-column, absent
     ici. Elle donnait des cartes de 263 px au lieu de 389. Regle retiree : le jour
     ou la page sera reorganisee, ce sera une decision, pas un effet de bord. */
}
body.home .vsa-card .vsa-foot{display:none;}

/* =========================================================================
   16. EN-TETE DES FICHES ET PAGINATION

   Les fiches boutique et categorie reprennent le balisage d'origine :
   .alert.alert-secondary dans un header.row.page-header. Le fond creme de cet
   encadre vient de la section 13, comme aujourd'hui. Rien a inventer ici.
   ========================================================================= */
#stores-archive-alert,#offer_categories-archive-alert{border:1px solid var(--vsa-border);border-radius:14px;}
#stores-archive-alert .page-title,#offer_categories-archive-alert .page-title{font-size:24px !important;font-weight:600 !important;text-align:center;}
.archive-description{font-size:15px;color:var(--vsa-ink-2);}
.vsa-bc{display:block;font-size:13px;line-height:1.4;color:var(--vsa-ink-3);margin:0 0 10px;padding:0;text-align:left;}
.vsa-bc a{color:var(--vsa-ink-3);text-decoration:none;}
.vsa-bc a:hover{color:var(--vsa-accent);text-decoration:underline;}
.vsa-bc [aria-current="page"]{color:var(--vsa-ink);font-weight:600;}
.vsa-bc .vsa-bc-sep{margin:0 6px;color:var(--vsa-ink-3);}
/* L'article de la boutique ou de la categorie, sous les offres. */
main.site-main > .card{border:1px solid var(--vsa-border);border-radius:14px;box-shadow:none;}
main.site-main > .card .card-body{text-align:left;}
main.site-main > .card .card-body h2,main.site-main > .card .card-body h3{text-align:left;}
/* Le titre des offres expirees reste a gauche, comme les autres titres de section. */
main.site-main > h2{font-size:20px !important;text-align:left;}
.visit-website .btn-primary{background-color:var(--vsa-accent);border-color:var(--vsa-accent);font-weight:600;transition:none;}
.visit-website .btn-primary:hover,.visit-website .btn-primary:focus{background-color:var(--vsa-accent-deep);border-color:var(--vsa-accent-deep);}
.store-banner img{width:100%;height:auto;}

.vsa-pagination ul{display:flex;flex-wrap:wrap;gap:6px;list-style:none;padding:0;margin:0 0 24px;justify-content:center;}
.vsa-pagination .page-numbers{display:inline-block;padding:7px 13px;border:1px solid var(--vsa-border);border-radius:9px;color:var(--vsa-ink-2);text-decoration:none;font-size:14px;font-weight:600;}
.vsa-pagination .page-numbers.current{background:var(--vsa-tag);border-color:var(--vsa-tag);color:var(--vsa-accent-deep);}
.vsa-pagination a.page-numbers:hover{border-color:var(--vsa-accent);color:var(--vsa-accent);}

/* =========================================================================
   17. ACCESSIBILITE
   Ajoute en 1.4.0, lot 4.

   Deux manques etaient herites de la feuille de 44 Ko non reprise :
   .screen-reader-text n'etait defini nulle part, donc les intitules destines
   aux lecteurs d'ecran s'affichaient a l'ecran ; et le lien d'evitement
   « Aller au contenu » restait invisible meme au clavier, donc inatteignable.
   ========================================================================= */

/* Visible pour un lecteur d'ecran, invisible a l'oeil. Regle du noyau. */
.screen-reader-text{
  border:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;
  padding:0;position:absolute;width:1px;word-wrap:normal !important;
}
/* Sauf au clavier : le lien d'evitement doit apparaitre quand il prend le focus. */
.screen-reader-text:focus,.skip-link:focus{
  clip-path:none;height:auto;width:auto;margin:0;padding:14px 20px;
  position:fixed;top:12px;left:12px;z-index:100000;
  background:#fff;color:var(--vsa-accent-deep);font-weight:600;
  border:2px solid var(--vsa-accent);border-radius:9px;text-decoration:none;
}

/* Un contour de focus net partout, jamais supprime sans remplacement. */
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,
summary:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--vsa-accent);outline-offset:2px;
}

/* =========================================================================
   18. LE VOTE
   Deux vrais boutons, atteignables au clavier et annonces par un lecteur
   d'ecran. Les compteurs sont ecrits par le serveur : sans JavaScript ils
   s'affichent, seul le vote est inactif.
   ========================================================================= */
.vsa-votes{display:inline-flex;align-items:center;gap:14px;}
.vsa-vote{
  display:inline-flex;align-items:center;gap:5px;
  background:none;border:none;padding:2px 4px;border-radius:7px;
  color:var(--vsa-ink-2);font-size:13px;font-weight:600;cursor:pointer;
  transition:none;
}
.vsa-vote:hover:not(:disabled){color:var(--vsa-accent);}
.vsa-vote:disabled{cursor:default;opacity:.55;}
.vsa-vote-nb{font-variant-numeric:tabular-nums;}
.vsa-vote-reponse{font-size:12px;color:var(--vsa-green);}
.vsa-vote-reponse:empty{display:none;}

/* =========================================================================
   19. LARGEUR DU CONTENEUR ET GRILLE DE L'ACCUEIL
   Ajoute en 1.5.0, apres mesure comparative.

   1. LARGEUR. Bootstrap arrete .container-xl a 1140 px. La production en fait
      1250, la feuille de 44 Ko du theme actuel elargissant le conteneur. Non
      reprise, tout le site etait donc 110 px plus etroit : colonne comprimee,
      cadre du logo plus petit que celui d'a cote. Mesure du 18/08/2026 :
      conteneur 1250, contenu 920, colonne 307.

   2. ACCUEIL. Trois offres par ligne sur ordinateur, une seule sur mobile et
      tablette. Les autres pages restent a deux par ligne.
   ========================================================================= */
@media(min-width:1200px){
  .container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1250px;}
}

@media(min-width:992px){
  body.home .coupon-box.coupon-box-list{flex:0 0 33.3333%;max-width:33.3333%;}
  body.home .coupon-box.coupon-box-list .vsa-compact{max-width:none;}
}
@media(max-width:991.98px){
  /* Une seule colonne sous 992 px : deux cartes cote a cote y deviennent
     illisibles, le titre passant sur quatre lignes. */
  body.home .coupon-box.coupon-box-list{flex:0 0 100%;max-width:100%;}
}

/* Le cadre du logo d'une boutique. Mesure sur la production : bordure fine,
   coins arrondis a 6 px, fond blanc, hauteur 64 px, largeur pleine. */
/* LARGEUR NATURELLE, PLAFONNEE. Un logo de boutique n'a pas de format impose :
   l'etirer sur toute la largeur de sa colonne le deforme des que l'ecran est
   etroit. Mesure du 18/08/2026 a 509 px : la production sert le logo ZenHotels
   a 300 px, sa taille propre, la ou width:100% l'amenait a 446. */
#store-info img{width:auto;max-width:100%;height:auto;max-height:120px;object-fit:contain;background:#fff;}
@media(max-width:767.98px){#store-info img{max-height:90px;}}

/* =========================================================================
   20. LES TEXTES REDACTIONNELS : UNE SEULE ECHELLE
   Ajoute en 1.8.0.

   L'article d'une boutique ou d'une categorie melait plusieurs echelles : la
   FAQ affichait ses questions en h3 plus GRANDES que le titre « FAQ » en h2
   juste au-dessus, ce qui inverse la hierarchie. Les liens etaient soulignes
   alors que la couleur suffit a les designer. Et selon l'origine du texte, la
   couleur du corps changeait.

   Une seule echelle pour tout ce bloc, quel que soit le niveau de titre.
   ========================================================================= */
main.site-main > .card .card-body{color:var(--vsa-ink-2);font-size:15px;line-height:1.6;}
main.site-main > .card .card-body h2{font-size:21px !important;font-weight:600;color:var(--vsa-ink);margin:26px 0 10px;text-align:left;}
main.site-main > .card .card-body h3{font-size:17px !important;font-weight:600;color:var(--vsa-ink);margin:18px 0 6px;text-align:left;}
main.site-main > .card .card-body h4{font-size:15px !important;font-weight:600;color:var(--vsa-ink);margin:14px 0 5px;text-align:left;}
main.site-main > .card .card-body h2:first-child,
main.site-main > .card .card-body h3:first-child{margin-top:0;}
main.site-main > .card .card-body p,
main.site-main > .card .card-body li{font-size:15px;color:var(--vsa-ink-2);}
main.site-main > .card .card-body strong{color:var(--vsa-ink);font-weight:600;}
main.site-main > .card .card-body a{color:var(--vsa-accent);text-decoration:none;}
main.site-main > .card .card-body a:hover,
main.site-main > .card .card-body a:focus{color:var(--vsa-accent-deep);text-decoration:none;}

/* =========================================================================
   21. LES OFFRES

   La bande creme posee derriere les listes en 1.8.0 est RETIREE : essayee,
   jugee inutile. Les cartes se detachent par leur bordure et par le blanc,
   sans aplat de fond. La classe reste dans les gabarits, sans decor : elle
   sert encore de point d'accroche pour l'espacement.
   ========================================================================= */
.vsa-bande{background:none;padding:0;}

/* L'espace entre l'encadre de titre et les offres etait de 48 px en haut et en
   bas : un vide qui ne portait rien. Reduit de moitie en haut. */
main.site-main > .vsa-bande.py-5{padding-top:18px !important;}

/* =========================================================================
   22. RESULTATS DE RECHERCHE
   ========================================================================= */
.vsa-recherche-titre{font-size:18px !important;text-align:left;margin:22px 0 8px;}
.vsa-resultat{
  display:flex;flex-direction:column;align-items:center;gap:5px;
  height:100%;padding:14px 10px;text-align:center;text-decoration:none;
  background:#fff;border:1px solid var(--vsa-border);border-radius:12px;
}
.vsa-resultat:hover,.vsa-resultat:focus{border-color:var(--vsa-accent);}
.vsa-resultat img{height:48px;width:100%;object-fit:contain;}
.vsa-resultat-nom{font-size:13.5px;font-weight:600;color:var(--vsa-ink);}
.vsa-resultat-nb{font-size:11.5px;color:#0f6e56;font-weight:600;}
.vsa-resultat-nb.vsa-sans{color:#b26b00;}

/* =========================================================================
   26. L'ENCART DU PANNEAU DE MENU

   OU IL EST, ET POURQUOI LA. Pose dans la barre en 1.8.0, il en changeait la
   hauteur : une image de 250 px de cote faisait passer la barre de 91 a 263 px
   et repoussait logo, menu et recherche. Il vit desormais DANS le panneau
   deroulant, en quatrieme colonne a cote des sections. Il n'occupe aucune place
   tant que personne n'a ouvert le menu, et ne peut plus deformer l'en-tete.

   L'EMPLACEMENT EST UNE BOITE FERMEE, de dimensions connues :

       240 x 200 px au maximum
       image : largeur pleine, 160 px de haut au maximum
       texte : quatre lignes au plus, coupees proprement au-dela

   Ce qui depasse est masque, jamais rendu. Format conseille pour un visuel :
   240 x 160 px, ou tout rapport proche de 3/2. Une image carree fonctionne et
   occupera 160 x 160.
   ========================================================================= */
.vsa-panneau-encart{
  break-inside:avoid;
  width:240px;max-height:200px;overflow:hidden;
  margin-top:6px;padding:12px 14px 4px;
  border-top:1px solid var(--vsa-border);
}
@media(min-width:992px){
  /* En colonnes, l'encart forme la derniere colonne : un filet a gauche plutot
     qu'un trait en travers. */
  .vsa-panneau-encart{
    border-top:0;border-left:1px solid var(--vsa-border);
    margin-top:0;padding:2px 4px 2px 16px;
  }
}
.vsa-menu-promo{max-width:100%;font-size:12.5px;line-height:1.35;color:var(--vsa-ink-2);}
.vsa-menu-promo img{
  display:block;width:100%;height:auto;max-height:160px;
  object-fit:contain;border-radius:8px;
}
.vsa-menu-promo p{
  margin:6px 0 0;
  display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;
}
.vsa-menu-promo a{color:var(--vsa-accent);font-weight:600;text-decoration:none;}
.vsa-menu-promo a:hover,.vsa-menu-promo a:focus{color:var(--vsa-accent-deep);}
.vsa-menu-promo a:has(img){display:block;}

/* =========================================================================
   24. LE PIED DE PAGE
   Ajoute en 1.8.1, apres mesure comparative du 18/08/2026.

   La structure etait deja la bonne : #footer-widget.p-5, une rangee, trois
   colonnes col-md-4, et les trois memes modules. Ce qui manquait, c'est
   l'habillage que la feuille de 44 Ko du theme actuel donnait aux liens.

   Mesures, production puis preproduction avant correctif :
     couleur        rgb(59,59,59)   contre  rgb(199,80,30)
     taille         14 px           contre  16 px
     casse          majuscules      contre  aucune
     soulignement   aucun           contre  souligne

   Le terracotta sur chaque lien de pied de page attirait l'oeil autant que les
   boutons d'offre, ce qui est exactement l'inverse de ce qu'un pied de page
   doit faire.
   ========================================================================= */
#footer-widget a,#footer-widget-wide a{
  color:#3b3b3b;font-size:14px;font-weight:400;
  text-transform:uppercase;text-decoration:none;letter-spacing:.02em;
}
#footer-widget a:hover,#footer-widget a:focus,
#footer-widget-wide a:hover,#footer-widget-wide a:focus{color:var(--vsa-accent);text-decoration:none;}
#footer-widget ul,#footer-widget-wide ul{list-style:none;margin:0;padding:0;}
#footer-widget li{margin:0 0 9px;}
#footer-widget p{font-size:14px;color:var(--vsa-ink-2);}
#footer-widget .widget-title,#footer-widget h3{
  font-size:14px;font-weight:700;text-transform:uppercase;
  color:var(--vsa-ink);margin:0 0 12px;text-align:left;
}
/* Les icones de reseaux sociaux gardent leur taille propre. */
#footer-widget a img,#footer-widget-wide a img{max-height:24px;width:auto;}

/* Un filet separe les modules de la ligne de mentions, comme en production. */
#colophon{border-top:1px solid var(--vsa-border);}
#colophon .site-info{font-size:13.5px;color:var(--vsa-ink-2);}
#colophon a{color:var(--vsa-ink);text-decoration:none;font-weight:600;}
#colophon a:hover,#colophon a:focus{color:var(--vsa-accent);}

/* =========================================================================
   25. LE PANNEAU DU MENU
   Un seul panneau, en colonnes quand il y a de quoi. Les intitules de section
   ne sont pas cliquables : ce sont des regroupements, pas des destinations.
   ========================================================================= */
.vsa-panneau{
  padding:14px 6px;border:1px solid var(--vsa-border);border-radius:12px;
  box-shadow:none;min-width:320px;
}
@media(min-width:992px){
  /* Trois colonnes de sections, plus la colonne de l'encart quand il existe. */
  .vsa-panneau{columns:3;column-gap:22px;min-width:620px;}
  .vsa-panneau:has(.vsa-panneau-encart){columns:4;min-width:820px;}
  .vsa-panneau > li{break-inside:avoid;}
}
.vsa-panneau .dropdown-header{
  padding:8px 14px 4px;font-size:11.5px;font-weight:700;
  text-transform:uppercase;letter-spacing:.04em;color:var(--vsa-ink-3);
}
.vsa-panneau .dropdown-item{
  padding:6px 14px;font-size:14px;color:var(--vsa-ink);border-radius:7px;
}
.vsa-panneau .dropdown-item:hover,
.vsa-panneau .dropdown-item:focus{background:var(--vsa-tag);color:var(--vsa-accent-deep);}
.vsa-panneau .dropdown-item.active{background:var(--vsa-tag);color:var(--vsa-accent-deep);}

/* =========================================================================
   27. LIGNE DE CONTROLE, FAMILLES, OFFRES EXPIREES, MOBILE
   Ajoute en 1.10.0.
   ========================================================================= */

/* Proposition 5 : la date du dernier controle, calculee, jamais saisie. */
.vsa-controle{margin:10px 0 0;font-size:13px;color:var(--vsa-green);font-weight:600;}

/* Proposition 3 : orienter avant d'enumerer, sur une page de famille. */
.vsa-famille{margin:18px 0 4px;}
.vsa-famille-titre{font-size:16px !important;text-align:left;margin:0 0 9px;color:var(--vsa-ink-2);font-weight:600;}
.vsa-famille-liste{display:flex;flex-wrap:wrap;gap:8px;}
.vsa-famille-lien{
  display:inline-flex;align-items:center;gap:7px;
  padding:8px 14px;border-radius:999px;text-decoration:none;
  background:#fff;border:1px solid var(--vsa-border);
  font-size:14px;font-weight:600;color:var(--vsa-ink);
}
.vsa-famille-lien:hover,.vsa-famille-lien:focus{border-color:var(--vsa-accent);color:var(--vsa-accent-deep);}
.vsa-famille-nb{
  font-size:11.5px;font-weight:700;padding:1px 8px;border-radius:999px;
  background:var(--vsa-tag);color:var(--vsa-accent-deep);
}

/* Proposition 4 : les offres expirees, repliees. <details> se replie sans
   JavaScript, et le contenu reste dans le document. */
.vsa-expirees{margin:8px 0 24px;}
.vsa-expirees > summary{
  cursor:pointer;list-style:none;
  padding:11px 15px;border:1px solid var(--vsa-border);border-radius:11px;
  background:#fff;font-size:14px;font-weight:600;color:var(--vsa-ink-2);
}
.vsa-expirees > summary::-webkit-details-marker{display:none;}
.vsa-expirees > summary::after{content:"+";float:right;font-weight:700;color:var(--vsa-accent);}
.vsa-expirees[open] > summary::after{content:"–";}
.vsa-expirees > summary:hover,.vsa-expirees > summary:focus{border-color:var(--vsa-accent);}
.vsa-expirees-nb{color:var(--vsa-ink-3);font-weight:400;}

/* SUR MOBILE, LA COLONNE RESTE APRES LE CONTENU. Correctif de la 1.12.1.
 *
 * La 1.10.0 la faisait remonter avant la fiche, pour rendre visibles les modules
 * de navigation qui n'arrivaient qu'apres cinquante cartes. L'intention etait de
 * ne remonter QUE le module des categories, mais le CSS deplacait la colonne
 * ENTIERE : le visiteur arrivait sur une fiche boutique et lisait d'abord les
 * autres boutiques et la liste des categories, avant meme de savoir ou il etait.
 * Exactement l'inverse de ce qu'on cherchait.
 *
 * Le CSS ne sait pas sortir un seul module d'un conteneur pour le poser
 * ailleurs : il ne peut que reordonner des freres. Faire remonter la seule barre
 * des categories demanderait de la rendre a un autre endroit du gabarit, ce qui
 * reste possible mais devient un choix a part entiere, pas un ajustement de
 * feuille de style.
 *
 * L'ordre naturel est donc retabli : la fiche d'abord, la colonne ensuite. */
@media(max-width:991.98px){
  #secondary .widget{margin-bottom:10px;}
}

/* =========================================================================
   28. LA RECHERCHE, ALLEGEE
   Ajoute en 1.11.0.

   Le bouton etait un aplat terracotta de la taille du champ. Dans une barre qui
   porte deja un logo, trois entrees de menu et un panneau deroulant, il pesait
   autant que le menu, pour une fonction qui n'est pas la principale du site.

   La loupe passe DANS le champ, en gris, et ne prend la couleur d'accent qu'au
   survol ou au focus. Elle reste un vrai bouton d'envoi : atteignable au
   clavier, annonce par un lecteur d'ecran.
   ========================================================================= */
.vsa-recherche{position:relative;display:flex;align-items:center;margin:0;}
.vsa-recherche-champ{
  width:100%;min-width:150px;max-width:230px;
  padding:7px 34px 7px 13px;
  font-size:13.5px;color:var(--vsa-ink);
  background:#fff;border:1px solid var(--vsa-border);border-radius:999px;
  transition:none;
}
.vsa-recherche-champ::placeholder{color:var(--vsa-ink-3);}
.vsa-recherche-champ:focus{
  outline:none;border-color:var(--vsa-accent);
  box-shadow:0 0 0 2px var(--vsa-tag);
}
.vsa-recherche-envoi{
  position:absolute;right:2px;top:50%;transform:translateY(-50%);
  display:flex;align-items:center;justify-content:center;
  width:30px;height:30px;padding:0;
  background:none;border:none;border-radius:999px;
  color:var(--vsa-ink-3);font-size:13px;cursor:pointer;transition:none;
}
.vsa-recherche-envoi:hover,.vsa-recherche-envoi:focus{color:var(--vsa-accent);}
.vsa-recherche-envoi:focus-visible{outline:2px solid var(--vsa-accent);outline-offset:1px;}

/* Sur mobile, le champ occupe la place disponible sans jamais deborder. */
.vsa-recherche-mobile{flex:0 1 44%;min-width:0;}
@media(max-width:991.98px){
  .vsa-recherche-champ{max-width:100%;font-size:13px;}
}

/* =========================================================================
   29. LA BARRE SUR MOBILE
   Ajoute en 1.12.0.

   TROIS DEFAUTS CORRIGES, ET UN CHOIX.

   1. La hauteur reservee etait ECRITE EN DUR : 96 px sur grand ecran, 128 sous
      992. Or la barre depend du logo, du menu et de la presence de la barre
      d'administration. Trop peu, le contenu passe dessous ; trop, une bande
      vide s'installe. Elle est desormais MESUREE par le script et posee dans
      --vsa-barre. Sans JavaScript, les valeurs ci-dessous servent de repli et
      restent correctes.

   2. Sous 783 px, la barre d'administration de WordPress n'est plus fixe : lui
      reserver 46 px ouvrait une fenetre par laquelle le contenu defilait.

   3. LA BARRE S'EFFACE QUAND ON DESCEND, revient des qu'on remonte. Sur un
      telephone de 850 px de haut, une barre de 90 px occupe plus d'un dixieme
      de l'ecran en permanence, sur des pages qui alignent cinquante offres.
      La faire disparaitre pendant la descente rend cette place a la lecture,
      et le moindre geste vers le haut la ramene : la recherche et le menu
      restent a un demi-geste, sans jamais remonter en haut de page.

      Aucune transition : la charte exclut les animations. La barre parait ou
      disparait, elle ne glisse pas.
   ========================================================================= */
@media(max-width:991.98px){
  .navbar.fixed-top{transition:none;}
  /* Posee par le script pendant la descente, retiree des qu'on remonte. */
  body.vsa-descend .navbar.fixed-top{transform:translateY(-100%);}
  /* La barre reste plus compacte une fois la page entamee. */
  body.vsa-defile .navbar.fixed-top .navbar-brand img,
  body.vsa-defile .navbar.fixed-top .custom-logo{max-height:44px;}
}

/* =========================================================================
   30. LE RETOUR EN HAUT

   Il n'apparait qu'apres trois hauteurs d'ecran : plus tot, il encombrerait
   sans servir. Place a droite, au-dessus du pouce, il ne recouvre ni le bouton
   d'une carte ni le pied de page.

   C'est un vrai bouton, atteignable au clavier et annonce par un lecteur
   d'ecran. Le retour est INSTANTANE si la personne a demande a son systeme de
   reduire les animations.
   ========================================================================= */
.vsa-haut{
  position:fixed;right:14px;bottom:16px;z-index:1030;
  display:none;align-items:center;justify-content:center;
  width:44px;height:44px;padding:0;
  background:#fff;color:var(--vsa-accent);
  border:1px solid var(--vsa-border);border-radius:999px;
  font-size:15px;cursor:pointer;transition:none;
  box-shadow:0 1px 4px rgba(0,0,0,.10);
}
.vsa-haut.vsa-visible{display:flex;}
.vsa-haut:hover,.vsa-haut:focus{color:#fff;background:var(--vsa-accent);border-color:var(--vsa-accent);}
.vsa-haut:focus-visible{outline:2px solid var(--vsa-accent);outline-offset:2px;}
@media print{.vsa-haut{display:none !important;}}

/* =========================================================================
   31. L'ESPACE SOUS LA BARRE
   Ajoute en 1.15.0.

   Les gabarits portent tous « pt-5 », soit 48 px, heritage du balisage
   d'origine. A cela s'ajoute la hauteur reservee sous la barre fixe, mesuree,
   d'environ 92 px. Le titre de la page et le premier module de la colonne
   commencaient donc 140 px sous le haut de l'ecran, sans que rien n'occupe cet
   espace.

   Reduit a 20 px, et 12 sur mobile ou la place vaut plus cher. La barre garde
   son propre espace : c'est le vide EN PLUS qui disparait.
   ========================================================================= */
#content > [class*="container"].pt-5{padding-top:20px !important;}
@media(max-width:991.98px){
  #content > [class*="container"].pt-5{padding-top:12px !important;}
}
/* La banniere d'une boutique, quand il y en a une, colle au haut de page. */
.store-banner{margin-top:0;}

/* =========================================================================
   32. LE PLIAGE D'UN TEXTE, PLACE OU L'ON VEUT

   Le shortcode [vsa_replier] plie n'importe quel passage derriere un
   « Lire la suite », comme le fait la description d'une offre.

   LE TEXTE ENTIER RESTE DANS LE DOCUMENT : il est seulement borne en hauteur.
   Les moteurs le lisent en totalite, et sans JavaScript rien n'est plie, le
   bouton n'existant pas. C'est ce qui permet de garder un texte long pour le
   referencement sans le faire subir au lecteur.
   ========================================================================= */
.vsa-repliable{position:relative;}
.js .vsa-repliable.vsa-clamp{
  display:-webkit-box;-webkit-line-clamp:var(--vsa-lignes,4);-webkit-box-orient:vertical;overflow:hidden;
}
/* Un degrade signale que le texte continue, sans rien masquer de definitif. */
.js .vsa-repliable.vsa-clamp::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:2.2em;
  background:linear-gradient(to bottom,rgba(255,255,255,0),#fff 85%);
  pointer-events:none;
}
.vsa-repliable > :first-child{margin-top:0;}
.vsa-repliable > :last-child{margin-bottom:0;}

/* =========================================================================
   33. LE BLOC D'OUTILS DE L'ANNUAIRE
   Ajoute en 1.15.1.

   Les deux rangees de filtres se retrouvaient collees sous le « Lire la suite »
   de l'introduction : rien ne disait ou finissait le discours et ou commencaient
   les commandes. Deux corrections, et une seule idee.

   1. LE TEXTE ET LES OUTILS SONT SEPARES par de l'espace et un filet. Un bouton
      de pliage a besoin d'air en dessous, sinon il se lit comme le premier
      element de ce qui suit.

   2. CHAQUE RANGEE PORTE SON INTITULE, « Univers » et « Disponibilite ». Une
      serie de pastilles sans titre oblige a deviner ce qu'on choisit, et a
      cliquer pour comprendre. L'intitule est discret, en petites majuscules :
      il informe sans peser.

   Le titre de section est un vrai h2 : il structure la page pour un lecteur
   d'ecran comme pour un moteur, la ou une simple ligne en gras n'aurait rien
   dit a ni l'un ni l'autre.
   ========================================================================= */
.vsa-repliable + .vsa-plus{margin-bottom:26px;}

.vsa-outils-annuaire{
  margin:0 0 26px;padding:16px 18px 6px;
  background:var(--vsa-cream);border:1px solid var(--vsa-border);border-radius:14px;
}
.vsa-outils-titre{
  font-size:16px !important;font-weight:600;text-align:left;
  color:var(--vsa-ink);margin:0 0 12px;
}
.vsa-outils-annuaire .vsa-boutiques-filtres{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 12px;
}
.vsa-filtre-label{
  flex:0 0 auto;min-width:96px;
  font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--vsa-ink-3);
}
.vsa-outils-annuaire .vsa-filtre{background:#fff;}
.vsa-outils-annuaire .vsa-filtre.vsa-actif{background:var(--vsa-tag);}

@media(max-width:575.98px){
  /* Sur mobile l'intitule passe au-dessus de sa rangee : cote a cote, il ne
     resterait pas la place d'une seule pastille. */
  .vsa-outils-annuaire .vsa-boutiques-filtres{gap:6px;}
  .vsa-filtre-label{flex:0 0 100%;min-width:0;margin-bottom:2px;}
}

/* =========================================================================
   34. LA PAGE « TOUTES LES CATEGORIES »
   Ajoute en 1.16.0.

   Une section par famille, en pleine largeur. Les sous-categories sans offre
   restent visibles, attenuees : une page qui promet de tout montrer ne peut pas
   en cacher une partie. L'ordre est alphabetique et stable, et non par nombre
   d'offres : on revient sur ce genre de page pour retrouver quelque chose au
   meme endroit.
   ========================================================================= */
.vsa-famille-bloc{margin:0 0 30px;}
.vsa-famille-bloc:last-child{margin-bottom:0;}

.vsa-famille-titre{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  font-size:19px !important;font-weight:600;text-align:left;
  margin:0 0 2px;padding-bottom:8px;border-bottom:1px solid var(--vsa-border);
}
.vsa-famille-titre a{color:var(--vsa-ink);text-decoration:none;}
.vsa-famille-titre a:hover,.vsa-famille-titre a:focus{color:var(--vsa-accent);}
.vsa-famille-compte{
  font-size:11.5px;font-weight:700;padding:2px 9px;border-radius:999px;
  background:var(--vsa-tag);color:var(--vsa-accent-deep);
}
.vsa-famille-resume{font-size:13px;color:var(--vsa-ink-3);margin:8px 0 12px;}

/* GRILLE A COLONNES FIXES. Les tuiles gardent leur largeur, quelle que soit
   leur nombre : une famille de deux sous-categories montre deux tuiles de
   taille normale, et la rangee n'est pas remplie. Essayee en 1.17.0, la version
   qui etirait les tuiles pour combler la rangee a ete ecartee : deux tuiles
   larges comme des banderoles disaient moins bien ce qu'elles etaient. */
.vsa-cat-grille{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;}
@media(max-width:1399.98px){.vsa-cat-grille{grid-template-columns:repeat(4,1fr);}}
@media(max-width:1199.98px){.vsa-cat-grille{grid-template-columns:repeat(3,1fr);}}
@media(max-width:767.98px){.vsa-cat-grille{grid-template-columns:repeat(3,1fr);}}
@media(max-width:479.98px){.vsa-cat-grille{grid-template-columns:repeat(2,1fr);}}

.vsa-cat-tuile{
  display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:6px;
  padding:16px 10px;text-align:center;text-decoration:none;
  background:#fff;border:1px solid var(--vsa-border);border-radius:12px;
  transition:none;
}
.vsa-cat-tuile:hover,.vsa-cat-tuile:focus{border-color:var(--vsa-accent);}
.vsa-cat-visuel{display:flex;align-items:center;justify-content:center;height:40px;}
.vsa-cat-visuel img{max-height:36px;max-width:100%;width:auto;object-fit:contain;}
.vsa-cat-picto{font-size:24px;line-height:1;}
.vsa-cat-nom{font-size:13.5px;font-weight:600;color:var(--vsa-ink);line-height:1.25;}
.vsa-cat-nb{font-size:11.5px;font-weight:600;color:#0f6e56;}

/* « Bientot » : la rubrique existe, elle attend sa premiere offre. Attenuee,
   jamais masquee, et la mention est explicite plutot qu'un zero sec. */
.vsa-cat-tuile.vsa-cat-vide{opacity:.58;}
.vsa-cat-tuile.vsa-cat-vide .vsa-cat-nb{color:#b26b00;}
.vsa-cat-tuile.vsa-cat-vide:hover,.vsa-cat-tuile.vsa-cat-vide:focus{opacity:1;}

/* Section des categories posee par [vsa_categories] : elle se comporte comme
   n'importe quel bloc de contenu, avec de l'air au-dessus et en dessous. */
.vsa-section-categories{margin:26px 0;}
.vsa-section-titre{font-size:20px !important;text-align:left;margin:0 0 14px;}

/* =========================================================================
   35. LE CHEVRON D'UN PARENT DE MENU
   Ajoute en 1.16.2.

   Bootstrap pose l'ouverture du panneau sur le lien lui-meme : le clic ouvre et
   n'atteint jamais la destination. Le libelle redevient un lien ordinaire, et
   ce bouton, a cote, ouvre le panneau. Deux cibles distinctes.

   Le bouton est assez large pour le doigt, 34 px, et porte un intitule que seul
   un lecteur d'ecran entend.
   ========================================================================= */
.navbar-nav .nav-item.dropdown{display:flex;align-items:center;}
.vsa-nav-chevron{
  display:inline-flex;align-items:center;justify-content:center;
  width:34px;padding:0;margin-left:-6px;
  background:none;border:none;color:inherit;cursor:pointer;transition:none;
}
.vsa-nav-chevron::after{margin:0;}
.vsa-nav-chevron:hover,.vsa-nav-chevron:focus{color:var(--vsa-accent);}
.vsa-nav-chevron:focus-visible{outline:2px solid var(--vsa-accent);outline-offset:2px;border-radius:6px;}
/* Le lien du parent perd le chevron que Bootstrap lui ajoutait. */
.navbar-nav .nav-item.dropdown > .nav-link:not(.vsa-nav-chevron)::after{display:none;}

@media(max-width:991.98px){
  /* Sur mobile, le libelle prend la largeur et le chevron reste a droite :
     deux cibles nettes, sans avoir a viser. */
  .navbar-nav .nav-item.dropdown{justify-content:space-between;width:100%;}
  .vsa-nav-chevron{width:44px;margin-left:0;}
}

/* =========================================================================
   36. LA COLONNE D'ANNONCE
   Ajoute en 1.18.0.

   Une colonne de droite reservee a un encart, sur les pages ou la colonne
   habituelle ferait doublon. Elle ne contient QUE ce qu'on y pose : ni
   categories, ni boutiques voisines.

   SA LARGEUR EST FIXE, 300 px, et c'est le point important. Un encart
   publicitaire arrive apres le reste de la page, souvent depuis un autre
   domaine : si la colonne se dimensionnait sur son contenu, la page se
   reorganiserait sous les yeux du visiteur au moment ou l'encart apparait, et
   les tuiles se decaleraient. L'espace est donc pris d'avance, vide ou plein.

   300 px est la largeur des formats courants, du 300x250 au 300x600. Une
   hauteur minimale de 250 px est reservee pour la meme raison.
   ========================================================================= */
@media(min-width:992px){
  /* La rangee ne doit pas renvoyer la colonne a la ligne : elle est etroite et
     fixe, c'est au contenu de s'adapter. */
  .cmd-page-toutes-categories > .row.flex-nowrap-lg{flex-wrap:nowrap;}
  .cmd-page-toutes-categories > .row.flex-nowrap-lg > #primary{min-width:0;}
}
.vsa-colonne-annonce{
  flex:0 0 300px;width:300px;max-width:300px;
  margin-left:24px;
}
.vsa-colonne-annonce .widget{
  min-height:250px;
  margin:0 0 18px;padding:0;
  background:none;border:0;border-radius:0;box-shadow:none;
}
.vsa-colonne-annonce .widget:last-child{margin-bottom:0;}
/* Ce qui depasse est borne plutot que de casser la largeur reservee. */
.vsa-colonne-annonce img,
.vsa-colonne-annonce iframe,
.vsa-colonne-annonce ins,
.vsa-colonne-annonce > .widget > *{max-width:300px;}
.vsa-colonne-annonce img{height:auto;}

@media(max-width:991.98px){
  /* Sous 992 px il n'y a pas la place d'une colonne : l'encart passe sous le
     contenu, centre, et garde sa largeur propre. */
  .vsa-colonne-annonce{
    flex:1 1 100%;width:100%;max-width:100%;
    margin:26px 0 0;display:flex;justify-content:center;
  }
  .vsa-colonne-annonce .widget{width:300px;max-width:100%;}
}
