{"product_id":"coussin-de-lecture","title":"Coussin de lecture","description":"\u003c!-- SECTION HÉROS PRODUIT --\u003e\n\u003csection id=\"productHero\" class=\"containerLizia\"\u003e\n  \u003c!-- Badge lecteurs satisfaits (mobile) --\u003e\n  \u003cdiv class=\"badgeReadersMobile mobileOnly\"\u003e\n    \u003cspan class=\"badgeReaders\"\u003e⭐ +100 000 lecteurs satisfaits\u003c\/span\u003e\n  \u003c\/div\u003e\n\n  \u003cdiv class=\"heroGrid\"\u003e\n    \u003c!-- Colonne Gauche: Galerie d'images --\u003e\n    \u003cdiv class=\"imageGallery\"\u003e\n      \u003cdiv class=\"mainImageContainer\"\u003e\n        \u003cimg src=\"..\/public\/placeholder.svg\" alt=\"Coussin de lecture\" id=\"mainProductImage\" class=\"mainImage\"\u003e\n        \u003cbutton id=\"prevImageBtn\" class=\"galleryNavBtn prev\" aria-label=\"Image précédente\"\u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n        \u003cbutton id=\"nextImageBtn\" class=\"galleryNavBtn next\" aria-label=\"Image suivante\"\u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n      \u003c\/div\u003e\n      \u003cdiv id=\"imageDots\" class=\"imageDots\"\u003e\u003c\/div\u003e\n      \u003cdiv id=\"thumbnailContainer\" class=\"thumbnailGrid\"\u003e\u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003c!-- Colonne Droite: Configuration --\u003e\n    \u003cdiv class=\"productConfig\"\u003e\n      \u003cdiv class=\"productHeader\"\u003e\n        \u003cspan class=\"badgeReaders desktopOnly\"\u003e⭐ +100 000 lecteurs satisfaits\u003c\/span\u003e\n        \u003ch1\u003eCOUSSIN DE LECTURE\u003c\/h1\u003e\n        \u003cdiv class=\"reviewsSummary\"\u003e\n          \u003cdiv class=\"stars\"\u003e\n            \u003c!-- Les étoiles seront insérées ici par le CSS ou JS --\u003e\n          \u003c\/div\u003e\n          \u003cspan class=\"rating\"\u003e4.6\/5\u003c\/span\u003e\n          \u003cspan class=\"reviewCount\"\u003e(536 avis)\u003c\/span\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"productSubtitle\"\u003e\n          \u003c!-- Coussin de lecture • Confort optimal --\u003e\n        \u003c\/p\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 1. Sélecteur de pack --\u003e\n      \u003cdiv class=\"configStep\"\u003e\n        \u003clabel class=\"stepLabel\"\u003e\n          \u003cspan class=\"stepLabelNumber\"\u003e1\u003c\/span\u003e\n          Pack\n        \u003c\/label\u003e\n        \u003cdiv id=\"packSelector\" class=\"packSelectorGrid\"\u003e\u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 2. Quantité et Réduction --\u003e\n      \u003cdiv class=\"configStep\"\u003e\n        \u003cdiv class=\"quantityDiscountWrapper\"\u003e\n          \u003cdiv class=\"quantitySection\"\u003e\n            \u003clabel class=\"stepLabel\"\u003e\n              \u003cspan class=\"stepLabelNumber\"\u003e2\u003c\/span\u003e\n              Quantité\n            \u003c\/label\u003e\n            \u003cdiv id=\"quantitySelector\" class=\"quantitySelectorContainer\"\u003e\u003c\/div\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"discountSection\"\u003e\n            \u003cdiv class=\"discountTitle\"\u003eRéduction sur ta commande\u003c\/div\u003e\n            \u003cdiv id=\"discountGauge\" class=\"discountGaugeContainer\"\u003e\u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 3. Couleurs \u0026 Personnalisation (conditionnel) --\u003e\n      \u003cdiv class=\"configStep\" id=\"liziaItemsSection\" style=\"display: none\"\u003e\n        \u003clabel id=\"colorsPersoLabel\" class=\"stepLabel\"\u003e\n          \u003cspan class=\"stepLabelNumber\"\u003e3\u003c\/span\u003e\n          Personnalisation\n        \u003c\/label\u003e\n        \u003cdiv id=\"liziaItemsContainer\" class=\"liziaItemsContainer\"\u003e\u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Bouton d'ajout au panier Mobile (au-dessus du prix) --\u003e\n      \u003cdiv class=\"mobileOnly\"\u003e\n        \u003cbutton id=\"addToCartBtnMobile\" class=\"addToCartBtnMobile\"\u003e\n          AJOUTER AU PANIER\n        \u003c\/button\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 5. Bouton d'ajout au panier Desktop --\u003e\n      \u003cbutton id=\"addToCartBtn\" class=\"addToCartBtn desktopOnly\"\u003e\n        AJOUTER AU PANIER\n      \u003c\/button\u003e\n\n      \u003c!-- Info livraison avec point vert animé --\u003e\n      \u003cdiv class=\"deliveryInfo\"\u003e\n        \u003cdiv class=\"deliveryIndicator\"\u003e\n          \u003cdiv class=\"deliveryDot\"\u003e\u003c\/div\u003e\n        \u003c\/div\u003e\n        \u003cspan class=\"deliveryText\"\u003eLivraison en 3 jours ouvrés\u003c\/span\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Badges de garantie --\u003e\n      \u003cdiv class=\"guaranteeBadges\"\u003e\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_fr.webp?v=1762266031\" alt=\"Made In France\" class=\"guaranteeIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eMADE IN FRANCE\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eQualité française\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_garanti.webp?v=1762266032\" alt=\"2 ans de garantie\" class=\"guaranteeIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e2 ANS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eGarantie\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_rembourser.webp?v=1762267150\" alt=\"15 jours satisfaits ou remboursé\" class=\"guaranteeIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e15 JOURS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eSatisfait ou remboursé\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_livraison.webp?v=1762266031\" alt=\"Livré en 1 à 3 jours\" class=\"guaranteeIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eLIVRÉ\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003e1-5 jours\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cdiv id=\"configEndSentinel\" class=\"configEndSentinel\" style=\"height: 1px; width: 100%\"\u003e\u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\n\u003c!-- Bouton Mobile déplacé au-dessus du prix (aucun bloc sticky séparé) --\u003e\n\n\u003c!-- SECTION COMMENT LIZIA FONCTIONNE --\u003e\n\u003csection id=\"howItWorks\" class=\"howItWorksSection\"\u003e\n  \u003cdiv class=\"containerLizia\"\u003e\n    \u003ch2 class=\"howItWorksTitle\" data-scroll-reveal\u003e\n      Comment fonctionne le coussin de lecture ?\n    \u003c\/h2\u003e\n    \u003cdiv class=\"howItWorksGrid\"\u003e\n      \u003c!-- Colonne Gauche: Accordéon --\u003e\n      \u003cdiv class=\"howItWorksContent\"\u003e\n        \u003c!-- Item 1 --\u003e\n        \u003cdiv class=\"howItWorksItem active\" data-index=\"0\"\u003e\n          \u003cdiv class=\"itemHeader\"\u003e\n            \u003cdiv class=\"itemNumber\"\u003e01\u003c\/div\u003e\n            \u003ch3 class=\"itemTitle\"\u003eConfort optimal\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Profitez d'un soutien ergonomique qui épouse parfaitement votre\n              dos et vos épaules pour une position de lecture idéale, où que\n              vous soyez.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 2 --\u003e\n        \u003cdiv class=\"howItWorksItem\" data-index=\"1\"\u003e\n          \u003cdiv class=\"itemHeader\"\u003e\n            \u003cdiv class=\"itemNumber\"\u003e02\u003c\/div\u003e\n            \u003ch3 class=\"itemTitle\"\u003ePosition idéale\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Maintient votre livre à la bonne hauteur et au bon angle pour\n              éviter les tensions cervicales et les douleurs aux poignets.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 3 --\u003e\n        \u003cdiv class=\"howItWorksItem\" data-index=\"2\"\u003e\n          \u003cdiv class=\"itemHeader\"\u003e\n            \u003cdiv class=\"itemNumber\"\u003e03\u003c\/div\u003e\n            \u003ch3 class=\"itemTitle\"\u003eLecture prolongée\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Lisez des heures sans fatigue grâce à son design pensé pour offrir\n              un confort durable et une relaxation totale.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Colonne Droite: Vidéo --\u003e\n      \u003cdiv class=\"videoCard\" data-scroll-reveal\u003e\n        \u003cdiv class=\"videoContainer\"\u003e\n          \u003cvideo class=\"liziaVideo\" data-video-id=\"1\" playsinline muted preload=\"metadata\"\u003e\n            \u003csource src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/333422d9fd34411cbaeda5b404b46544.mp4\" type=\"video\/mp4\"\u003e\u003c\/video\u003e\n          \u003cdiv class=\"videoControls\"\u003e\n            \u003cbutton class=\"videoPlayBtn\" data-video-id=\"1\" aria-label=\"Play\/Pause\"\u003e\n              \u003csvg class=\"playIcon\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 24 24\" fill=\"white\" width=\"24\" height=\"24\"\u003e\n                \u003cpath d=\"M8 5v14l11-7z\"\u003e\u003c\/path\u003e\n              \u003c\/svg\u003e\n              \u003csvg class=\"pauseIcon\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 24 24\" fill=\"white\" width=\"24\" height=\"24\" style=\"display: none\"\u003e\n                \u003cpath d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\"\u003e\u003c\/path\u003e\n              \u003c\/svg\u003e\n            \u003c\/button\u003e\n            \u003cbutton class=\"videoMuteBtn\" data-video-id=\"1\" aria-label=\"Mute\/Unmute\"\u003e\n              \u003csvg class=\"unmuteIcon\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 24 24\" fill=\"white\" width=\"24\" height=\"24\"\u003e\n                \u003cpath d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\"\u003e\u003c\/path\u003e\n              \u003c\/svg\u003e\n              \u003csvg class=\"muteIcon\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 24 24\" fill=\"white\" width=\"24\" height=\"24\" style=\"display: none\"\u003e\n                \u003cpath d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\"\u003e\u003c\/path\u003e\n              \u003c\/svg\u003e\n            \u003c\/button\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\n\u003c!-- SECTION AVANT\/APRÈS --\u003e\n\u003csection id=\"beforeAfterSection\" class=\"beforeAfterSection\"\u003e\n  \u003cdiv class=\"containerLizia\"\u003e\n    \u003cdiv class=\"beforeAfterGrid\"\u003e\n      \u003c!-- Colonne Gauche: Texte (Desktop) \/ Haut (Mobile) --\u003e\n      \u003cdiv class=\"beforeAfterContent\"\u003e\n        \u003ch2 class=\"beforeAfterTitle\" data-scroll-reveal\u003e\n          Lire n'a jamais été aussi confortable\n        \u003c\/h2\u003e\n        \u003ch3 class=\"beforeAfterSubtitle\" data-scroll-reveal\u003e\n          Sans fatigue, à une seule main\n        \u003c\/h3\u003e\n        \u003cp class=\"beforeAfterDescription\" data-scroll-reveal\u003e\n          Adapté à tous les pouces, gagnez en flexibilité où que vous soyez.\n          Sans effort, appréciez pleinement votre lecture, y compris dans\n          l'obscurité sans déranger personne.\n        \u003c\/p\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Colonne Droite: Images avec Toggle (Desktop) \/ Bas (Mobile) --\u003e\n      \u003cdiv class=\"beforeAfterImages\" data-scroll-reveal\u003e\n        \u003cdiv class=\"lightToggleContainer\"\u003e\n          \u003cdiv class=\"lightToggle\"\u003e\n            \u003cbutton id=\"lightToggleBtn\" class=\"lightToggleBtn\" aria-label=\"Toggle light\"\u003e\n              \u003cspan class=\"lightToggleOn\"\u003e\n                \u003csvg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewbox=\"0 0 24 24\" fill=\"white\" width=\"16\" height=\"16\"\u003e\n                  \u003cpath d=\"M9 21c0 .5.4 1 1 1h4c.6 0 1-.5 1-1v-1H9v1zm3-19C8.1 2 5 5.1 5 9c0 2.4 1.2 4.5 3 5.7V17c0 .5.4 1 1 1h6c.6 0 1-.5 1-1v-2.3c1.8-1.3 3-3.4 3-5.7 0-3.9-3.1-7-7-7z\"\u003e\u003c\/path\u003e\n                \u003c\/svg\u003e\n                LIGHT ON\n              \u003c\/span\u003e\n              \u003cspan class=\"lightToggleOff\"\u003eOFF\u003c\/span\u003e\n            \u003c\/button\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"beforeAfterImageContainer\"\u003e\n          \u003cimg id=\"beforeAfterImageOff\" src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\" alt=\"Lizia light off\" class=\"beforeAfterImage beforeAfterImageOff\"\u003e\n          \u003cimg id=\"beforeAfterImageOn\" src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\" alt=\"Lizia light on\" class=\"beforeAfterImage beforeAfterImageOn\"\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\n\u003c!-- BANDEAU MÉDIAS INFINI --\u003e\n\u003csection class=\"mediaBanner\"\u003e\n  \u003cdiv class=\"mediaBannerWrapper\"\u003e\n    \u003cdiv class=\"mediaBannerSlide\"\u003e\n      \u003ca href=\"https:\/\/www.europe1.fr\/emissions\/demain-au-bureau\/lizia-laccessoire-de-lecture-3-en-1-4163529\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_europe1.webp?v=1763465558\" alt=\"Europe1\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.tf1.fr\/tmc\/quotidien-avec-yann-barthes\/videos\/linventeur-lizia-la-future-meilleure-amie-des-lecteurs-signee-cedric-le-guern-et-lucas-moysan-57742169.html\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_quotidien.webp?v=1763465558\" alt=\"Quotidien\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.bfmtv.com\/economie\/replay-emissions\/good-morning-business\/la-pepite-lizia-permet-de-maintenir-les-pages-d-un-livre-ouvertes-a-une-main-par-noemie-wira-19-12_VN-202212190036.html\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_bfm_business.webp?v=1763465558\" alt=\"Bfm-tv-business\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.ouest-france.fr\/bretagne\/roudouallec-56110\/roudouallec-ils-creent-un-accessoire-de-lecture-innovant-16121898-f0a2-11ec-9262-0032434e6459\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_ouest_france.webp?v=1763465558\" alt=\"Ouest-France\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.tf1.fr\/tmc\/quotidien-avec-yann-barthes\/videos\/linventeur-lizia-la-future-meilleure-amie-des-lecteurs-signee-cedric-le-guern-et-lucas-moysan-57742169.html\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tf1.webp?v=1763465558\" alt=\"Tf1\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.letelegramme.fr\/morbihan\/roudouallec-56110\/deux-jeunes-bretons-donnent-un-coup-de-pouce-aux-lecteurs-avec-lizia-281522.php\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_telegramme.webp?v=1763465558\" alt=\"Le-telegramme\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.m6.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\" alt=\"M6\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/entrepreneurs.lesechos.fr\/developpement-entreprise\/strategie\/de-linvention-futee-au-succes-commercial-lizia-a-la-conquete-des-fanas-de-lecture-2094778\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_les_echos.webp?v=1763465557\" alt=\"Les-echos\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.tf1.fr\/tmc\/quotidien-avec-yann-barthes\/videos\/linventeur-lizia-la-future-meilleure-amie-des-lecteurs-signee-cedric-le-guern-et-lucas-moysan-57742169.html\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tmc.webp?v=1763465559\" alt=\"TMC\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.nrj.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_nrj.webp?v=1763465558\" alt=\"NRJ\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.7jours.fr\/actualites\/lizia-rennes-jeune-pousse-eclaire-lecture\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_7_jours.webp?v=1763465558\" alt=\"7-jours\"\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.lejournaldesentreprises.com\/breve\/lizia-lance-son-accessoire-de-lecture-en-belgique-et-en-suisse-2129508\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_le_journal_des_entreprises.webp?v=1763465558\" alt=\"le-journal-des-entreprises\"\u003e\n      \u003c\/a\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\n\u003c!-- SECTION ENGAGEMENTS LIZIA --\u003e\n\u003csection id=\"valuesSection\" class=\"valuesSection\" data-scroll-reveal\u003e\n  \u003cdiv class=\"containerLizia\"\u003e\n    \u003cdiv class=\"valuesHeader\"\u003e\n      \u003ch2 class=\"valuesTitle\"\u003eLES ENGAGEMENTS LIZIA\u003c\/h2\u003e\n      \u003cp id=\"valuesSubtitle\" class=\"valuesSubtitle\"\u003e\n        Imaginée à Rennes, Lizia est une innovation française conçue pour offrir\n        un confort de lecture durable, avec des partenaires locaux et des\n        matériaux responsables.\n      \u003c\/p\u003e\n    \u003c\/div\u003e\n\n    \u003cdiv class=\"valuesGrid\"\u003e\n      \u003c!-- Colonne Gauche: Liste des engagements --\u003e\n      \u003cdiv class=\"valuesList\"\u003e\n        \u003c!-- Item 1 --\u003e\n        \u003cdiv class=\"valueItem active\" data-index=\"0\" data-title=\"Fabriqué en France\" data-desc=\"Nos pièces en nylon sont produites dans l'Ouest de la France puis assemblées en Bretagne. En choisissant Lizia, vous soutenez une fabrication 100 % française, un circuit court et un savoir-faire industriel local, de la conception à l'expédition depuis Rennes.\"\u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- France Map Icon --\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_france.svg?v=1763826898\" alt=\"France\" class=\"valueIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eFabriqué en France\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003e\n              Production et assemblage dans l'Ouest\n            \u003c\/p\u003e\n            \u003c!-- Mobile Description (Hidden by default) --\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Nos pièces en nylon sont produites dans l'Ouest de la France puis\n              assemblées en Bretagne. En choisissant Lizia, vous soutenez une\n              fabrication 100 % française, un circuit court et un savoir-faire\n              industriel local, de la conception à l'expédition depuis Rennes.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 2 --\u003e\n        \u003cdiv class=\"valueItem\" data-index=\"1\" data-title=\"Assemblé en ESAT\" data-desc=\"Lizia s'engage socialement en confiant l'assemblage de ses produits à des ESAT (Établissements et Services d'Aide par le Travail) partenaires en Bretagne, favorisant l'insertion professionnelle des personnes en situation de handicap.\"\u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Wheelchair\/Accessibility Icon --\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_esat.svg?v=1763826898\" alt=\"ESAT\" class=\"valueIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAssemblé en ESAT\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003ePartenaires locaux\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia s'engage socialement en confiant l'assemblage de ses\n              produits à des ESAT (Établissements et Services d'Aide par le\n              Travail) partenaires en Bretagne, favorisant l'insertion\n              professionnelle des personnes en situation de handicap.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 3 --\u003e\n        \u003cdiv class=\"valueItem\" data-index=\"2\" data-title=\"Matériaux recyclables\" data-desc=\"Nous utilisons du PA12 technique pour sa robustesse et sa durabilité, ainsi que des emballages en carton 100% recyclables. Notre démarche d'éco-conception vise à minimiser notre impact environnemental.\"\u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Leaf\/Recycle Icon --\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_recyclable.svg?v=1763826898\" alt=\"Recyclable\" class=\"valueIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eMatériaux recyclables\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003ePA12 technique, carton recyclable\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Nous utilisons du PA12 technique pour sa robustesse et sa\n              durabilité, ainsi que des emballages en carton 100% recyclables.\n              Notre démarche d'éco-conception vise à minimiser notre impact\n              environnemental.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 4 --\u003e\n        \u003cdiv class=\"valueItem\" data-index=\"3\" data-title=\"Médaillé au Lépine\" data-desc=\"L'innovation Lizia a été reconnue et récompensée par une médaille au prestigieux Concours Lépine, gage de son ingéniosité et de sa qualité.\"\u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Medal Icon --\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_medaille.svg?v=1763826898\" alt=\"Médaille\" class=\"valueIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eMédaillé au Lépine\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eInnovation récompensée\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              L'innovation Lizia a été reconnue et récompensée par une médaille\n              au prestigieux Concours Lépine, gage de son ingéniosité et de sa\n              qualité.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 5 --\u003e\n        \u003cdiv class=\"valueItem\" data-index=\"4\" data-title=\"Breveté à l'international\" data-desc=\"Notre technologie unique est protégée par des brevets internationaux, assurant l'exclusivité de notre solution de lecture à une main.\"\u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Globe\/Patent Icon --\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_brevet.svg?v=1763996122\" alt=\"Breveté\" class=\"valueIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eBreveté à l'international\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eInnovation protégée\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Notre technologie unique est protégée par des brevets\n              internationaux, assurant l'exclusivité de notre solution de\n              lecture à une main.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 6 --\u003e\n        \u003cdiv class=\"valueItem\" data-index=\"5\" data-title=\"Réalisé par 2 étudiants\" data-desc=\"Lizia est née de la passion et de l'entrepreneuriat de deux étudiants, déterminés à améliorer le quotidien des lecteurs.\"\u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Users\/Students Icon --\u003e\n            \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_etudiant.svg?v=1763996122\" alt=\"Étudiants\" class=\"valueIconImg\"\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eRéalisé par 2 étudiants\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eJeune entreprise innovante\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia est née de la passion et de l'entrepreneuriat de deux\n              étudiants, déterminés à améliorer le quotidien des lecteurs.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Colonne Droite: Détail (Desktop Only) --\u003e\n      \u003cdiv class=\"valuesDetail desktopOnly\"\u003e\n        \u003cdiv class=\"detailCard\"\u003e\n          \u003ch3 id=\"detailTitle\" class=\"detailTitle\"\u003eFabriqué en France\u003c\/h3\u003e\n          \u003cp id=\"detailDesc\" class=\"detailDesc\"\u003e\n            Nos pièces en nylon sont produites dans l'Ouest de la France puis\n            assemblées en Bretagne. En choisissant Lizia, vous soutenez une\n            fabrication 100 % française, un circuit court et un savoir-faire\n            industriel local, de la conception à l'expédition depuis Rennes.\n          \u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\u003c!-- SECTION AVIS --\u003e\n\u003csection id=\"reviewsSection\" class=\"reviewsSection\"\u003e\n  \u003cdiv class=\"containerLizia\"\u003e\n    \u003cdiv class=\"reviewsHeader\"\u003e\n      \u003ch2 class=\"reviewsTitle\" data-scroll-reveal\u003e\n        Adopté par de nombreux lecteurs\n      \u003c\/h2\u003e\n      \u003cdiv class=\"reviewsHeaderSummary\" data-scroll-reveal\u003e\n        \u003cdiv class=\"reviewsStars\"\u003e★★★★★\u003c\/div\u003e\n        \u003cspan class=\"reviewsRating\"\u003e4.6\u003c\/span\u003e\n        \u003cspan class=\"reviewsCount\"\u003e| 536 avis\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cbutton class=\"reviewsNavBtn prev\" aria-label=\"Avis précédents\"\u003e\n      \u003csvg width=\"24\" height=\"24\" viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u003e\n        \u003cpolyline points=\"15 18 9 12 15 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cbutton class=\"reviewsNavBtn next\" aria-label=\"Avis suivants\"\u003e\n      \u003csvg width=\"24\" height=\"24\" viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u003e\n        \u003cpolyline points=\"9 18 15 12 9 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cdiv id=\"reviewsContainer\" class=\"reviewsContainer\" data-scroll-reveal\u003e\u003c\/div\u003e\n    \u003cdiv id=\"reviewsDots\" class=\"reviewsDots\"\u003e\u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\n\u003c!-- Popup de détails des packs --\u003e\n\u003cdiv id=\"packPopup\" class=\"popupOverlay\"\u003e\n  \u003cdiv class=\"popupContent\"\u003e\n    \u003cbutton class=\"popupClose\" id=\"popupClose\"\u003e×\u003c\/button\u003e\n    \u003cimg id=\"popupImage\" class=\"popupImage\" src=\"\" alt=\"\"\u003e\n    \u003ch2 id=\"popupTitle\" class=\"popupTitle\"\u003e\u003c\/h2\u003e\n    \u003cp id=\"popupDescription\" class=\"popupDescription\"\u003e\u003c\/p\u003e\n    \u003cul id=\"popupFeatures\" class=\"popupFeatures\"\u003e\u003c\/ul\u003e\n  \u003c\/div\u003e\n\u003c\/div\u003e\n\n\u003c!-- BLOC SELECTEUR CACHÉS --\u003e\n\u003c!-- SELECTEUR COUSSIN SEUL --\u003e\n\u003cform method=\"post\" action=\"\/cart\/add\" id=\"prodForm\" accept-charset=\"UTF-8\" class=\"prod_form prod_form_header product_main_form_15775468388701\" enctype=\"multipart\/form-data\" novalidate=\"novalidate\" data-product_id=\"15775468388701\" data-other=\"prod_form_footer\" data-product-form=\"true\" style=\"display: none\"\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\"\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\"\u003e\n  \u003cselect name=\"id\" id=\"ProductSelect_15775468388701TEST\" class=\"product-single__variants\" style=\"display: none\"\u003e\n    \u003coption selected value=\"62707534889309\"\u003e\n      Coussin de lecture — 34,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cselect name=\"id\" id=\"ProductSelect_15775468388701\" class=\"product-single__variants\" style=\"display: none\"\u003e\n    \u003coption selected value=\"62707534889309\"\u003e\n      Coussin de lecture — 34,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantité\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_15775468388701\" class=\"qtyminus\"\u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\u003e\u003c\/a\u003e\n    \u003cinput type=\"text\" name=\"quantity\" id=\"updates_15775468388701\" class=\"quantity\" value=\"1\"\u003e\n    \u003ca href=\"#\" field=\"updates_15775468388701\" class=\"qtyplus\"\u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n  \u003cbutton onclick=\"snapAddToCart('EUR',3495,15775468388701)\" type=\"submit\" data-text=\"AJOUTER AU PANIER\" name=\"add\" data-product_id=\"15775468388701\" id=\"AddToCart\" class=\"add_to_cart_btn add_to_cart_btn_15775468388701 button\"\u003e\n    \u003csvg version=\"1.1\" id=\"Calque_1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" x=\"0px\" y=\"0px\" viewbox=\"0 0 595.28 841.89\" enable-background=\"new 0 0 595.28 841.89\" xml:space=\"preserve\"\u003e\n      \u003cpath fill-rule=\"evenodd\" clip-rule=\"evenodd\" fill=\"#FFFFFF\" d=\"M508.697,492.036H368.719v139.977\n                            c0,29.123-6.573,50.864-19.678,65.071c-13.2,14.304-30.526,21.489-51.615,21.489c-20.887,0-37.961-7.027-51.211-21.287\n                            c-13.055-14.054-19.682-35.75-19.682-65.273V492.036H86.556c-28.314,0-49.853-6.574-64.464-19.683C7.382,459.158,0,442.03,0,421.142\n                            c0-21.084,7.18-38.414,21.489-51.61c14.207-13.109,35.948-19.682,65.066-19.682h139.978V209.873\n                            c0-28.315,6.573-49.855,19.682-64.464c13.2-14.711,30.324-22.091,51.211-22.091c21.089,0,38.415,7.179,51.615,21.489\n                            c13.105,14.206,19.678,35.948,19.678,65.067V349.85h139.978c29.123,0,50.864,6.576,65.07,19.682\n                            c14.306,13.2,21.489,30.526,21.489,51.61c0,20.888-7.027,37.963-21.287,51.211C559.915,485.413,538.22,492.036,508.697,492.036\n                            L508.697,492.036z\"\u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_15775468388701\"\u003eAJOUTER AU PANIER\u003c\/span\u003e\n    \u003cspan class=\"add-to-cart-loading\"\u003e\u003c\/span\u003e\n    \u003cb\u003e\u003c\/b\u003e\n  \u003c\/button\u003e\n  \u003cinput type=\"hidden\" name=\"product-id\" value=\"15775468388701\"\u003e\n\u003c\/form\u003e\n\n\u003cscript\u003e\ndocument.addEventListener(\"DOMContentLoaded\", () =\u003e {\n  \/\/ --- CONSTANTES ET DONNÉES ---\n  const CUSHION_PRICE = 34.95; \/\/ Prix fixe du coussin seul\n  const LIZIA_BASE_PRICE = 24.95; \/\/ Prix de base du Lizia (pour calcul pack lizia-cushion)\n\n  const PACK_PRICES = {\n    \"cushion-only\": 34.95, \/\/ Prix fixe\n    \"lizia-cushion\": null, \/\/ Calculé dynamiquement : (34.95 + 24.95) * 0.95 = 56.91€\n  };\n  const PACK_ORIGINAL_PRICES = {\n    \"cushion-only\": null,\n    \"lizia-cushion\": CUSHION_PRICE + LIZIA_BASE_PRICE, \/\/ 34.95 + 24.95 = 59.90€ (prix sans réduction)\n  };\n  const PERSONALIZATION_PRICE = 4.95;\n\n  \/\/ Mapping des variants Shopify par pack\/couleur\/personnalisation\n  const variantIDs = {\n    \/\/ Coussin seul - un seul variant (placeholder, sera remplacé par le vrai variant ID)\n    15775468388701: {\n      Default: { normal: \"62707534889309\" },\n    },\n    \/\/ Pack Lizia + Coussin - réutilise le mapping du pack Lizia seul\n    8501710225757: {\n      \/\/ Lizia seul (réutilisé pour lizia-cushion)\n      Blanc: { normal: \"47976891023709\", personnalise: \"47976891056477\" },\n      Noir: { normal: \"47976891089245\", personnalise: \"47976891122013\" },\n      Rouge: { normal: \"47976891154781\", personnalise: \"47976891187549\" },\n      Vert: { normal: \"47976891220317\", personnalise: \"47976891253085\" },\n      Bleu: { normal: \"47976891285853\", personnalise: \"47976891318621\" },\n      Rose: { normal: \"54484174340445\", personnalise: \"54484174373213\" },\n      Violet: { normal: \"54484174438749\", personnalise: \"54484174471517\" },\n      Orange: { normal: \"54980518543709\", personnalise: \"54980518576477\" },\n    },\n  };\n\n  \/\/ Mapping des noms de packs vers leurs IDs Shopify\n  const packIDMapping = {\n    \"cushion-only\": \"15775468388701\",\n    \"lizia-cushion\": \"8501710225757\", \/\/ Réutilise le mapping du pack Lizia seul\n  };\n\n  \/\/ Reviews data\n  const reviewsData = [\n    {\n      firstName: \"Laura\",\n      lastName: \"M\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Une découverte géniale !\",\n      content: \"Je l'ai reçue et c'est trop cool !\",\n      verified: true,\n    },\n    {\n      firstName: \"Nathalie\",\n      lastName: \"M\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Lecture facile sans gêner les autres, parfait !\",\n      content:\n        \"J'ai reçu mes Lizia il y a quelques jours. C'est Noël en septembre quel bonheur de pouvoir lire dans mon lit sans déranger mon conjoint, dans les transports...\",\n      verified: true,\n    },\n    {\n      firstName: \"Audrey\",\n      lastName: \"T\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Pratique et fiable, je l'utilise depuis longtemps\",\n      content:\n        \"C'est génial pour lire j'en est un depuis plusieurs mois c'est top !\",\n      verified: true,\n    },\n    {\n      firstName: \"Sophie\",\n      lastName: \"D\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Indispensable pour mes soirées lecture\",\n      content:\n        \"Je ne peux plus m'en passer ! Parfait pour lire le soir sans allumer la lumière de la chambre.\",\n      verified: true,\n    },\n    {\n      firstName: \"Marc\",\n      lastName: \"L\",\n      gender: \"Lecteur\",\n      rating: 5,\n      date: { month: \"juin\", year: 2025 },\n      title: \"Cadeau parfait pour les lecteurs\",\n      content:\n        \"Offert à ma femme, elle adore ! La qualité est au rendez-vous et le design est élégant.\",\n      verified: true,\n    },\n    {\n      firstName: \"Émilie\",\n      lastName: \"R\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Lizia révolutionne mes moments lecture\",\n      content:\n        \"Fini les bras fatigués et les problèmes de lumière ! Je recommande à 100%.\",\n      verified: true,\n    },\n    {\n      firstName: \"Thomas\",\n      lastName: \"B\",\n      gender: \"Lecteur\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Excellent produit, conforme à mes attentes\",\n      content:\n        \"Livraison rapide, produit de qualité. Je lis maintenant partout sans contrainte.\",\n      verified: true,\n    },\n    {\n      firstName: \"Charlotte\",\n      lastName: \"V\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Un must-have pour tous les lecteurs\",\n      content:\n        \"Simple, efficace et tellement pratique. Mes enfants m'ont même demandé les leurs !\",\n      verified: true,\n    },\n    {\n      firstName: \"Pierre\",\n      lastName: \"G\",\n      gender: \"Lecteur\",\n      rating: 5,\n      date: { month: \"mai\", year: 2025 },\n      title: \"Qualité française au top\",\n      content:\n        \"Ravi de soutenir une entreprise française. Le produit est robuste et bien pensé.\",\n      verified: true,\n    },\n    {\n      firstName: \"Isabelle\",\n      lastName: \"F\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Parfait pour lire en voyage\",\n      content:\n        \"Je l'emmène partout avec moi : train, avion, hôtel. C'est devenu mon accessoire indispensable.\",\n      verified: true,\n    },\n    {\n      firstName: \"Antoine\",\n      lastName: \"H\",\n      gender: \"Lecteur\",\n      rating: 5,\n      date: { month: \"juin\", year: 2025 },\n      title: \"Confortable et discret\",\n      content:\n        \"La lampe est puissante mais ne dérange personne. Idéal pour les lectures nocturnes.\",\n      verified: true,\n    },\n    {\n      firstName: \"Camille\",\n      lastName: \"P\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Design élégant et fonctionnel\",\n      content:\n        \"J'adore le design épuré et les couleurs disponibles. En plus, c'est super pratique !\",\n      verified: true,\n    },\n    {\n      firstName: \"Julien\",\n      lastName: \"M\",\n      gender: \"Lecteur\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Rapport qualité-prix excellent\",\n      content:\n        \"Pour le prix, c'est vraiment un excellent investissement. Je le recommande sans hésiter.\",\n      verified: true,\n    },\n    {\n      firstName: \"Léa\",\n      lastName: \"S\",\n      gender: \"Lectrice\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Mes enfants l'adorent aussi\",\n      content:\n        \"Mes enfants l'utilisent tous les soirs pour leur lecture du soir. C'est devenu un rituel !\",\n      verified: true,\n    },\n    {\n      firstName: \"Maxime\",\n      lastName: \"C\",\n      gender: \"Lecteur\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Innovation au service de la lecture\",\n      content:\n        \"Enfin un produit qui pense aux vrais besoins des lecteurs. Bravo pour cette innovation !\",\n      verified: true,\n    },\n  ];\n\n  \/\/ Images des packs par couleur (affichage dynamique)\n  const PACK_IMAGES_BY_COLOR = {\n    \"cushion-only\": {\n      \/\/ image pack coussin seul\n      default:\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/coussin__livre_600x600.webp?v=1764085473\",\n    },\n    \"lizia-cushion\": {\n      \/\/ image pack lizia + coussin\n      rose: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_lizia_et_lizia.webp?v=1765360384\",\n      vert: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_lizia_et_lizia.webp?v=1765360384\",\n      blanc:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_lizia_et_lizia.webp?v=1765360384\",\n      noir: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_lizia_et_lizia.webp?v=1765360384\",\n      bleu: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_lizia_et_lizia.webp?v=1765360384\",\n      violet:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_lizia_et_lizia.webp?v=1765360384\",\n    },\n  };\n\n  const PACKS = [\n    {\n      id: \"cushion-only\",\n      name: \"Coussin seul\",\n      price: 34.95,\n      short: \"Coussin seul\",\n      description:\n        \"Le coussin de lecture, parfait pour un confort optimal lors de vos moments de lecture.\",\n      features: [\n        \"Coussin ergonomique\",\n        \"Confort optimal\",\n        \"Design élégant\",\n        \"Matériaux de qualité\",\n      ],\n      image:\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/coussin_pack_600x600.webp?v=1764085474\",\n    },\n    {\n      id: \"lizia-cushion\",\n      name: \"Lampe + Coussin\",\n      price: (CUSHION_PRICE + LIZIA_BASE_PRICE) * 0.95, \/\/ (34.95 + 24.95) * 0.95 = 56.91€\n      originalPrice: CUSHION_PRICE + LIZIA_BASE_PRICE, \/\/ 34.95 + 24.95 = 59.90€\n      badge: \"-5%\",\n      badgeColor: \"accent\",\n      description:\n        \"Le pack Lizia avec coussin de lecture, pour un confort de lecture optimal.\",\n      features: [\n        \"Lampe de lecture Lizia\",\n        \"Coussin de lecture\",\n        \"Marque-page intégré\",\n        \"Lecture à une main\",\n        \"Batterie longue durée\",\n        \"Design compact et élégant\",\n      ],\n      image:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_lizia_et_lizia.webp?v=1765360384\",\n    },\n  ];\n\n  const COLORS = [\n    { id: \"rose\", hex: \"#f4a6d7\" },\n    { id: \"vert\", hex: \"#cde2c5\" },\n    { id: \"blanc\", hex: \"#f4f4f4\", border: true },\n    { id: \"noir\", hex: \"#212121\" },\n    { id: \"bleu\", hex: \"#7ebfcd\" },\n    { id: \"violet\", hex: \"#d1a1e9\" },\n  ];\n\n  \/\/ Configuration des pochettes\n  const POUCH_CONFIG = {\n    enabled: true, \/\/ Facilement désactivable\n    showSelector: false, \/\/ Masquer le sélecteur si false (même avec plusieurs options). Mettre à true pour réafficher.\n    defaultIndex: 0, \/\/ Facilement modifiable (0 = premier, 1 = deuxième)\n    options: [\n      {\n        id: \"naturel\",\n        name: \"Naturel\",\n        description: \"Coton + Lin\",\n        image:\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pochette_lin_seule.webp?v=1761660968\",\n      },\n      {\n        id: \"colore\",\n        name: \"Coloré\",\n        description: \"Microfibre\",\n        image:\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pochette_micro_seule.webp?v=1761660968\",\n      },\n    ],\n  };\n\n  \/\/ Images par variante (pack + couleur)\n  const PRODUCT_IMAGES_BY_VARIANT = {\n    \"cushion-only\": {\n      \/\/ Le coussin seul n'a pas de variantes de couleur, donc une seule liste d'images\n      \/\/ coussin_pack_600x600.webp est l'image du pack, pas dans les vignettes\n      default: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_livre_3_en_1.webp?v=1765360384\",\n        \"VIDEO:https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/333422d9fd34411cbaeda5b404b46544.mp4\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_livre_confortable_ergonomique.webp?v=1765360383\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_livre_caracteristiques.webp?v=1765360384\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_livre_compatible_lizia.webp?v=1765360384\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_livre_compatible_numerique.webp?v=1765360384\",\n      ],\n    },\n    \"lizia-cushion\": {\n      \/\/ Pack Lizia + Coussin - réutilise les images du pack Lizia seul (temporairement)\n      rose: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_rose_iso.webp?v=1762181719\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_ergonomique.webp?v=1763996161\",\n      ],\n      vert: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_vert_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_ergonomique.webp?v=1763996161\",\n      ],\n      blanc: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_blanc_iso.webp?v=1762181719\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_ergonomique.webp?v=1763996162\",\n      ],\n      noir: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir.webp?v=1763396479\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_noir_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_ergonomique.webp?v=1763996162\",\n      ],\n      bleu: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_bleu_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_ergonomique.webp?v=1763996161\",\n      ],\n      violet: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_violet_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_ergonomique.webp?v=1763996161\",\n      ],\n    },\n    \/\/ Garder lizia pour référence (utilisé dans getProductImages comme fallback)\n    lizia: {\n      rose: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_rose_iso.webp?v=1762181719\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_ergonomique.webp?v=1763996161\",\n      ],\n      vert: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_vert_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_ergonomique.webp?v=1763996161\",\n      ],\n      blanc: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_blanc_iso.webp?v=1762181719\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_ergonomique.webp?v=1763996162\",\n      ],\n      noir: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir.webp?v=1763396479\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_noir_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_ergonomique.webp?v=1763996162\",\n      ],\n      bleu: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_bleu_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_ergonomique.webp?v=1763996161\",\n      ],\n      violet: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_violet_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_ergonomique.webp?v=1763996161\",\n      ],\n    },\n    \"lizia-cable\": {\n      rose: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_rose_iso.webp?v=1762181719\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_ergonomique.webp?v=1763996161\",\n      ],\n      vert: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_vert_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_ergonomique.webp?v=1763996161\",\n      ],\n      blanc: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_blanc_iso.webp?v=1762181719\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_ergonomique.webp?v=1763996162\",\n      ],\n      noir: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir.webp?v=1763396479\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_noir_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_ergonomique.webp?v=1763996162\",\n      ],\n      bleu: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_bleu_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_nuit.webp?v=1761662769\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_ergonomique.webp?v=1763996161\",\n      ],\n      violet: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet.webp?v=1763396480\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_violet_iso.webp?v=1762181720\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_technique.webp?v=1763996161\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_ergonomique.webp?v=1763996161\",\n      ],\n    },\n    \"lizia-cable-pouch\": {\n      rose: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_rose_iso.webp?v=1762181719\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_ergonomique.webp?v=1763996161\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_rose_iso.webp?v=1762181719\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_rose_ergonomique.webp?v=1763996161\",\n        ],\n      },\n      vert: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_vert_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_ergonomique.webp?v=1763996161\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_vert_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_vert_ergonomique.webp?v=1763996161\",\n        ],\n      },\n      blanc: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_blanc_iso.webp?v=1762181719\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_ergonomique.webp?v=1763996162\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_blanc_iso.webp?v=1762181719\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_blanc_ergonomique.webp?v=1763996162\",\n        ],\n      },\n      noir: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir.webp?v=1763396479\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_noir_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_ergonomique.webp?v=1763996162\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir.webp?v=1763396479\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_noir_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_noir_ergonomique.webp?v=1763996162\",\n        ],\n      },\n      bleu: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_bleu_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_nuit.webp?v=1761662769\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_ergonomique.webp?v=1763996161\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_bleu_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_nuit.webp?v=1761662769\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_bleu_ergonomique.webp?v=1763996161\",\n        ],\n      },\n      violet: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_violet_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_ergonomique.webp?v=1763996161\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet.webp?v=1763396480\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_lizia_violet_iso.webp?v=1762181720\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_nuit.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_technique.webp?v=1763996161\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/fr_presentation_lizia_violet_ergonomique.webp?v=1763996161\",\n        ],\n      },\n    },\n  };\n\n  \/\/ Suivi de l'état de lecture des vidéos (autoplay, pause utilisateur)\n  const videoPlaybackStates = {};\n\n  \/\/ Fonction pour obtenir les images selon la variante\n  function getProductImages() {\n    const pack = state.pack;\n\n    \/\/ Pack \"cushion-only\" : pas de variantes de couleur, utiliser \"default\"\n    if (pack === \"cushion-only\") {\n      return (\n        PRODUCT_IMAGES_BY_VARIANT[pack]?.[\"default\"] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"cushion-only\"][\"default\"]\n      );\n    }\n\n    \/\/ Pack \"lizia-cushion\" : utiliser les images du pack Lizia selon la couleur\n    if (pack === \"lizia-cushion\") {\n      const color = state.colors[0] || \"vert\";\n      return (\n        PRODUCT_IMAGES_BY_VARIANT[pack]?.[color] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"lizia\"]?.[color] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"lizia\"][\"vert\"]\n      );\n    }\n\n    \/\/ Fallback pour les autres packs (ne devrait pas arriver)\n    const color = state.colors[0] || \"vert\";\n    return (\n      PRODUCT_IMAGES_BY_VARIANT[pack]?.[color] ||\n      PRODUCT_IMAGES_BY_VARIANT[\"lizia\"][\"vert\"]\n    );\n  }\n\n  \/\/ --- ÉTAT DE L'APPLICATION ---\n  let state = {\n    pack: \"cushion-only\", \/\/ Pack par défaut : coussin seul\n    quantity: 1,\n    colors: [\"vert\"], \/\/ Seulement utilisé si pack lizia-cushion\n    personalization: [\"\"], \/\/ Seulement utilisé si pack lizia-cushion\n    currentImageIndex: 0,\n  };\n\n  \/\/ Tracker le pack précédent pour détecter les changements\n  let previousPack = state.pack;\n  \/\/ Tracker la quantité précédente pour les animations\n  let previousQuantity = state.quantity;\n\n  \/\/ Cache d'images préchargées pour éviter le lag\n  const imageCache = new Map();\n  \/\/ Cache spécifique pour les images de la galerie principale (préchargées et décodées)\n  const galleryImageCache = new Map();\n\n  \/\/ Fonction de préchargement des images\n  function preloadPackImages() {\n    Object.keys(PACK_IMAGES_BY_COLOR).forEach((packId) =\u003e {\n      Object.keys(PACK_IMAGES_BY_COLOR[packId]).forEach((colorId) =\u003e {\n        const url = PACK_IMAGES_BY_COLOR[packId][colorId];\n        if (!imageCache.has(url)) {\n          const img = new Image();\n          img.src = url;\n          imageCache.set(url, img);\n        }\n      });\n    });\n  }\n\n  \/\/ Précharger et décoder toutes les images de la galerie actuelle\n  function preloadGalleryImages(imageUrls) {\n    imageUrls.forEach((url) =\u003e {\n      \/\/ Ignorer les vidéos (qui commencent par \"VIDEO:\")\n      if (url.startsWith(\"VIDEO:\")) {\n        return;\n      }\n\n      if (!galleryImageCache.has(url)) {\n        const img = new Image();\n        img.src = url;\n        \/\/ Décoder l'image pour qu'elle soit prête à l'affichage\n        img\n          .decode()\n          .then(() =\u003e {\n            galleryImageCache.set(url, img);\n          })\n          .catch((err) =\u003e {\n            console.warn(\"Erreur de décodage de l'image:\", url, err);\n            \/\/ Mettre quand même en cache même si le décodage échoue\n            galleryImageCache.set(url, img);\n          });\n      }\n    });\n  }\n\n  \/\/ Fonction pour obtenir l'image d'un pack selon la couleur\n  function getPackImage(packId, colorId = null) {\n    \/\/ Pour le pack \"cushion-only\", retourner directement l'image du pack (pas de couleur)\n    if (packId === \"cushion-only\") {\n      return PACKS.find((p) =\u003e p.id === packId)?.image;\n    }\n\n    \/\/ Pour le pack \"lizia-cushion\", utiliser la couleur sélectionnée\n    if (packId === \"lizia-cushion\") {\n      const color = colorId || state.colors[0] || \"vert\";\n      return (\n        PACK_IMAGES_BY_COLOR[\"lizia\"]?.[color] ||\n        PACKS.find((p) =\u003e p.id === packId)?.image\n      );\n    }\n\n    \/\/ Pour les autres packs (fallback), utiliser la couleur\n    const color = colorId || state.colors[0] || \"vert\";\n    return (\n      PACK_IMAGES_BY_COLOR[packId]?.[color] ||\n      PACKS.find((p) =\u003e p.id === packId)?.image\n    );\n  }\n\n  \/\/ --- SÉLECTEURS DOM ---\n  const mainImage = document.getElementById(\"mainProductImage\");\n  const mainImageContainer = document.querySelector(\".mainImageContainer\");\n  const prevBtn = document.getElementById(\"prevImageBtn\");\n  const nextBtn = document.getElementById(\"nextImageBtn\");\n  const imageDotsContainer = document.getElementById(\"imageDots\");\n  const thumbnailContainer = document.getElementById(\"thumbnailContainer\");\n  const packSelectorContainer = document.getElementById(\"packSelector\");\n  const pouchSelectorContainer = document.getElementById(\"pouchSelector\");\n  const quantitySelectorContainer = document.getElementById(\"quantitySelector\");\n  const discountGaugeContainer = document.getElementById(\"discountGauge\");\n  const liziaItemsContainer = document.getElementById(\"liziaItemsContainer\");\n  const addToCartBtn = document.getElementById(\"addToCartBtn\");\n  const addToCartBtnMobile = document.getElementById(\"addToCartBtnMobile\");\n  const colorsPersoLabel = document.getElementById(\"colorsPersoLabel\");\n\n  \/\/ Sélecteurs pour la popup\n  const packPopup = document.getElementById(\"packPopup\");\n  const popupClose = document.getElementById(\"popupClose\");\n  const popupImage = document.getElementById(\"popupImage\");\n  const popupTitle = document.getElementById(\"popupTitle\");\n  const popupDescription = document.getElementById(\"popupDescription\");\n  const popupFeatures = document.getElementById(\"popupFeatures\");\n\n  \/\/ --- FONCTIONS DE RENDU ---\n\n  \/** Ajouter les contrôles vidéo natifs *\/\n  function setupVideoControls(videoElement) {\n    if (!videoElement) return;\n    videoElement.setAttribute(\"controls\", \"\");\n  }\n\n  \/** Rendu de la galerie d'images *\/\n  function renderImageGallery() {\n    if (!mainImage || !mainImageContainer) return;\n\n    const currentImages = getProductImages();\n    if (!currentImages || currentImages.length === 0) return;\n\n    \/\/ Précharger et décoder toutes les images de la galerie pour un changement instantané\n    preloadGalleryImages(currentImages);\n\n    \/\/ Utiliser l'image du cache si disponible, sinon charger normalement\n    const currentImageUrl = currentImages[state.currentImageIndex];\n    if (!currentImageUrl) return;\n\n    const isVideo = currentImageUrl.startsWith(\"VIDEO:\");\n    const actualUrl = isVideo\n      ? currentImageUrl.replace(\"VIDEO:\", \"\")\n      : currentImageUrl;\n\n    \/\/ Gérer les vidéos différemment des images\n    if (isVideo) {\n      \/\/ Masquer l'image\n      if (mainImage) {\n        mainImage.style.display = \"none\";\n      }\n\n      \/\/ Vérifier si une vidéo existe déjà, sinon en créer une\n      let videoElement = mainImageContainer.querySelector(\"video.mainImage\");\n      if (!videoElement) {\n        videoElement = document.createElement(\"video\");\n        videoElement.className = \"mainImage\";\n        videoElement.setAttribute(\"playsinline\", \"\");\n        videoElement.setAttribute(\"muted\", \"\");\n        videoElement.setAttribute(\"autoplay\", \"\");\n        videoElement.setAttribute(\"loop\", \"\");\n        videoElement.setAttribute(\"controls\", \"\");\n        videoElement.style.width = \"100%\";\n        videoElement.style.height = \"100%\";\n        videoElement.style.objectFit = \"cover\";\n        videoElement.style.position = \"absolute\";\n        videoElement.style.top = \"0\";\n        videoElement.style.left = \"0\";\n        videoElement.style.display = \"block\";\n        videoElement.style.cursor = \"pointer\";\n        videoElement.style.backgroundColor = \"#000\"; \/\/ Fond noir pour éviter le fond rose\n        mainImageContainer.appendChild(videoElement);\n      } else {\n        videoElement.style.display = \"block\";\n        videoElement.style.cursor = \"pointer\";\n      }\n\n      \/\/ Ajouter les contrôles vidéo (play\/pause + barre de progression)\n      setupVideoControls(videoElement);\n\n      \/\/ Vérifier si la source existe et si l'URL a changé\n      let source = videoElement.querySelector(\"source\");\n      const currentSrc = source ? source.src : \"\";\n\n      if (!source || currentSrc !== actualUrl) {\n        \/\/ Vider la vidéo et recréer la source\n        videoElement.innerHTML = \"\";\n        source = document.createElement(\"source\");\n        source.src = actualUrl;\n        source.type = \"video\/mp4\";\n        videoElement.appendChild(source);\n\n        \/\/ Réinitialiser la vidéo à 0 et charger\n        videoElement.currentTime = 0;\n        videoElement.load();\n        videoElement.play().catch((err) =\u003e {\n          console.warn(\"Erreur de lecture vidéo:\", err);\n        });\n      } else {\n        \/\/ Même vidéo, mais toujours réinitialiser à 0\n        videoElement.currentTime = 0;\n        if (videoElement.paused) {\n          \/\/ Si la vidéo est déjà chargée mais en pause, la relancer\n          videoElement.play().catch((err) =\u003e {\n            console.warn(\"Erreur de lecture vidéo:\", err);\n          });\n        }\n      }\n    } else {\n      \/\/ Masquer la vidéo si elle existe et afficher l'image\n      const videoElement = mainImageContainer.querySelector(\"video\");\n      if (videoElement) {\n        videoElement.style.display = \"none\";\n        videoElement.pause();\n      }\n      if (mainImage) {\n        mainImage.style.display = \"block\";\n\n        const cachedImage = galleryImageCache.get(currentImageUrl);\n        if (cachedImage \u0026\u0026 cachedImage.complete) {\n          \/\/ L'image est déjà chargée et décodée, l'utiliser directement\n          mainImage.src = actualUrl;\n        } else {\n          \/\/ Charger l'image normalement\n          mainImage.src = actualUrl;\n        }\n      }\n    }\n\n    \/\/ Points de navigation\n    if (imageDotsContainer) {\n      imageDotsContainer.innerHTML = currentImages\n        .map(\n          (_, index) =\u003e\n            `\u003cbutton class=\"dot ${\n              index === state.currentImageIndex ? \"active\" : \"\"\n            }\" data-index=\"${index}\"\u003e\u003c\/button\u003e`\n        )\n        .join(\"\");\n    }\n\n    \/\/ Miniatures avec lazy loading\n    if (thumbnailContainer) {\n      thumbnailContainer.innerHTML = currentImages\n        .map((img, index) =\u003e {\n          const isVideoThumb = img.startsWith(\"VIDEO:\");\n          const actualUrl = isVideoThumb ? img.replace(\"VIDEO:\", \"\") : img;\n          return `\u003cbutton class=\"thumbnailBtn ${\n            index === state.currentImageIndex ? \"active\" : \"\"\n          }\" data-index=\"${index}\"\u003e\n                ${\n                  isVideoThumb\n                    ? `\u003cdiv class=\"videoThumbnail\" style=\"position: relative; width: 100%; height: 100%;\"\u003e\n                        \u003cvideo class=\"videoPreview\" muted playsinline preload=\"metadata\" style=\"width: 100%; height: 100%; object-fit: cover; opacity: 0.7;\"\u003e\n                          \u003csource src=\"${actualUrl}\" type=\"video\/mp4\"\u003e\n                        \u003c\/video\u003e\n                        \u003cdiv class=\"playButtonOverlay\" style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 40px; height: 40px; background: rgba(16, 171, 150, 0.9); border-radius: 50%; display: flex; align-items: center; justify-content: center; pointer-events: none;\"\u003e\n                          \u003csvg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"white\" style=\"margin-left: 2px;\"\u003e\n                            \u003cpath d=\"M8 5v14l11-7z\"\/\u003e\n                          \u003c\/svg\u003e\n                        \u003c\/div\u003e\n                      \u003c\/div\u003e`\n                    : `\u003cimg data-src=\"${actualUrl}\" alt=\"Vue ${\n                        index + 1\n                      }\" class=\"lazy-image\"\u003e`\n                }\n            \u003c\/button\u003e`;\n        })\n        .join(\"\");\n    }\n\n    \/\/ Charger les images visibles\n    loadVisibleImages();\n\n    \/\/ Réinitialiser l'observer pour les nouvelles images\n    setTimeout(() =\u003e {\n      const lazyImages = document.querySelectorAll(\".lazy-image\");\n      lazyImages.forEach((img) =\u003e imageObserver.observe(img));\n    }, 100);\n  }\n\n  \/** Charger les images visibles (lazy loading optimisé) *\/\n  function loadVisibleImages() {\n    const lazyImages = document.querySelectorAll(\".lazy-image\");\n\n    lazyImages.forEach((element) =\u003e {\n      const rect = element.getBoundingClientRect();\n      const isVisible =\n        rect.top \u003c window.innerHeight + 200 \u0026\u0026 rect.bottom \u003e -200; \/\/ Zone de chargement anticipé\n\n      if (isVisible) {\n        \/\/ Gérer les images\n        if (element.tagName === \"IMG\" \u0026\u0026 element.dataset.src \u0026\u0026 !element.src) {\n          \/\/ Charger directement l'image sans délai\n          element.src = element.dataset.src;\n          element.classList.add(\"loaded\");\n        }\n        \/\/ Gérer les vidéos dans les miniatures - seulement charger la première frame (preload=\"metadata\")\n        else if (\n          element.tagName === \"VIDEO\" \u0026\u0026\n          element.classList.contains(\"videoPreview\")\n        ) {\n          \/\/ Ne rien faire - la vidéo dans les miniatures utilise preload=\"metadata\"\n          \/\/ pour charger seulement la première frame, mais ne se lance pas automatiquement\n        }\n      }\n    });\n  }\n\n  \/** Rendu du sélecteur de packs *\/\n  function renderPackSelector() {\n    packSelectorContainer.innerHTML = PACKS.map((pack, index) =\u003e {\n      \/\/ Badge \"Top offre\" pour le pack 2 (lizia-cushion)\n      const topBadge = index === 1 ? \"Top Offre\" : null;\n\n      \/\/ Afficher le \"i\" seulement pour le pack avec Top badge\n      const showInfoInBadge = topBadge \u0026\u0026 pack.id === \"lizia-cushion\";\n\n      \/\/ Obtenir l'image selon la couleur sélectionnée (ou image par défaut pour cushion-only)\n      const packImage = getPackImage(pack.id);\n\n      \/\/ Calculer le prix pour le pack lizia-cushion\n      let displayPrice = pack.price;\n      if (pack.id === \"lizia-cushion\" \u0026\u0026 pack.price === null) {\n        \/\/ Calculer dynamiquement : (34.95 + 24.95) * 0.95 = 56.91€\n        displayPrice = (CUSHION_PRICE + LIZIA_BASE_PRICE) * 0.95;\n      }\n\n      return `\n            \u003cdiv class=\"packWrapper ${\n              state.pack === pack.id ? \"selected\" : \"\"\n            }\"\u003e\n                ${\n                  topBadge\n                    ? `\u003cbutton class=\"packBadge packBadgeTop\" data-pack=\"${\n                        pack.id\n                      }\" role=\"button\" tabindex=\"0\" aria-label=\"En savoir plus sur ${topBadge}\"\u003e\n                        \u003cspan class=\"badgeTopText\"\u003e${topBadge}\u003c\/span\u003e\n                        ${\n                          showInfoInBadge\n                            ? '\u003cspan class=\"badgeTopInfo\"\u003ei\u003c\/span\u003e'\n                            : \"\"\n                        }\n                      \u003c\/button\u003e`\n                    : \"\"\n                }\n                \u003cbutton class=\"packOption ${\n                  state.pack === pack.id ? \"selected\" : \"\"\n                }\" data-pack=\"${pack.id}\"\u003e\n                    ${\n                      pack.badge\n                        ? `\u003cspan class=\"packBadge packBadgeDiscount\"\u003e${pack.badge}\u003c\/span\u003e`\n                        : \"\"\n                    }\n                    \u003cdiv class=\"packImageContainer\"\u003e\n                        \u003cimg src=\"${packImage}\" alt=\"${\n        pack.name\n      }\" class=\"packImage loaded\" data-pack-id=\"${pack.id}\" \/\u003e\n                    \u003c\/div\u003e\n                    \u003ch3 class=\"packName\"\u003e${pack.name}\u003c\/h3\u003e\n                    \u003cdiv class=\"packPriceContainer\"\u003e\n                        ${\n                          pack.originalPrice\n                            ? `\u003cspan class=\"packOriginalPrice\"\u003e${pack.originalPrice.toFixed(\n                                2\n                              )}€\u003c\/span\u003e`\n                            : \"\"\n                        }\n                        \u003cspan class=\"packPrice\"\u003e${displayPrice.toFixed(\n                          2\n                        )}€\u003c\/span\u003e\n                    \u003c\/div\u003e\n                    ${\n                      state.pack === pack.id\n                        ? `\u003cspan class=\"packCheckmark\"\u003e✓\u003c\/span\u003e`\n                        : \"\"\n                    }\n                \u003c\/button\u003e\n            \u003c\/div\u003e\n        `;\n    }).join(\"\");\n  }\n\n  \/** Mise à jour optimisée des images de packs sans re-render complet *\/\n  function updatePackImages() {\n    \/\/ Mettre à jour seulement les images des packs\n    PACKS.forEach((pack) =\u003e {\n      const container = document.querySelector(\n        `.packImage[data-pack-id=\"${pack.id}\"]`\n      )?.parentElement;\n\n      if (!container) return;\n\n      const currentImg = container.querySelector(\".packImage\");\n      if (!currentImg) return;\n\n      const newImageUrl = getPackImage(pack.id);\n      const currentSrc = currentImg.src;\n\n      \/\/ Ne mettre à jour que si l'image a changé\n      if (!currentSrc.includes(newImageUrl)) {\n        \/\/ Vérifier si l'image est déjà en cache\n        const cachedImg = imageCache.get(newImageUrl);\n        const isCached =\n          cachedImg \u0026\u0026 cachedImg.complete \u0026\u0026 cachedImg.naturalWidth \u003e 0;\n\n        \/\/ Créer une nouvelle image par-dessus l'ancienne\n        const newImg = document.createElement(\"img\");\n        newImg.className = \"packImage loading\";\n        newImg.dataset.packId = pack.id;\n        newImg.alt = pack.name;\n        newImg.src = newImageUrl; \/\/ Définir le src immédiatement\n\n        \/\/ Si l'image est en cache, l'afficher immédiatement\n        if (isCached) {\n          container.appendChild(newImg);\n\n          \/\/ Forcer le reflow pour que le navigateur charge l'image depuis le cache\n          newImg.offsetHeight;\n\n          \/\/ Transition immédiate sans délai\n          requestAnimationFrame(() =\u003e {\n            currentImg.classList.add(\"fading-out\");\n            newImg.classList.remove(\"loading\");\n            newImg.classList.add(\"loaded\");\n          });\n\n          \/\/ Supprimer l'ancienne image après le fade complet\n          setTimeout(() =\u003e {\n            if (currentImg.parentElement === container) {\n              container.removeChild(currentImg);\n            }\n          }, 550);\n        } else {\n          \/\/ Si pas en cache, utiliser le système de preload\n          container.appendChild(newImg);\n\n          \/\/ Précharger l'image\n          const preloader = new Image();\n          preloader.onload = () =\u003e {\n            \/\/ Mettre en cache pour les prochaines fois\n            imageCache.set(newImageUrl, preloader);\n\n            \/\/ Commencer le fade out de l'ancienne image\n            requestAnimationFrame(() =\u003e {\n              currentImg.classList.add(\"fading-out\");\n\n              \/\/ Fade in de la nouvelle image\n              requestAnimationFrame(() =\u003e {\n                newImg.classList.remove(\"loading\");\n                newImg.classList.add(\"loaded\");\n              });\n            });\n\n            \/\/ Supprimer l'ancienne image après le fade complet\n            setTimeout(() =\u003e {\n              if (currentImg.parentElement === container) {\n                container.removeChild(currentImg);\n              }\n            }, 550);\n          };\n\n          preloader.onerror = () =\u003e {\n            \/\/ En cas d'erreur, retirer la nouvelle image\n            if (newImg.parentElement === container) {\n              container.removeChild(newImg);\n            }\n          };\n\n          preloader.src = newImageUrl;\n        }\n      }\n    });\n  }\n\n  \/** Rendu du sélecteur de pochette *\/\n  function renderPouchSelector(packChanged = false) {\n    \/\/ Désactiver complètement le sélecteur de pochette pour le coussin\n    \/\/ Masquer la section et vider le contenu\n    if (pouchSelectorContainer) {\n      pouchSelectorContainer.classList.remove(\"has-pouch\");\n      pouchSelectorContainer.innerHTML = \"\";\n      pouchSelectorContainer.style.display = \"none\";\n    }\n    return;\n\n    \/\/ Ne render que si c'est vide ou si le pack a changé\n    if (!pouchSelectorContainer.querySelector(\".pouchSection\") || packChanged) {\n      pouchSelectorContainer.innerHTML = `\n        \u003cdiv class=\"pouchSection\"\u003e\n          \u003ch3 class=\"pouchTitle\"\u003eChoisissez votre pochette\u003c\/h3\u003e\n          \u003cdiv class=\"pouchOptions\"\u003e\n            ${POUCH_CONFIG.options\n              .map(\n                (pouch) =\u003e `\n                \u003cbutton class=\"pouchOption ${\n                  state.pouch === pouch.id ? \"selected\" : \"\"\n                }\" data-pouch=\"${pouch.id}\"\u003e\n                  \u003cdiv class=\"pouchImage\"\u003e\n                    \u003cimg src=\"${pouch.image}\" alt=\"${\n                  pouch.name\n                }\" class=\"pouch-img\" \/\u003e\n                  \u003c\/div\u003e\n                  \u003cdiv class=\"pouchInfo\"\u003e\n                    \u003ch4 class=\"pouchName\"\u003e${pouch.name}\u003c\/h4\u003e\n                    \u003cp class=\"pouchDescription\"\u003e${pouch.description}\u003c\/p\u003e\n                  \u003c\/div\u003e\n                  ${\n                    state.pouch === pouch.id\n                      ? `\u003cspan class=\"pouchCheckmark\"\u003e✓\u003c\/span\u003e`\n                      : \"\"\n                  }\n                \u003c\/button\u003e\n              `\n              )\n              .join(\"\")}\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      `;\n      \/\/ Ajouter la classe après un petit délai pour déclencher la transition\n      setTimeout(() =\u003e {\n        pouchSelectorContainer.classList.add(\"has-pouch\");\n      }, 10);\n    } else {\n      \/\/ Juste mettre à jour les boutons sélectionnés sans rerender\n      document.querySelectorAll(\".pouchOption\").forEach((btn) =\u003e {\n        const pouchId = btn.dataset.pouch;\n        if (pouchId === state.pouch) {\n          btn.classList.add(\"selected\");\n          if (!btn.querySelector(\".pouchCheckmark\")) {\n            btn.insertAdjacentHTML(\n              \"beforeend\",\n              '\u003cspan class=\"pouchCheckmark\"\u003e✓\u003c\/span\u003e'\n            );\n          }\n        } else {\n          btn.classList.remove(\"selected\");\n          const checkmark = btn.querySelector(\".pouchCheckmark\");\n          if (checkmark) checkmark.remove();\n        }\n      });\n    }\n  }\n\n  \/** Calcul de la réduction totale (pack + quantité) *\/\n  function calculateTotalDiscount() {\n    \/\/ Réduction du pack\n    let packDiscount = 0;\n    if (state.pack === \"lizia-cushion\") {\n      packDiscount = 5; \/\/ -5% sur le pack Lizia + Coussin\n    }\n    \/\/ Le pack \"cushion-only\" n'a pas de réduction de pack\n\n    \/\/ Réduction de la quantité (comme Lizia : -5% à qté 2, -10% à qté 3+, pas plus)\n    let quantityDiscount = 0;\n    if (state.quantity === 2) {\n      quantityDiscount = 5; \/\/ -5%\n    } else if (state.quantity \u003e= 3) {\n      quantityDiscount = 10; \/\/ -10% (maximum pour la quantité)\n    }\n\n    return {\n      packDiscount,\n      quantityDiscount,\n      total: packDiscount + quantityDiscount,\n    };\n  }\n\n  \/** Rendu du sélecteur de quantité avec système à 3 bulles *\/\n  function renderQuantitySelector() {\n    const qty = state.quantity;\n\n    \/\/ Déterminer les bulles à afficher\n    let leftBubble, middleBubble, rightBubble;\n    let leftSelected = false;\n    let middleSelected = false;\n\n    if (qty === 1) {\n      leftBubble = { type: \"number\", value: 1, action: \"set\" };\n      middleBubble = { type: \"number\", value: 2, action: \"set\" };\n      rightBubble = { type: \"increment\", value: \"+\", action: \"increment\" };\n      leftSelected = true;\n      middleSelected = false;\n    } else if (qty === 2) {\n      leftBubble = { type: \"number\", value: 1, action: \"set\" };\n      middleBubble = { type: \"number\", value: 2, action: \"set\" };\n      rightBubble = { type: \"increment\", value: \"+\", action: \"increment\" };\n      leftSelected = false;\n      middleSelected = true;\n    } else {\n      leftBubble = { type: \"decrement\", value: \"-\", action: \"decrement\" };\n      middleBubble = { type: \"number\", value: qty, action: \"set\" };\n      rightBubble = { type: \"increment\", value: \"+\", action: \"increment\" };\n      leftSelected = false;\n      middleSelected = true;\n    }\n\n    quantitySelectorContainer.innerHTML = `\n      \u003cdiv class=\"quantityBubbles\"\u003e\n        \u003cbutton class=\"quantityBubble ${\n          leftSelected ? \"selected\" : \"\"\n        }\" data-action=\"${leftBubble.action}\" data-value=\"${leftBubble.value}\"\u003e\n          ${leftBubble.value}\n        \u003c\/button\u003e\n        \u003cbutton class=\"quantityBubble ${\n          middleSelected ? \"selected\" : \"\"\n        }\" data-action=\"${middleBubble.action}\" data-value=\"${\n      middleBubble.value\n    }\"\u003e\n          ${middleBubble.value}\n        \u003c\/button\u003e\n        \u003cbutton class=\"quantityBubble\" data-action=\"${\n          rightBubble.action\n        }\" data-value=\"${rightBubble.value}\"\u003e\n          ${rightBubble.value}\n        \u003c\/button\u003e\n      \u003c\/div\u003e\n    `;\n  }\n\n  \/** Rendu de la jauge de réduction *\/\n  function renderDiscountGauge() {\n    const discount = calculateTotalDiscount();\n    const discountValue = discount.total;\n    const maxDiscount = 15;\n    let fillPercentage = (discountValue \/ maxDiscount) * 101;\n    fillPercentage = Math.min(Math.max(fillPercentage, 0), 101);\n\n    let progressBar = discountGaugeContainer.querySelector(\n      \".discountProgressBar\"\n    );\n    let progressFill = discountGaugeContainer.querySelector(\n      \".discountProgressFill\"\n    );\n\n    if (!progressBar || !progressFill) {\n      discountGaugeContainer.innerHTML = `\n        \u003cdiv class=\"discountGaugeMain\"\u003e\n          \u003cdiv class=\"discountProgressBar\"\u003e\n            \u003cdiv class=\"discountProgressFill\" style=\"width: ${fillPercentage}%\"\u003e\n              \u003cspan class=\"discountLabel discountLabel--fill\"\u003e0%\u003c\/span\u003e\n            \u003c\/div\u003e\n            \u003cspan class=\"discountLabel discountLabel--base\"\u003e0%\u003c\/span\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      `;\n      progressBar = discountGaugeContainer.querySelector(\n        \".discountProgressBar\"\n      );\n      progressFill = discountGaugeContainer.querySelector(\n        \".discountProgressFill\"\n      );\n    }\n\n    if (progressFill) {\n      progressFill.style.width = `${fillPercentage}%`;\n    }\n\n    \/\/ Mettre à jour les labels de réduction\n    updateDiscountLabels(discountValue);\n  }\n\n  function updateDiscountLabels(currentDiscount) {\n    const discountValues = [0, 5, 10, 15];\n    let closestDiscount = discountValues[0];\n    let minDiff = Math.abs(currentDiscount - closestDiscount);\n\n    discountValues.forEach((val) =\u003e {\n      const diff = Math.abs(currentDiscount - val);\n      if (diff \u003c minDiff) {\n        minDiff = diff;\n        closestDiscount = val;\n      }\n    });\n\n    const progressBar = document.querySelector(\".discountProgressBar\");\n    const fillLabel = document.querySelector(\".discountLabel--fill\");\n    const baseLabel = document.querySelector(\".discountLabel--base\");\n    const isZero = closestDiscount === 0;\n\n    if (progressBar) {\n      progressBar.classList.toggle(\"zero\", isZero);\n    }\n\n    if (fillLabel) {\n      fillLabel.textContent = isZero ? \"0%\" : `-${closestDiscount.toString()}%`;\n    }\n\n    if (baseLabel) {\n      baseLabel.textContent = \"0%\";\n    }\n  }\n\n  \/** Animer le pourcentage avec un compteur *\/\n  function animatePercentage(element, targetValue) {\n    const currentText = element.textContent;\n    const currentValue = parseInt(currentText.replace(\/[^0-9]\/g, \"\")) || 0;\n\n    if (currentValue === targetValue) return;\n\n    \/\/ Ajouter l'animation pop\n    element.classList.add(\"updating\");\n    setTimeout(() =\u003e {\n      element.classList.remove(\"updating\");\n    }, 400);\n\n    \/\/ Durée de l'animation synchronisée avec la barre (800ms)\n    const duration = 800; \/\/ Même durée que la transition CSS de la barre\n    const steps = 30; \/\/ Plus de steps pour une animation plus fluide\n    const increment = (targetValue - currentValue) \/ steps;\n    const stepDuration = duration \/ steps;\n\n    let current = currentValue;\n    let step = 0;\n\n    const interval = setInterval(() =\u003e {\n      step++;\n      current += increment;\n\n      if (step \u003e= steps) {\n        element.textContent = `-${targetValue}%`;\n        clearInterval(interval);\n      } else {\n        element.textContent = `-${Math.round(current)}%`;\n      }\n    }, stepDuration);\n  }\n\n  \/** Rendu des items Lizia (couleur + personnalisation) *\/\n  function renderLiziaItems() {\n    \/\/ Afficher uniquement si le pack \"lizia-cushion\" est sélectionné\n    const liziaItemsSection = document.getElementById(\"liziaItemsSection\");\n    if (state.pack !== \"lizia-cushion\") {\n      if (liziaItemsSection) {\n        liziaItemsSection.style.display = \"none\";\n      }\n      if (liziaItemsContainer) {\n        liziaItemsContainer.innerHTML = \"\";\n      }\n      return;\n    }\n\n    \/\/ Afficher la section si elle était masquée\n    if (liziaItemsSection) {\n      liziaItemsSection.style.display = \"\";\n    }\n\n    \/\/ Préserver le numéro de step si présent\n    const stepNumber = colorsPersoLabel?.querySelector(\".stepLabelNumber\");\n    if (stepNumber) {\n      colorsPersoLabel.innerHTML =\n        '\u003cspan class=\"stepLabelNumber\"\u003e3\u003c\/span\u003e Couleur';\n    } else if (colorsPersoLabel) {\n      colorsPersoLabel.innerHTML = \"Couleur\";\n    }\n\n    \/\/ Détecter si la quantité a changé\n    const quantityIncreased = state.quantity \u003e previousQuantity;\n    const quantityDecreased = state.quantity \u003c previousQuantity;\n\n    \/\/ Si la quantité a diminué, animer la sortie des derniers items avant de les retirer\n    if (quantityDecreased) {\n      const itemsToRemove = liziaItemsContainer.querySelectorAll(\".liziaItem\");\n      const itemsToAnimate = Array.from(itemsToRemove).slice(state.quantity);\n\n      if (itemsToAnimate.length \u003e 0) {\n        itemsToAnimate.forEach((item, idx) =\u003e {\n          item.classList.add(\"fadeOutSlide\");\n          item.style.animationDelay = `${idx * 0.05}s`;\n        });\n\n        \/\/ Attendre la fin de l'animation avant de re-render\n        setTimeout(() =\u003e {\n          renderLiziaItemsContent();\n          previousQuantity = state.quantity;\n          addEventListeners(); \/\/ Ré-attacher les écouteurs après le rendu\n        }, 300 + itemsToAnimate.length * 50);\n        return;\n      }\n    }\n\n    renderLiziaItemsContent();\n    previousQuantity = state.quantity;\n  }\n\n  \/** Contenu du rendu des items Lizia (séparé pour la réutilisation) *\/\n  function renderLiziaItemsContent() {\n    const quantityIncreased = state.quantity \u003e previousQuantity;\n    const newItemsStartIndex = quantityIncreased ? previousQuantity : 0;\n\n    liziaItemsContainer.innerHTML = Array.from({ length: state.quantity })\n      .map((_, index) =\u003e {\n        \/\/ Ajouter l'animation seulement aux nouveaux items\n        const shouldAnimate = quantityIncreased \u0026\u0026 index \u003e= newItemsStartIndex;\n        const animationClass = shouldAnimate ? \" fadeInSlide\" : \"\";\n        const animationDelay = shouldAnimate\n          ? `style=\"animation-delay: ${(index - newItemsStartIndex) * 0.1}s\"`\n          : \"\";\n\n        return `\n            \u003c!-- Version Desktop --\u003e\n            \u003cdiv class=\"liziaItem${animationClass}\" ${animationDelay}\u003e\n                \u003cdiv class=\"liziaItemRow\"\u003e\n                    \u003cdiv class=\"liziaItemHeader\"\u003eLizia ${index + 1}\u003c\/div\u003e\n                    \u003cdiv class=\"colorSelector\"\u003e\n                        ${COLORS.filter((color) =\u003e color.id !== \"rose\")\n                          .map(\n                            (color) =\u003e `\n                            \u003cbutton \n                                class=\"colorBtn ${\n                                  state.colors[index] === color.id\n                                    ? \"selected\"\n                                    : \"\"\n                                } ${color.border ? \"with-border\" : \"\"}\" \n                                style=\"background-color: ${color.hex};\" \n                                data-color=\"${color.id}\" \n                                data-index=\"${index}\"\u003e\n                                ${\n                                  state.colors[index] === color.id\n                                    ? `\u003csvg class=\"colorCheckmark\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 600 600\"\u003e\n                                        \u003cpath fill=\"${\n                                          color.id === \"blanc\" ||\n                                          color.id === \"vert\"\n                                            ? \"#000\"\n                                            : \"#fff\"\n                                        }\" d=\"M583.45,72.97c-21.24-21.24-60.79-23.57-81.67,0-94.04,106.15-184.61,215.56-272.23,327.12-11.4-11.84-22.5-23.96-33.61-36.05-32.21-35.07-64.03-70.49-97.58-104.3-22.16-22.34-59.49-22.18-81.67,0-22.32,22.32-22.16,59.33,0,81.67,63.31,63.83,118.42,138.66,190.17,193.44,27.28,20.83,61.73,1.67,79.02-20.72-17.69,22.91-5.56,7.2-1.37,1.82,6.47-8.31,12.97-16.59,19.48-24.87,22.49-28.6,45.2-57.02,68.04-85.34,68.7-85.17,138.87-169.19,211.43-251.1,20.86-23.54,23.25-58.42,0-81.67Z\"\/\u003e\n                                      \u003c\/svg\u003e`\n                                    : \"\"\n                                }\n                            \u003c\/button\u003e\n                        `\n                          )\n                          .join(\"\")}\n                    \u003c\/div\u003e\n                    \u003cdiv class=\"persoContainer\"\u003e\n                        ${\n                          index === 0\n                            ? '\u003cdiv class=\"persoPriceOverlay\"\u003e\u003cdiv class=\"persoLeftGroup\"\u003e\u003cspan class=\"persoIconOverlay\"\u003e\u003csvg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-sparkles\"\u003e\u003cpath d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\"\u003e\u003c\/path\u003e\u003cpath d=\"M20 3v4\"\u003e\u003c\/path\u003e\u003cpath d=\"M22 5h-4\"\u003e\u003c\/path\u003e\u003cpath d=\"M4 17v2\"\u003e\u003c\/path\u003e\u003cpath d=\"M5 18H3\"\u003e\u003c\/path\u003e\u003c\/svg\u003e\u003c\/span\u003e\u003cspan class=\"persoLabelOverlay\"\u003eGravure (optionnel)\u003c\/span\u003e\u003c\/div\u003e\u003cspan class=\"persoPriceText\"\u003e+4.95€\u003c\/span\u003e\u003c\/div\u003e'\n                            : \"\"\n                        }\n                        \u003cdiv class=\"persoInputWrapper\"\u003e\n                            \u003cdiv class=\"inputContainer\"\u003e\n                                \u003cinput \n                                    type=\"text\" \n                                    class=\"persoInput ${\n                                      (\n                                        state.personalization[index] || \"\"\n                                      ).trim()\n                                        ? \"has-content\"\n                                        : \"\"\n                                    }\" \n                                    placeholder=\"Ajouter une gravure\" \n                                    maxlength=\"25\" \n                                    value=\"${\n                                      state.personalization[index] || \"\"\n                                    }\" \n                                    data-index=\"${index}\"\u003e\n                                \u003cdiv class=\"charCounter ${\n                                  (state.personalization[index] || \"\").length \u003e\n                                  20\n                                    ? \"warning\"\n                                    : \"\"\n                                }\"\u003e\n                                    ${\n                                      (state.personalization[index] || \"\")\n                                        .length\n                                    }\/25\n                                \u003c\/div\u003e\n                            \u003c\/div\u003e\n                        \u003c\/div\u003e\n                    \u003c\/div\u003e\n                \u003c\/div\u003e\n            \u003c\/div\u003e\n            \n            \u003c!-- Version Mobile --\u003e\n            \u003cdiv class=\"liziaItemMobile color-${state.colors[index] || \"vert\"}\"\u003e\n                \u003cdiv class=\"liziaItemRowMobile\"\u003e\n                    \u003cdiv class=\"liziaItemHeaderMobile\"\u003eLizia ${index + 1}\u003c\/div\u003e\n                    \u003cdiv class=\"colorSelectorMobile\"\u003e\n                    ${COLORS.filter((color) =\u003e color.id !== \"rose\")\n                      .map(\n                        (color) =\u003e `\n                        \u003cbutton \n                            class=\"colorBtnMobile ${\n                              state.colors[index] === color.id ? \"selected\" : \"\"\n                            } ${color.border ? \"with-border\" : \"\"}\" \n                            style=\"background-color: ${color.hex};\" \n                            data-color=\"${color.id}\" \n                            data-index=\"${index}\"\u003e\n                            ${\n                              state.colors[index] === color.id\n                                ? `\u003csvg class=\"colorCheckmark\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 600 600\"\u003e\n                                    \u003cpath fill=\"${\n                                      color.id === \"blanc\" ||\n                                      color.id === \"vert\"\n                                        ? \"#000\"\n                                        : \"#fff\"\n                                    }\" d=\"M583.45,72.97c-21.24-21.24-60.79-23.57-81.67,0-94.04,106.15-184.61,215.56-272.23,327.12-11.4-11.84-22.5-23.96-33.61-36.05-32.21-35.07-64.03-70.49-97.58-104.3-22.16-22.34-59.49-22.18-81.67,0-22.32,22.32-22.16,59.33,0,81.67,63.31,63.83,118.42,138.66,190.17,193.44,27.28,20.83,61.73,1.67,79.02-20.72-17.69,22.91-5.56,7.2-1.37,1.82,6.47-8.31,12.97-16.59,19.48-24.87,22.49-28.6,45.2-57.02,68.04-85.34,68.7-85.17,138.87-169.19,211.43-251.1,20.86-23.54,23.25-58.42,0-81.67Z\"\/\u003e\n                                  \u003c\/svg\u003e`\n                                : \"\"\n                            }\n                        \u003c\/button\u003e\n                      `\n                      )\n                      .join(\"\")}\n                    \u003c\/div\u003e\n                \u003c\/div\u003e\n                \u003cdiv class=\"persoSectionMobile\"\u003e\n                    \u003cdiv class=\"persoHeaderMobile\"\u003e\n                        \u003cdiv class=\"persoLabelMobile\"\u003e\n                            \u003cspan class=\"persoIcon\"\u003e\n                                \u003csvg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-sparkles\"\u003e\n                                    \u003cpath d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\"\u003e\u003c\/path\u003e\n                                    \u003cpath d=\"M20 3v4\"\u003e\u003c\/path\u003e\n                                    \u003cpath d=\"M22 5h-4\"\u003e\u003c\/path\u003e\n                                    \u003cpath d=\"M4 17v2\"\u003e\u003c\/path\u003e\n                                    \u003cpath d=\"M5 18H3\"\u003e\u003c\/path\u003e\n                                \u003c\/svg\u003e\n                            \u003c\/span\u003e\n                            \u003cspan\u003eGravure (optionnel)\u003c\/span\u003e\n                        \u003c\/div\u003e\n                        \u003cdiv class=\"persoPriceMobile\"\u003e+4.95€\u003c\/div\u003e\n                    \u003c\/div\u003e\n                    \u003cdiv class=\"persoInputWrapperMobile\"\u003e\n                        \u003cdiv class=\"inputContainerMobile\"\u003e\n                            \u003cinput \n                                type=\"text\" \n                                class=\"persoInputMobile ${\n                                  (state.personalization[index] || \"\").trim()\n                                    ? \"has-content\"\n                                    : \"\"\n                                }\" \n                                placeholder=\"Ex: Bonne lecture !\" \n                                maxlength=\"25\" \n                                value=\"${state.personalization[index] || \"\"}\" \n                                data-index=\"${index}\"\u003e\n                            \u003cdiv class=\"charCounterMobile ${\n                              (state.personalization[index] || \"\").length \u003e 20\n                                ? \"warning\"\n                                : \"\"\n                            }\"\u003e\n                                ${\n                                  (state.personalization[index] || \"\").length\n                                }\/25\n                            \u003c\/div\u003e\n                        \u003c\/div\u003e\n                    \u003c\/div\u003e\n                \u003c\/div\u003e\n            \u003c\/div\u003e\n        `;\n      })\n      .join(\"\");\n  }\n\n  \/** Rendu du bloc de prix *\/\n  function renderPrice() {\n    \/\/ 1. Calculer le pourcentage de réduction total\n    const discount = calculateTotalDiscount();\n    const totalDiscountPercent = discount.total;\n\n    \/\/ 2. Déterminer le prix de référence unitaire (Prix barré ou Prix de base si pas de barré)\n    let refPrice = PACK_ORIGINAL_PRICES[state.pack] || PACK_PRICES[state.pack];\n\n    \/\/ Pour le pack \"lizia-cushion\", utiliser le prix original (sans réduction) comme référence\n    if (state.pack === \"lizia-cushion\") {\n      refPrice =\n        PACK_ORIGINAL_PRICES[state.pack] || CUSHION_PRICE + LIZIA_BASE_PRICE; \/\/ 59.90€\n    }\n\n    \/\/ 3. Calculer le prix unitaire remisé\n    let unitPrice;\n    if (state.quantity \u003e= 2) {\n      \/\/ Si quantité \u003e= 2, on applique le % total sur le prix de référence\n      unitPrice = refPrice * (1 - totalDiscountPercent \/ 100);\n    } else {\n      \/\/ Si quantité 1, on garde le prix pack défini (pour éviter les écarts d'arrondi)\n      if (state.pack === \"lizia-cushion\") {\n        unitPrice = (CUSHION_PRICE + LIZIA_BASE_PRICE) * 0.95;\n      } else {\n        unitPrice = PACK_PRICES[state.pack];\n      }\n    }\n\n    \/\/ 4. Calculer le total pour la quantité\n    const totalPackPrice = unitPrice * state.quantity;\n\n    \/\/ 5. Ajouter la personnalisation\n    const persoCount = state.personalization.filter(\n      (p) =\u003e p \u0026\u0026 p.length \u003e 0\n    ).length;\n    const totalPersoPrice = persoCount * PERSONALIZATION_PRICE;\n\n    const totalPrice = totalPackPrice + totalPersoPrice;\n\n    \/\/ 6. Calculer l'économie réalisée (Prix Réf * Qté - Prix Payé hors perso)\n    const totalReferencePrice = refPrice * state.quantity;\n    const savings = totalReferencePrice - totalPackPrice;\n\n    \/\/ Mettre à jour les deux boutons avec le prix\n    let buttonText = `AJOUTER AU PANIER • ${totalPrice.toFixed(2)}€`;\n    if (savings \u003e 0.01) {\n      buttonText = `AJOUTER AU PANIER • \u003cspan style=\"text-decoration: line-through; opacity: 0.6; margin-right: 8px;\"\u003e${totalReferencePrice.toFixed(\n        2\n      )}€\u003c\/span\u003e${totalPrice.toFixed(2)}€`;\n    }\n    addToCartBtn.innerHTML = buttonText;\n    if (addToCartBtnMobile) {\n      addToCartBtnMobile.innerHTML = buttonText;\n    }\n  }\n\n  \/** Afficher la popup de détails du pack *\/\n  function showPackPopup(packId) {\n    const pack = PACKS.find((p) =\u003e p.id === packId);\n    if (!pack) return;\n\n    popupImage.src = pack.image;\n    popupImage.alt = pack.name;\n    popupTitle.textContent = pack.name;\n    popupDescription.textContent = pack.description;\n\n    \/\/ Afficher les caractéristiques\n    popupFeatures.innerHTML = pack.features\n      .map((feature) =\u003e `\u003cli\u003e${feature}\u003c\/li\u003e`)\n      .join(\"\");\n\n    \/\/ Afficher la popup\n    packPopup.classList.add(\"show\");\n    document.body.style.overflow = \"hidden\";\n  }\n\n  \/** Masquer la popup *\/\n  function hidePackPopup() {\n    packPopup.classList.remove(\"show\");\n    document.body.style.overflow = \"auto\";\n  }\n\n  \/\/ --- FONCTION DE MISE À JOUR GLOBALE ---\n  function updateUI() {\n    \/\/ Synchroniser l'état avant de rendre\n    if (state.colors.length !== state.quantity) {\n      const newColors = Array.from({ length: state.quantity });\n      const newPerso = Array.from({ length: state.quantity });\n      for (let i = 0; i \u003c state.quantity; i++) {\n        newColors[i] =\n          state.colors[i] || state.colors[state.colors.length - 1] || \"vert\";\n        newPerso[i] = state.personalization[i] || \"\";\n      }\n      state.colors = newColors;\n      state.personalization = newPerso;\n    }\n\n    \/\/ Détecter si le pack a changé\n    const packChanged = previousPack !== state.pack;\n    previousPack = state.pack;\n\n    renderImageGallery();\n    renderPackSelector();\n    renderPouchSelector(packChanged);\n    renderQuantitySelector();\n    renderDiscountGauge();\n    renderLiziaItems();\n    renderPrice();\n    addEventListeners(); \/\/ Ré-attacher les écouteurs après chaque rendu\n  }\n\n  \/\/ --- FONCTION DE MISE À JOUR OPTIMISÉE ---\n  function updateUIOptimized(forceImageReload = false) {\n    \/\/ Synchroniser l'état avant de rendre\n    if (state.colors.length !== state.quantity) {\n      const newColors = Array.from({ length: state.quantity });\n      const newPerso = Array.from({ length: state.quantity });\n      for (let i = 0; i \u003c state.quantity; i++) {\n        newColors[i] =\n          state.colors[i] || state.colors[state.colors.length - 1] || \"vert\";\n        newPerso[i] = state.personalization[i] || \"\";\n      }\n      state.colors = newColors;\n      state.personalization = newPerso;\n    }\n\n    \/\/ Détecter si le pack a changé\n    const packChanged = previousPack !== state.pack;\n    previousPack = state.pack;\n\n    \/\/ Ne recharger les images que si nécessaire\n    if (forceImageReload) {\n      renderImageGallery();\n    } else {\n      \/\/ Si pas de rechargement d'images, juste mettre à jour les vignettes\n      updateThumbnailsOnly();\n    }\n\n    renderPackSelector();\n    renderPouchSelector(packChanged);\n    renderQuantitySelector();\n    renderDiscountGauge();\n    renderLiziaItems();\n    renderPrice();\n    addEventListeners();\n  }\n\n  \/\/ --- FONCTION DE NAVIGATION D'IMAGES AVEC SLIDE ---\n  function updateImageNavigation(direction = null) {\n    const currentImages = getProductImages();\n    const currentImageUrl = currentImages[state.currentImageIndex];\n    const isVideo = currentImageUrl \u0026\u0026 currentImageUrl.startsWith(\"VIDEO:\");\n    const actualUrl = isVideo\n      ? currentImageUrl.replace(\"VIDEO:\", \"\")\n      : currentImageUrl;\n\n    \/\/ Détecter ce qui est actuellement affiché\n    const videoElement = mainImageContainer\n      ? mainImageContainer.querySelector(\"video.mainImage\")\n      : null;\n    const videoDisplay = videoElement\n      ? window.getComputedStyle(videoElement).display\n      : \"none\";\n    const imageDisplay = mainImage\n      ? window.getComputedStyle(mainImage).display\n      : \"none\";\n    const currentIsVideo = videoElement \u0026\u0026 videoDisplay !== \"none\";\n    const currentIsImage = mainImage \u0026\u0026 imageDisplay !== \"none\";\n\n    \/\/ Si pas de direction (clic sur miniature), changement direct\n    if (!direction) {\n      if (isVideo) {\n        \/\/ Masquer l'image si elle existe\n        if (mainImage) {\n          mainImage.style.display = \"none\";\n        }\n\n        \/\/ Vérifier si une vidéo existe déjà\n        let finalVideoElement = mainImageContainer\n          ? mainImageContainer.querySelector(\"video.mainImage\")\n          : null;\n\n        if (!finalVideoElement) {\n          \/\/ Créer une nouvelle vidéo\n          finalVideoElement = document.createElement(\"video\");\n          finalVideoElement.className = \"mainImage\";\n          finalVideoElement.setAttribute(\"playsinline\", \"\");\n          finalVideoElement.setAttribute(\"muted\", \"\");\n          finalVideoElement.setAttribute(\"autoplay\", \"\");\n          finalVideoElement.setAttribute(\"loop\", \"\");\n          finalVideoElement.setAttribute(\"controls\", \"\");\n          finalVideoElement.style.width = \"100%\";\n          finalVideoElement.style.height = \"100%\";\n          finalVideoElement.style.objectFit = \"cover\";\n          finalVideoElement.style.position = \"absolute\";\n          finalVideoElement.style.top = \"0\";\n          finalVideoElement.style.left = \"0\";\n          finalVideoElement.style.display = \"block\";\n          finalVideoElement.style.cursor = \"pointer\";\n          finalVideoElement.style.backgroundColor = \"#000\"; \/\/ Fond noir pour éviter le fond rose\n\n          const source = document.createElement(\"source\");\n          source.src = actualUrl;\n          source.type = \"video\/mp4\";\n          finalVideoElement.appendChild(source);\n\n          if (mainImageContainer) {\n            mainImageContainer.appendChild(finalVideoElement);\n          }\n        } else {\n          \/\/ Utiliser la vidéo existante\n          finalVideoElement.style.display = \"block\";\n          finalVideoElement.style.left = \"0\";\n          finalVideoElement.style.transition = \"\";\n\n          \/\/ Vérifier si la source doit être mise à jour\n          const source = finalVideoElement.querySelector(\"source\");\n          const currentSrc = source ? source.src : \"\";\n\n          if (!source || currentSrc !== actualUrl) {\n            \/\/ Mettre à jour la source\n            finalVideoElement.innerHTML = \"\";\n            const newSource = document.createElement(\"source\");\n            newSource.src = actualUrl;\n            newSource.type = \"video\/mp4\";\n            finalVideoElement.appendChild(newSource);\n            finalVideoElement.currentTime = 0;\n            finalVideoElement.load();\n            finalVideoElement.play().catch((err) =\u003e {\n              console.warn(\"Erreur de lecture vidéo:\", err);\n            });\n          } else {\n            \/\/ Réinitialiser à 0\n            finalVideoElement.currentTime = 0;\n            if (finalVideoElement.paused) {\n              finalVideoElement.play().catch((err) =\u003e {\n                console.warn(\"Erreur de lecture vidéo:\", err);\n              });\n            }\n          }\n        }\n\n        \/\/ Ajouter les contrôles vidéo (play\/pause + barre de progression)\n        setupVideoControls(finalVideoElement);\n      } else {\n        \/\/ Masquer la vidéo si elle existe\n        if (videoElement) {\n          videoElement.style.display = \"none\";\n          videoElement.pause();\n          videoElement.currentTime = 0;\n        }\n        \/\/ Afficher l'image\n        if (mainImage) {\n          mainImage.style.display = \"block\";\n          mainImage.src = actualUrl;\n          mainImage.style.left = \"0\";\n          mainImage.style.transition = \"\";\n        }\n      }\n      \/\/ Mettre à jour les dots et miniatures\n      updateDotsAndThumbnails();\n      \/\/ Réattacher les event listeners\n      addEventListeners();\n      return;\n    }\n\n    \/\/ Si direction est fournie (flèche ou swipe), animer la transition\n    const container =\n      mainImageContainer || (mainImage ? mainImage.parentElement : null);\n    if (!container) {\n      renderImageGallery();\n      updateDotsAndThumbnails();\n      addEventListeners();\n      return;\n    }\n\n    \/\/ Préparer l'élément actuel pour l'animation\n    let currentElement;\n    if (currentIsVideo \u0026\u0026 videoElement) {\n      currentElement = videoElement;\n    } else if (currentIsImage \u0026\u0026 mainImage) {\n      currentElement = mainImage;\n    }\n\n    \/\/ Fonction pour animer le slide\n    function animateSlide(tempEl) {\n      \/\/ Forcer le reflow\n      tempEl.offsetHeight;\n\n      \/\/ Animer les deux éléments ensemble\n      tempEl.style.transition = \"left 0.3s ease-out\";\n      if (currentElement) {\n        currentElement.style.transition = \"left 0.3s ease-out\";\n      }\n\n      if (direction === \"left\") {\n        \/\/ Swipe vers la gauche : nouveau élément arrive de la droite\n        if (currentElement) {\n          currentElement.style.left = \"-100%\";\n        }\n        tempEl.style.left = \"0\";\n      } else {\n        \/\/ Swipe vers la droite : nouveau élément arrive de la gauche\n        if (currentElement) {\n          currentElement.style.left = \"100%\";\n        }\n        tempEl.style.left = \"0\";\n      }\n    }\n\n    \/\/ Créer l'élément temporaire pour la transition\n    let tempElement;\n    if (isVideo) {\n      \/\/ Créer une vidéo temporaire\n      tempElement = document.createElement(\"video\");\n      tempElement.className = \"mainImage\";\n      tempElement.setAttribute(\"playsinline\", \"\");\n      tempElement.setAttribute(\"muted\", \"\");\n      tempElement.setAttribute(\"autoplay\", \"\");\n      tempElement.setAttribute(\"loop\", \"\");\n      tempElement.setAttribute(\"controls\", \"\");\n      tempElement.style.width = \"100%\";\n      tempElement.style.height = \"100%\";\n      tempElement.style.objectFit = \"cover\";\n      tempElement.style.position = \"absolute\";\n      tempElement.style.top = \"0\";\n      tempElement.style.left = direction === \"left\" ? \"100%\" : \"-100%\";\n      tempElement.style.cursor = \"pointer\";\n      tempElement.style.backgroundColor = \"#000\"; \/\/ Fond noir pour éviter le fond rose\n\n      const source = document.createElement(\"source\");\n      source.src = actualUrl;\n      source.type = \"video\/mp4\";\n      tempElement.appendChild(source);\n\n      \/\/ Ajouter pause\/play au clic\n      tempElement.addEventListener(\"click\", () =\u003e {\n        if (tempElement.paused) {\n          tempElement.play().catch(() =\u003e {});\n        } else {\n          tempElement.pause();\n        }\n      });\n\n      container.appendChild(tempElement);\n      \/\/ Charger la vidéo avant d'animer\n      tempElement.currentTime = 0;\n      tempElement.load();\n\n      \/\/ Attendre que la vidéo soit prête avant d'animer\n      const startAnimation = () =\u003e {\n        tempElement.play().catch(() =\u003e {});\n        animateSlide(tempElement);\n      };\n\n      if (tempElement.readyState \u003e= 2) {\n        \/\/ La vidéo est déjà chargée\n        startAnimation();\n      } else {\n        \/\/ Attendre que la vidéo soit chargée\n        tempElement.addEventListener(\"loadeddata\", startAnimation, {\n          once: true,\n        });\n        tempElement.addEventListener(\"canplay\", startAnimation, { once: true });\n        \/\/ Timeout de sécurité\n        setTimeout(startAnimation, 100);\n      }\n    } else {\n      \/\/ Créer une image temporaire\n      tempElement = document.createElement(\"img\");\n      tempElement.className = \"mainImage\";\n      tempElement.src = actualUrl;\n      tempElement.style.position = \"absolute\";\n      tempElement.style.top = \"0\";\n      tempElement.style.width = \"100%\";\n      tempElement.style.height = \"100%\";\n      tempElement.style.objectFit = \"cover\";\n      tempElement.style.left = direction === \"left\" ? \"100%\" : \"-100%\";\n\n      container.appendChild(tempElement);\n\n      \/\/ Pour les images, animer directement\n      animateSlide(tempElement);\n    }\n\n    \/\/ Après l'animation, nettoyer et afficher le bon élément\n    setTimeout(() =\u003e {\n      if (isVideo) {\n        \/\/ Masquer l'image si elle existe\n        if (mainImage) {\n          mainImage.style.display = \"none\";\n        }\n\n        \/\/ Supprimer toutes les vidéos existantes sauf celle temporaire\n        const existingVideos =\n          mainImageContainer.querySelectorAll(\"video.mainImage\");\n        existingVideos.forEach((vid) =\u003e {\n          if (vid !== tempElement \u0026\u0026 container.contains(vid)) {\n            vid.pause();\n            container.removeChild(vid);\n          }\n        });\n\n        \/\/ Transformer l'élément temporaire en élément permanent\n        tempElement.style.transition = \"\";\n        tempElement.style.left = \"0\";\n        \/\/ Ajouter les contrôles vidéo (play\/pause + barre de progression)\n        setupVideoControls(tempElement);\n      } else {\n        \/\/ Masquer la vidéo si elle existe\n        if (videoElement) {\n          videoElement.style.display = \"none\";\n          videoElement.pause();\n          videoElement.currentTime = 0;\n        }\n        \/\/ Afficher l'image\n        if (mainImage) {\n          mainImage.style.display = \"block\";\n          mainImage.src = actualUrl;\n          mainImage.style.transition = \"\";\n          mainImage.style.left = \"0\";\n        }\n\n        \/\/ Supprimer l'élément temporaire\n        if (container.contains(tempElement)) {\n          container.removeChild(tempElement);\n        }\n      }\n\n      \/\/ Réinitialiser les styles de l'élément actuel\n      if (currentElement \u0026\u0026 currentElement !== tempElement) {\n        currentElement.style.transition = \"\";\n        currentElement.style.left = \"\";\n      }\n\n      \/\/ Mettre à jour les dots et miniatures\n      updateDotsAndThumbnails();\n      \/\/ Réattacher les event listeners\n      addEventListeners();\n    }, 300);\n  }\n\n  \/\/ Fonction helper pour mettre à jour les dots et miniatures\n  function updateDotsAndThumbnails() {\n    const dots = document.querySelectorAll(\".dot\");\n    dots.forEach((dot, index) =\u003e {\n      if (index === state.currentImageIndex) {\n        dot.classList.add(\"active\");\n      } else {\n        dot.classList.remove(\"active\");\n      }\n    });\n\n    const thumbnails = document.querySelectorAll(\".thumbnailBtn\");\n    thumbnails.forEach((thumb, index) =\u003e {\n      if (index === state.currentImageIndex) {\n        thumb.classList.add(\"active\");\n      } else {\n        thumb.classList.remove(\"active\");\n      }\n    });\n  }\n\n  \/\/ --- FONCTION DE MISE À JOUR DES VIGNETTES SANS FLASH ---\n  function updateThumbnailsOnly() {\n    \/\/ Mettre à jour seulement les classes des vignettes existantes\n    const thumbnails = document.querySelectorAll(\".thumbnailBtn\");\n    thumbnails.forEach((thumb, index) =\u003e {\n      if (index === state.currentImageIndex) {\n        thumb.classList.add(\"active\");\n      } else {\n        thumb.classList.remove(\"active\");\n      }\n    });\n  }\n\n  \/\/ --- FONCTION POUR METTRE À JOUR LES IMAGES ET VIGNETTES ---\n  function updateImagesAndThumbnails() {\n    \/\/ Utiliser renderImageGallery pour gérer les vidéos correctement\n    renderImageGallery();\n    return;\n\n    \/\/ Mettre à jour les vignettes avec les nouvelles images\n    const thumbnails = document.querySelectorAll(\".thumbnailBtn\");\n    thumbnails.forEach((thumb, thumbIndex) =\u003e {\n      const img = thumb.querySelector(\"img\");\n      if (img \u0026\u0026 currentImages[thumbIndex]) {\n        img.src = currentImages[thumbIndex];\n      }\n\n      if (thumbIndex === state.currentImageIndex) {\n        thumb.classList.add(\"active\");\n      } else {\n        thumb.classList.remove(\"active\");\n      }\n    });\n\n    \/\/ Mettre à jour les dots\n    const dots = document.querySelectorAll(\".dot\");\n    dots.forEach((dot, dotIndex) =\u003e {\n      if (dotIndex === state.currentImageIndex) {\n        dot.classList.add(\"active\");\n      } else {\n        dot.classList.remove(\"active\");\n      }\n    });\n  }\n\n  \/\/ --- FONCTIONS D'AJOUT AU PANIER ---\n\n  \/**\n   * Valide et filtre le texte de personnalisation\n   * Autorise : lettres avec accents, chiffres, espaces, emojis cœur, guillemets\n   *\/\n  function validatePersonalizationText(text) {\n    const regex = \/^[a-zA-ZÀ-ÿ0-9\\s❤️\"']+$\/;\n    if (!regex.test(text)) {\n      \/\/ Supprimer les caractères non autorisés\n      return text.replace(\/[^a-zA-ZÀ-ÿ0-9\\s❤️\"']+\/g, \"\");\n    }\n    return text;\n  }\n\n  \/**\n   * Récupère l'ID du variant Shopify selon le pack, la couleur et la personnalisation\n   *\/\n  function getVariantID(packID, color, isPersonalized) {\n    \/\/ Pack \"cushion-only\" : un seul variant, pas de couleur ni personnalisation\n    if (packID === \"15775468388701\") {\n      return variantIDs[packID].Default.normal; \/\/ Retourne le variant ID depuis le mapping\n    }\n\n    \/\/ Pack \"lizia-cushion\" : réutiliser le mapping du pack Lizia seul (8501710225757)\n    if (packID === \"8501710225757\") {\n      \/\/ Capitaliser le nom de la couleur pour matcher les clés des variants\n      const colorKey = color.charAt(0).toUpperCase() + color.slice(1);\n\n      if (!variantIDs[packID] || !variantIDs[packID][colorKey]) {\n        console.error(\n          `Variant non trouvé pour pack ${packID}, couleur ${colorKey}`\n        );\n        return null;\n      }\n\n      return isPersonalized\n        ? variantIDs[packID][colorKey][\"personnalise\"]\n        : variantIDs[packID][colorKey][\"normal\"];\n    }\n\n    \/\/ Fallback pour les autres packs (ne devrait pas arriver)\n    const colorKey = color.charAt(0).toUpperCase() + color.slice(1);\n    if (!variantIDs[packID] || !variantIDs[packID][colorKey]) {\n      console.error(\n        `Variant non trouvé pour pack ${packID}, couleur ${colorKey}`\n      );\n      return null;\n    }\n\n    return isPersonalized\n      ? variantIDs[packID][colorKey][\"personnalise\"]\n      : variantIDs[packID][colorKey][\"normal\"];\n  }\n\n  \/**\n   * Ajoute plusieurs produits au panier via l'API Shopify\n   *\/\n  function addMultipleToCart(products) {\n    const items = products.map((product) =\u003e ({\n      id: product.variantID,\n      quantity: product.quantity,\n      properties: product.properties,\n    }));\n\n    return fetch(\"\/cart\/add.js\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application\/json\",\n        Accept: \"application\/json\",\n      },\n      body: JSON.stringify({ items }),\n    })\n      .then((response) =\u003e {\n        if (!response.ok) {\n          return Promise.reject(\"Erreur de réponse du serveur\");\n        }\n        return response.json();\n      })\n      .then((data) =\u003e {\n        console.log(\"Produits ajoutés au panier:\", data);\n        return data;\n      })\n      .catch((error) =\u003e {\n        console.error(\"Erreur lors de l'ajout au panier:\", error);\n        throw error;\n      });\n  }\n\n  \/\/ --- GESTIONNAIRES D'ÉVÉNEMENTS ---\n  function addEventListeners() {\n    \/\/ Galerie d'images\n    prevBtn.onclick = () =\u003e {\n      const currentImages = getProductImages();\n      state.currentImageIndex =\n        (state.currentImageIndex - 1 + currentImages.length) %\n        currentImages.length;\n      updateImageNavigation(\"right\"); \/\/ Image vient de la gauche\n    };\n    nextBtn.onclick = () =\u003e {\n      const currentImages = getProductImages();\n      state.currentImageIndex =\n        (state.currentImageIndex + 1) % currentImages.length;\n      updateImageNavigation(\"left\"); \/\/ Image vient de la droite\n    };\n    document.querySelectorAll(\".dot, .thumbnailBtn\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        state.currentImageIndex = parseInt(e.currentTarget.dataset.index);\n        updateImageNavigation(); \/\/ Fonction optimisée pour la navigation\n      };\n    });\n\n    \/\/ Gestion du swipe sur mobile pour la galerie d'images\n    const mainImageContainer = document.querySelector(\".mainImageContainer\");\n    if (mainImageContainer) {\n      let touchStartX = 0;\n      let touchStartY = 0;\n      let currentTouchX = 0;\n      let isDragging = false;\n      let isHorizontalSwipe = false;\n      let nextImage = null;\n      let prevImage = null;\n      let isAnimating = false;\n      const minSwipeDistance = 50; \/\/ Distance minimale pour valider le swipe\n\n      mainImageContainer.addEventListener(\n        \"touchstart\",\n        (e) =\u003e {\n          if (isAnimating) return; \/\/ Empêcher le swipe pendant une animation\n\n          touchStartX = e.touches[0].clientX;\n          touchStartY = e.touches[0].clientY;\n          currentTouchX = touchStartX;\n          isDragging = true;\n          isHorizontalSwipe = false;\n\n          \/\/ Nettoyer les images précédentes au cas où\n          cleanupSwipeImages();\n\n          \/\/ Créer les images voisines pour le swipe\n          const currentImages = getProductImages();\n          const container = mainImage.parentElement;\n\n          \/\/ Image suivante (à droite)\n          nextImage = document.createElement(\"img\");\n          const nextIndex =\n            (state.currentImageIndex + 1) % currentImages.length;\n          nextImage.src = currentImages[nextIndex];\n          nextImage.className = \"mainImage swipe-temp-image\";\n          nextImage.style.position = \"absolute\";\n          nextImage.style.top = \"0\";\n          nextImage.style.left = \"100%\";\n          nextImage.style.width = \"100%\";\n          nextImage.style.height = \"100%\";\n          nextImage.style.objectFit = \"cover\";\n          nextImage.style.transition = \"none\";\n          nextImage.style.pointerEvents = \"none\";\n          container.appendChild(nextImage);\n\n          \/\/ Image précédente (à gauche)\n          prevImage = document.createElement(\"img\");\n          const prevIndex =\n            (state.currentImageIndex - 1 + currentImages.length) %\n            currentImages.length;\n          prevImage.src = currentImages[prevIndex];\n          prevImage.className = \"mainImage swipe-temp-image\";\n          prevImage.style.position = \"absolute\";\n          prevImage.style.top = \"0\";\n          prevImage.style.left = \"-100%\";\n          prevImage.style.width = \"100%\";\n          prevImage.style.height = \"100%\";\n          prevImage.style.objectFit = \"cover\";\n          prevImage.style.transition = \"none\";\n          prevImage.style.pointerEvents = \"none\";\n          container.appendChild(prevImage);\n\n          \/\/ Désactiver la transition pendant le drag\n          mainImage.style.transition = \"none\";\n        },\n        { passive: true }\n      );\n\n      mainImageContainer.addEventListener(\n        \"touchmove\",\n        (e) =\u003e {\n          if (!isDragging || isAnimating) return;\n\n          currentTouchX = e.touches[0].clientX;\n          const currentTouchY = e.touches[0].clientY;\n          const deltaX = currentTouchX - touchStartX;\n          const deltaY = currentTouchY - touchStartY;\n\n          \/\/ Détecter si c'est un swipe horizontal ou vertical\n          if (!isHorizontalSwipe \u0026\u0026 Math.abs(deltaX) \u003e 10) {\n            if (Math.abs(deltaX) \u003e Math.abs(deltaY)) {\n              isHorizontalSwipe = true;\n            }\n          }\n\n          \/\/ Si c'est un swipe horizontal, bloquer le scroll vertical\n          if (isHorizontalSwipe) {\n            e.preventDefault();\n\n            const containerWidth = mainImageContainer.offsetWidth;\n            const percentage = (deltaX \/ containerWidth) * 100;\n\n            \/\/ Déplacer les trois images en fonction du swipe\n            mainImage.style.left = `${percentage}%`;\n            if (nextImage) nextImage.style.left = `${100 + percentage}%`;\n            if (prevImage) prevImage.style.left = `${-100 + percentage}%`;\n          }\n        },\n        { passive: false }\n      ); \/\/ passive: false pour pouvoir preventDefault\n\n      mainImageContainer.addEventListener(\n        \"touchend\",\n        (e) =\u003e {\n          if (!isDragging || isAnimating) return;\n\n          \/\/ Si ce n'était pas un swipe horizontal, ne rien faire\n          if (!isHorizontalSwipe) {\n            cleanupSwipeImages();\n            isDragging = false;\n            return;\n          }\n\n          isAnimating = true;\n          const swipeDistance = currentTouchX - touchStartX;\n          const containerWidth = mainImageContainer.offsetWidth;\n          const swipePercentage = Math.abs(swipeDistance \/ containerWidth);\n\n          \/\/ Réactiver les transitions\n          mainImage.style.transition = \"left 0.3s ease-out\";\n          if (nextImage) nextImage.style.transition = \"left 0.3s ease-out\";\n          if (prevImage) prevImage.style.transition = \"left 0.3s ease-out\";\n\n          const currentImages = getProductImages();\n\n          \/\/ Si le swipe est assez grand, changer d'image\n          if (\n            swipePercentage \u003e 0.25 ||\n            Math.abs(swipeDistance) \u003e minSwipeDistance\n          ) {\n            if (swipeDistance \u003e 0) {\n              \/\/ Swipe vers la droite = image précédente\n              const newIndex =\n                (state.currentImageIndex - 1 + currentImages.length) %\n                currentImages.length;\n\n              mainImage.style.left = \"100%\";\n              if (prevImage) prevImage.style.left = \"0\";\n              if (nextImage) nextImage.style.left = \"200%\";\n\n              setTimeout(() =\u003e {\n                state.currentImageIndex = newIndex;\n                mainImage.src = currentImages[state.currentImageIndex];\n                mainImage.style.transition = \"\";\n                mainImage.style.left = \"0\";\n                cleanupSwipeImages();\n                updateImageDots();\n                isAnimating = false;\n              }, 300);\n            } else {\n              \/\/ Swipe vers la gauche = image suivante\n              const newIndex =\n                (state.currentImageIndex + 1) % currentImages.length;\n\n              mainImage.style.left = \"-100%\";\n              if (nextImage) nextImage.style.left = \"0\";\n              if (prevImage) prevImage.style.left = \"-200%\";\n\n              setTimeout(() =\u003e {\n                state.currentImageIndex = newIndex;\n                mainImage.src = currentImages[state.currentImageIndex];\n                mainImage.style.transition = \"\";\n                mainImage.style.left = \"0\";\n                cleanupSwipeImages();\n                updateImageDots();\n                isAnimating = false;\n              }, 300);\n            }\n          } else {\n            \/\/ Swipe trop petit, revenir à la position initiale\n            mainImage.style.left = \"0\";\n            if (nextImage) nextImage.style.left = \"100%\";\n            if (prevImage) prevImage.style.left = \"-100%\";\n\n            setTimeout(() =\u003e {\n              cleanupSwipeImages();\n              isAnimating = false;\n            }, 300);\n          }\n\n          isDragging = false;\n        },\n        { passive: true }\n      );\n\n      function cleanupSwipeImages() {\n        const container = mainImage.parentElement;\n        \/\/ Nettoyer toutes les images temporaires\n        const tempImages = container.querySelectorAll(\".swipe-temp-image\");\n        tempImages.forEach((img) =\u003e {\n          if (img.parentElement) {\n            container.removeChild(img);\n          }\n        });\n        nextImage = null;\n        prevImage = null;\n      }\n\n      function updateImageDots() {\n        \/\/ Mettre à jour les dots\n        const dots = document.querySelectorAll(\".dot\");\n        dots.forEach((dot, index) =\u003e {\n          if (index === state.currentImageIndex) {\n            dot.classList.add(\"active\");\n          } else {\n            dot.classList.remove(\"active\");\n          }\n        });\n\n        \/\/ Mettre à jour les miniatures\n        const thumbnails = document.querySelectorAll(\".thumbnailBtn\");\n        thumbnails.forEach((thumb, index) =\u003e {\n          if (index === state.currentImageIndex) {\n            thumb.classList.add(\"active\");\n          } else {\n            thumb.classList.remove(\"active\");\n          }\n        });\n      }\n    }\n\n    \/\/ Sélecteur de pack\n    document.querySelectorAll(\".packOption\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        state.pack = e.currentTarget.dataset.pack;\n        updateUIOptimized(true);\n      };\n    });\n\n    \/\/ Cliquer sur le wrapper sélectionne aussi le pack\n    document.querySelectorAll(\".packWrapper\").forEach((el) =\u003e {\n      const packOption = el.querySelector(\".packOption\");\n      if (packOption) {\n        el.onclick = (e) =\u003e {\n          \/\/ Ne pas déclencher si on clique sur le badge ou sur le \"i\"\n          if (\n            e.target.classList.contains(\"packBadgeTop\") ||\n            e.target.classList.contains(\"badgeTopInfo\") ||\n            e.target.classList.contains(\"badgeTopText\") ||\n            e.target.closest(\".packBadgeTop\")\n          ) {\n            return;\n          }\n          state.pack = packOption.dataset.pack;\n          updateUIOptimized(true);\n        };\n      }\n    });\n\n    \/\/ Sélecteur de pochette\n    document.querySelectorAll(\".pouchOption\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        state.pouch = e.currentTarget.dataset.pouch;\n        updateUIOptimized(true);\n      };\n    });\n\n    \/\/ Sélecteur de quantité - nouveau système à 3 bulles\n    document.querySelectorAll(\".quantityBubble\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        const action = e.currentTarget.dataset.action;\n        const value = e.currentTarget.dataset.value;\n\n        if (action === \"set\") {\n          \/\/ Définir directement la quantité\n          state.quantity = parseInt(value);\n          updateUIOptimized(false);\n        } else if (action === \"increment\") {\n          \/\/ Incrémenter la quantité (max 20)\n          if (state.quantity \u003c 20) {\n            state.quantity = state.quantity + 1;\n            updateUIOptimized(false);\n          }\n        } else if (action === \"decrement\") {\n          \/\/ Décrémenter la quantité (min 1)\n          if (state.quantity \u003e 1) {\n            state.quantity = state.quantity - 1;\n            updateUIOptimized(false);\n          }\n        }\n      };\n    });\n\n    \/\/ Couleurs (ancien et nouveau)\n    document.querySelectorAll(\".colorBtn, .colorBtnMobile\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        const index = parseInt(e.currentTarget.dataset.index);\n        const color = e.currentTarget.dataset.color;\n        state.colors[index] = color;\n\n        \/\/ Mettre à jour les images des packs en fonction de la première couleur\n        if (index === 0) {\n          updatePackImages();\n        }\n\n        updateUIOptimized(true);\n      };\n    });\n\n    \/\/ Personnalisation (ancien et nouveau)\n    document\n      .querySelectorAll(\".persoInput, .persoInputMobile\")\n      .forEach((el) =\u003e {\n        el.oninput = (e) =\u003e {\n          const index = parseInt(e.currentTarget.dataset.index);\n\n          \/\/ Valider et filtrer le texte\n          const validatedText = validatePersonalizationText(e.target.value);\n\n          \/\/ Si le texte a été filtré, mettre à jour l'input\n          if (validatedText !== e.target.value) {\n            e.target.value = validatedText;\n          }\n\n          state.personalization[index] = validatedText;\n\n          \/\/ Mettre à jour le compteur de caractères\n          const charCounter = e.currentTarget.nextElementSibling;\n          const length = validatedText.length;\n\n          \/\/ Ajouter\/retirer la classe has-content\n          if (validatedText.trim()) {\n            e.target.classList.add(\"has-content\");\n          } else {\n            e.target.classList.remove(\"has-content\");\n          }\n\n          if (charCounter) {\n            charCounter.textContent = `${length}\/25`;\n            charCounter.classList.toggle(\"warning\", length \u003e 20);\n          }\n\n          renderPrice();\n        };\n      });\n\n    \/\/ Badges Top (avec \"i\" intégré) cliquables pour afficher la popup\n    document.querySelectorAll(\".packBadgeTop\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        e.stopPropagation(); \/\/ Empêcher la sélection de pack\n        const packId = e.currentTarget.dataset.pack;\n        if (packId) {\n          showPackPopup(packId);\n        }\n      };\n      \/\/ Support du clavier (Enter\/Space)\n      el.onkeydown = (e) =\u003e {\n        if (e.key === \"Enter\" || e.key === \" \") {\n          e.preventDefault();\n          e.stopPropagation();\n          const packId = e.currentTarget.dataset.pack;\n          if (packId) {\n            showPackPopup(packId);\n          }\n        }\n      };\n    });\n\n    \/\/ Fermeture de la popup\n    popupClose.onclick = hidePackPopup;\n    packPopup.onclick = (e) =\u003e {\n      if (e.target === packPopup) {\n        hidePackPopup();\n      }\n    };\n\n    \/\/ Force focus on mobile input\n    document\n      .querySelectorAll(\n        \".persoInputWrapperMobile, .inputContainerMobile, .persoInputMobile\"\n      )\n      .forEach((el) =\u003e {\n        el.addEventListener(\"click\", (e) =\u003e {\n          const wrapper = e.currentTarget.closest(\".persoInputWrapperMobile\");\n          if (wrapper) {\n            const input = wrapper.querySelector(\".persoInputMobile\");\n            if (input) {\n              input.focus();\n            }\n          }\n        });\n      });\n  }\n\n  \/\/ --- INITIALISATION ---\n  updateUI();\n\n  \/\/ Précharger les images des packs pour éviter le lag\n  preloadPackImages();\n\n  \/\/ Initialiser la section avis\n  renderReviewsSection();\n\n  \/\/ Initialiser le lazy loading optimisé\n  setTimeout(() =\u003e {\n    loadVisibleImages();\n  }, 100);\n\n  \/\/ Observer pour le lazy loading avec Intersection Observer\n  const imageObserver = new IntersectionObserver(\n    (entries) =\u003e {\n      entries.forEach((entry) =\u003e {\n        if (entry.isIntersecting) {\n          const element = entry.target;\n          \/\/ Gérer les images\n          if (\n            element.tagName === \"IMG\" \u0026\u0026\n            element.dataset.src \u0026\u0026\n            !element.src\n          ) {\n            \/\/ Charger directement l'image sans délai\n            element.src = element.dataset.src;\n            element.classList.add(\"loaded\");\n            imageObserver.unobserve(element);\n          }\n          \/\/ Gérer les vidéos dans les miniatures - seulement charger la première frame (preload=\"metadata\")\n          else if (\n            element.tagName === \"VIDEO\" \u0026\u0026\n            element.classList.contains(\"videoPreview\")\n          ) {\n            \/\/ Ne rien faire - la vidéo dans les miniatures utilise preload=\"metadata\"\n            \/\/ pour charger seulement la première frame, mais ne se lance pas automatiquement\n            imageObserver.unobserve(element);\n          }\n        }\n      });\n    },\n    {\n      rootMargin: \"100px 0px\", \/\/ Charger 100px avant que l'image soit visible\n      threshold: 0.1,\n    }\n  );\n\n  \/\/ Observer toutes les images lazy\n  setTimeout(() =\u003e {\n    const lazyImages = document.querySelectorAll(\".lazy-image\");\n    lazyImages.forEach((img) =\u003e imageObserver.observe(img));\n  }, 200);\n\n  \/\/ --- LOGIQUE D'AJOUT AU PANIER ---\n\n  \/\/ Fonction partagée pour l'ajout au panier\n  async function handleAddToCart() {\n    \/\/ Cas spécial : Pack \"lizia-cushion\" - ajouter séparément Lizia et Coussin\n    if (state.pack === \"lizia-cushion\") {\n      const liziaPackID = \"8501710225757\"; \/\/ ID du pack Lizia seul\n      const cushionPackID = \"15775468388701\"; \/\/ ID du coussin seul\n      const cushionVariantID = variantIDs[cushionPackID].Default.normal; \/\/ Variant du coussin\n\n      const liziaProductsToAdd = [];\n\n      \/\/ Pour chaque quantité, préparer l'ajout du Lizia avec sa couleur\/personnalisation\n      for (let i = 0; i \u003c state.quantity; i++) {\n        const color = state.colors[i] || \"vert\"; \/\/ Fallback sur \"vert\" pour le pack lizia-cushion\n        const isPersonalized =\n          state.personalization[i] \u0026\u0026\n          state.personalization[i].trim().length \u003e 0;\n\n        const liziaVariantID = getVariantID(liziaPackID, color, isPersonalized);\n\n        if (!liziaVariantID) {\n          console.error(\n            `Impossible de récupérer le variant Lizia pour ${color}`\n          );\n          return;\n        }\n\n        liziaProductsToAdd.push({\n          variantID: liziaVariantID,\n          quantity: 1,\n          properties: isPersonalized\n            ? { Personnalisation: state.personalization[i] }\n            : {},\n        });\n      }\n\n      \/\/ Ajouter tous les Lizia via l'API\n      if (liziaProductsToAdd.length \u003e 0) {\n        try {\n          await addMultipleToCart(liziaProductsToAdd);\n        } catch (error) {\n          console.error(\"Erreur lors de l'ajout des Lizia:\", error);\n          return;\n        }\n      }\n\n      \/\/ Ajouter les coussins via l'API : quantity - 1 (le dernier sera ajouté via le formulaire)\n      \/\/ Si quantity = 1, on n'ajoute rien via l'API, tout passe par le formulaire\n      if (state.quantity \u003e 1) {\n        try {\n          await addMultipleToCart([\n            {\n              variantID: cushionVariantID,\n              quantity: state.quantity - 1, \/\/ Ajouter quantity - 1 via l'API\n              properties: {},\n            },\n          ]);\n        } catch (error) {\n          console.error(\"Erreur lors de l'ajout des coussins:\", error);\n          return;\n        }\n      }\n\n      \/\/ Ajouter le dernier coussin via le formulaire (pour déclencher le cart slide)\n      \/\/ Sélectionner le variant du coussin dans le formulaire caché\n      const optionSelector = `#ProductSelect_${cushionPackID}TEST option[value=\"${cushionVariantID}\"], #ProductSelect_${cushionPackID} option[value=\"${cushionVariantID}\"]`;\n      const optionMatches = document.querySelectorAll(optionSelector);\n      let selectOption = null;\n      if (optionMatches.length \u003e 0) {\n        selectOption = optionMatches[0];\n      }\n\n      if (selectOption) {\n        selectOption.selected = true;\n      } else {\n        console.error(\n          \"Option de sélection non trouvée pour le variant coussin:\",\n          cushionVariantID\n        );\n        return;\n      }\n\n      \/\/ Mettre la quantité à 1 pour le clic (on ajoute toujours 1 via le formulaire)\n      const quantityInput = document.getElementById(`updates_${cushionPackID}`);\n      if (quantityInput) {\n        quantityInput.value = 1;\n        quantityInput.setAttribute(\"value\", \"1\");\n        \/\/ Déclencher les événements pour s'assurer que la valeur est prise en compte\n        quantityInput.dispatchEvent(new Event(\"input\", { bubbles: true }));\n        quantityInput.dispatchEvent(new Event(\"change\", { bubbles: true }));\n      } else {\n        console.error(\n          \"Champ de quantité introuvable pour le coussin:\",\n          `updates_${cushionPackID}`\n        );\n        return;\n      }\n\n      \/\/ Cliquer sur le bouton d'ajout au panier caché du coussin (ajoute le dernier et déclenche le cart slide)\n      const addToCartButtons = document.getElementsByClassName(\n        `add_to_cart_btn_${cushionPackID}`\n      );\n      if (addToCartButtons.length \u003e 0) {\n        \/\/ Attendre un peu avant de cliquer pour s'assurer que l'API a terminé (si quantity \u003e 1)\n        if (state.quantity \u003e 1) {\n          await new Promise((resolve) =\u003e setTimeout(resolve, 200));\n        }\n        addToCartButtons[0].click();\n      } else {\n        console.error(\n          \"Bouton d'ajout au panier introuvable pour le coussin:\",\n          cushionPackID\n        );\n        return;\n      }\n\n      return; \/\/ Sortir de la fonction après avoir traité le pack lizia-cushion\n    }\n\n    \/\/ Comportement normal pour les autres packs\n    \/\/ Récupérer l'ID du pack actuel\n    let packID;\n    if (state.pack === \"lizia-cable-pouch\") {\n      packID = packIDMapping[state.pack][state.pouch];\n    } else {\n      packID = packIDMapping[state.pack];\n    }\n\n    const productsToAdd = [];\n\n    \/\/ Gérer les produits 2 à X (si quantité \u003e 1)\n    if (state.quantity \u003e 1) {\n      for (let i = 1; i \u003c state.quantity; i++) {\n        \/\/ Pour le coussin seul, pas de couleur ni personnalisation\n        let color = null;\n        let isPersonalized = false;\n        if (packID !== \"15775468388701\") {\n          color = state.colors[i];\n          isPersonalized =\n            state.personalization[i] \u0026\u0026\n            state.personalization[i].trim().length \u003e 0;\n        }\n        const variantID = getVariantID(\n          packID,\n          color || \"Default\",\n          isPersonalized\n        );\n\n        if (variantID) {\n          productsToAdd.push({\n            variantID: variantID,\n            quantity: 1,\n            properties: isPersonalized\n              ? { Personnalisation: state.personalization[i] }\n              : {},\n          });\n        } else {\n          console.error(\n            `Impossible de récupérer le variant pour ${color || \"Default\"}`\n          );\n          return;\n        }\n      }\n\n      \/\/ Ajouter les produits 2 à X via l'API\n      if (productsToAdd.length \u003e 0) {\n        try {\n          await addMultipleToCart(productsToAdd);\n        } catch (error) {\n          console.error(\"Erreur lors de l'ajout multiple:\", error);\n          return;\n        }\n      }\n    }\n\n    \/\/ Gérer le premier produit (ou le seul produit si quantité = 1) via le formulaire caché\n    \/\/ Pour le coussin seul, pas de couleur ni personnalisation\n    let firstColor = null;\n    let firstIsPersonalized = false;\n    if (packID !== \"15775468388701\") {\n      firstColor = state.colors[0];\n      firstIsPersonalized =\n        state.personalization[0] \u0026\u0026 state.personalization[0].trim().length \u003e 0;\n    }\n\n    const firstVariantID = getVariantID(\n      packID,\n      firstColor || \"Default\",\n      firstIsPersonalized\n    );\n\n    if (!firstVariantID) {\n      console.error(\n        \"Impossible de récupérer le variant pour le premier produit\"\n      );\n      return;\n    }\n\n    \/\/ Mapper le packID vers l'ID du champ de personnalisation (seulement pour les packs avec personnalisation)\n    let personalizeInputID;\n    switch (packID) {\n      case \"8501710225757\":\n        personalizeInputID = \"personalize-input1\";\n        break;\n      case \"9868036014429\":\n        personalizeInputID = \"personalize-input2\";\n        break;\n      case \"9868043878749\":\n        personalizeInputID = \"personalize-input3\";\n        break;\n      case \"9868106465629\":\n        personalizeInputID = \"personalize-input4\";\n        break;\n      default:\n        personalizeInputID = null;\n    }\n\n    \/\/ Sélectionner le variant dans le formulaire caché\n    const optionSelector = `#ProductSelect_${packID}TEST option[value=\"${firstVariantID}\"], #ProductSelect_${packID} option[value=\"${firstVariantID}\"]`;\n    const optionMatches = document.querySelectorAll(optionSelector);\n    let selectOption = null;\n    if (optionMatches.length \u003e 0) {\n      \/\/ Pour le coussin seul, utiliser le premier match\n      \/\/ Pour Lizia seul (8501710225757), utiliser l'index 1 car il y a 2 formulaires\n      if (packID === \"8501710225757\" \u0026\u0026 optionMatches.length \u003e 1) {\n        selectOption = optionMatches[1];\n      } else {\n        selectOption = optionMatches[0];\n      }\n    }\n\n    if (selectOption) {\n      selectOption.selected = true;\n    } else {\n      console.error(\n        \"Option de sélection non trouvée pour le variant:\",\n        firstVariantID\n      );\n    }\n\n    \/\/ Définir le texte de personnalisation (seulement si le champ existe)\n    if (personalizeInputID) {\n      const personalizeInput = document.getElementById(personalizeInputID);\n      if (personalizeInput) {\n        personalizeInput.value = firstIsPersonalized\n          ? state.personalization[0]\n          : \"\";\n      }\n    }\n\n    \/\/ Cliquer sur le bouton d'ajout au panier caché\n    const addToCartButtons = document.getElementsByClassName(\n      `add_to_cart_btn_${packID}`\n    );\n    if (addToCartButtons.length \u003e 0) {\n      \/\/ Utiliser l'index 1 pour Lizia seul (car il y a 2 formulaires), 0 pour les autres\n      const buttonIndex = packID === \"8501710225757\" ? 1 : 0;\n      addToCartButtons[buttonIndex].click();\n    } else {\n      console.error(\n        \"Bouton d'ajout au panier introuvable pour le pack:\",\n        packID\n      );\n    }\n  }\n\n  \/\/ Attacher les événements aux deux boutons\n  addToCartBtn.addEventListener(\"click\", handleAddToCart);\n  if (addToCartBtnMobile) {\n    addToCartBtnMobile.addEventListener(\"click\", handleAddToCart);\n  }\n\n  \/\/ --- GESTION DES AVIS ---\n  function formatReviewDate(month, year) {\n    \/\/ Capitalize first letter and ensure max 4 characters\n    const formattedMonth = month.charAt(0).toUpperCase() + month.slice(1);\n    return `${formattedMonth.substring(0, 4)}. ${year}`;\n  }\n\n  function renderReviewsSection() {\n    const container = document.getElementById(\"reviewsContainer\");\n    if (!container) return;\n\n    \/\/ Dupliquer les cartes pour l'effet infini (3 fois : avant, milieu, après)\n    const tripleData = [...reviewsData, ...reviewsData, ...reviewsData];\n\n    \/\/ Generate review cards HTML\n    const reviewsHTML = tripleData\n      .map((review) =\u003e {\n        const stars = \"★\".repeat(review.rating);\n        const firstLetter = review.firstName.charAt(0).toUpperCase();\n        const formattedDate = formatReviewDate(\n          review.date.month,\n          review.date.year\n        );\n\n        return `\n        \u003cdiv class=\"reviewCard\"\u003e\n          \u003cdiv class=\"reviewQuote\"\u003e\"\u003c\/div\u003e\n          \u003cdiv class=\"reviewHeader\"\u003e\n            \u003cdiv class=\"reviewAvatar\"\u003e${firstLetter}\u003c\/div\u003e\n            \u003cdiv class=\"reviewAuthor\"\u003e\n              \u003cdiv class=\"reviewName\"\u003e${review.firstName} ${\n          review.lastName\n        }.\u003c\/div\u003e\n              \u003cdiv class=\"reviewRole\"\u003e${review.gender}\u003c\/div\u003e\n            \u003c\/div\u003e\n            \u003cdiv class=\"reviewMeta\"\u003e\n              \u003cdiv class=\"reviewStars\"\u003e${stars}\u003c\/div\u003e\n              \u003cdiv class=\"reviewDate\"\u003e${formattedDate}\u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"reviewContent\"\u003e\n            \u003ch3 class=\"reviewTitle\"\u003e${review.title}\u003c\/h3\u003e\n            \u003cp class=\"reviewBody\"\u003e${review.content}\u003c\/p\u003e\n            ${\n              review.verified ? '\u003cdiv class=\"reviewVerified\"\u003eVérifié\u003c\/div\u003e' : \"\"\n            }\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      `;\n      })\n      .join(\"\");\n\n    container.innerHTML = reviewsHTML;\n\n    \/\/ Navigation arrows are now handled by HTML\/CSS and initReviewsNavigation function\n\n    \/\/ Render navigation dots\n    renderReviewsDots();\n\n    \/\/ Initialize swipe navigation\n    initReviewsSwipe();\n  }\n\n  function renderReviewsDots() {\n    const dotsContainer = document.getElementById(\"reviewsDots\");\n    if (!dotsContainer) return;\n\n    \/\/ Calculer le nombre de dots : on exclut le premier et le dernier\n    \/\/ Sur desktop, on voit 3 cartes à la fois, donc reviewsData.length - 2 dots\n    const numDots = Math.max(1, reviewsData.length - 2);\n\n    const dotsHTML = Array.from({ length: numDots })\n      .map(\n        (_, index) =\u003e `\n      \u003cbutton class=\"reviewDot ${\n        index === 0 ? \"active\" : \"\"\n      }\" data-index=\"${index}\" aria-label=\"Avis ${index + 1}\"\u003e\u003c\/button\u003e\n    `\n      )\n      .join(\"\");\n\n    dotsContainer.innerHTML = dotsHTML;\n\n    \/\/ Add click listeners to dots\n    const dots = dotsContainer.querySelectorAll(\".reviewDot\");\n    dots.forEach((dot) =\u003e {\n      dot.addEventListener(\"click\", () =\u003e {\n        const index = parseInt(dot.dataset.index);\n        scrollToReviewByDot(index);\n      });\n    });\n  }\n\n  function scrollToReviewByDot(dotIndex) {\n    const container = document.getElementById(\"reviewsContainer\");\n    if (!container) return;\n\n    const cards = container.querySelectorAll(\".reviewCard\");\n    \/\/ Commencer au milieu du set dupliqué + 1 pour sauter la première carte\n    const targetIndex = reviewsData.length + dotIndex + 1;\n\n    if (cards[targetIndex]) {\n      container.scrollTo({\n        left: cards[targetIndex].offsetLeft - container.offsetLeft,\n        behavior: \"smooth\",\n      });\n    }\n  }\n\n  function scrollToReview(index) {\n    const container = document.getElementById(\"reviewsContainer\");\n    if (!container) return;\n\n    const cards = container.querySelectorAll(\".reviewCard\");\n    if (cards[index]) {\n      cards[index].scrollIntoView({\n        behavior: \"smooth\",\n        block: \"nearest\",\n        inline: \"center\",\n      });\n    }\n  }\n\n  function updateReviewsDots() {\n    const container = document.getElementById(\"reviewsContainer\");\n    const dotsContainer = document.getElementById(\"reviewsDots\");\n    if (!container || !dotsContainer) return;\n\n    const cards = Array.from(container.querySelectorAll(\".reviewCard\"));\n    const dots = Array.from(dotsContainer.querySelectorAll(\".reviewDot\"));\n\n    \/\/ Calculate which card is currently in view\n    const containerRect = container.getBoundingClientRect();\n    const containerCenter = containerRect.left + containerRect.width \/ 2;\n\n    let activeIndex = 0;\n    let minDistance = Infinity;\n\n    cards.forEach((card, index) =\u003e {\n      const cardRect = card.getBoundingClientRect();\n      const cardCenter = cardRect.left + cardRect.width \/ 2;\n      const distance = Math.abs(cardCenter - containerCenter);\n\n      if (distance \u003c minDistance) {\n        minDistance = distance;\n        activeIndex = index;\n      }\n    });\n\n    \/\/ Mapper l'index de la carte à l'index du dot\n    \/\/ On a 3x reviewsData.length cartes, donc on module par reviewsData.length\n    let dotIndex = (activeIndex % reviewsData.length) - 1; \/\/ -1 car on exclut la première\n\n    \/\/ Ajuster si nécessaire\n    if (dotIndex \u003c 0) dotIndex = 0;\n    if (dotIndex \u003e= dots.length) dotIndex = dots.length - 1;\n\n    \/\/ Update dots - simple : actif ou inactif\n    dots.forEach((dot, index) =\u003e {\n      if (index === dotIndex) {\n        dot.classList.add(\"active\");\n      } else {\n        dot.classList.remove(\"active\");\n      }\n    });\n  }\n\n  function initReviewsSwipe() {\n    const container = document.getElementById(\"reviewsContainer\");\n    if (!container) return;\n\n    const cards = container.querySelectorAll(\".reviewCard\");\n    if (cards.length === 0) return;\n\n    \/\/ Calculer la largeur d'une section (set original)\n    const sectionWidth = container.scrollWidth \/ 3;\n\n    \/\/ Positionner au centre du set dupliqué (milieu)\n    container.scrollLeft = sectionWidth;\n\n    \/\/ Gestion de l'infinite scroll\n    function handleInfiniteScroll() {\n      const scrollLeft = container.scrollLeft;\n      const maxScroll = container.scrollWidth - container.clientWidth;\n\n      \/\/ Si on arrive à la fin, revenir au milieu\n      if (scrollLeft \u003e= maxScroll - 10) {\n        container.scrollLeft =\n          sectionWidth + (scrollLeft - maxScroll + sectionWidth);\n      }\n      \/\/ Si on arrive au début, aller à la fin du milieu\n      else if (scrollLeft \u003c= 10) {\n        container.scrollLeft = sectionWidth + scrollLeft;\n      }\n    }\n\n    \/\/ Update dots on scroll avec infinite scroll - EN TEMPS RÉEL\n    let scrollTimeout;\n    let isUserScrolling = false;\n\n    container.addEventListener(\"scroll\", () =\u003e {\n      isUserScrolling = true;\n      \/\/ Update dots immédiatement pendant le scroll\n      updateReviewsDots();\n\n      clearTimeout(scrollTimeout);\n      scrollTimeout = setTimeout(() =\u003e {\n        handleInfiniteScroll();\n        isUserScrolling = false;\n      }, 150);\n    });\n\n    \/\/ Touch swipe support (mobile) - simple comme le drag PC\n    let isTouchDragging = false;\n\n    container.addEventListener(\"touchstart\", () =\u003e {\n      isTouchDragging = true;\n    });\n\n    container.addEventListener(\"touchend\", () =\u003e {\n      isTouchDragging = false;\n    });\n\n    \/\/ Mouse drag support (desktop) - simple, comme le swipe mobile\n    let isMouseDown = false;\n    let startX;\n    let scrollLeft;\n\n    container.addEventListener(\"mousedown\", (e) =\u003e {\n      isMouseDown = true;\n      startX = e.pageX - container.offsetLeft;\n      scrollLeft = container.scrollLeft;\n      container.style.cursor = \"grabbing\";\n    });\n\n    container.addEventListener(\"mouseleave\", () =\u003e {\n      isMouseDown = false;\n      container.style.cursor = \"grab\";\n    });\n\n    container.addEventListener(\"mouseup\", () =\u003e {\n      isMouseDown = false;\n      container.style.cursor = \"grab\";\n    });\n\n    container.addEventListener(\"mousemove\", (e) =\u003e {\n      if (!isMouseDown) return;\n      e.preventDefault();\n      const x = e.pageX - container.offsetLeft;\n      const walk = (x - startX) * 1.5;\n      container.scrollLeft = scrollLeft - walk;\n    });\n\n    \/\/ Initialize dots\n    updateReviewsDots();\n  }\n\n  \/\/ --- GESTION DES VIDÉOS ---\n  \/\/ Fonction pour mettre à jour l'icône play\/pause (accessible globalement)\n  function updatePlayButton(videoId, isPlaying) {\n    const button = document.querySelector(\n      `.videoPlayBtn[data-video-id=\"${videoId}\"]`\n    );\n    if (!button) return;\n\n    const playIcon = button.querySelector(\".playIcon\");\n    const pauseIcon = button.querySelector(\".pauseIcon\");\n\n    if (isPlaying) {\n      button.style.opacity = \"0\";\n      button.style.pointerEvents = \"none\";\n    } else {\n      button.style.opacity = \"1\";\n      button.style.pointerEvents = \"auto\";\n      playIcon.style.display = \"block\";\n      if (pauseIcon) pauseIcon.style.display = \"none\";\n    }\n  }\n\n  function initVideoControls() {\n    const videos = document.querySelectorAll(\".liziaVideo\");\n    const playButtons = document.querySelectorAll(\".videoPlayBtn\");\n    const muteButtons = document.querySelectorAll(\".videoMuteBtn\");\n    const videoContainers = document.querySelectorAll(\".videoContainer\");\n\n    \/\/ Fonction pour mettre en pause toutes les autres vidéos\n    function pauseOtherVideos(currentVideoId) {\n      videos.forEach((video) =\u003e {\n        const videoId = video.dataset.videoId;\n        if (videoId !== currentVideoId \u0026\u0026 !video.paused) {\n          video.pause();\n          updatePlayButton(videoId, false);\n          const playbackState = videoPlaybackStates[videoId];\n          if (playbackState) {\n            playbackState.userPaused = false;\n          }\n        }\n      });\n    }\n\n    \/\/ Fonction pour mettre à jour l'icône mute\/unmute\n    function updateMuteButton(videoId, isMuted) {\n      const button = document.querySelector(\n        `.videoMuteBtn[data-video-id=\"${videoId}\"]`\n      );\n      if (!button) return;\n\n      const unmuteIcon = button.querySelector(\".unmuteIcon\");\n      const muteIcon = button.querySelector(\".muteIcon\");\n\n      if (isMuted) {\n        unmuteIcon.style.display = \"none\";\n        muteIcon.style.display = \"block\";\n      } else {\n        unmuteIcon.style.display = \"block\";\n        muteIcon.style.display = \"none\";\n      }\n    }\n\n    \/\/ Fonction pour toggle play\/pause\n    function togglePlay(video) {\n      const videoId = video.dataset.videoId;\n      const playbackState =\n        videoPlaybackStates[videoId] ||\n        (videoPlaybackStates[videoId] = {\n          hasAutoPlayed: false,\n          userPaused: false,\n        });\n\n      if (video.paused) {\n        \/\/ Mettre en pause toutes les autres vidéos\n        pauseOtherVideos(videoId);\n\n        video\n          .play()\n          .then(() =\u003e {\n            playbackState.hasAutoPlayed = true;\n            playbackState.userPaused = false;\n            updatePlayButton(videoId, true);\n          })\n          .catch((err) =\u003e console.warn(\"Lecture vidéo impossible:\", err));\n      } else {\n        video.pause();\n        playbackState.userPaused = true;\n        updatePlayButton(videoId, false);\n      }\n    }\n\n    \/\/ Fonction pour toggle mute\/unmute\n    function toggleMute(video) {\n      const videoId = video.dataset.videoId;\n      video.muted = !video.muted;\n      updateMuteButton(videoId, video.muted);\n    }\n\n    \/\/ Initialiser toutes les vidéos\n    videos.forEach((video) =\u003e {\n      const videoId = video.dataset.videoId;\n      const playbackState =\n        videoPlaybackStates[videoId] ||\n        (videoPlaybackStates[videoId] = {\n          hasAutoPlayed: false,\n          userPaused: false,\n        });\n\n      \/\/ Initialiser l'état du bouton mute selon l'attribut HTML ou la propriété\n      updateMuteButton(videoId, video.muted);\n\n      \/\/ Event listener pour la fin de la vidéo\n      video.addEventListener(\"ended\", () =\u003e {\n        updatePlayButton(videoId, false);\n      });\n\n      \/\/ Mettre à jour l'icône et unmute automatique quand la vidéo commence à jouer\n      video.addEventListener(\"play\", () =\u003e {\n        updatePlayButton(videoId, true);\n        playbackState.hasAutoPlayed = true;\n        playbackState.userPaused = false;\n        if (video.muted) {\n          video.muted = false;\n          updateMuteButton(videoId, false);\n        }\n      });\n\n      \/\/ Mettre à jour l'icône quand la vidéo est en pause\n      video.addEventListener(\"pause\", () =\u003e {\n        updatePlayButton(videoId, false);\n      });\n    });\n\n    \/\/ Event listeners pour les boutons play\/pause\n    playButtons.forEach((button) =\u003e {\n      button.addEventListener(\"click\", (e) =\u003e {\n        e.stopPropagation();\n        const videoId = button.dataset.videoId;\n        const video = document.querySelector(\n          `.liziaVideo[data-video-id=\"${videoId}\"]`\n        );\n        if (video) {\n          togglePlay(video);\n        }\n      });\n    });\n\n    \/\/ Event listeners pour les boutons mute\/unmute\n    muteButtons.forEach((button) =\u003e {\n      button.addEventListener(\"click\", (e) =\u003e {\n        e.stopPropagation();\n        const videoId = button.dataset.videoId;\n        const video = document.querySelector(\n          `.liziaVideo[data-video-id=\"${videoId}\"]`\n        );\n        if (video) {\n          toggleMute(video);\n        }\n      });\n    });\n\n    \/\/ Event listeners pour cliquer sur le container vidéo\n    videoContainers.forEach((container) =\u003e {\n      container.addEventListener(\"click\", (e) =\u003e {\n        \/\/ Ne pas déclencher si on clique sur les boutons\n        if (\n          e.target.closest(\".videoPlayBtn\") ||\n          e.target.closest(\".videoMuteBtn\")\n        ) {\n          return;\n        }\n\n        const video = container.querySelector(\".liziaVideo\");\n        if (video) {\n          togglePlay(video);\n        }\n      });\n    });\n  }\n\n  \/\/ Initialiser les contrôles vidéo\n  initVideoControls();\n\n  \/\/ Forcer le chargement du premier frame de la vidéo pour afficher le poster\/thumbnail\n  function loadVideoPosters() {\n    const videos = document.querySelectorAll(\".liziaVideo\");\n    videos.forEach((video) =\u003e {\n      \/\/ Supprimer l'attribut poster pour utiliser la première frame\n      video.removeAttribute(\"poster\");\n\n      \/\/ Forcer le chargement des métadonnées\n      video.load();\n\n      \/\/ Charger le premier frame pour l'afficher comme poster\n      const loadFirstFrame = () =\u003e {\n        if (video.readyState \u003e= 2) {\n          \/\/ HAVE_CURRENT_DATA\n          \/\/ Charger le premier frame en avançant légèrement puis en revenant\n          video.currentTime = 0.1;\n          video.addEventListener(\n            \"seeked\",\n            () =\u003e {\n              video.currentTime = 0;\n              video.pause();\n            },\n            { once: true }\n          );\n        } else {\n          \/\/ Attendre que les métadonnées soient chargées\n          video.addEventListener(\"loadeddata\", loadFirstFrame, { once: true });\n        }\n      };\n\n      \/\/ Charger la première frame sur tous les appareils\n      if (video.readyState \u003e= 1) {\n        \/\/ HAVE_METADATA\n        loadFirstFrame();\n      } else {\n        video.addEventListener(\"loadedmetadata\", loadFirstFrame, {\n          once: true,\n        });\n      }\n    });\n  }\n\n  \/\/ Charger les posters vidéo après un court délai pour laisser le DOM se charger\n  setTimeout(() =\u003e {\n    loadVideoPosters();\n  }, 300);\n\n  \/\/ --- ANIMATIONS AU SCROLL ---\n  function initScrollAnimations() {\n    const isMobile = window.innerWidth \u003c= 767;\n\n    \/\/ Sur mobile, révéler immédiatement les vidéos pour éviter le fond blanc\n    if (isMobile) {\n      const videoCards = document.querySelectorAll(\n        \".videoCard[data-scroll-reveal]\"\n      );\n      videoCards.forEach((card) =\u003e {\n        card.classList.add(\"revealed\");\n      });\n    }\n\n    const observerOptions = {\n      root: null,\n      rootMargin: isMobile ? \"0px\" : \"0px 0px -10% 0px\", \/\/ Sur mobile, déclencher immédiatement\n      threshold: isMobile ? 0.01 : 0.15, \/\/ Sur mobile, déclencher dès qu'un pixel est visible\n    };\n\n    const observer = new IntersectionObserver((entries) =\u003e {\n      entries.forEach((entry) =\u003e {\n        if (entry.isIntersecting) {\n          \/\/ Ajouter la classe revealed pour déclencher l'animation\n          entry.target.classList.add(\"revealed\");\n\n          \/\/ Autoplay supprimé selon demande utilisateur\n          \/\/ La vidéo ne se lance que au clic\n        }\n      });\n    }, observerOptions);\n\n    \/\/ Observer tous les éléments avec l'attribut data-scroll-reveal\n    const elementsToReveal = document.querySelectorAll(\"[data-scroll-reveal]\");\n    elementsToReveal.forEach((element) =\u003e {\n      \/\/ Ne pas observer les vidéos sur mobile car elles sont déjà révélées\n      if (!isMobile || !element.classList.contains(\"videoCard\")) {\n        observer.observe(element);\n      }\n    });\n  }\n\n  \/\/ Initialiser les animations au scroll\n  initScrollAnimations();\n\n  \/\/ --- GESTION DU TOGGLE AVANT\/APRÈS ---\n  function initBeforeAfterToggle() {\n    const toggleBtn = document.getElementById(\"lightToggleBtn\");\n    const imageContainer = document.querySelector(\".beforeAfterImageContainer\");\n    const gridContainer = document.querySelector(\".beforeAfterGrid\");\n\n    if (!toggleBtn || !imageContainer) return;\n\n    \/\/ Précharger les deux images pour un basculement instantané\n    const imageOff =\n      \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\";\n    const imageOn =\n      \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\";\n\n    \/\/ Précharger les images\n    const preloadOff = new Image();\n    preloadOff.src = imageOff;\n    const preloadOn = new Image();\n    preloadOn.src = imageOn;\n\n    \/\/ État initial: light ON (activé par défaut)\n    let isLightOn = true;\n\n    function updateImage() {\n      if (isLightOn) {\n        toggleBtn.classList.add(\"active\");\n        if (imageContainer) {\n          imageContainer.classList.add(\"light-on\");\n        }\n        if (gridContainer) {\n          gridContainer.classList.add(\"light-on\");\n        }\n      } else {\n        toggleBtn.classList.remove(\"active\");\n        if (imageContainer) {\n          imageContainer.classList.remove(\"light-on\");\n        }\n        if (gridContainer) {\n          gridContainer.classList.remove(\"light-on\");\n        }\n      }\n    }\n\n    toggleBtn.addEventListener(\"click\", () =\u003e {\n      isLightOn = !isLightOn;\n      updateImage();\n    });\n\n    \/\/ Initialiser l'état\n    updateImage();\n  }\n\n  \/\/ Initialiser le toggle\n  initBeforeAfterToggle();\n\n  \/\/ --- LOGIQUE ACCORDÉON \"COMMENT ÇA MARCHE\" ---\n  const accordionItems = document.querySelectorAll(\".howItWorksItem\");\n\n  accordionItems.forEach((item) =\u003e {\n    item.addEventListener(\"click\", () =\u003e {\n      \/\/ Si l'élément est déjà actif, on ne fait rien\n      if (item.classList.contains(\"active\")) return;\n\n      \/\/ Fermer tous les autres éléments\n      accordionItems.forEach((otherItem) =\u003e {\n        otherItem.classList.remove(\"active\");\n      });\n\n      \/\/ Ouvrir l'élément cliqué\n      item.classList.add(\"active\");\n    });\n  });\n\n  \/\/ --- BANDEAU MÉDIAS INFINI ---\n  const initInfiniteMediaBanner = () =\u003e {\n    const banner = document.querySelector(\".mediaBanner\");\n    if (!banner) return;\n    const wrapper = banner.querySelector(\".mediaBannerWrapper\");\n    if (!wrapper) return;\n    const originalSlide = wrapper.querySelector(\".mediaBannerSlide\");\n    if (!originalSlide) return;\n\n    \/\/ Variables\n    let slideWidthValue = 0;\n    let isDragging = false;\n    let startX = 0;\n    let currentTranslateX = 0;\n    let hasMoved = false;\n\n    \/\/ Set touch-action to allow vertical scroll natively\n    banner.style.touchAction = \"pan-y\";\n\n    \/\/ Setup dimensions and clones\n    const calculateAndSetup = () =\u003e {\n      \/\/ Get width of the single slide containing all logos\n      let slideWidth = originalSlide.getBoundingClientRect().width;\n      if (!slideWidth) slideWidth = originalSlide.offsetWidth;\n      if (!slideWidth) slideWidth = originalSlide.scrollWidth;\n\n      if (!slideWidth) {\n        requestAnimationFrame(calculateAndSetup);\n        return;\n      }\n\n      const bannerWidth = banner.offsetWidth || window.innerWidth;\n      \/\/ We need enough clones to cover the screen + buffer\n      const slidesNeeded = Math.ceil((bannerWidth * 2) \/ slideWidth) + 1;\n      const currentSlides =\n        wrapper.querySelectorAll(\".mediaBannerSlide\").length;\n      const clonesNeeded = Math.max(0, slidesNeeded - currentSlides);\n\n      for (let i = 0; i \u003c clonesNeeded; i++) {\n        const clonedSlide = originalSlide.cloneNode(true);\n        wrapper.appendChild(clonedSlide);\n      }\n\n      slideWidthValue = slideWidth;\n\n      \/\/ Inject CSS Animation\n      let styleElement = document.getElementById(\"mediaBannerAnimation\");\n      if (!styleElement) {\n        styleElement = document.createElement(\"style\");\n        styleElement.id = \"mediaBannerAnimation\";\n        document.head.appendChild(styleElement);\n      }\n\n      const isMobile = window.innerWidth \u003c= 767;\n      const animationDuration = isMobile ? \"20s\" : \"40s\";\n\n      \/\/ Define animation to move exactly one slide width\n      styleElement.textContent = `\n        @keyframes slideMediaInfinite {\n          0% { transform: translateX(0); }\n          100% { transform: translateX(-${slideWidthValue}px); }\n        }\n        .mediaBannerWrapper {\n          animation: slideMediaInfinite ${animationDuration} linear infinite;\n          display: flex; \/* Ensure slides are side by side *\/\n          width: max-content; \/* Ensure wrapper takes full width of content *\/\n        }\n        .mediaBannerWrapper.dragging {\n          animation: none !important; \/* Stop animation during drag *\/\n        }\n      `;\n    };\n\n    \/\/ --- Event Handlers ---\n\n    const handleStart = (clientX) =\u003e {\n      isDragging = true;\n      hasMoved = false;\n      startX = clientX;\n\n      \/\/ Get current position to resume\/drag from there\n      const computedStyle = window.getComputedStyle(wrapper);\n      const matrix = computedStyle.transform;\n      if (matrix \u0026\u0026 matrix !== \"none\") {\n        const values = matrix.match(\/matrix.*\\((.+)\\)\/);\n        if (values) {\n          const matrixValues = values[1].split(\", \");\n          currentTranslateX = parseFloat(matrixValues[4]) || 0;\n        }\n      } else {\n        currentTranslateX = 0;\n      }\n\n      wrapper.classList.add(\"dragging\"); \/\/ Stops CSS animation\n      wrapper.style.transform = `translateX(${currentTranslateX}px)`; \/\/ Freeze at current pos\n\n      wrapper.style.cursor = \"grabbing\";\n      wrapper.style.userSelect = \"none\";\n    };\n\n    const handleMove = (clientX, e) =\u003e {\n      if (!isDragging) return;\n\n      const dx = clientX - startX;\n\n      \/\/ Threshold for click vs drag\n      if (Math.abs(dx) \u003e 5) {\n        hasMoved = true;\n        const links = wrapper.querySelectorAll(\"a\");\n        links.forEach((link) =\u003e (link.style.pointerEvents = \"none\"));\n      }\n\n      \/\/ Move\n      let newPos = currentTranslateX + dx;\n\n      \/\/ Normalize (Infinite Loop Logic for Drag)\n      if (slideWidthValue \u003e 0) {\n        while (newPos \u003e 0) newPos -= slideWidthValue;\n        while (newPos \u003c= -slideWidthValue) newPos += slideWidthValue;\n      }\n\n      wrapper.style.transform = `translateX(${newPos}px)`;\n    };\n\n    const handleEnd = () =\u003e {\n      if (!isDragging) return;\n\n      isDragging = false;\n      wrapper.style.cursor = \"\";\n      wrapper.style.userSelect = \"\";\n\n      \/\/ Re-enable links\n      setTimeout(() =\u003e {\n        const links = wrapper.querySelectorAll(\"a\");\n        links.forEach((link) =\u003e (link.style.pointerEvents = \"\"));\n      }, 50);\n\n      \/\/ Calculate progress and set negative delay to resume\n      \/\/ This tricks the CSS animation to start from the current position\n      if (slideWidthValue \u003e 0) {\n        \/\/ Get the current transform value from the style (set during drag)\n        \/\/ We need to parse it back because currentTranslateX might be stale if we didn't update it in handleMove?\n        \/\/ No, handleMove updates wrapper.style.transform directly but doesn't update currentTranslateX global?\n        \/\/ Wait, handleMove DOES NOT update currentTranslateX global in the last version I saw?\n        \/\/ Let's check handleMove again.\n\n        \/\/ Actually, handleMove uses `let newPos` and sets style.\n        \/\/ It DOES NOT update `currentTranslateX`.\n        \/\/ So `currentTranslateX` is still the start position!\n        \/\/ I need to read the current transform from the wrapper style.\n\n        const currentTransform = wrapper.style.transform;\n        const match = currentTransform.match(\/translateX\\(([^)]+)px\\)\/);\n        if (match) {\n          const currentPos = parseFloat(match[1]);\n          const progress = currentPos \/ slideWidthValue;\n          const delay = progress * 40; \/\/ 40s duration\n          wrapper.style.animationDelay = `${delay}s`;\n        }\n      }\n\n      \/\/ Reset to CSS animation\n      wrapper.classList.remove(\"dragging\");\n      wrapper.style.transform = \"\";\n    };\n\n    \/\/ Listeners\n    banner.addEventListener(\"mousedown\", (e) =\u003e {\n      if (e.target.closest(\"a\")) return; \/\/ Let links work if not dragging\n      e.preventDefault();\n      handleStart(e.clientX);\n    });\n\n    document.addEventListener(\"mousemove\", (e) =\u003e {\n      handleMove(e.clientX, e);\n    });\n\n    document.addEventListener(\"mouseup\", handleEnd);\n\n    banner.addEventListener(\n      \"touchstart\",\n      (e) =\u003e {\n        handleStart(e.touches[0].clientX);\n      },\n      { passive: false }\n    );\n\n    document.addEventListener(\n      \"touchmove\",\n      (e) =\u003e {\n        handleMove(e.touches[0].clientX, e);\n      },\n      { passive: false }\n    );\n\n    document.addEventListener(\"touchend\", handleEnd);\n\n    \/\/ Init\n    requestAnimationFrame(calculateAndSetup);\n\n    window.addEventListener(\"resize\", () =\u003e {\n      requestAnimationFrame(calculateAndSetup);\n    });\n  };\n\n  \/\/ Initialiser le bandeau médias\n  initInfiniteMediaBanner();\n\n  \/\/ --- LOGIQUE SECTION ENGAGEMENTS (VALUES) ---\n  const valueItems = document.querySelectorAll(\".valueItem\");\n  const detailTitle = document.getElementById(\"detailTitle\");\n  const detailDesc = document.getElementById(\"detailDesc\");\n  const valuesSubtitle = document.getElementById(\"valuesSubtitle\");\n\n  const updateMobileSubtitle = (item) =\u003e {\n    if (valuesSubtitle \u0026\u0026 window.innerWidth \u003c= 900) {\n      const desc = item.getAttribute(\"data-desc\");\n      valuesSubtitle.textContent = desc;\n    }\n  };\n\n  valueItems.forEach((item) =\u003e {\n    item.addEventListener(\"click\", () =\u003e {\n      \/\/ Unify logic: Always set active class (works for both desktop and mobile now)\n      valueItems.forEach((i) =\u003e i.classList.remove(\"active\"));\n      item.classList.add(\"active\");\n\n      \/\/ --- Desktop Logic ---\n      if (window.innerWidth \u003e 900 \u0026\u0026 detailTitle \u0026\u0026 detailDesc) {\n        const title = item.getAttribute(\"data-title\");\n        const desc = item.getAttribute(\"data-desc\");\n        detailTitle.textContent = title;\n        detailDesc.textContent = desc;\n      }\n\n      \/\/ --- Mobile Logic ---\n      \/\/ Update the subtitle with the full description\n      updateMobileSubtitle(item);\n    });\n  });\n\n  \/\/ Default State: Active first item\n  if (valueItems.length \u003e 0) {\n    \/\/ Ensure first item is active on load for both\n    valueItems.forEach((i) =\u003e i.classList.remove(\"active\"));\n    valueItems[0].classList.add(\"active\");\n\n    \/\/ On mobile, also update the subtitle immediately\n    if (window.innerWidth \u003c= 900) {\n      updateMobileSubtitle(valueItems[0]);\n    }\n  }\n\n  \/\/ --- NAVIGATION AVIS (REVIEWS) ---\n  function initReviewsNavigation() {\n    const container = document.getElementById(\"reviewsContainer\");\n    const prevBtn = document.querySelector(\".reviewsNavBtn.prev\");\n    const nextBtn = document.querySelector(\".reviewsNavBtn.next\");\n\n    if (!container || !prevBtn || !nextBtn) return;\n\n    const scrollAmount = 360 + 32; \/\/ Card width + gap\n\n    prevBtn.addEventListener(\"click\", () =\u003e {\n      container.scrollBy({\n        left: -scrollAmount,\n        behavior: \"smooth\",\n      });\n    });\n\n    nextBtn.addEventListener(\"click\", () =\u003e {\n      container.scrollBy({\n        left: scrollAmount,\n        behavior: \"smooth\",\n      });\n    });\n  }\n\n  initReviewsNavigation();\n\n  \/\/ --- SCROLL TO REVIEWS ---\n  const reviewsSummary = document.querySelector(\".reviewsSummary\");\n  const reviewsSection = document.getElementById(\"reviewsSection\");\n\n  if (reviewsSummary \u0026\u0026 reviewsSection) {\n    reviewsSummary.addEventListener(\"click\", () =\u003e {\n      reviewsSection.scrollIntoView({ behavior: \"smooth\" });\n    });\n  }\n});\n\n\u003c\/script\u003e\n","brand":"Lizia","offers":[{"title":"Default Title","offer_id":62707534889309,"sku":"COU-LEC-V1","price":34.95,"currency_code":"EUR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/coussin_pack.png?v=1764690948","url":"https:\/\/lizia.fr\/it\/products\/coussin-de-lecture","provider":"Lizia","version":"1.0","type":"link"}