{"title":"Accessories","description":"\u003c!----\u003e","products":[{"product_id":"cable-de-chargement","title":"Charging cable","description":"\u003cmeta charset=\"utf-8\"\u003e\n\u003cp\u003e\u003cstrong\u003e30 cm Micro USB cable for Lizia\u003c\/strong\u003e\u003c\/p\u003e\n\u003cp\u003eRecharge your Lizia with ease and efficiency thanks to our 30 cm Micro USB Cable specially designed for your favorite reading accessory. You never have to worry about the battery life of your Lizia again, this compact cable will allow you to always keep it ready to accompany you on your literary adventures.\u003c\/p\u003e\n\u003cp\u003e\u003cstrong\u003eMain Features :\u003c\/strong\u003e\u003c\/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eUniversal Compatibility\u003c\/strong\u003e : This micro USB cable is compatible with most devices, including your Lizia, as well as many smartphones, tablets, e-readers, and other electronic devices.\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eOptimal Length\u003c\/strong\u003e : With its 30 cm length, this cable is perfectly sized to be used with Lizia without unnecessary bulk. It's also easy to store in your bag or pocket.\u003c\/p\u003e\n\u003c\/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eQuality Design\u003c\/strong\u003e : Made with quality materials, this cable is designed to withstand daily use while ensuring fast and reliable charging.\u003c\/p\u003e\n\u003c\/li\u003e\n\u003c\/ul\u003e\n\u003cp\u003eNever let your Lizia run out of battery again. With our 30cm Micro USB Cable, you can enjoy your passion for reading with peace of mind, knowing that your favorite reading accessory will always be ready to serve you.\u003c\/p\u003e\n\u003cp\u003eOrder yours today and give your Lizia the accessory she needs to stay by your side, wherever your reading takes you.\u003c\/p\u003e","brand":"Ma boutique","offers":[{"title":"Default Title","offer_id":47823704719709,"sku":"CABL-BLA-V1","price":3.95,"currency_code":"EUR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/38.jpg?v=1718186322"},{"product_id":"lizia","title":"Lizia - 3-in-1 reading lamp ","description":"\u003c!-- PRODUCT HERO SECTION --\u003e\n\u003csection id=\"productHero\" class=\"containerLizia\"\u003e\n  \u003c!-- Badge satisfied readers (mobile) --\u003e\n  \u003cdiv class=\"badgeReadersMobile mobileOnly\"\u003e\n    \u003cspan class=\"badgeReaders\"\u003e +100,000 satisfied readers\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\n          src=\"..\/public\/placeholder.svg\"\n          alt=\"Lizia lamp\"\n          id=\"mainProductImage\"\n          class=\"mainImage\"\n        \/\u003e\n        \u003cbutton\n          id=\"prevImageBtn\"\n          class=\"galleryNavBtn prev\"\n          aria-label=\"Previous image\"\n        \u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n        \u003cbutton\n          id=\"nextImageBtn\"\n          class=\"galleryNavBtn next\"\n          aria-label=\"Next image\"\n        \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\"\n          \u003e+100,000 satisfied readers\u003c\/span\n        \u003e\n        \u003ch1\u003e3-IN-1 READING LAMP\u003c\/h1\u003e\n        \u003cdiv class=\"reviewsSummary\"\u003e\n          \u003cdiv class=\"stars\"\u003e\n            \u003c!-- Stars will be inserted here by CSS or JS --\u003e\n          \u003c\/div\u003e\n          \u003cspan class=\"rating\"\u003e4.6\/5\u003c\/span\u003e\n          \u003cspan class=\"reviewCount\"\u003e(536 reviews)\u003c\/span\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"productSubtitle\"\u003e\n          \u003c!-- Reading lamp • Bookmark • One-handed reading --\u003e\n        \u003c\/p\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 1. Sélecteur de pack (label \"Pack\" masqué) --\u003e\n      \u003cdiv class=\"configStep configStep--pack\"\u003e\n        \u003clabel class=\"stepLabel stepLabel--hidden\" aria-hidden=\"true\"\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. Sélecteur de pochette --\u003e\n      \u003cdiv class=\"configStep\"\u003e\n        \u003cdiv id=\"pouchSelector\" class=\"pouchSelector\"\u003e\u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 3. Quantity and Discount --\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              Quantity\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\"\u003eDiscount on your order\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!-- 4. Colors \u0026 Personalization --\u003e\n      \u003cdiv class=\"configStep\"\u003e\n        \u003clabel id=\"colorsPersoLabel\" class=\"stepLabel\"\u003e\n          \u003cspan class=\"stepLabelNumber\"\u003e3\u003c\/span\u003e\n          Personalization\n        \u003c\/label\u003e\n        \u003cdiv id=\"liziaItemsContainer\" class=\"liziaItemsContainer\"\u003e\u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Mobile add to cart button (above price) --\u003e\n      \u003cdiv class=\"mobileOnly\"\u003e\n        \u003cbutton id=\"addToCartBtnMobile\" class=\"addToCartBtnMobile\"\u003e\n          ADD TO CART\n        \u003c\/button\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 4. Prix et Récapitulatif --\u003e\n      \u003cdiv id=\"priceDisplay\" class=\"priceDisplayContainer\"\u003e\u003c\/div\u003e\n\n      \u003c!-- M6 Countdown + CTA (desktop: centered, above cart button) --\u003e\n      \u003cdiv class=\"m6CountdownCtaWrapper desktopOnly\"\u003e\n        \u003cdiv class=\"m6CountdownBar desktopOnly\" aria-label=\"Limited offer - Countdown\"\u003e\n          \u003cdiv class=\"m6CountdownBarLeft\"\u003e\n            \u003cdiv class=\"m6CountdownBarLeftInner\"\u003e\n              \u003cdiv class=\"m6CountdownRow1\"\u003e\n                \u003cimg src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\" alt=\"M6\" class=\"m6CountdownLogo\" \/\u003e\n                \u003cspan class=\"m6CountdownTitle\"\u003eLimited offer\u003c\/span\u003e\n              \u003c\/div\u003e\n              \u003cspan class=\"m6CountdownSub\"\u003eLow stock.\u003c\/span\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"m6CountdownBarRight\"\u003e\n            \u003cdiv class=\"m6CountdownCell\"\u003e\n              \u003cspan class=\"countdown-days\"\u003e09\u003c\/span\u003e\n              \u003cspan class=\"countdown-label\"\u003eDays\u003c\/span\u003e\n            \u003c\/div\u003e\n            \u003cdiv class=\"m6CountdownCell\"\u003e\n              \u003cspan class=\"countdown-hours\"\u003e05\u003c\/span\u003e\n              \u003cspan class=\"countdown-label\"\u003eHours\u003c\/span\u003e\n            \u003c\/div\u003e\n            \u003cdiv class=\"m6CountdownCell\"\u003e\n              \u003cspan class=\"countdown-minutes\"\u003e26\u003c\/span\u003e\n              \u003cspan class=\"countdown-label\"\u003eMinutes\u003c\/span\u003e\n            \u003c\/div\u003e\n            \u003cdiv class=\"m6CountdownCell\"\u003e\n              \u003cspan class=\"countdown-seconds\"\u003e51\u003c\/span\u003e\n              \u003cspan class=\"countdown-label\"\u003eSeconds\u003c\/span\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n        \u003c!-- 5. Desktop add to cart button --\u003e\n        \u003cbutton id=\"addToCartBtn\" class=\"addToCartBtn desktopOnly\"\u003e\n          ADD TO CART\n        \u003c\/button\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Delivery info with animated green dot --\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\"\u003eDelivery in 3 business days\u003c\/span\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Guarantee badges --\u003e\n      \u003cdiv class=\"guaranteeBadges\"\u003e\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_fr.webp?v=1762266031\"\n              alt=\"Made In France\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eFRENCH\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eMade in France\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_garanti.webp?v=1762266032\"\n              alt=\"2 year warranty\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e2 YEARS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eWarranty\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_rembourser.webp?v=1762267150\"\n              alt=\"15 days satisfaction guaranteed or money back\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e15 DAYS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eSatisfaction guaranteed\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\" style=\"display: none;\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_livraison.webp?v=1762266031\"\n              alt=\"Delivered in 1 to 3 days\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eDELIVERED\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003e1-5 days\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cdiv\n      id=\"configEndSentinel\"\n      class=\"configEndSentinel\"\n      style=\"height: 1px; width: 100%\"\n    \u003e\u003c\/div\u003e\n    \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 HOW LIZIA WORKS --\u003e\n\u003csection id=\"howItWorks\" class=\"howItWorksSection\"\u003e\n  \u003cdiv class=\"containerLizia\"\u003e\n    \u003ch2 class=\"howItWorksTitle\" data-scroll-reveal\u003e\n      How does Lizia work?\n    \u003c\/h2\u003e\n    \u003cdiv class=\"howItWorksGrid\"\u003e\n      \u003c!-- Left column: Accordion --\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\"\u003eRead with one hand\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Lizia slides around your thumb and keeps the book perfectly open,\n              wherever you are.\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\"\u003eIlluminates\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Enjoy a soft and precise light that doesn't glare and doesn't\n              disturb those around you.\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\"\u003eBookmark\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Never lose your page again thanks to its integrated and practical\n              bookmark function.\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\n            class=\"liziaVideo\"\n            data-video-id=\"1\"\n            playsinline\n            muted\n            preload=\"metadata\"\n          \u003e\n            \u003csource\n              src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/6270dc5936c44a3a97d40e48209e8199.mp4\"\n              type=\"video\/mp4\"\n            \/\u003e\n          \u003c\/video\u003e\n          \u003cdiv class=\"videoControls\"\u003e\n            \u003cbutton\n              class=\"videoPlayBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Play\/Pause\"\n            \u003e\n              \u003csvg\n                class=\"playIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath d=\"M8 5v14l11-7z\" \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"pauseIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" \/\u003e\n              \u003c\/svg\u003e\n            \u003c\/button\u003e\n            \u003cbutton\n              class=\"videoMuteBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Mute\/Unmute\"\n            \u003e\n              \u003csvg\n                class=\"unmuteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"muteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\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!-- BANDEAU MÉDIAS INFINI --\u003e\n\u003csection class=\"mediaBanner\"\u003e\n  \u003cdiv class=\"mediaBannerWrapper\"\u003e\n    \u003cdiv class=\"mediaBannerSlide\"\u003e\n      \u003ca\n        href=\"https:\/\/www.europe1.fr\/emissions\/demain-au-bureau\/lizia-laccessoire-de-lecture-3-en-1-4163529\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_europe1.webp?v=1763465558\"\n          alt=\"Europe1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_quotidien.webp?v=1763465558\"\n          alt=\"Quotidien\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo mediaLogo--bfm\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_bfm_business.webp?v=1763465558\"\n          alt=\"Bfm-tv-business\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.ouest-france.fr\/bretagne\/roudouallec-56110\/roudouallec-ils-creent-un-accessoire-de-lecture-innovant-16121898-f0a2-11ec-9262-0032434e6459\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_ouest_france.webp?v=1763465558\"\n          alt=\"Ouest-France\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo mediaLogo--tf1\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tf1.webp?v=1763465558\"\n          alt=\"Tf1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.letelegramme.fr\/morbihan\/roudouallec-56110\/deux-jeunes-bretons-donnent-un-coup-de-pouce-aux-lecteurs-avec-lizia-281522.php\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_telegramme.webp?v=1763465558\"\n          alt=\"Le-telegramme\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.m6.fr\/\"\n        target=\"_blank\"\n        class=\"mediaLogo mediaLogo--m6\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\"\n          alt=\"M6\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/entrepreneurs.lesechos.fr\/developpement-entreprise\/strategie\/de-linvention-futee-au-succes-commercial-lizia-a-la-conquete-des-fanas-de-lecture-2094778\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_les_echos.webp?v=1763465557\"\n          alt=\"Les-echos\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tmc.webp?v=1763465559\"\n          alt=\"TMC\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.nrj.fr\/\"\n        target=\"_blank\"\n        class=\"mediaLogo mediaLogo--nrj\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_nrj.webp?v=1763465558\"\n          alt=\"NRJ\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.7jours.fr\/actualites\/lizia-rennes-jeune-pousse-eclaire-lecture\/\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_7_jours.webp?v=1763465558\"\n          alt=\"7-jours\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.lejournaldesentreprises.com\/breve\/lizia-lance-son-accessoire-de-lecture-en-belgique-et-en-suisse-2129508\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_le_journal_des_entreprises.webp?v=1763465558\"\n          alt=\"le-journal-des-entreprises\"\n        \/\u003e\n      \u003c\/a\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\n\u003c!-- BEFORE\/AFTER SECTION --\u003e\n\u003csection id=\"beforeAfterSection\" class=\"beforeAfterSection\"\u003e\n  \u003cdiv class=\"containerLizia\"\u003e\n    \u003cdiv class=\"beforeAfterGrid\"\u003e\n      \u003c!-- Left column: Text (Desktop) \/ Top (Mobile) --\u003e\n      \u003cdiv class=\"beforeAfterContent\"\u003e\n        \u003ch2 class=\"beforeAfterTitle\" data-scroll-reveal\u003e\n          Reading has never been so comfortable\n        \u003c\/h2\u003e\n        \u003ch3 class=\"beforeAfterSubtitle\" data-scroll-reveal\u003e\n          Without fatigue, with one hand\n        \u003c\/h3\u003e\n        \u003cp class=\"beforeAfterDescription\" data-scroll-reveal\u003e\n          Fits all thumbs, gain flexibility wherever you are. Without effort,\n          fully enjoy your reading, even in the dark without disturbing anyone.\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\n              id=\"lightToggleBtn\"\n              class=\"lightToggleBtn\"\n              aria-label=\"Toggle light\"\n            \u003e\n              \u003cspan class=\"lightToggleOn\"\u003e\n                \u003csvg\n                  xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"white\"\n                  width=\"16\"\n                  height=\"16\"\n                \u003e\n                  \u003cpath\n                    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\"\n                  \/\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\n            id=\"beforeAfterImageOff\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\"\n            alt=\"Lizia light off\"\n            class=\"beforeAfterImage beforeAfterImageOff\"\n          \/\u003e\n          \u003cimg\n            id=\"beforeAfterImageOn\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\"\n            alt=\"Lizia light on\"\n            class=\"beforeAfterImage beforeAfterImageOn\"\n          \/\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\u003c\/section\u003e\n\n\n\n\u003c!-- LIZIA COMMITMENTS SECTION --\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\"\u003eLIZIA COMMITMENTS\u003c\/h2\u003e\n      \u003cp id=\"valuesSubtitle\" class=\"valuesSubtitle\"\u003e\n        Imagined in Rennes, Lizia is a French innovation designed to offer\n        lasting reading comfort, with local partners and responsible materials.\n      \u003c\/p\u003e\n    \u003c\/div\u003e\n\n    \u003cdiv class=\"valuesGrid\"\u003e\n      \u003c!-- List of commitments --\u003e\n      \u003cdiv class=\"valuesList\"\u003e\n      \u003c!-- Item 1 --\u003e\n      \u003cdiv\n        class=\"valueItem active\"\n        data-index=\"0\"\n        data-title=\"Made in France\"\n        data-desc=\"Our nylon parts are produced in Western France and then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain and local industrial know-how, from design to shipping from Rennes.\"\n      \u003e\n        \u003cdiv class=\"valueIcon\"\u003e\n          \u003c!-- France Map Icon --\u003e\n          \u003cimg\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_france.svg?v=1763826898\"\n            alt=\"France\"\n            class=\"valueIconImg\"\n          \/\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"valueContent\"\u003e\n          \u003ch3 class=\"valueItemTitle\"\u003eMade in France\u003c\/h3\u003e\n          \u003cp class=\"valueItemShortDesc\"\u003e\n            Production and assembly in the West\n          \u003c\/p\u003e\n          \u003c!-- Mobile Description (Hidden by default) --\u003e\n          \u003cp class=\"valueItemFullDescMobile\"\u003e\n            Our nylon parts are produced in Western France and then assembled in\n            Brittany. By choosing Lizia, you support 100% French manufacturing, a\n            short supply chain and local industrial know-how, from design to\n            shipping from Rennes.\n          \u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Item 2 --\u003e\n      \u003cdiv\n        class=\"valueItem\"\n        data-index=\"1\"\n        data-title=\"Assembled in ESAT\"\n        data-desc=\"Lizia is socially committed by entrusting the assembly of its products to partner ESATs (Work Assistance Establishments and Services) in Brittany, promoting the professional integration of people with disabilities.\"\n      \u003e\n        \u003cdiv class=\"valueIcon\"\u003e\n          \u003c!-- Wheelchair\/Accessibility Icon --\u003e\n          \u003cimg\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_esat.svg?v=1763826898\"\n            alt=\"ESAT\"\n            class=\"valueIconImg\"\n          \/\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"valueContent\"\u003e\n          \u003ch3 class=\"valueItemTitle\"\u003eAssembled in ESAT\u003c\/h3\u003e\n          \u003cp class=\"valueItemShortDesc\"\u003eLocal partners\u003c\/p\u003e\n          \u003cp class=\"valueItemFullDescMobile\"\u003e\n            Lizia is socially committed by entrusting the assembly of its\n            products to partner ESATs (Work Assistance Establishments and\n            Services) in Brittany, promoting the professional integration of\n            people with disabilities.\n          \u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Item 3 --\u003e\n      \u003cdiv\n        class=\"valueItem\"\n        data-index=\"2\"\n        data-title=\"Recyclable materials\"\n        data-desc=\"We use technical PA12 for its robustness and durability, as well as 100% recyclable cardboard packaging. Our eco-design approach aims to minimize our environmental impact.\"\n      \u003e\n        \u003cdiv class=\"valueIcon\"\u003e\n          \u003c!-- Leaf\/Recycle Icon --\u003e\n          \u003cimg\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_recyclable.svg?v=1763826898\"\n            alt=\"Recyclable\"\n            class=\"valueIconImg\"\n          \/\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"valueContent\"\u003e\n          \u003ch3 class=\"valueItemTitle\"\u003eRecyclable materials\u003c\/h3\u003e\n          \u003cp class=\"valueItemShortDesc\"\u003eTechnical PA12, recyclable cardboard\u003c\/p\u003e\n          \u003cp class=\"valueItemFullDescMobile\"\u003e\n            We use technical PA12 for its robustness and durability, as well as\n            100% recyclable cardboard packaging. Our eco-design approach aims\n            to minimize our environmental impact.\n          \u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Item 4 --\u003e\n      \u003cdiv\n        class=\"valueItem\"\n        data-index=\"3\"\n        data-title=\"Lépine Award Winner\"\n        data-desc=\"The Lizia innovation has been recognized and awarded a medal at the prestigious Lépine Competition, a testament to its ingenuity and quality.\"\n      \u003e\n        \u003cdiv class=\"valueIcon\"\u003e\n          \u003c!-- Medal Icon --\u003e\n          \u003cimg\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_medaille.svg?v=1763826898\"\n            alt=\"Medal\"\n            class=\"valueIconImg\"\n          \/\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"valueContent\"\u003e\n          \u003ch3 class=\"valueItemTitle\"\u003eLépine Award Winner\u003c\/h3\u003e\n          \u003cp class=\"valueItemShortDesc\"\u003eAward-winning innovation\u003c\/p\u003e\n          \u003cp class=\"valueItemFullDescMobile\"\u003e\n            The Lizia innovation has been recognized and awarded a medal at the\n            prestigious Lépine Competition, a testament to its ingenuity and\n            quality.\n          \u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Item 5 --\u003e\n      \u003cdiv\n        class=\"valueItem\"\n        data-index=\"4\"\n        data-title=\"Internationally patented\"\n        data-desc=\"Our unique technology is protected by international patents, ensuring the exclusivity of our one-handed reading solution.\"\n      \u003e\n        \u003cdiv class=\"valueIcon\"\u003e\n          \u003c!-- Globe\/Patent Icon --\u003e\n          \u003cimg\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_brevet.svg?v=1763996122\"\n            alt=\"Patented\"\n            class=\"valueIconImg\"\n          \/\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"valueContent\"\u003e\n          \u003ch3 class=\"valueItemTitle\"\u003eInternationally patented\u003c\/h3\u003e\n          \u003cp class=\"valueItemShortDesc\"\u003eProtected innovation\u003c\/p\u003e\n          \u003cp class=\"valueItemFullDescMobile\"\u003e\n            Our unique technology is protected by international patents,\n            ensuring the exclusivity of our one-handed reading solution.\n          \u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Item 6 --\u003e\n      \u003cdiv\n        class=\"valueItem\"\n        data-index=\"5\"\n        data-title=\"Created by 2 students\"\n        data-desc=\"Lizia was born from the passion and entrepreneurship of two students, determined to improve readers' daily lives.\"\n      \u003e\n        \u003cdiv class=\"valueIcon\"\u003e\n          \u003c!-- Users\/Students Icon --\u003e\n          \u003cimg\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_etudiant.svg?v=1763996122\"\n            alt=\"Students\"\n            class=\"valueIconImg\"\n          \/\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"valueContent\"\u003e\n          \u003ch3 class=\"valueItemTitle\"\u003eCreated by 2 students\u003c\/h3\u003e\n          \u003cp class=\"valueItemShortDesc\"\u003eYoung innovative company\u003c\/p\u003e\n          \u003cp class=\"valueItemFullDescMobile\"\u003e\n            Lizia was born from the passion and entrepreneurship of two\n            students, determined to improve readers' daily lives.\n          \u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n\n      \u003c!-- Detail (Desktop only) --\u003e\n      \u003cdiv class=\"valuesDetail desktopOnly\"\u003e\n        \u003cdiv class=\"detailCard\"\u003e\n          \u003ch3 id=\"detailTitle\" class=\"detailTitle\"\u003eMade in France\u003c\/h3\u003e\n          \u003cp id=\"detailDesc\" class=\"detailDesc\"\u003e\n            Our nylon parts are produced in Western France and then assembled in\n            Brittany. By choosing Lizia, you support 100% French manufacturing, a\n            short supply chain and local industrial know-how, from design to\n            shipping from 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!-- REVIEWS SECTION --\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        Adopted by many readers\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 reviews\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cbutton class=\"reviewsNavBtn prev\" aria-label=\"Previous reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \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=\"Next reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \u003e\n        \u003cpolyline points=\"9 18 15 12 9 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cdiv\n      id=\"reviewsContainer\"\n      class=\"reviewsContainer\"\n      data-scroll-reveal\n    \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\u0026times;\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 LIZIA SEUL --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_8501710225757\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"8501710225757\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_8501710225757-option-0\"\n      \u003eCouleur\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option1\"\n      id=\"ProductSelect_8501710225757-option-0\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Blanc\"\n        name=\"Couleur\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_47976891023709 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-0_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-0_1\"\u003eBlanc\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Noir\"\n        name=\"Couleur\"\n        data-varient_id=\"1_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_47976891056477 product_varient_radio_1_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-1_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-1_1\"\u003eNoir\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rouge\"\n        name=\"Couleur\"\n        data-varient_id=\"2_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_51647359451485 product_varient_radio_2_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-2_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-2_1\"\u003eRouge\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Vert\"\n        name=\"Couleur\"\n        data-varient_id=\"3_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_47976891089245 product_varient_radio_3_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-3_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-3_1\"\u003eVert\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Bleu\"\n        name=\"Couleur\"\n        data-varient_id=\"4_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_47976891122013 product_varient_radio_4_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-4_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-4_1\"\u003eBleu\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rose\"\n        name=\"Couleur\"\n        data-varient_id=\"5_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_51647359484253 product_varient_radio_5_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-5_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-5_1\"\u003eRose\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Violet\"\n        name=\"Couleur\"\n        data-varient_id=\"6_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_47976891154781 product_varient_radio_6_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-6_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-6_1\"\u003eViolet\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Orange\"\n        name=\"Couleur\"\n        data-varient_id=\"7_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_54484174373213 product_varient_radio_7_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8501710225757-option-7_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-7_1\"\u003eOrange\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_8501710225757-option-1\"\n      \u003ePersonnalisation\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option2\"\n      id=\"ProductSelect_8501710225757-option-1\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Non Personnalisé\"\n        name=\"Personnalisation\"\n        data-varient_id=\"0_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_47976891023709 product_varient_radio_0_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_8501710225757-option-0_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-0_2\"\n        \u003eNon Personnalisé\u003c\/label\n      \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Personnalisé\"\n        name=\"Personnalisation\"\n        data-varient_id=\"1_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_47976891056477 product_varient_radio_1_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_8501710225757-option-1_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-1_2\"\u003ePersonnalisé\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Fête des pères\"\n        name=\"Personnalisation\"\n        data-varient_id=\"2_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_51647359451485 product_varient_radio_2_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_8501710225757-option-2_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8501710225757-option-2_2\"\u003eFête des pères\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_8501710225757TEST\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption\n      data-img=\"files\/produit_simple_color_blanc_V1_1eccda42-063c-436b-9801-87b6952f7192.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      selected\n      data-sku=\"LAMP-BLA-NP-V1\"\n      value=\"47976891023709\"\n    \u003e\n      Blanc \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"products\/produit_simple_perso_blanc_V2.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"LAMP-BLA-PE-V1\"\n      value=\"47976891056477\"\n    \u003e\n      Blanc \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      disabled\n      data-img=\"files\/perso_fete_des_peres_blanc.png\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"51647359451485\"\n    \u003e\n      Blanc \/ Fête des pères — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/produit_simple_color_noir_V1_47d5c895-3cf1-45a9-8aed-bdf9a25079c2.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"LAMP-NOI-NP-V1\"\n      value=\"47976891089245\"\n    \u003e\n      Noir \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"products\/produit_simple_perso_noir_V2.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"LAMP-NOI-PE-V1\"\n      value=\"47976891122013\"\n    \u003e\n      Noir \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      disabled\n      data-img=\"files\/perso_fete_des_peres_noir.png\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"51647359484253\"\n    \u003e\n      Noir \/ Fête des pères — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/produit_simple_color_rouge_V4_1ced6b23-df40-411b-8a95-c393ac143c6e.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"LAMP-ROU-NP-V1\"\n      value=\"47976891154781\"\n    \u003e\n      Rouge \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/produit_simple_perso_rouge_V3.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"LAMP-ROU-PE-V1\"\n      value=\"47976891187549\"\n    \u003e\n      Rouge \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      disabled\n      data-img=\"files\/perso_fete_des_peres_rouge.png\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"51647359517021\"\n    \u003e\n      Rouge \/ Fête des pères — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/produit_simple_color_vert_V3_3d5acb33-199e-4a74-a9c5-04b3954dcccc.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"LAMP-VER-NP-V1\"\n      value=\"47976891220317\"\n    \u003e\n      Vert \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/produit_simple_perso_green_V2_e28fa29c-8ec8-435e-b101-033144797e21.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"LAMP-VER-PE-V1\"\n      value=\"47976891253085\"\n    \u003e\n      Vert \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      disabled\n      data-img=\"files\/perso_fete_des_peres_vert.png\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"51647359549789\"\n    \u003e\n      Vert \/ Fête des pères — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/produit_simple_color_bleu_V2_60e72ca1-907d-4d8f-b0e8-6efb8a4c12e6.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"LAMP-BLE-NP-V1\"\n      value=\"47976891285853\"\n    \u003e\n      Bleu \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/produit_simple_perso_bleu_V3.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"LAMP-BLE-PE-V1\"\n      value=\"47976891318621\"\n    \u003e\n      Bleu \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      disabled\n      data-img=\"files\/perso_fete_des_peres_bleu.png\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2995\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"29,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"51647359582557\"\n    \u003e\n      Bleu \/ Fête des pères — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/rose.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"LAMP-RS-NP-V1\"\n      value=\"54484174340445\"\n    \u003e\n      Rose \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/rose.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"LAMP-RS-PE-V1\"\n      value=\"54484174373213\"\n    \u003e\n      Rose \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      disabled\n      data-img=\"files\/rose.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"54484174405981\"\n    \u003e\n      Rose \/ Fête des pères — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/violet_ef6c03b3-4a91-43c5-8555-6d08aab5c26c.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"LAMP-VIO-NP-V1\"\n      value=\"54484174438749\"\n    \u003e\n      Violet \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/violet_ef6c03b3-4a91-43c5-8555-6d08aab5c26c.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"LAMP-VIO-PE-V1\"\n      value=\"54484174471517\"\n    \u003e\n      Violet \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      disabled\n      data-img=\"files\/violet_ef6c03b3-4a91-43c5-8555-6d08aab5c26c.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"54484174504285\"\n    \u003e\n      Violet \/ Fête des pères — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/orange_big.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2495\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"24,95 EUR\"\n      data-sku=\"\"\n      value=\"54980518543709\"\n    \u003e\n      Orange \/ Non Personnalisé — 24,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/orange_big.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2995\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"29,95 EUR\"\n      data-sku=\"\"\n      value=\"54980518576477\"\n    \u003e\n      Orange \/ Personnalisé — 29,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cdiv class=\"containerPersonnalisation\"\u003e\n    \u003cdiv id=\"personalize-field\" style=\"display: none\"\u003e\n      \u003clabel for=\"personalize-input\"\u003ePersonalized text\u003c\/label\u003e\n      \u003cinput\n        type=\"text\"\n        id=\"personalize-input1\"\n        value=\"Bonne lecture !\"\n        style=\"color: #999\"\n        maxlength=\"25\"\n        minlength=\"1\"\n        name=\"properties[Personnalisation]\"\n      \/\u003e\n      \u003cspan id=\"character-count\"\u003e0\/25\u003c\/span\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_8501710225757\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_8501710225757\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_8501710225757\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',2495,8501710225757)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"8501710225757\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_8501710225757 button pulse\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewbox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_8501710225757\"\n      \u003eADD TO CART\u003c\/span\n    \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=\"8501710225757\" \/\u003e\n\u003c\/form\u003e\n\u003c!-- SELECTEUR pack Lizia \u0026 Cable --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_9868036014429\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"9868036014429\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_9868036014429-option-0\"\n      \u003eLizia - Lampe de lecture 3-en-1 — Couleur\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option1\"\n      id=\"ProductSelect_9868036014429-option-0\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Blanc\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011450717 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-0_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-0_1\"\u003eBlanc\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Noir\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"1_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011483485 product_varient_radio_1_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-1_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-1_1\"\u003eNoir\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rouge\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"2_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011549021 product_varient_radio_2_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-2_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-2_1\"\u003eRouge\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Vert\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"3_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011581789 product_varient_radio_3_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-3_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-3_1\"\u003eVert\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Bleu\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"4_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011647325 product_varient_radio_4_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-4_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-4_1\"\u003eBleu\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rose\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"5_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011680093 product_varient_radio_5_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-5_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-5_1\"\u003eRose\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Violet\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"6_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011745629 product_varient_radio_6_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-6_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-6_1\"\u003eViolet\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Orange\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"7_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_54539959828829 product_varient_radio_7_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868036014429-option-7_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-7_1\"\u003eOrange\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_9868036014429-option-1\"\n      \u003eLizia - Lampe de lecture 3-en-1 —Personnalisation\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option2\"\n      id=\"ProductSelect_9868036014429-option-1\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Non Personnalisé\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Personnalisation\"\n        data-varient_id=\"0_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011450717 product_varient_radio_0_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_9868036014429-option-0_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-0_2\"\n        \u003eNon Personnalisé\u003c\/label\n      \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Personnalisé\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Personnalisation\"\n        data-varient_id=\"1_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780011483485 product_varient_radio_1_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_9868036014429-option-1_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868036014429-option-1_2\"\u003ePersonnalisé\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cselect name=\"id\" id=\"ProductSelect_9868036014429\" style=\"display: none\"\u003e\n    \u003coption value=\"53780011450717\"\u003eBlanc \/ Non Personnalisé — 27,46 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011483485\"\u003eBlanc \/ Personnalisé — 32,21 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011549021\"\u003eNoir \/ Non Personnalisé — 27,46 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011581789\"\u003eNoir \/ Personnalisé — 32,21 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011647325\"\u003eRouge \/ Non Personnalisé — 27,46 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011680093\"\u003eRouge \/ Personnalisé — 32,21 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011745629\"\u003eVert \/ Non Personnalisé — 27,46 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011778397\"\u003eVert \/ Personnalisé — 32,21 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011843933\"\u003eBleu \/ Non Personnalisé — 27,46 EUR\u003c\/option\u003e\n    \u003coption value=\"53780011876701\"\u003eBleu \/ Personnalisé — 32,21 EUR\u003c\/option\u003e\n    \u003coption value=\"54539959796061\"\u003eRose \/ Non Personnalisé — 27,45 EUR\u003c\/option\u003e\n    \u003coption value=\"54539959828829\"\u003eRose \/ Personnalisé — 32,20 EUR\u003c\/option\u003e\n    \u003coption value=\"54539959861597\"\u003e\n      Violet \/ Non Personnalisé — 27,45 EUR\n    \u003c\/option\u003e\n    \u003coption value=\"54539959894365\"\u003eViolet \/ Personnalisé — 32,20 EUR\u003c\/option\u003e\n    \u003coption value=\"54983340196189\"\u003e\n      Orange \/ Non Personnalisé — 27,45 EUR\n    \u003c\/option\u003e\n    \u003coption value=\"54983340228957\"\u003eOrange \/ Personnalisé — 32,20 EUR\u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cdiv class=\"containerPersonnalisation\"\u003e\n    \u003cdiv id=\"personalize-field\" style=\"display: none\"\u003e\n      \u003clabel for=\"personalize-input\"\u003ePersonalized text\u003c\/label\u003e\n      \u003cinput\n        type=\"text\"\n        id=\"personalize-input2\"\n        value=\"Bonne lecture !\"\n        style=\"color: #999\"\n        maxlength=\"25\"\n        minlength=\"1\"\n        name=\"properties[Personnalisation]\"\n      \/\u003e\n      \u003cspan id=\"character-count\"\u003e0\/25\u003c\/span\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_9868036014429\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_9868036014429\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_9868036014429\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',2746,9868036014429)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"9868036014429\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_9868036014429 button\"\n  \u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_9868036014429\"\n      \u003eADD TO CART\u003c\/span\n    \u003e\n    \u003cspan class=\"add-to-cart-loading\"\u003e\u003c\/span\u003e\n    \u003cb\u003e\u003c\/b\u003e\n  \u003c\/button\u003e\n\u003c\/form\u003e\n\u003c!-- SELECTEUR pack Lizia \u0026 Cable \u0026 pochette Lin --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_9868043878749\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"9868043878749\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_9868043878749-option-0\"\n      \u003eLizia - Lampe de lecture 3-en-1 — Couleur\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option1\"\n      id=\"ProductSelect_9868043878749-option-0\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Blanc\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780072989021 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-0_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-0_1\"\u003eBlanc\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Noir\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"1_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780073021789 product_varient_radio_1_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-1_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-1_1\"\u003eNoir\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rouge\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"2_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780073087325 product_varient_radio_2_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-2_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-2_1\"\u003eRouge\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Vert\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"3_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780073120093 product_varient_radio_3_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-3_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-3_1\"\u003eVert\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Bleu\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"4_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780073185629 product_varient_radio_4_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-4_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-4_1\"\u003eBleu\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rose\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"5_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780073218397 product_varient_radio_5_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-5_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-5_1\"\u003eRose\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Violet\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"6_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780073283933 product_varient_radio_6_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-6_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-6_1\"\u003eViolet\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Orange\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"7_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_54552879137117 product_varient_radio_7_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868043878749-option-7_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-7_1\"\u003eOrange\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_9868043878749-option-1\"\n      \u003eLizia - Lampe de lecture 3-en-1 —Personnalisation\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option2\"\n      id=\"ProductSelect_9868043878749-option-1\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Non Personnalisé\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Personnalisation\"\n        data-varient_id=\"0_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780072989021 product_varient_radio_0_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_9868043878749-option-0_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-0_2\"\n        \u003eNon Personnalisé\u003c\/label\n      \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Personnalisé\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Personnalisation\"\n        data-varient_id=\"1_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780073021789 product_varient_radio_1_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_9868043878749-option-1_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868043878749-option-1_2\"\u003ePersonnalisé\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_9868043878749\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      selected\n      data-sku=\"\"\n      value=\"53780072989021\"\n    \u003e\n      Blanc \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780073021789\"\n    \u003e\n      Blanc \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780073087325\"\n    \u003e\n      Noir \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780073120093\"\n    \u003e\n      Noir \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780073185629\"\n    \u003e\n      Rouge \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780073218397\"\n    \u003e\n      Rouge \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780073283933\"\n    \u003e\n      Vert \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780073316701\"\n    \u003e\n      Vert \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780073382237\"\n    \u003e\n      Bleu \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780073415005\"\n    \u003e\n      Bleu \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3045\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"30,45 EUR\"\n      data-sku=\"\"\n      value=\"54552879104349\"\n    \u003e\n      Rose \/ Non Personnalisé — 30,45 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3495\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"34,95 EUR\"\n      data-sku=\"\"\n      value=\"54552879137117\"\n    \u003e\n      Rose \/ Personnalisé — 34,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3045\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"30,45 EUR\"\n      data-sku=\"\"\n      value=\"54552879169885\"\n    \u003e\n      Violet \/ Non Personnalisé — 30,45 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3495\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"34,95 EUR\"\n      data-sku=\"\"\n      value=\"54552879202653\"\n    \u003e\n      Violet \/ Personnalisé — 34,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3045\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"30,45 EUR\"\n      data-sku=\"\"\n      value=\"54983311163741\"\n    \u003e\n      Orange \/ Non Personnalisé — 30,45 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3495\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"34,95 EUR\"\n      data-sku=\"\"\n      value=\"54983311196509\"\n    \u003e\n      Orange \/ Personnalisé — 34,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cdiv class=\"containerPersonnalisation\"\u003e\n    \u003cdiv id=\"personalize-field\" style=\"display: none\"\u003e\n      \u003clabel for=\"personalize-input\"\u003ePersonalized text\u003c\/label\u003e\n      \u003cinput\n        type=\"text\"\n        id=\"personalize-input3\"\n        value=\"Bonne lecture !\"\n        style=\"color: #999\"\n        maxlength=\"25\"\n        minlength=\"1\"\n        name=\"properties[Personnalisation]\"\n      \/\u003e\n      \u003cspan id=\"character-count\"\u003e0\/25\u003c\/span\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_9868043878749\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_9868043878749\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_9868043878749\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',3047,9868043878749)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"9868043878749\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_9868043878749 button\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewbox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_9868043878749\"\n      \u003eADD TO CART\u003c\/span\n    \u003e\n    \u003cspan class=\"add-to-cart-loading\"\u003e\u003c\/span\u003e\n    \u003cb\u003e\u003c\/b\u003e\n  \u003c\/button\u003e\n\u003c\/form\u003e\n\u003c!-- SELECTEUR pack Lizia \u0026 Cable \u0026 pochette Microfibre --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_9868106465629\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"9868106465629\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_9868106465629-option-0\"\n      \u003eLizia - Lampe de lecture 3-en-1 — Couleur\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option1\"\n      id=\"ProductSelect_9868106465629-option-0\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Blanc\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211106141 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-0_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-0_1\"\u003eBlanc\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Noir\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"1_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211138909 product_varient_radio_1_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-1_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-1_1\"\u003eNoir\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rouge\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"2_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211204445 product_varient_radio_2_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-2_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-2_1\"\u003eRouge\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Vert\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"3_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211237213 product_varient_radio_3_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-3_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-3_1\"\u003eVert\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Bleu\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"4_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211302749 product_varient_radio_4_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-4_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-4_1\"\u003eBleu\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Rose\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"5_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211335517 product_varient_radio_5_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-5_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-5_1\"\u003eRose\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Violet\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"6_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211401053 product_varient_radio_6_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-6_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-6_1\"\u003eViolet\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Orange\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Couleur\"\n        data-varient_id=\"7_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_undefined product_varient_radio_7_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_9868106465629-option-7_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-7_1\"\u003eOrange\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003clabel\n      class=\"single-option-radio__label\"\n      for=\"ProductSelect_9868106465629-option-1\"\n      \u003eLizia - Lampe de lecture 3-en-1 —Personnalisation\u003c\/label\n    \u003e\n    \u003cdiv\n      class=\"single-option-radio\"\n      data-option=\"option2\"\n      id=\"ProductSelect_9868106465629-option-1\"\n    \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Non Personnalisé\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Personnalisation\"\n        data-varient_id=\"0_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211106141 product_varient_radio_0_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_9868106465629-option-0_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-0_2\"\n        \u003eNon Personnalisé\u003c\/label\n      \u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Personnalisé\"\n        name=\"Lizia - Lampe de lecture 3-en-1 — Personnalisation\"\n        data-varient_id=\"1_2\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_53780211138909 product_varient_radio_1_2\"\n        data-option=\"option2\"\n        id=\"ProductSelect_9868106465629-option-1_2\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_9868106465629-option-1_2\"\u003ePersonnalisé\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_9868106465629\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      selected\n      data-sku=\"\"\n      value=\"53780211106141\"\n    \u003e\n      Blanc \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780211138909\"\n    \u003e\n      Blanc \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780211204445\"\n    \u003e\n      Noir \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780211237213\"\n    \u003e\n      Noir \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780211302749\"\n    \u003e\n      Rouge \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780211335517\"\n    \u003e\n      Rouge \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780211401053\"\n    \u003e\n      Vert \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780211433821\"\n    \u003e\n      Vert \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3047\"\n      data-compare_at_price=\"\"\n      data-price=\"30,47 EUR\"\n      data-sku=\"\"\n      value=\"53780211499357\"\n    \u003e\n      Bleu \/ Non Personnalisé — 30,47 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"\"\n      data-price_o=\"3497\"\n      data-compare_at_price=\"\"\n      data-price=\"34,97 EUR\"\n      data-sku=\"\"\n      value=\"53780211532125\"\n    \u003e\n      Bleu \/ Personnalisé — 34,97 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3045\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"30,45 EUR\"\n      data-sku=\"\"\n      value=\"54553142002013\"\n    \u003e\n      Rose \/ Non Personnalisé — 30,45 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3495\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"34,95 EUR\"\n      data-sku=\"\"\n      value=\"54553142034781\"\n    \u003e\n      Rose \/ Personnalisé — 34,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3045\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"30,45 EUR\"\n      data-sku=\"\"\n      value=\"54553142067549\"\n    \u003e\n      Violet \/ Non Personnalisé — 30,45 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3495\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"34,95 EUR\"\n      data-sku=\"\"\n      value=\"54553142100317\"\n    \u003e\n      Violet \/ Personnalisé — 34,95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3045\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"30,45 EUR\"\n      data-sku=\"\"\n      value=\"54983274692957\"\n    \u003e\n      Orange \/ Non Personnalisé — 30,45 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"3385\"\n      data-price_o=\"3495\"\n      data-compare_at_price=\"33,85 EUR\"\n      data-price=\"34,95 EUR\"\n      data-sku=\"\"\n      value=\"54983274725725\"\n    \u003e\n      Orange \/ Personnalisé — 34,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cdiv class=\"containerPersonnalisation\"\u003e\n    \u003cdiv id=\"personalize-field\" style=\"display: none\"\u003e\n      \u003clabel for=\"personalize-input\"\u003ePersonalized text\u003c\/label\u003e\n      \u003cinput\n        type=\"text\"\n        id=\"personalize-input4\"\n        value=\"Bonne lecture !\"\n        style=\"color: #999\"\n        maxlength=\"25\"\n        minlength=\"1\"\n        name=\"properties[Personnalisation]\"\n      \/\u003e\n      \u003cspan id=\"character-count\"\u003e0\/25\u003c\/span\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_9868106465629\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_9868106465629\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_9868106465629\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',3047,9868106465629)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"9868106465629\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_9868106465629 button\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewbox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_9868106465629\"\n      \u003eADD TO CART\u003c\/span\n    \u003e\n    \u003cspan class=\"add-to-cart-loading\"\u003e\u003c\/span\u003e\n    \u003cb\u003e\u003c\/b\u003e\n  \u003c\/button\u003e\n\u003c\/form\u003e\n\u003c!-- FIN DU BLOC SELECTEUR CACHÉS--\u003e\n\n\u003cscript\u003e\ndocument.addEventListener(\"DOMContentLoaded\", () =\u003e {\n  \/\/ --- CONSTANTS AND DATA ---\n  const PACK_PRICES = {\n    lizia: 24.95,\n    \"lizia-cable\": 27.45,\n    \"lizia-cable-pouch\": 30.47,\n    \"lizia-cable-pouch-tv\": 24.95,\n  };\n  const PACK_ORIGINAL_PRICES = {\n    lizia: null,\n    \"lizia-cable\": 28.9,\n    \"lizia-cable-pouch\": 33.85,\n    \"lizia-cable-pouch-tv\": 33.85,\n  };\n  const PERSONALIZATION_PRICE = 4.95;\n\n  \/\/ Mapping of Shopify variants by pack\/color\/personalization\n  const variantIDs = {\n    8501710225757: {\n      \/\/ Lizia seul\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    9868036014429: {\n      \/\/ Lizia + cable\n      Blanc: { normal: \"53780011450717\", personnalise: \"53780011483485\" },\n      Noir: { normal: \"53780011549021\", personnalise: \"53780011581789\" },\n      Rouge: { normal: \"53780011647325\", personnalise: \"53780011680093\" },\n      Vert: { normal: \"53780011745629\", personnalise: \"53780011778397\" },\n      Bleu: { normal: \"53780011843933\", personnalise: \"53780011876701\" },\n      Rose: { normal: \"54539959796061\", personnalise: \"54539959828829\" },\n      Violet: { normal: \"54539959861597\", personnalise: \"54539959894365\" },\n      Orange: { normal: \"54983340196189\", personnalise: \"54983340228957\" },\n    },\n    9868043878749: {\n      \/\/ Lizia + cable + pochette lin\n      Blanc: { normal: \"53780072989021\", personnalise: \"53780073021789\" },\n      Noir: { normal: \"53780073087325\", personnalise: \"53780073120093\" },\n      Rouge: { normal: \"53780073185629\", personnalise: \"53780073218397\" },\n      Vert: { normal: \"53780073283933\", personnalise: \"53780073316701\" },\n      Bleu: { normal: \"53780073382237\", personnalise: \"53780073415005\" },\n      Rose: { normal: \"54552879104349\", personnalise: \"54552879137117\" },\n      Violet: { normal: \"54552879169885\", personnalise: \"54552879202653\" },\n      Orange: { normal: \"54983311163741\", personnalise: \"54983311196509\" },\n    },\n    9868106465629: {\n      \/\/ Lizia + cable + pochette microfibre\n      Blanc: { normal: \"53780211106141\", personnalise: \"53780211138909\" },\n      Noir: { normal: \"53780211204445\", personnalise: \"53780211237213\" },\n      Rouge: { normal: \"53780211302749\", personnalise: \"53780211335517\" },\n      Vert: { normal: \"53780211401053\", personnalise: \"53780211433821\" },\n      Bleu: { normal: \"53780211499357\", personnalise: \"53780211532125\" },\n      Rose: { normal: \"54553142002013\", personnalise: \"54553142034781\" },\n      Violet: { normal: \"54553142067549\", personnalise: \"54553142100317\" },\n      Orange: { normal: \"54983274692957\", personnalise: \"54983274725725\" },\n    },\n  };\n\n  \/\/ Mapping of pack names to their Shopify IDs\n  const packIDMapping = {\n    lizia: \"8501710225757\",\n    \"lizia-cable\": \"9868036014429\",\n    \"lizia-cable-pouch\": {\n      naturel: \"9868043878749\",\n      colore: \"9868106465629\",\n    },\n    \"lizia-cable-pouch-tv\": \"9868106465629\", \/\/ Pack vu à la TV = 2× pack microfibre uniquement\n  };\n\n  \/\/ Reviews data\n  const reviewsData = [\n    {\n      firstName: \"Laura\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sep\", year: 2025 },\n      title: \"An amazing discovery!\",\n      content: \"I received it and it's so cool!\",\n      verified: true,\n    },\n    {\n      firstName: \"Nathalie\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sep\", year: 2025 },\n      title: \"Easy reading without disturbing others, perfect!\",\n      content:\n        \"I received my Lizia a few days ago. It's Christmas in September, what a joy to be able to read in my bed without disturbing my partner, on public transport...\",\n      verified: true,\n    },\n    {\n      firstName: \"Audrey\",\n      lastName: \"T\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Jul\", year: 2025 },\n      title: \"Practical and reliable, I've been using it for a long time\",\n      content:\n        \"It's great for reading, I've had one for several months, it's awesome!\",\n      verified: true,\n    },\n    {\n      firstName: \"Sophie\",\n      lastName: \"D\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Essential for my reading evenings\",\n      content:\n        \"I can't do without it anymore! Perfect for reading at night without turning on the bedroom light.\",\n      verified: true,\n    },\n    {\n      firstName: \"Marc\",\n      lastName: \"L\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Jun\", year: 2025 },\n      title: \"Perfect gift for readers\",\n      content:\n        \"Given to my wife, she loves it! The quality is there and the design is elegant.\",\n      verified: true,\n    },\n    {\n      firstName: \"Émilie\",\n      lastName: \"R\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sep\", year: 2025 },\n      title: \"Lizia revolutionizes my reading moments\",\n      content: \"No more tired arms and lighting problems! I recommend it 100%.\",\n      verified: true,\n    },\n    {\n      firstName: \"Thomas\",\n      lastName: \"B\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Jul\", year: 2025 },\n      title: \"Excellent product, meets my expectations\",\n      content:\n        \"Fast delivery, quality product. I now read everywhere without constraints.\",\n      verified: true,\n    },\n    {\n      firstName: \"Charlotte\",\n      lastName: \"V\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"A must-have for all readers\",\n      content:\n        \"Simple, effective and so practical. My children even asked for their own!\",\n      verified: true,\n    },\n    {\n      firstName: \"Pierre\",\n      lastName: \"G\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"May\", year: 2025 },\n      title: \"Top French quality\",\n      content:\n        \"Happy to support a French company. The product is robust and well thought out.\",\n      verified: true,\n    },\n    {\n      firstName: \"Isabelle\",\n      lastName: \"F\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sep\", year: 2025 },\n      title: \"Perfect for reading while traveling\",\n      content:\n        \"I take it everywhere with me: train, plane, hotel. It has become my essential accessory.\",\n      verified: true,\n    },\n    {\n      firstName: \"Antoine\",\n      lastName: \"H\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Jun\", year: 2025 },\n      title: \"Comfortable and discreet\",\n      content:\n        \"The lamp is powerful but doesn't disturb anyone. Ideal for nighttime reading.\",\n      verified: true,\n    },\n    {\n      firstName: \"Camille\",\n      lastName: \"P\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Elegant and functional design\",\n      content:\n        \"I love the clean design and available colors. Plus, it's super practical!\",\n      verified: true,\n    },\n    {\n      firstName: \"Julien\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Jul\", year: 2025 },\n      title: \"Excellent value for money\",\n      content:\n        \"For the price, it's really an excellent investment. I recommend it without hesitation.\",\n      verified: true,\n    },\n    {\n      firstName: \"Léa\",\n      lastName: \"S\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sep\", year: 2025 },\n      title: \"My children love it too\",\n      content:\n        \"My children use it every evening for their bedtime reading. It has become a ritual!\",\n      verified: true,\n    },\n    {\n      firstName: \"Maxime\",\n      lastName: \"C\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Innovation at the service of reading\",\n      content:\n        \"Finally a product that thinks about the real needs of readers. Bravo for this innovation!\",\n      verified: true,\n    },\n  ];\n\n  \/\/ Pack images by color (for dynamic display)\n  const PACK_IMAGES_BY_COLOR = {\n    lizia: {\n      rose: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_seul_rose.webp?v=1762267649\",\n      vert: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_seul_vert.webp?v=1762267649\",\n      blanc:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_seul_blanc.webp?v=1762267649\",\n      noir: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_seul_noir.webp?v=1762267649\",\n      bleu: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_seul_bleu.webp?v=1762267649\",\n      violet:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_seul_violet.webp?v=1762267650\",\n    },\n    \"lizia-cable\": {\n      rose: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable_rose.webp?v=1762268016\",\n      vert: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable_vert.webp?v=1762268017\",\n      blanc:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable_blanc.webp?v=1762268016\",\n      noir: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable_noir.webp?v=1762268016\",\n      bleu: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable_bleu.webp?v=1762268069\",\n      violet:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable_violet.webp?v=1762268017\",\n    },\n    \"lizia-cable-pouch\": {\n      rose: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_complet_rose.webp?v=1762268735\",\n      vert: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/10.png?v=1771524975\",\n      blanc:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/13_54687a2a-8220-448a-8ab2-1fc4ecca315e.png?v=1771524974\",\n      noir: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/12.png?v=1771524974\",\n      bleu: \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/visuel_x2_site.png?v=1771525033\",\n      violet:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/11_54f3bdfb-597f-4a6b-a12f-246ef920f6dc.png?v=1771524975\",\n    },\n  };\n\n  const PACKS = [\n    {\n      id: \"lizia\",\n      name: \"Light\",\n      price: 24.95,\n      short: \"Lamp only\",\n      description:\n        \"The Lizia reading lamp, perfect for reading comfortably in all situations.\",\n      features: [\n        \"High-quality LED lamp\",\n        \"Integrated bookmark\",\n        \"One-handed reading\",\n        \"Long-lasting battery\",\n        \"Compact and elegant design\",\n      ],\n      image:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia.webp?v=1762182090\",\n    },\n    {\n      id: \"lizia-cable\",\n      name: \"Light + Cable\",\n      price: 27.45,\n      originalPrice: 28.9,\n      badge: \"-5%\",\n      badgeColor: \"accent\",\n      description:\n        \"The Lizia pack with charging cable, ideal for extended use.\",\n      features: [\n        \"Lizia reading lamp\",\n        \"Mini USB charging cable\",\n        \"Integrated bookmark\",\n        \"One-handed reading\",\n        \"Long-lasting battery\",\n        \"Compact and elegant design\",\n      ],\n      image:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable.webp?v=1764673605\",\n    },\n    {\n      id: \"lizia-cable-pouch-tv\",\n      name: \"2x Light 2x Cable 2x Pouch\",\n      price: 49.9,\n      originalPrice: 67.7,\n      badge: null,\n      badgeColor: null,\n      description:\n        \"2 Lizia reading lamps with charging cables and microfiber pouches, at the best price.\",\n      features: [\n        \"2 Lizia reading lamps\",\n        \"2 mini USB charging cables\",\n        \"2 microfiber pouches\",\n        \"Integrated bookmark\",\n        \"One-handed reading\",\n        \"Long-lasting battery\",\n        \"Compact and elegant design\",\n      ],\n      image:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/pack_lizia_cable_pochette.webp?v=1762183214\",\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  \/\/ Pouch configuration\n  const POUCH_CONFIG = {\n    enabled: true, \/\/ Easily deactivatable\n    showSelector: false, \/\/ Hide selector if false (even with multiple options). Set to true to show again.\n    defaultIndex: 0, \/\/ Easily modifiable (0 = first, 1 = second)\n    options: [\n      {\n        id: \"naturel\",\n        name: \"Natural\",\n        description: \"Cotton + Linen\",\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: \"Colored\",\n        description: \"Microfiber\",\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 by variant (pack + color)\n  const PRODUCT_IMAGES_BY_VARIANT = {\n    lizia: {\n      rose: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_rose_iso.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_ergonomique.webp?v=1764341006\",\n      ],\n      vert: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_vert_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_ergonomique.webp?v=1764341006\",\n      ],\n      blanc: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_blanc_iso.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_ergonomique.webp?v=1764338030\",\n      ],\n      noir: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_noir_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_ergonomique.webp?v=1764338030\",\n      ],\n      bleu: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_bleu_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_ergonomique.webp?v=1764341006\",\n      ],\n      violet: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_violet_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_ergonomique.webp?v=1764341006\",\n      ],\n    },\n    \"lizia-cable\": {\n      rose: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_rose_iso.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_ergonomique.webp?v=1764341006\",\n      ],\n      vert: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_vert_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_ergonomique.webp?v=1764341006\",\n      ],\n      blanc: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_blanc_iso.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_ergonomique.webp?v=1764338030\",\n      ],\n      noir: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_noir_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_ergonomique.webp?v=1764338030\",\n      ],\n      bleu: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_bleu_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_ergonomique.webp?v=1764341006\",\n      ],\n      violet: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet.webp?v=1764338031\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_violet_iso.webp?v=1764338030\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_technique.webp?v=1764341006\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_ergonomique.webp?v=1764341006\",\n      ],\n    },\n    \"lizia-cable-pouch\": {\n      rose: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_rose_iso.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_ergonomique.webp?v=1764341006\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_rose_iso.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_rose_ergonomique.webp?v=1764341006\",\n        ],\n      },\n      vert: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_vert_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_ergonomique.webp?v=1764341006\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_vert_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_vert_ergonomique.webp?v=1764341006\",\n        ],\n      },\n      blanc: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_blanc_iso.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_ergonomique.webp?v=1764338030\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_blanc_iso.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_blanc_ergonomique.webp?v=1764338030\",\n        ],\n      },\n      noir: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_noir_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_ergonomique.webp?v=1764338030\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_noir_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_noir_ergonomique.webp?v=1764338030\",\n        ],\n      },\n      bleu: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_bleu_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_ergonomique.webp?v=1764341006\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_bleu_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_bleu_ergonomique.webp?v=1764341006\",\n        ],\n      },\n      violet: {\n        naturel: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_violet_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_ergonomique.webp?v=1764341006\",\n        ],\n        colore: [\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet.webp?v=1764338031\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_lizia_violet_iso.webp?v=1764338030\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_nuit.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_technique.webp?v=1764341006\",\n          \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/en_presentation_lizia_violet_ergonomique.webp?v=1764341006\",\n        ],\n      },\n    },\n  };\n\n  \/\/ Video playback state tracking (autoplay, user pause)\n  const videoPlaybackStates = {};\n\n  \/\/ Fonction pour obtenir les images selon la variante\n  function getProductImages() {\n    const pack = state.pack;\n    const color = state.colors[0] || \"vert\"; \/\/ Use first selected color\n\n    \/\/ TV pack = same visual as complete microfiber pack\n    if (pack === \"lizia-cable-pouch-tv\") {\n      return (\n        PRODUCT_IMAGES_BY_VARIANT[\"lizia-cable-pouch\"]?.[color]?.[\"colore\"] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"lizia\"][\"vert\"]\n      );\n    }\n\n    \/\/ If pack with pouch, handle pouch variants\n    if (pack === \"lizia-cable-pouch\") {\n      const pouch =\n        state.pouch || POUCH_CONFIG.options[POUCH_CONFIG.defaultIndex].id;\n      return (\n        PRODUCT_IMAGES_BY_VARIANT[pack]?.[color]?.[pouch] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"lizia\"][\"vert\"]\n      );\n    }\n\n    \/\/ For other packs, normal structure\n    return (\n      PRODUCT_IMAGES_BY_VARIANT[pack]?.[color] ||\n      PRODUCT_IMAGES_BY_VARIANT[\"lizia\"][\"vert\"]\n    );\n  }\n\n  \/\/ --- APPLICATION STATE ---\n  let state = {\n    pack: \"lizia-cable-pouch-tv\",\n    quantity: 2,\n    colors: [\"vert\", \"vert\"],\n    personalization: [\"\", \"\"],\n    currentImageIndex: 0,\n    pouch: \"colore\",\n  };\n\n  \/\/ Track previous pack to detect changes\n  let previousPack = state.pack;\n  \/\/ Track previous quantity for animations\n  let previousQuantity = state.quantity;\n\n  \/\/ Preloaded image cache to avoid lag\n  const imageCache = new Map();\n  \/\/ Cache for main gallery images (preloaded and decoded)\n  const galleryImageCache = new Map();\n\n  \/\/ Image preload function\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  \/\/ Preload and decode all current gallery images\n  function preloadGalleryImages(imageUrls) {\n    imageUrls.forEach((url) =\u003e {\n      \/\/ Skip videos (starting with \"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        \/\/ Decode image so it's ready for display\n        img\n          .decode()\n          .then(() =\u003e {\n            galleryImageCache.set(url, img);\n          })\n          .catch((err) =\u003e {\n            console.warn(\"Image decode error:\", url, err);\n            \/\/ Cache anyway even if decode fails\n            galleryImageCache.set(url, img);\n          });\n      }\n    });\n  }\n\n  \/\/ Get pack image by color\n  function getPackImage(packId, colorId = null) {\n    \/\/ TV pack uses same images as complete pack\n    if (packId === \"lizia-cable-pouch-tv\") {\n      return getPackImage(\n        \"lizia-cable-pouch\",\n        colorId || state.colors[0] || \"vert\",\n      );\n    }\n    \/\/ If no color specified, use first color from state\n    const color = colorId || state.colors[0] || \"vert\";\n\n    \/\/ Return image by color, or default image\n    return (\n      PACK_IMAGES_BY_COLOR[packId]?.[color] ||\n      PACKS.find((p) =\u003e p.id === packId)?.image\n    );\n  }\n\n  \/\/ --- DOM SELECTORS ---\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 priceDisplayContainer = document.getElementById(\"priceDisplay\");\n  const addToCartBtn = document.getElementById(\"addToCartBtn\");\n  const addToCartBtnMobile = document.getElementById(\"addToCartBtnMobile\");\n  const colorsPersoLabel = document.getElementById(\"colorsPersoLabel\");\n\n  \/\/ Popup selectors\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  \/\/ --- RENDER FUNCTIONS ---\n\n  \/** Add native video controls *\/\n  function setupVideoControls(videoElement) {\n    if (!videoElement) return;\n    videoElement.setAttribute(\"controls\", \"\");\n  }\n\n  \/** Image gallery render *\/\n  function renderImageGallery() {\n    if (!mainImage || !mainImageContainer) return;\n\n    const currentImages = getProductImages();\n    if (!currentImages || currentImages.length === 0) return;\n\n    \/\/ Preload and decode all gallery images for instant change\n    preloadGalleryImages(currentImages);\n\n    \/\/ Use cached image if available, otherwise load normally\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    \/\/ Handle videos differently from images\n    if (isVideo) {\n      \/\/ Hide image\n      if (mainImage) {\n        mainImage.style.display = \"none\";\n      }\n\n      \/\/ Check if video already exists, otherwise create one\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(\"Video playback error:\", 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(\"Video playback error:\", 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(244, 232, 74, 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  const M6_LOGO_URL =\n    \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\";\n\n  \/** Rendu du sélecteur de packs *\/\n  function renderPackSelector() {\n    packSelectorContainer.innerHTML = PACKS.map((pack, index) =\u003e {\n      \/\/ Badge \"Top Vente\" pour le pack 2 (lizia-cable), \"Vu à la télé\" pour le pack 3 (Pack vu à la TV)\n      const topBadge = index === 1 ? \"Top Vente\" : null;\n      const tvBadge = index === 2 ? \"As seen on TV\" : null;\n\n      \/\/ Afficher le \"i\" seulement pour le pack Top Vente\n      const showInfoInBadge = topBadge \u0026\u0026 pack.id === \"lizia-cable\";\n\n      \/\/ Obtenir l'image selon la couleur sélectionnée\n      const packImage = getPackImage(pack.id);\n\n      return `\n            \u003cdiv class=\"packWrapper ${pack.id === \"lizia-cable-pouch-tv\" ? \"packWrapper--tv\" : \"\"} ${\n              state.pack === pack.id ? \"selected\" : \"\"\n            }\"\u003e\n                ${\n                  tvBadge\n                    ? `\u003cbutton class=\"packBadge packBadgeTop packBadgeTopTv\" data-pack=\"${pack.id}\" role=\"button\" tabindex=\"0\" aria-label=\"Pack vu à la TV\"\u003e\n                        \u003cspan class=\"badgeTopText\"\u003e${tvBadge}\u003c\/span\u003e\n                      \u003c\/button\u003e`\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                    ${\n                      pack.id === \"lizia-cable-pouch-tv\"\n                        ? `\u003cdiv class=\"packName packName--lines\" aria-label=\"2x Light, 2x Cables, 2x Pouches\"\u003e\n                            \u003cspan\u003e2x Light\u003c\/span\u003e\n                            \u003cspan\u003e2x Cables\u003c\/span\u003e\n                            \u003cspan\u003e2x Pouches\u003c\/span\u003e\n                          \u003c\/div\u003e`\n                        : `\u003ch3 class=\"packName\"\u003e${pack.name}\u003c\/h3\u003e`\n                    }\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${pack.price.toFixed(2)}€\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    \/\/ Afficher seulement si le pack contient une pochette, si la sélection est activée\n    \/\/ ET s'il y a plus d'une option de pochette disponible ET si showSelector est activé\n    const hasMultipleOptions =\n      POUCH_CONFIG.options \u0026\u0026 POUCH_CONFIG.options.length \u003e 1;\n    const shouldShowSelector = POUCH_CONFIG.showSelector \u0026\u0026 hasMultipleOptions;\n\n    \/\/ Afficher seulement pour \"lizia-cable-pouch\" (pas pour \"lizia-cable-pouch-tv\" = microfibre uniquement)\n    if (\n      state.pack !== \"lizia-cable-pouch\" ||\n      !POUCH_CONFIG.enabled ||\n      !shouldShowSelector\n    ) {\n      \/\/ Retirer la classe pour masquer avec transition\n      pouchSelectorContainer.classList.remove(\"has-pouch\");\n      \/\/ Animation de sortie SEULEMENT si le pack a changé\n      const existingSection =\n        pouchSelectorContainer.querySelector(\".pouchSection\");\n      if (existingSection \u0026\u0026 packChanged) {\n        existingSection.style.animation =\n          \"slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1)\";\n        setTimeout(() =\u003e {\n          pouchSelectorContainer.innerHTML = \"\";\n        }, 400);\n      } else if (!existingSection) {\n        pouchSelectorContainer.innerHTML = \"\";\n      }\n      return;\n    }\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    \/\/ Pack vu à la TV : -27% (33,85 → 24,95), jauge remplie à 100%\n    if (state.pack === \"lizia-cable-pouch-tv\") {\n      return {\n        packDiscount: 27,\n        quantityDiscount: 0,\n        total: 27,\n      };\n    }\n    \/\/ Pack discount\n    let packDiscount = 0;\n    if (state.pack === \"lizia-cable\") {\n      packDiscount = 5; \/\/ -5%\n    } else if (state.pack === \"lizia-cable-pouch\") {\n      packDiscount = 10; \/\/ -10%\n    }\n\n    \/\/ Quantity discount\n    let quantityDiscount = 0;\n    if (state.quantity === 2) {\n      quantityDiscount = 5; \/\/ -5%\n    } else if (state.quantity \u003e= 3) {\n      quantityDiscount = 10; \/\/ -10%\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    const isTvPack = state.pack === \"lizia-cable-pouch-tv\";\n\n    \/\/ Pack vu à la TV : quantité min 2 (pas de 1)\n    let leftBubble, middleBubble, rightBubble;\n    let leftSelected = false;\n    let middleSelected = false;\n    let leftDisabled = false;\n\n    if (isTvPack) {\n      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        leftDisabled = 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    } else 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 ${leftSelected ? \"selected\" : \"\"} ${\n          leftDisabled ? \"disabled\" : \"\"\n        }\" data-action=\"${leftBubble.action}\" data-value=\"${leftBubble.value}\" ${\n          leftDisabled ? 'aria-disabled=\"true\"' : \"\"\n        }\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 isTvPack = state.pack === \"lizia-cable-pouch-tv\";\n    const maxDiscount = isTvPack ? 27 : 20;\n    \/\/ 101% pour combler le blanc à droite (bordure + border-radius de la jauge)\n    let fillPercentage = isTvPack ? 101 : (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    \/\/ Update discount labels\n    updateDiscountLabels(discountValue, isTvPack);\n  }\n\n  function updateDiscountLabels(currentDiscount, isTvPack = false) {\n    const discountValues = [0, 5, 10, 15, 20];\n    let displayPercent;\n    if (isTvPack \u0026\u0026 currentDiscount \u003e= 26) {\n      displayPercent = 27;\n    } else {\n      let closestDiscount = discountValues[0];\n      let minDiff = Math.abs(currentDiscount - closestDiscount);\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      displayPercent = closestDiscount;\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 = displayPercent === 0;\n\n    if (progressBar) {\n      progressBar.classList.toggle(\"zero\", isZero);\n    }\n\n    if (fillLabel) {\n      fillLabel.textContent = isZero ? \"0%\" : `-${displayPercent}%`;\n    }\n\n    if (baseLabel) {\n      baseLabel.textContent = \"0%\";\n    }\n  }\n\n  \/** Animate percentage with counter *\/\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    \/\/ Add pop animation\n    element.classList.add(\"updating\");\n    setTimeout(() =\u003e {\n      element.classList.remove(\"updating\");\n    }, 400);\n\n    \/\/ Animation duration synced with bar (800ms)\n    const duration = 800; \/\/ Same as CSS transition of bar\n    const steps = 30; \/\/ More steps for smoother animation\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  \/** Render Lizia items (color + personalization) *\/\n  function renderLiziaItems() {\n    \/\/ Preserve step number if present\n    const stepNumber = colorsPersoLabel.querySelector(\".stepLabelNumber\");\n    if (stepNumber) {\n      colorsPersoLabel.innerHTML =\n        '\u003cspan class=\"stepLabelNumber\"\u003e3\u003c\/span\u003e Color';\n    } else {\n      colorsPersoLabel.innerHTML = \"Color\";\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(\n          () =\u003e {\n            renderLiziaItemsContent();\n            previousQuantity = state.quantity;\n            addEventListeners(); \/\/ Ré-attacher les écouteurs après le rendu\n          },\n          300 + itemsToAnimate.length * 50,\n        );\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\"\u003eEngraving (optional)\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=\"Add an engraving\" \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\u003eEngraving (optional)\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=\"E.g. Happy reading!\" \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\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    const refPrice =\n      PACK_ORIGINAL_PRICES[state.pack] || PACK_PRICES[state.pack];\n\n    \/\/ 3. Calculer le prix unitaire remisé\n    let unitPrice;\n    if (state.pack === \"lizia-cable-pouch-tv\") {\n      \/\/ Pack vu à la TV : prix unitaire 24,95 € (réf 33,85 €) → affiché 67,70 barré \/ 49,90 pour qty 2\n      unitPrice = PACK_PRICES[state.pack];\n    } else 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      unitPrice = PACK_PRICES[state.pack];\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    const originalPackPrice = PACK_ORIGINAL_PRICES[state.pack];\n\n    const packNameRaw =\n      (PACKS.find((p) =\u003e p.id === state.pack) || {}).name || \"Pack\";\n    \/\/ Nettoyer le HTML du nom pour le label du panier\n    const packName = packNameRaw.replace(\/\u003cbr\\s*\\\/?\u003e\/gi, \" \");\n    const colorHexById = COLORS.reduce((acc, c) =\u003e {\n      acc[c.id] = c.hex;\n      return acc;\n    }, {});\n\n    const colorBubbles = state.colors\n      .slice(0, state.quantity)\n      .map((c, idx) =\u003e {\n        const hex = colorHexById[c] || \"#ddd\";\n        return `\u003cspan class=\"colorDot\" title=\"Lizia ${\n          idx + 1\n        }: ${c}\" style=\"background-color:${hex};\"\u003e\u003c\/span\u003e`;\n      })\n      .join(\"\");\n\n    const items = [];\n    items.push({\n      label: `${packName} ×${state.quantity}`,\n      value: totalPackPrice,\n    });\n    if (persoCount \u003e 0) {\n      items.push({\n        label: `Engraving (${persoCount})`,\n        value: persoCount * PERSONALIZATION_PRICE,\n      });\n    }\n\n    const priceContent = `\n      \u003cdiv class=\"priceHeader\"\u003e\n        \u003cdiv class=\"priceHeaderLeft\"\u003e\n          ${\n            savings \u003e 0.01\n              ? `\u003cspan class=\"priceOriginal\"\u003e${totalReferencePrice.toFixed(\n                  2,\n                )}€\u003c\/span\u003e`\n              : \"\"\n          }\n          \u003cspan class=\"priceTotal\"\u003e${totalPrice.toFixed(2)}€\u003c\/span\u003e\n        \u003c\/div\u003e\n        ${\n          savings \u003e 0\n            ? `\u003cspan class=\"priceSavingsChip\"\u003e-${savings.toFixed(2)}€\u003c\/span\u003e`\n            : \"\"\n        }\n      \u003c\/div\u003e\n      \u003cdiv class=\"priceDivider\"\u003e\u003c\/div\u003e\n      \u003cdiv class=\"priceItems\"\u003e\n        ${items\n          .map(\n            (it) =\u003e `\n              \u003cdiv class=\"priceItemRow\"\u003e\n                \u003cspan class=\"label\"\u003e${it.label}\u003c\/span\u003e\n                \u003cspan class=\"value\"\u003e${it.value.toFixed(2)}€\u003c\/span\u003e\n              \u003c\/div\u003e`,\n          )\n          .join(\"\")}\n        \u003cdiv class=\"colorSummary\"\u003e\n          \u003cspan class=\"label\"\u003eColors\u003c\/span\u003e\n          \u003cdiv class=\"colorBubbles\"\u003e${colorBubbles}\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    `;\n\n    if (priceDisplayContainer) {\n      priceDisplayContainer.innerHTML = priceContent;\n    }\n\n    \/\/ Mettre à jour les deux boutons avec le prix\n    let buttonText = `ADD TO CART • €${totalPrice.toFixed(2)}`;\n    if (savings \u003e 0.01) {\n      buttonText = `ADD TO CART • \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(\"Video playback error:\", 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(\"Video playback error:\", 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    \/\/ 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  \/**\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(\"Server response error\");\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(\"Add to cart error:\", error);\n        throw error;\n      });\n  }\n\n  \/**\n   * Détermine les cadeaux gratuits selon le montant total affiché\n   * Retourne un array d'objets { variantID, quantity, properties } à ajouter\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        if (state.pack === \"lizia-cable-pouch-tv\") {\n          state.quantity = 2;\n          state.pouch = \"colore\";\n        } else {\n          state.quantity = 1;\n        }\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, le \"i\" ou le logo\n          if (\n            e.target.classList.contains(\"packBadgeTop\") ||\n            e.target.classList.contains(\"badgeTopInfo\") ||\n            e.target.classList.contains(\"badgeTopText\") ||\n            e.target.classList.contains(\"packBadgeTopLogo\") ||\n            e.target.closest(\".packBadgeTop\") ||\n            e.target.closest(\".packInfoBtnTv\")\n          ) {\n            return;\n          }\n          state.pack = packOption.dataset.pack;\n          if (state.pack === \"lizia-cable-pouch-tv\") {\n            state.quantity = 2;\n            state.pouch = \"colore\";\n          } else {\n            state.quantity = 1;\n          }\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        if (e.currentTarget.classList.contains(\"disabled\")) return;\n        const action = e.currentTarget.dataset.action;\n        const value = e.currentTarget.dataset.value;\n\n        if (action === \"set\") {\n          const newQty = parseInt(value);\n          if (state.pack === \"lizia-cable-pouch-tv\" \u0026\u0026 newQty === 1) return;\n          state.quantity = newQty;\n          updateUIOptimized(false);\n        } else if (action === \"increment\") {\n          if (state.quantity \u003c 20) {\n            state.quantity = state.quantity + 1;\n            updateUIOptimized(false);\n          }\n        } else if (action === \"decrement\") {\n          const minQty = state.pack === \"lizia-cable-pouch-tv\" ? 2 : 1;\n          if (state.quantity \u003e minQty) {\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    \/\/ \"i\" du Pack vu à la TV : ouvrir la popup description\n    document.querySelectorAll(\".packInfoBtnTv\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        e.stopPropagation();\n        const packId = e.currentTarget.dataset.pack;\n        if (packId) showPackPopup(packId);\n      };\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) showPackPopup(packId);\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    \/\/ 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    \/\/ ÉTAPE 1 : Préparer 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        const color = state.colors[i];\n        const isPersonalized =\n          state.personalization[i] \u0026\u0026\n          state.personalization[i].trim().length \u003e 0;\n        const variantID = getVariantID(packID, color, isPersonalized);\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(`Impossible de récupérer le variant pour ${color}`);\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(\"Multiple add error:\", error);\n          return;\n        }\n      }\n    }\n\n    \/\/ ÉTAPE 2 : Gérer le premier produit via le formulaire caché (trigger le cart slide)\n    const firstColor = state.colors[0];\n    const firstIsPersonalized =\n      state.personalization[0] \u0026\u0026 state.personalization[0].trim().length \u003e 0;\n    const firstVariantID = getVariantID(\n      packID,\n      firstColor,\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\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    }\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      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\n    const personalizeInput = document.getElementById(personalizeInputID);\n    if (personalizeInput) {\n      personalizeInput.value = firstIsPersonalized\n        ? state.personalization[0]\n        : \"\";\n    }\n\n    \/\/ Cliquer sur le bouton d'ajout au panier caché (trigger le cart slide)\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, 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      return;\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  \/\/ --- M6 LIMITED OFFER COUNTDOWN (Days \/ Hours \/ Minutes \/ Seconds) ---\n  (function initM6Countdown() {\n    \/\/ Countdown end: February 26 at 23:59:59 (midnight 27\/02)\n    const countdownEnd = new Date(2026, 1, 26, 23, 59, 59, 999); \/\/ Feb 26, 2026 at 23:59 (month 0=Jan, 1=Feb)\n    const pad = (n) =\u003e String(Math.max(0, Math.floor(n))).padStart(2, \"0\");\n\n    function updateM6Countdown() {\n      const now = new Date();\n      const diff = countdownEnd - now;\n      if (diff \u003c= 0) {\n        document\n          .querySelectorAll(\".countdown-days\")\n          .forEach((el) =\u003e (el.textContent = \"00\"));\n        document\n          .querySelectorAll(\".countdown-hours\")\n          .forEach((el) =\u003e (el.textContent = \"00\"));\n        document\n          .querySelectorAll(\".countdown-minutes\")\n          .forEach((el) =\u003e (el.textContent = \"00\"));\n        document\n          .querySelectorAll(\".countdown-seconds\")\n          .forEach((el) =\u003e (el.textContent = \"00\"));\n        return;\n      }\n      const days = diff \/ (1000 * 60 * 60 * 24);\n      const hours = (diff % (1000 * 60 * 60 * 24)) \/ (1000 * 60 * 60);\n      const minutes = (diff % (1000 * 60 * 60)) \/ (1000 * 60);\n      const seconds = (diff % (1000 * 60)) \/ 1000;\n      document\n        .querySelectorAll(\".countdown-days\")\n        .forEach((el) =\u003e (el.textContent = pad(days)));\n      document\n        .querySelectorAll(\".countdown-hours\")\n        .forEach((el) =\u003e (el.textContent = pad(hours)));\n      document\n        .querySelectorAll(\".countdown-minutes\")\n        .forEach((el) =\u003e (el.textContent = pad(minutes)));\n      document\n        .querySelectorAll(\".countdown-seconds\")\n        .forEach((el) =\u003e (el.textContent = pad(seconds)));\n    }\n\n    updateM6Countdown();\n    setInterval(updateM6Countdown, 1000);\n  })();\n\n  \/\/ --- REVIEWS ---\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    \/\/ Duplicate cards for infinite effect (3x: before, middle, after)\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      \/\/ 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      \/\/ Sur mobile, charger immédiatement\n      if (window.innerWidth \u003c= 767) {\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\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":"Ma boutique","offers":[{"title":"White \/ Not Personalized","offer_id":47976891023709,"sku":"LAMP-BLA-NP-V1","price":24.95,"currency_code":"EUR","in_stock":true},{"title":"White \/ Custom","offer_id":47976891056477,"sku":"LAMP-BLA-PE-V1","price":29.95,"currency_code":"EUR","in_stock":true},{"title":"White \/ Father's Day","offer_id":51647359451485,"sku":"LAMP-BLA-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":true},{"title":"Black \/ Not Personalized","offer_id":47976891089245,"sku":"LAMP-NOI-NP-V1","price":24.95,"currency_code":"EUR","in_stock":true},{"title":"Black \/ Custom","offer_id":47976891122013,"sku":"LAMP-NOI-PE-V1","price":29.95,"currency_code":"EUR","in_stock":true},{"title":"Black \/ Father's Day","offer_id":51647359484253,"sku":"LAMP-NOI-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":true},{"title":"Red \/ Not Personalized","offer_id":47976891154781,"sku":"LAMP-ROU-NP-V1","price":24.95,"currency_code":"EUR","in_stock":false},{"title":"Red \/ Custom","offer_id":47976891187549,"sku":"LAMP-ROU-PE-V1","price":29.95,"currency_code":"EUR","in_stock":false},{"title":"Red \/ Father's Day","offer_id":51647359517021,"sku":"LAMP-ROU-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":false},{"title":"Green \/ Not Personalized","offer_id":47976891220317,"sku":"LAMP-VER-NP-V1","price":24.95,"currency_code":"EUR","in_stock":true},{"title":"Green \/ Custom","offer_id":47976891253085,"sku":"LAMP-VER-PE-V1","price":29.95,"currency_code":"EUR","in_stock":true},{"title":"Green \/ Father's Day","offer_id":51647359549789,"sku":"LAMP-VER-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":true},{"title":"Blue \/ Not Personalized","offer_id":47976891285853,"sku":"LAMP-BLE-NP-V1","price":24.95,"currency_code":"EUR","in_stock":true},{"title":"Blue \/ Custom","offer_id":47976891318621,"sku":"LAMP-BLE-PE-V1","price":29.95,"currency_code":"EUR","in_stock":true},{"title":"Blue \/ Father's Day","offer_id":51647359582557,"sku":"LAMP-BLE-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":true},{"title":"Rose \/ Not Personalized","offer_id":54484174340445,"sku":"LAMP-ROS-NP-V1","price":24.95,"currency_code":"EUR","in_stock":false},{"title":"Rose \/ Custom","offer_id":54484174373213,"sku":"LAMP-ROS-PE-V1","price":29.95,"currency_code":"EUR","in_stock":false},{"title":"Rose \/ Father's Day","offer_id":54484174405981,"sku":"LAMP-ROS-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":false},{"title":"Violet \/ Not Personalized","offer_id":54484174438749,"sku":"LAMP-VIO-NP-V1","price":24.95,"currency_code":"EUR","in_stock":true},{"title":"Violet \/ Custom","offer_id":54484174471517,"sku":"LAMP-VIO-PE-V1","price":29.95,"currency_code":"EUR","in_stock":true},{"title":"Violet \/ Father's Day","offer_id":54484174504285,"sku":"LAMP-VIO-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":true},{"title":"Orange \/ Not Personalized","offer_id":54980518543709,"sku":"LAMP-ORA-NP-V1","price":24.95,"currency_code":"EUR","in_stock":false},{"title":"Orange \/ Custom","offer_id":54980518576477,"sku":"LAMP-ORA-PE-V1","price":29.95,"currency_code":"EUR","in_stock":false},{"title":"Orange \/ Father's Day","offer_id":54980518609245,"sku":"LAMP-ORA-PREPER-V1","price":27.95,"currency_code":"EUR","in_stock":false}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/9_804a8c58-a6fd-44af-b722-1c5c2cf5fae5.png?v=1734085996"},{"product_id":"pochette-microfibre","title":"Microfiber pouch","description":"\u003c!-- SECTION HÉROS PRODUIT --\u003e\n\u003csection id=\"productHero\" class=\"containerLizia\"\u003e\n  \u003c!-- Satisfied readers badge (mobile) --\u003e\n  \u003cdiv class=\"badgeReadersMobile mobileOnly\"\u003e\n    \u003cspan class=\"badgeReaders\"\u003e⭐ +100,000 satisfied readers\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\n          src=\"..\/public\/placeholder.svg\"\n          alt=\"Microfiber pouch\"\n          id=\"mainProductImage\"\n          class=\"mainImage\"\n        \/\u003e\n        \u003cbutton\n          id=\"prevImageBtn\"\n          class=\"galleryNavBtn prev\"\n          aria-label=\"Previous image\"\n        \u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n        \u003cbutton\n          id=\"nextImageBtn\"\n          class=\"galleryNavBtn next\"\n          aria-label=\"Next image\"\n        \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\"\n          \u003e⭐ +100,000 satisfied readers\u003c\/span\n        \u003e\n        \u003ch1\u003eMicrofiber pouch\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 reviews)\u003c\/span\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"productSubtitle\"\u003e\n          \u003c!-- Microfiber pouch --\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              Quantity\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\"\u003eDiscount on your order\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          Personalization\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          ADD TO CART\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\"\u003eDelivery in 3 business days\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\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_fr.webp?v=1762266031\"\n              alt=\"Made In France\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eMADE IN FRANCE\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eFrench Quality\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_garanti.webp?v=1762266032\"\n              alt=\"2 ans de garantie\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e2 ANS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eWarranty\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_rembourser.webp?v=1762267150\"\n              alt=\"15 jours satisfaits ou remboursé\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e15 JOURS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eSatisfied or refunded\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_livraison.webp?v=1762266031\"\n              alt=\"Livré en 1 à 3 jours\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eLIVRÉ\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003e1-5 days\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cdiv\n      id=\"configEndSentinel\"\n      class=\"configEndSentinel\"\n      style=\"height: 1px; width: 100%\"\n    \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      How does Lizia work?\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\"\u003eRead with one hand\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Lizia slips around your thumb and holds the book perfectly open, wherever you are.\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\"\u003eIlluminates\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Enjoy a soft and precise light that doesn't dazzle or disturb those around you.\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\"\u003eBookmark\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Never lose your page again thanks to its integrated and practical bookmark function.\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\n            class=\"liziaVideo\"\n            data-video-id=\"1\"\n            playsinline\n            muted\n            preload=\"metadata\"\n          \u003e\n            \u003csource\n              src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/6270dc5936c44a3a97d40e48209e8199.mp4\"\n              type=\"video\/mp4\"\n            \/\u003e\n          \u003c\/video\u003e\n          \u003cdiv class=\"videoControls\"\u003e\n            \u003cbutton\n              class=\"videoPlayBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Play\/Pause\"\n            \u003e\n              \u003csvg\n                class=\"playIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath d=\"M8 5v14l11-7z\" \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"pauseIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" \/\u003e\n              \u003c\/svg\u003e\n            \u003c\/button\u003e\n            \u003cbutton\n              class=\"videoMuteBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Mute\/Unmute\"\n            \u003e\n              \u003csvg\n                class=\"unmuteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"muteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\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          Reading has never been so comfortable\n        \u003c\/h2\u003e\n        \u003ch3 class=\"beforeAfterSubtitle\" data-scroll-reveal\u003e\n          Without fatigue, with one hand\n        \u003c\/h3\u003e\n        \u003cp class=\"beforeAfterDescription\" data-scroll-reveal\u003e\n          Adapted to all thumbs, gain flexibility wherever you are. Effortlessly enjoy your reading, even in the dark without disturbing anyone.\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\n              id=\"lightToggleBtn\"\n              class=\"lightToggleBtn\"\n              aria-label=\"Toggle light\"\n            \u003e\n              \u003cspan class=\"lightToggleOn\"\u003e\n                \u003csvg\n                  xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"white\"\n                  width=\"16\"\n                  height=\"16\"\n                \u003e\n                  \u003cpath\n                    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\"\n                  \/\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\n            id=\"beforeAfterImageOff\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\"\n            alt=\"Lizia light off\"\n            class=\"beforeAfterImage beforeAfterImageOff\"\n          \/\u003e\n          \u003cimg\n            id=\"beforeAfterImageOn\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\"\n            alt=\"Lizia light on\"\n            class=\"beforeAfterImage beforeAfterImageOn\"\n          \/\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\n        href=\"https:\/\/www.europe1.fr\/emissions\/demain-au-bureau\/lizia-laccessoire-de-lecture-3-en-1-4163529\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_europe1.webp?v=1763465558\"\n          alt=\"Europe1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_quotidien.webp?v=1763465558\"\n          alt=\"Quotidien\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_bfm_business.webp?v=1763465558\"\n          alt=\"Bfm-tv-business\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.ouest-france.fr\/bretagne\/roudouallec-56110\/roudouallec-ils-creent-un-accessoire-de-lecture-innovant-16121898-f0a2-11ec-9262-0032434e6459\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_ouest_france.webp?v=1763465558\"\n          alt=\"Ouest-France\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tf1.webp?v=1763465558\"\n          alt=\"Tf1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.letelegramme.fr\/morbihan\/roudouallec-56110\/deux-jeunes-bretons-donnent-un-coup-de-pouce-aux-lecteurs-avec-lizia-281522.php\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_telegramme.webp?v=1763465558\"\n          alt=\"Le-telegramme\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.m6.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\"\n          alt=\"M6\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/entrepreneurs.lesechos.fr\/developpement-entreprise\/strategie\/de-linvention-futee-au-succes-commercial-lizia-a-la-conquete-des-fanas-de-lecture-2094778\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_les_echos.webp?v=1763465557\"\n          alt=\"Les-echos\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tmc.webp?v=1763465559\"\n          alt=\"TMC\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.nrj.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_nrj.webp?v=1763465558\"\n          alt=\"NRJ\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.7jours.fr\/actualites\/lizia-rennes-jeune-pousse-eclaire-lecture\/\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_7_jours.webp?v=1763465558\"\n          alt=\"7-jours\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.lejournaldesentreprises.com\/breve\/lizia-lance-son-accessoire-de-lecture-en-belgique-et-en-suisse-2129508\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_le_journal_des_entreprises.webp?v=1763465558\"\n          alt=\"le-journal-des-entreprises\"\n        \/\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\"\u003eLIZIA'S COMMITMENTS\u003c\/h2\u003e\n      \u003cp id=\"valuesSubtitle\" class=\"valuesSubtitle\"\u003e\n        Conceived in Rennes, Lizia is a French innovation designed to offer lasting reading comfort, with local partners and responsible materials.\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\n          class=\"valueItem active\"\n          data-index=\"0\"\n          data-title=\"Made in France\"\n          data-desc=\"Our nylon parts are produced in Western France then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain, and local industrial expertise, from design to shipping from Rennes.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- France Map Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_france.svg?v=1763826898\"\n              alt=\"France\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eMade in France\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003e\n              Production and assembly in the West\n            \u003c\/p\u003e\n            \u003c!-- Mobile Description (Hidden by default) --\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our nylon parts are produced in Western France then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain, and local industrial expertise, from design to shipping from Rennes.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 2 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"1\"\n          data-title=\"Assembled in ESAT\"\n          data-desc=\"Lizia is socially committed by entrusting the assembly of its products to partner ESATs (Establishments and Services for Assistance through Work) in Brittany, promoting the professional integration of people with disabilities.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Wheelchair\/Accessibility Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_esat.svg?v=1763826898\"\n              alt=\"ESAT\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAssembled in ESAT\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eLocal partners\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia is socially committed by entrusting the assembly of its products to partner ESATs (Establishments and Services for Assistance through Work) in Brittany, promoting the professional integration of people with disabilities.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 3 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"2\"\n          data-title=\"Recyclable materials\"\n          data-desc=\"We use technical PA12 for its robustness and durability, as well as 100% recyclable cardboard packaging. Our eco-design approach aims to minimize our environmental impact.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Leaf\/Recycle Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_recyclable.svg?v=1763826898\"\n              alt=\"Recyclable\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eRecyclable materials\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eTechnical PA12, recyclable cardboard\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              We use technical PA12 for its robustness and durability, as well as 100% recyclable cardboard packaging. Our eco-design approach aims to minimize our environmental impact.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 4 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"3\"\n          data-title=\"Awarded at Lépine\"\n          data-desc=\"The Lizia innovation has been recognized and awarded a medal at the prestigious Lépine Competition, a testament to its ingenuity and quality.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Medal Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_medaille.svg?v=1763826898\"\n              alt=\"Médaille\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAwarded at Lépine\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eAward-winning innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              The Lizia innovation has been recognized and awarded a medal at the prestigious Lépine Competition, a testament to its ingenuity and quality.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 5 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"4\"\n          data-title=\"Internationally patented\"\n          data-desc=\"Our unique technology is protected by international patents, ensuring the exclusivity of our one-hand reading solution.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Globe\/Patent Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_brevet.svg?v=1763996122\"\n              alt=\"Breveté\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eInternationally patented\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eProtected innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our unique technology is protected by international patents, ensuring the exclusivity of our one-hand reading solution.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 6 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"5\"\n          data-title=\"Created by 2 students\"\n          data-desc=\"Lizia was born from the passion and entrepreneurship of two students, determined to improve the daily lives of readers.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Users\/Students Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_etudiant.svg?v=1763996122\"\n              alt=\"Étudiants\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eCreated by 2 students\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eYoung innovative company\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia was born from the passion and entrepreneurship of two students, determined to improve the daily lives of readers.\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\"\u003eMade in France\u003c\/h3\u003e\n          \u003cp id=\"detailDesc\" class=\"detailDesc\"\u003e\n            Our nylon parts are produced in Western France then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain, and local industrial expertise, from design to shipping from 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        Adopted by many readers\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 reviews\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cbutton class=\"reviewsNavBtn prev\" aria-label=\"Previous reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \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=\"Next reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \u003e\n        \u003cpolyline points=\"9 18 15 12 9 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cdiv\n      id=\"reviewsContainer\"\n      class=\"reviewsContainer\"\n      data-scroll-reveal\n    \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\u0026times;\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 POCHETTE MICROFIBRE --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_8750054539613\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"8750054539613\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \n  \u003cdiv class=\"radio-wrapper\" style=\"display: none;\"\u003e\n    \u003cdiv class=\"single-option-radio\" data-option=\"option1\" id=\"ProductSelect_8750054539613-option-0\"\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Default Title\"\n        name=\"Title\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_49177953698141 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8750054539613-option-0_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8750054539613-option-0_1\"\u003eDefault Title\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_8750054539613\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"595\"\n      data-price_o=\"495\"\n      data-compare_at_price=\"5,95 EUR\"\n      data-price=\"4,95 EUR\"\n      selected=\"selected\"\n      data-sku=\"POCH-MIC-V1\"\n      value=\"49177953698141\"\n    \u003e\n      Default Title — 4,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_8750054539613\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_8750054539613\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_8750054539613\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',495,8750054539613)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"8750054539613\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_8750054539613 button\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_8750054539613\"\n      \u003eADD TO CART\u003c\/span\n    \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=\"8750054539613\" \/\u003e\n\u003c\/form\u003e\n\n\u003cscript\u003e\ndocument.addEventListener(\"DOMContentLoaded\", () =\u003e {\n  \/\/ --- CONSTANTES ET DONNÉES ---\n  const POCHETTE_PRICE = 4.95; \/\/ Prix fixe de la pochette\n  const POCHETTE_ORIGINAL_PRICE = 5.95; \/\/ Prix barré de la pochette\n\n  const PACK_PRICES = {\n    \"pochette-only\": 4.95, \/\/ Prix fixe\n  };\n  const PACK_ORIGINAL_PRICES = {\n    \"pochette-only\": 5.95, \/\/ Prix barré\n  };\n\n  \/\/ Mapping des variants Shopify\n  const variantIDs = {\n    \/\/ Pochette seule - un seul variant\n    8750054539613: {\n      Default: { normal: \"49177953698141\" },\n    },\n  };\n\n  \/\/ Mapping des noms de packs vers leurs IDs Shopify\n  const packIDMapping = {\n    \"pochette-only\": \"8750054539613\",\n  };\n\n  \/\/ Reviews data\n  const reviewsData = [\n    {\n      firstName: \"Laura\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"A great discovery!\",\n      content: \"I received it and it's super cool!\",\n      verified: true,\n    },\n    {\n      firstName: \"Nathalie\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Easy reading without disturbing others, perfect!\",\n      content:\n        \"I received my Lizia a few days ago. It's Christmas in September, what a joy to be able to read in my bed without disturbing my partner, on public transport...\",\n      verified: true,\n    },\n    {\n      firstName: \"Audrey\",\n      lastName: \"T\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Practical and reliable, I've been using it for a long time\",\n      content:\n        \"It's great for reading, I've had one for several months and it's top!\",\n      verified: true,\n    },\n    {\n      firstName: \"Sophie\",\n      lastName: \"D\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Essential for my reading evenings\",\n      content:\n        \"I can't do without it anymore! Perfect for reading in the evening without turning on the bedroom light.\",\n      verified: true,\n    },\n    {\n      firstName: \"Marc\",\n      lastName: \"L\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juin\", year: 2025 },\n      title: \"Perfect gift for readers\",\n      content:\n        \"Given to my wife, she loves it! The quality is there and the design is elegant.\",\n      verified: true,\n    },\n    {\n      firstName: \"Émilie\",\n      lastName: \"R\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Lizia revolutionizes my reading moments\",\n      content:\n        \"No more tired arms and light problems! I recommend it 100%.\",\n      verified: true,\n    },\n    {\n      firstName: \"Thomas\",\n      lastName: \"B\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Excellent product, meets my expectations\",\n      content:\n        \"Fast delivery, quality product. I now read everywhere without constraints.\",\n      verified: true,\n    },\n    {\n      firstName: \"Charlotte\",\n      lastName: \"V\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"A must-have for all readers\",\n      content:\n        \"Simple, effective and so practical. My children even asked for theirs!\",\n      verified: true,\n    },\n    {\n      firstName: \"Pierre\",\n      lastName: \"G\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"mai\", year: 2025 },\n      title: \"Top French quality\",\n      content:\n        \"Happy to support a French company. The product is robust and well thought out.\",\n      verified: true,\n    },\n    {\n      firstName: \"Isabelle\",\n      lastName: \"F\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Perfect for travel reading\",\n      content:\n        \"I take it everywhere with me: train, plane, hotel. It has become my essential accessory.\",\n      verified: true,\n    },\n    {\n      firstName: \"Antoine\",\n      lastName: \"H\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juin\", year: 2025 },\n      title: \"Comfortable and discreet\",\n      content:\n        \"The lamp is powerful but doesn't disturb anyone. Ideal for nocturnal readings.\",\n      verified: true,\n    },\n    {\n      firstName: \"Camille\",\n      lastName: \"P\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Elegant and functional design\",\n      content:\n        \"I love the sleek design and available colors. Plus, it's super practical!\",\n      verified: true,\n    },\n    {\n      firstName: \"Julien\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Excellent value for money\",\n      content:\n        \"For the price, it's really an excellent investment. I recommend it without hesitation.\",\n      verified: true,\n    },\n    {\n      firstName: \"Léa\",\n      lastName: \"S\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"My children love it too\",\n      content:\n        \"My children use it every evening for their bedtime reading. It has become a ritual!\",\n      verified: true,\n    },\n    {\n      firstName: \"Maxime\",\n      lastName: \"C\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Innovation for reading\",\n      content:\n        \"Finally a product that thinks about the real needs of readers. Bravo for this innovation!\",\n      verified: true,\n    },\n  ];\n\n  \/\/ Images des packs (Pochette n'a qu'un seul pack)\n  const PACK_IMAGES_BY_COLOR = {\n    \"pochette-only\": {\n      default: \"https:\/\/lizia.fr\/cdn\/shop\/files\/presentation_pochette_micro_600x600.jpg?v=1766095696\",\n    },\n  };\n\n  const PACKS = [\n    {\n      id: \"pochette-only\",\n      name: \"Microfiber pouch\",\n      price: 4.95,\n      originalPrice: 5.95,\n      short: \"Pouch\",\n      description:\n        \"Microfiber pouch perfect for protecting your Lizia. Its attractive colors (red and green) make it an ideal pouch for gifting a Lizia at Christmas.\",\n      features: [\n        \"Microfiber pouch\",\n        \"Optimal protection\",\n        \"Attractive colors\",\n        \"Ideal for gifting\",\n      ],\n      image:\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/presentation_pochette_micro_600x600.jpg?v=1766095696\",\n    },\n  ];\n\n  \/\/ Images par variante (Pochette n'a qu'un seul pack)\n  const PRODUCT_IMAGES_BY_VARIANT = {\n    \"pochette-only\": {\n      default: [\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/presentation_pochette_micro_600x600.jpg?v=1766095696\",\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    \/\/ Pochette n'a qu'un seul pack avec des images par défaut\n    if (pack === \"pochette-only\") {\n      return (\n        PRODUCT_IMAGES_BY_VARIANT[pack]?.[\"default\"] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"pochette-only\"][\"default\"]\n      );\n    }\n\n    \/\/ Fallback\n    return PRODUCT_IMAGES_BY_VARIANT[\"pochette-only\"][\"default\"];\n  }\n\n  \/\/ --- ÉTAT DE L'APPLICATION ---\n  let state = {\n    pack: \"pochette-only\", \/\/ Pack par défaut : Pochette seule\n    quantity: 1,\n    colors: [], \/\/ Non utilisé pour Pochette\n    personalization: [], \/\/ Non utilisé pour Pochette\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\n  function getPackImage(packId, colorId = null) {\n    \/\/ Pochette n'a qu'un seul pack, retourner directement l'image\n    if (packId === \"pochette-only\") {\n      return PACKS.find((p) =\u003e p.id === packId)?.image;\n    }\n\n    \/\/ Fallback\n    return PACKS.find((p) =\u003e p.id === \"pochette-only\")?.image;\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 Offer\" : 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=\"Learn more about ${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 (Pochette n'a pas de réduction) *\/\n  function calculateTotalDiscount() {\n    \/\/ Pochette n'a aucune réduction, prix fixe à 4.95€ par unité\n    return {\n      packDiscount: 0,\n      quantityDiscount: 0,\n      total: 0,\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    \/\/ Pochette n'a pas de couleurs ni personnalisation, masquer la section\n    const liziaItemsSection = document.getElementById(\"liziaItemsSection\");\n    if (liziaItemsSection) {\n      liziaItemsSection.style.display = \"none\";\n    }\n    if (liziaItemsContainer) {\n      liziaItemsContainer.innerHTML = \"\";\n    }\n    return;\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    \/\/ Prix unitaire fixe : 4.95€ pour Pochette\n    const unitPrice = PACK_PRICES[state.pack]; \/\/ 4.95€\n    const originalUnitPrice = PACK_ORIGINAL_PRICES[state.pack] || unitPrice; \/\/ 5.95€\n\n    \/\/ Calcul simple : prix unitaire × quantité (pas de réduction)\n    const totalPrice = unitPrice * state.quantity;\n\n    \/\/ Calculer l'économie réalisée (Prix Original * Qté - Prix Payé)\n    const totalReferencePrice = originalUnitPrice * state.quantity;\n    const savings = totalReferencePrice - totalPrice;\n\n    \/\/ Mettre à jour les deux boutons avec le prix\n    let buttonText = `ADD TO CART • ${totalPrice.toFixed(2)}€`;\n    if (savings \u003e 0.01) {\n      buttonText = `ADD TO CART • \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    \/\/ Pochette n'a pas de couleurs ni personnalisation, pas besoin de synchroniser\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    \/\/ Pochette n'a pas de couleurs ni personnalisation, pas besoin de synchroniser\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\n   *\/\n  function getVariantID(packID, color, isPersonalized) {\n    \/\/ Pochette : un seul variant, pas de couleur ni personnalisation\n    if (packID === \"8750054539613\") {\n      return variantIDs[packID].Default.normal; \/\/ Retourne le variant ID depuis le mapping\n    }\n\n    \/\/ Fallback\n    console.error(`Variant non trouvé pour pack ${packID}`);\n    return null;\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 et personnalisation : non utilisées pour Markia\n    \/\/ Les event listeners sont laissés pour compatibilité mais ne feront rien\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    \/\/ Pochette : produit simple, pas de couleurs ni personnalisation\n    const packID = packIDMapping[state.pack]; \/\/ \"8750054539613\"\n    const productsToAdd = [];\n\n    \/\/ Gérer les produits 2 à X (si quantité \u003e 1)\n    if (state.quantity \u003e 1) {\n      const variantID = getVariantID(packID, \"Default\", false);\n      if (variantID) {\n        productsToAdd.push({\n          variantID: variantID,\n          quantity: state.quantity - 1, \/\/ Ajouter quantity - 1 via l'API\n          properties: {},\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    const firstVariantID = getVariantID(packID, \"Default\", false);\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    \/\/ Sélectionner le variant dans le formulaire caché\n    const optionSelector = `#ProductSelect_${packID} option[value=\"${firstVariantID}\"]`;\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:\",\n        firstVariantID\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_${packID}`);\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    }\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      \/\/ 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 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\"\u003eVerified\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":"Ma boutique","offers":[{"title":"Default Title","offer_id":49177953698141,"sku":"POCH-MIC-V1","price":4.95,"currency_code":"EUR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/presentation_pochette_micro.jpg?v=1766095696"},{"product_id":"pochette-coton-et-lin","title":"Cotton and linen pouch","description":"\u003c!-- SECTION HÉROS PRODUIT --\u003e\n\u003csection id=\"productHero\" class=\"containerLizia\"\u003e\n  \u003c!-- Satisfied readers badge (mobile) --\u003e\n  \u003cdiv class=\"badgeReadersMobile mobileOnly\"\u003e\n    \u003cspan class=\"badgeReaders\"\u003e⭐ +100,000 satisfied readers\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\n          src=\"..\/public\/placeholder.svg\"\n          alt=\"Cotton and linen pouch\"\n          id=\"mainProductImage\"\n          class=\"mainImage\"\n        \/\u003e\n        \u003cbutton\n          id=\"prevImageBtn\"\n          class=\"galleryNavBtn prev\"\n          aria-label=\"Previous image\"\n        \u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n        \u003cbutton\n          id=\"nextImageBtn\"\n          class=\"galleryNavBtn next\"\n          aria-label=\"Next image\"\n        \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\"\n          \u003e⭐ +100,000 satisfied readers\u003c\/span\n        \u003e\n        \u003ch1\u003eCotton and linen pouch\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 reviews)\u003c\/span\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"productSubtitle\"\u003e\n          \u003c!-- Microfiber pouch --\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              Quantity\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\"\u003eDiscount on your order\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          Personalization\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          ADD TO CART\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\"\u003eDelivery in 3 business days\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\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_fr.webp?v=1762266031\"\n              alt=\"Made In France\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eMADE IN FRANCE\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eFrench Quality\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_garanti.webp?v=1762266032\"\n              alt=\"2 ans de garantie\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e2 ANS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eWarranty\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_rembourser.webp?v=1762267150\"\n              alt=\"15 jours satisfaits ou remboursé\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e15 JOURS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eSatisfied or refunded\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_livraison.webp?v=1762266031\"\n              alt=\"Livré en 1 à 3 jours\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eLIVRÉ\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003e1-5 days\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cdiv\n      id=\"configEndSentinel\"\n      class=\"configEndSentinel\"\n      style=\"height: 1px; width: 100%\"\n    \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      How does Lizia work?\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\"\u003eRead with one hand\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Lizia slips around your thumb and holds the book perfectly open, wherever you are.\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\"\u003eIlluminates\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Enjoy a soft and precise light that doesn't dazzle or disturb those around you.\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\"\u003eBookmark\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Never lose your page again thanks to its integrated and practical bookmark function.\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\n            class=\"liziaVideo\"\n            data-video-id=\"1\"\n            playsinline\n            muted\n            preload=\"metadata\"\n          \u003e\n            \u003csource\n              src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/6270dc5936c44a3a97d40e48209e8199.mp4\"\n              type=\"video\/mp4\"\n            \/\u003e\n          \u003c\/video\u003e\n          \u003cdiv class=\"videoControls\"\u003e\n            \u003cbutton\n              class=\"videoPlayBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Play\/Pause\"\n            \u003e\n              \u003csvg\n                class=\"playIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath d=\"M8 5v14l11-7z\" \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"pauseIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" \/\u003e\n              \u003c\/svg\u003e\n            \u003c\/button\u003e\n            \u003cbutton\n              class=\"videoMuteBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Mute\/Unmute\"\n            \u003e\n              \u003csvg\n                class=\"unmuteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"muteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\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          Reading has never been so comfortable\n        \u003c\/h2\u003e\n        \u003ch3 class=\"beforeAfterSubtitle\" data-scroll-reveal\u003e\n          Without fatigue, with one hand\n        \u003c\/h3\u003e\n        \u003cp class=\"beforeAfterDescription\" data-scroll-reveal\u003e\n          Adapted to all thumbs, gain flexibility wherever you are. Effortlessly enjoy your reading, even in the dark without disturbing anyone.\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\n              id=\"lightToggleBtn\"\n              class=\"lightToggleBtn\"\n              aria-label=\"Toggle light\"\n            \u003e\n              \u003cspan class=\"lightToggleOn\"\u003e\n                \u003csvg\n                  xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"white\"\n                  width=\"16\"\n                  height=\"16\"\n                \u003e\n                  \u003cpath\n                    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\"\n                  \/\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\n            id=\"beforeAfterImageOff\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\"\n            alt=\"Lizia light off\"\n            class=\"beforeAfterImage beforeAfterImageOff\"\n          \/\u003e\n          \u003cimg\n            id=\"beforeAfterImageOn\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\"\n            alt=\"Lizia light on\"\n            class=\"beforeAfterImage beforeAfterImageOn\"\n          \/\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\n        href=\"https:\/\/www.europe1.fr\/emissions\/demain-au-bureau\/lizia-laccessoire-de-lecture-3-en-1-4163529\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_europe1.webp?v=1763465558\"\n          alt=\"Europe1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_quotidien.webp?v=1763465558\"\n          alt=\"Quotidien\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_bfm_business.webp?v=1763465558\"\n          alt=\"Bfm-tv-business\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.ouest-france.fr\/bretagne\/roudouallec-56110\/roudouallec-ils-creent-un-accessoire-de-lecture-innovant-16121898-f0a2-11ec-9262-0032434e6459\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_ouest_france.webp?v=1763465558\"\n          alt=\"Ouest-France\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tf1.webp?v=1763465558\"\n          alt=\"Tf1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.letelegramme.fr\/morbihan\/roudouallec-56110\/deux-jeunes-bretons-donnent-un-coup-de-pouce-aux-lecteurs-avec-lizia-281522.php\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_telegramme.webp?v=1763465558\"\n          alt=\"Le-telegramme\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.m6.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\"\n          alt=\"M6\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/entrepreneurs.lesechos.fr\/developpement-entreprise\/strategie\/de-linvention-futee-au-succes-commercial-lizia-a-la-conquete-des-fanas-de-lecture-2094778\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_les_echos.webp?v=1763465557\"\n          alt=\"Les-echos\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tmc.webp?v=1763465559\"\n          alt=\"TMC\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.nrj.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_nrj.webp?v=1763465558\"\n          alt=\"NRJ\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.7jours.fr\/actualites\/lizia-rennes-jeune-pousse-eclaire-lecture\/\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_7_jours.webp?v=1763465558\"\n          alt=\"7-jours\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.lejournaldesentreprises.com\/breve\/lizia-lance-son-accessoire-de-lecture-en-belgique-et-en-suisse-2129508\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_le_journal_des_entreprises.webp?v=1763465558\"\n          alt=\"le-journal-des-entreprises\"\n        \/\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\"\u003eLIZIA'S COMMITMENTS\u003c\/h2\u003e\n      \u003cp id=\"valuesSubtitle\" class=\"valuesSubtitle\"\u003e\n        Conceived in Rennes, Lizia is a French innovation designed to offer lasting reading comfort, with local partners and responsible materials.\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\n          class=\"valueItem active\"\n          data-index=\"0\"\n          data-title=\"Made in France\"\n          data-desc=\"Our nylon parts are produced in Western France then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain, and local industrial expertise, from design to shipping from Rennes.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- France Map Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_france.svg?v=1763826898\"\n              alt=\"France\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eMade in France\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003e\n              Production and assembly in the West\n            \u003c\/p\u003e\n            \u003c!-- Mobile Description (Hidden by default) --\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our nylon parts are produced in Western France then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain, and local industrial expertise, from design to shipping from Rennes.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 2 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"1\"\n          data-title=\"Assembled in ESAT\"\n          data-desc=\"Lizia is socially committed by entrusting the assembly of its products to partner ESATs (Establishments and Services for Assistance through Work) in Brittany, promoting the professional integration of people with disabilities.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Wheelchair\/Accessibility Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_esat.svg?v=1763826898\"\n              alt=\"ESAT\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAssembled in ESAT\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eLocal partners\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia is socially committed by entrusting the assembly of its products to partner ESATs (Establishments and Services for Assistance through Work) in Brittany, promoting the professional integration of people with disabilities.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 3 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"2\"\n          data-title=\"Recyclable materials\"\n          data-desc=\"We use technical PA12 for its robustness and durability, as well as 100% recyclable cardboard packaging. Our eco-design approach aims to minimize our environmental impact.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Leaf\/Recycle Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_recyclable.svg?v=1763826898\"\n              alt=\"Recyclable\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eRecyclable materials\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eTechnical PA12, recyclable cardboard\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              We use technical PA12 for its robustness and durability, as well as 100% recyclable cardboard packaging. Our eco-design approach aims to minimize our environmental impact.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 4 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"3\"\n          data-title=\"Awarded at Lépine\"\n          data-desc=\"The Lizia innovation has been recognized and awarded a medal at the prestigious Lépine Competition, a testament to its ingenuity and quality.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Medal Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_medaille.svg?v=1763826898\"\n              alt=\"Médaille\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAwarded at Lépine\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eAward-winning innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              The Lizia innovation has been recognized and awarded a medal at the prestigious Lépine Competition, a testament to its ingenuity and quality.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 5 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"4\"\n          data-title=\"Internationally patented\"\n          data-desc=\"Our unique technology is protected by international patents, ensuring the exclusivity of our one-hand reading solution.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Globe\/Patent Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_brevet.svg?v=1763996122\"\n              alt=\"Breveté\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eInternationally patented\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eProtected innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our unique technology is protected by international patents, ensuring the exclusivity of our one-hand reading solution.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 6 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"5\"\n          data-title=\"Created by 2 students\"\n          data-desc=\"Lizia was born from the passion and entrepreneurship of two students, determined to improve the daily lives of readers.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Users\/Students Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_etudiant.svg?v=1763996122\"\n              alt=\"Étudiants\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eCreated by 2 students\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eYoung innovative company\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia was born from the passion and entrepreneurship of two students, determined to improve the daily lives of readers.\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\"\u003eMade in France\u003c\/h3\u003e\n          \u003cp id=\"detailDesc\" class=\"detailDesc\"\u003e\n            Our nylon parts are produced in Western France then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain, and local industrial expertise, from design to shipping from 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        Adopted by many readers\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 reviews\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cbutton class=\"reviewsNavBtn prev\" aria-label=\"Previous reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \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=\"Next reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \u003e\n        \u003cpolyline points=\"9 18 15 12 9 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cdiv\n      id=\"reviewsContainer\"\n      class=\"reviewsContainer\"\n      data-scroll-reveal\n    \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\u0026times;\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 POCHETTE MICROFIBRE --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_8750055686493\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"8750055686493\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \n  \u003cdiv class=\"radio-wrapper\" style=\"display: none;\"\u003e\n    \u003cdiv class=\"single-option-radio\" data-option=\"option1\" id=\"ProductSelect_8750055686493-option-0\"\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Default Title\"\n        name=\"Title\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_49177965265245 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8750055686493-option-0_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8750055686493-option-0_1\"\u003eDefault Title\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_8750055686493\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"595\"\n      data-price_o=\"495\"\n      data-compare_at_price=\"5,95 EUR\"\n      data-price=\"4,95 EUR\"\n      selected=\"selected\"\n      data-sku=\"POCH-COT-V1\"\n      value=\"49177965265245\"\n    \u003e\n      Default Title — 4,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_8750055686493\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_8750055686493\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_8750055686493\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',495,8750055686493)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"8750055686493\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_8750055686493 button\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_8750055686493\"\n      \u003eADD TO CART\u003c\/span\n    \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=\"8750055686493\" \/\u003e\n\u003c\/form\u003e\n\n\u003cscript\u003e\ndocument.addEventListener(\"DOMContentLoaded\", () =\u003e {\n  \/\/ --- CONSTANTES ET DONNÉES ---\n  const POCHETTE_PRICE = 4.95; \/\/ Prix fixe de la pochette\n  const POCHETTE_ORIGINAL_PRICE = 5.95; \/\/ Prix barré de la pochette\n\n  const PACK_PRICES = {\n    \"pochette-only\": 4.95, \/\/ Prix fixe\n  };\n  const PACK_ORIGINAL_PRICES = {\n    \"pochette-only\": 5.95, \/\/ Prix barré\n  };\n\n  \/\/ Mapping des variants Shopify\n  const variantIDs = {\n    \/\/ Pochette seule - un seul variant\n    8750055686493: {\n      Default: { normal: \"49177965265245\" },\n    },\n  };\n\n  \/\/ Mapping des noms de packs vers leurs IDs Shopify\n  const packIDMapping = {\n    \"pochette-only\": \"8750055686493\",\n  };\n\n  \/\/ Reviews data\n  const reviewsData = [\n    {\n      firstName: \"Laura\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"A great discovery!\",\n      content: \"I received it and it's super cool!\",\n      verified: true,\n    },\n    {\n      firstName: \"Nathalie\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Easy reading without disturbing others, perfect!\",\n      content:\n        \"I received my Lizia a few days ago. It's Christmas in September, what a joy to be able to read in my bed without disturbing my partner, on public transport...\",\n      verified: true,\n    },\n    {\n      firstName: \"Audrey\",\n      lastName: \"T\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Practical and reliable, I've been using it for a long time\",\n      content:\n        \"It's great for reading, I've had one for several months and it's top!\",\n      verified: true,\n    },\n    {\n      firstName: \"Sophie\",\n      lastName: \"D\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Essential for my reading evenings\",\n      content:\n        \"I can't do without it anymore! Perfect for reading in the evening without turning on the bedroom light.\",\n      verified: true,\n    },\n    {\n      firstName: \"Marc\",\n      lastName: \"L\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juin\", year: 2025 },\n      title: \"Perfect gift for readers\",\n      content:\n        \"Given to my wife, she loves it! The quality is there and the design is elegant.\",\n      verified: true,\n    },\n    {\n      firstName: \"Émilie\",\n      lastName: \"R\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Lizia revolutionizes my reading moments\",\n      content:\n        \"No more tired arms and light problems! I recommend it 100%.\",\n      verified: true,\n    },\n    {\n      firstName: \"Thomas\",\n      lastName: \"B\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Excellent product, meets my expectations\",\n      content:\n        \"Fast delivery, quality product. I now read everywhere without constraints.\",\n      verified: true,\n    },\n    {\n      firstName: \"Charlotte\",\n      lastName: \"V\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"A must-have for all readers\",\n      content:\n        \"Simple, effective and so practical. My children even asked for theirs!\",\n      verified: true,\n    },\n    {\n      firstName: \"Pierre\",\n      lastName: \"G\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"mai\", year: 2025 },\n      title: \"Top French quality\",\n      content:\n        \"Happy to support a French company. The product is robust and well thought out.\",\n      verified: true,\n    },\n    {\n      firstName: \"Isabelle\",\n      lastName: \"F\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"Perfect for travel reading\",\n      content:\n        \"I take it everywhere with me: train, plane, hotel. It has become my essential accessory.\",\n      verified: true,\n    },\n    {\n      firstName: \"Antoine\",\n      lastName: \"H\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juin\", year: 2025 },\n      title: \"Comfortable and discreet\",\n      content:\n        \"The lamp is powerful but doesn't disturb anyone. Ideal for nocturnal readings.\",\n      verified: true,\n    },\n    {\n      firstName: \"Camille\",\n      lastName: \"P\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Elegant and functional design\",\n      content:\n        \"I love the sleek design and available colors. Plus, it's super practical!\",\n      verified: true,\n    },\n    {\n      firstName: \"Julien\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"juil\", year: 2025 },\n      title: \"Excellent value for money\",\n      content:\n        \"For the price, it's really an excellent investment. I recommend it without hesitation.\",\n      verified: true,\n    },\n    {\n      firstName: \"Léa\",\n      lastName: \"S\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"sept\", year: 2025 },\n      title: \"My children love it too\",\n      content:\n        \"My children use it every evening for their bedtime reading. It has become a ritual!\",\n      verified: true,\n    },\n    {\n      firstName: \"Maxime\",\n      lastName: \"C\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"août\", year: 2025 },\n      title: \"Innovation for reading\",\n      content:\n        \"Finally a product that thinks about the real needs of readers. Bravo for this innovation!\",\n      verified: true,\n    },\n  ];\n\n  \/\/ Images des packs (Pochette n'a qu'un seul pack)\n  const PACK_IMAGES_BY_COLOR = {\n    \"pochette-only\": {\n      default: \"https:\/\/lizia.fr\/cdn\/shop\/files\/36_600x600.jpg?v=1718186322\",\n    },\n  };\n\n  const PACKS = [\n    {\n      id: \"pochette-only\",\n      name: \"Cotton and linen pouch\",\n      price: 4.95,\n      originalPrice: 5.95,\n      short: \"Pouch\",\n      description:\n        \"The perfect natural pouch to protect your Lizia. Made from linen and cotton, it is very pleasant to the touch\",\n      features: [\n        \"Cotton and linen pouch\",\n        \"Optimal protection\",\n        \"Natural materials\",\n        \"Pleasant to the touch\",\n      ],\n      image:\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/36_600x600.jpg?v=1718186322\",\n    },\n  ];\n\n  \/\/ Images par variante (Pochette n'a qu'un seul pack)\n  const PRODUCT_IMAGES_BY_VARIANT = {\n    \"pochette-only\": {\n      default: [\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/36_600x600.jpg?v=1718186322\",\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    \/\/ Pochette n'a qu'un seul pack avec des images par défaut\n    if (pack === \"pochette-only\") {\n      return (\n        PRODUCT_IMAGES_BY_VARIANT[pack]?.[\"default\"] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"pochette-only\"][\"default\"]\n      );\n    }\n\n    \/\/ Fallback\n    return PRODUCT_IMAGES_BY_VARIANT[\"pochette-only\"][\"default\"];\n  }\n\n  \/\/ --- ÉTAT DE L'APPLICATION ---\n  let state = {\n    pack: \"pochette-only\", \/\/ Pack par défaut : Pochette seule\n    quantity: 1,\n    colors: [], \/\/ Non utilisé pour Pochette\n    personalization: [], \/\/ Non utilisé pour Pochette\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\n  function getPackImage(packId, colorId = null) {\n    \/\/ Pochette n'a qu'un seul pack, retourner directement l'image\n    if (packId === \"pochette-only\") {\n      return PACKS.find((p) =\u003e p.id === packId)?.image;\n    }\n\n    \/\/ Fallback\n    return PACKS.find((p) =\u003e p.id === \"pochette-only\")?.image;\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 Offer\" : 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=\"Learn more about ${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 (Pochette n'a pas de réduction) *\/\n  function calculateTotalDiscount() {\n    \/\/ Pochette n'a aucune réduction, prix fixe à 4.95€ par unité\n    return {\n      packDiscount: 0,\n      quantityDiscount: 0,\n      total: 0,\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    \/\/ Pochette n'a pas de couleurs ni personnalisation, masquer la section\n    const liziaItemsSection = document.getElementById(\"liziaItemsSection\");\n    if (liziaItemsSection) {\n      liziaItemsSection.style.display = \"none\";\n    }\n    if (liziaItemsContainer) {\n      liziaItemsContainer.innerHTML = \"\";\n    }\n    return;\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    \/\/ Prix unitaire fixe : 4.95€ pour Pochette\n    const unitPrice = PACK_PRICES[state.pack]; \/\/ 4.95€\n    const originalUnitPrice = PACK_ORIGINAL_PRICES[state.pack] || unitPrice; \/\/ 5.95€\n\n    \/\/ Calcul simple : prix unitaire × quantité (pas de réduction)\n    const totalPrice = unitPrice * state.quantity;\n\n    \/\/ Calculer l'économie réalisée (Prix Original * Qté - Prix Payé)\n    const totalReferencePrice = originalUnitPrice * state.quantity;\n    const savings = totalReferencePrice - totalPrice;\n\n    \/\/ Mettre à jour les deux boutons avec le prix\n    let buttonText = `ADD TO CART • ${totalPrice.toFixed(2)}€`;\n    if (savings \u003e 0.01) {\n      buttonText = `ADD TO CART • \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    \/\/ Pochette n'a pas de couleurs ni personnalisation, pas besoin de synchroniser\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    \/\/ Pochette n'a pas de couleurs ni personnalisation, pas besoin de synchroniser\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\n   *\/\n  function getVariantID(packID, color, isPersonalized) {\n    \/\/ Pochette : un seul variant, pas de couleur ni personnalisation\n    if (packID === \"8750055686493\") {\n      return variantIDs[packID].Default.normal; \/\/ Retourne le variant ID depuis le mapping\n    }\n\n    \/\/ Fallback\n    console.error(`Variant non trouvé pour pack ${packID}`);\n    return null;\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 et personnalisation : non utilisées pour Markia\n    \/\/ Les event listeners sont laissés pour compatibilité mais ne feront rien\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    \/\/ Pochette : produit simple, pas de couleurs ni personnalisation\n    const packID = packIDMapping[state.pack]; \/\/ \"8750055686493\"\n    const productsToAdd = [];\n\n    \/\/ Gérer les produits 2 à X (si quantité \u003e 1)\n    if (state.quantity \u003e 1) {\n      const variantID = getVariantID(packID, \"Default\", false);\n      if (variantID) {\n        productsToAdd.push({\n          variantID: variantID,\n          quantity: state.quantity - 1, \/\/ Ajouter quantity - 1 via l'API\n          properties: {},\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    const firstVariantID = getVariantID(packID, \"Default\", false);\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    \/\/ Sélectionner le variant dans le formulaire caché\n    const optionSelector = `#ProductSelect_${packID} option[value=\"${firstVariantID}\"]`;\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:\",\n        firstVariantID\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_${packID}`);\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    }\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      \/\/ 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 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\"\u003eVerified\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":"Ma boutique","offers":[{"title":"Default Title","offer_id":49177965265245,"sku":"POCH-COT-V1","price":4.95,"currency_code":"EUR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/36.jpg?v=1718186322"},{"product_id":"housse-de-livre-2-en-1-pliable","title":"2-in-1 Foldable Book Cover ","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 satisfied readers\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\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mb.webp?v=1766161710\"\n          alt=\"Housse Lizia\"\n          id=\"mainProductImage\"\n          class=\"mainImage\"\n        \/\u003e\n        \u003cbutton\n          id=\"prevImageBtn\"\n          class=\"galleryNavBtn prev\"\n          aria-label=\"Previous image\"\n        \u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n        \u003cbutton\n          id=\"nextImageBtn\"\n          class=\"galleryNavBtn next\"\n          aria-label=\"Next image\"\n        \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\"\n          \u003e⭐ +100,000 satisfied readers\u003c\/span\n        \u003e\n        \u003ch1\u003eHousse Lizia\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 reviews)\u003c\/span\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"productSubtitle\"\u003e\n          \u003c!-- Pochette en microfibre --\u003e\n        \u003c\/p\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- 1. Sélecteur de couleur --\u003e\n      \u003cdiv class=\"configStep\"\u003e\n        \u003clabel class=\"stepLabel\"\u003e\n          \u003cspan class=\"stepLabelNumber\"\u003e1\u003c\/span\u003e\n          Color\n        \u003c\/label\u003e\n        \u003cdiv id=\"colorSelector\" class=\"colorSelectorContainer\"\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              Quantity\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\"\u003eDiscount on your order\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          Personalization\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          ADD TO CART\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        ADD TO CART\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\"\u003eDelivery in 3 business days\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\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_fr.webp?v=1762266031\"\n              alt=\"Made In France\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eMADE IN FRANCE\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eFrench quality\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_garanti.webp?v=1762266032\"\n              alt=\"2 ans de garantie\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e2 YEARS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eWarranty\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_rembourser.webp?v=1762267150\"\n              alt=\"15 jours satisfaits ou remboursé\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e15 DAYS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eSatisfied or refunded\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_livraison.webp?v=1762266031\"\n              alt=\"Livré en 1 à 3 jours\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eDELIVERED\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003e1-5 days\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cdiv\n      id=\"configEndSentinel\"\n      class=\"configEndSentinel\"\n      style=\"height: 1px; width: 100%\"\n    \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      How does Lizia work?\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\"\u003eOptimal protection\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              The Lizia case protects your reading lamp from shocks,\n              scratches, and dust, thus extending its lifespan.\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\"\u003eEasy transport\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Take your Lizia everywhere safely thanks to this\n              elegant and practical case, perfect for transport.\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\"\u003eElegant design\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Made in France with quality materials, the Lizia case\n              combines protection and aesthetics to preserve your reading\n              accessory.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003c!-- Colonne Droite: Image --\u003e\n      \u003cdiv class=\"videoCard\" data-scroll-reveal\u003e\n        \u003cdiv class=\"videoContainer\"\u003e\n          \u003cimg\n            src=\"https:\/\/lizia.fr\/cdn\/shop\/files\/housse_bleu_600x600.webp?v=1765194028\"\n            alt=\"Housse Lizia\"\n            style=\"\n              width: 100%;\n              height: 100%;\n              object-fit: cover;\n              border-radius: 12px;\n            \"\n          \/\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          Reading has never been so comfortable\n        \u003c\/h2\u003e\n        \u003ch3 class=\"beforeAfterSubtitle\" data-scroll-reveal\u003e\n          Without fatigue, with one hand\n        \u003c\/h3\u003e\n        \u003cp class=\"beforeAfterDescription\" data-scroll-reveal\u003e\n          Adapted to all thumbs, gain flexibility wherever you are.\n          Without effort, fully enjoy your reading, including in\n          the dark without disturbing anyone.\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\n              id=\"lightToggleBtn\"\n              class=\"lightToggleBtn\"\n              aria-label=\"Toggle light\"\n            \u003e\n              \u003cspan class=\"lightToggleOn\"\u003e\n                \u003csvg\n                  xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"white\"\n                  width=\"16\"\n                  height=\"16\"\n                \u003e\n                  \u003cpath\n                    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\"\n                  \/\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\n            id=\"beforeAfterImageOff\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\"\n            alt=\"Lizia light off\"\n            class=\"beforeAfterImage beforeAfterImageOff\"\n          \/\u003e\n          \u003cimg\n            id=\"beforeAfterImageOn\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\"\n            alt=\"Lizia light on\"\n            class=\"beforeAfterImage beforeAfterImageOn\"\n          \/\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\n        href=\"https:\/\/www.europe1.fr\/emissions\/demain-au-bureau\/lizia-laccessoire-de-lecture-3-en-1-4163529\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_europe1.webp?v=1763465558\"\n          alt=\"Europe1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_quotidien.webp?v=1763465558\"\n          alt=\"Quotidien\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_bfm_business.webp?v=1763465558\"\n          alt=\"Bfm-tv-business\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.ouest-france.fr\/bretagne\/roudouallec-56110\/roudouallec-ils-creent-un-accessoire-de-lecture-innovant-16121898-f0a2-11ec-9262-0032434e6459\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_ouest_france.webp?v=1763465558\"\n          alt=\"Ouest-France\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tf1.webp?v=1763465558\"\n          alt=\"Tf1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.letelegramme.fr\/morbihan\/roudouallec-56110\/deux-jeunes-bretons-donnent-un-coup-de-pouce-aux-lecteurs-avec-lizia-281522.php\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_telegramme.webp?v=1763465558\"\n          alt=\"Le-telegramme\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.m6.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\"\n          alt=\"M6\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/entrepreneurs.lesechos.fr\/developpement-entreprise\/strategie\/de-linvention-futee-au-succes-commercial-lizia-a-la-conquete-des-fanas-de-lecture-2094778\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_les_echos.webp?v=1763465557\"\n          alt=\"Les-echos\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tmc.webp?v=1763465559\"\n          alt=\"TMC\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.nrj.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_nrj.webp?v=1763465558\"\n          alt=\"NRJ\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.7jours.fr\/actualites\/lizia-rennes-jeune-pousse-eclaire-lecture\/\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_7_jours.webp?v=1763465558\"\n          alt=\"7-jours\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.lejournaldesentreprises.com\/breve\/lizia-lance-son-accessoire-de-lecture-en-belgique-et-en-suisse-2129508\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_le_journal_des_entreprises.webp?v=1763465558\"\n          alt=\"le-journal-des-entreprises\"\n        \/\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\"\u003eLIZIA COMMITMENTS\u003c\/h2\u003e\n      \u003cp id=\"valuesSubtitle\" class=\"valuesSubtitle\"\u003e\n        Imagined in Rennes, Lizia is a French innovation designed to offer\n        lasting reading comfort, with local partners and\n        responsible materials.\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\n          class=\"valueItem active\"\n          data-index=\"0\"\n          data-title=\"Fabriqué en France\"\n          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.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- France Map Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_france.svg?v=1763826898\"\n              alt=\"France\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eMade in France\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003e\n              Production and assembly in the West\n            \u003c\/p\u003e\n            \u003c!-- Mobile Description (Hidden by default) --\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our nylon parts are produced in Western France and then\n              assembled in Brittany. By choosing Lizia, you support a\n              100% French manufacturing, short supply chain, and local\n              industrial know-how, from design to shipping from Rennes.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 2 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"1\"\n          data-title=\"Assemblé en ESAT\"\n          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.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Wheelchair\/Accessibility Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_esat.svg?v=1763826898\"\n              alt=\"ESAT\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAssembled in ESAT\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eLocal partners\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia is socially committed by entrusting the assembly of its\n              products to ESAT partner facilities (Work Assistance\n              Establishments and Services) in Brittany, promoting the professional\n              integration of people with disabilities.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 3 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"2\"\n          data-title=\"Matériaux recyclables\"\n          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.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Leaf\/Recycle Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_recyclable.svg?v=1763826898\"\n              alt=\"Recyclable\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eRecyclable materials\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eTechnical PA12, recyclable cardboard\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              We use technical PA12 for its robustness and\n              durability, as well as 100% recyclable cardboard packaging.\n              Our eco-design approach aims to minimize our environmental\n              impact.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 4 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"3\"\n          data-title=\"Médaillé au Lépine\"\n          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é.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Medal Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_medaille.svg?v=1763826898\"\n              alt=\"Médaille\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eLépine Medal winner\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eAward-winning innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              The Lizia innovation has been recognized and awarded a medal\n              at the prestigious Lépine Competition, a testament to its ingenuity and\n              quality.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 5 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"4\"\n          data-title=\"Breveté à l'international\"\n          data-desc=\"Notre technologie unique est protégée par des brevets internationaux, assurant l'exclusivité de notre solution de lecture à une main.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Globe\/Patent Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_brevet.svg?v=1763996122\"\n              alt=\"Breveté\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eInternationally patented\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eProtected innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our unique technology is protected by international\n              patents, ensuring the exclusivity of our one-handed\n              reading solution.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 6 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"5\"\n          data-title=\"Réalisé par 2 étudiants\"\n          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.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Users\/Students Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_etudiant.svg?v=1763996122\"\n              alt=\"Étudiants\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eCreated by 2 students\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eYoung innovative company\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia was born from the passion and entrepreneurship of two\n              students, determined to improve readers' daily lives.\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\"\u003eMade in France\u003c\/h3\u003e\n          \u003cp id=\"detailDesc\" class=\"detailDesc\"\u003e\n            Our nylon parts are produced in Western France and then\n            assembled in Brittany. By choosing Lizia, you support a\n            100% French manufacturing, short supply chain, and local\n            industrial know-how, from design to shipping from 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        Adopted by many readers\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 reviews\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cbutton class=\"reviewsNavBtn prev\" aria-label=\"Previous reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \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=\"Next reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \u003e\n        \u003cpolyline points=\"9 18 15 12 9 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cdiv\n      id=\"reviewsContainer\"\n      class=\"reviewsContainer\"\n      data-scroll-reveal\n    \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\u0026times;\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 HOUSSE --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_8910785347933\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"8910785347933\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \n  \u003cdiv class=\"radio-wrapper\"\u003e\n    \u003cdiv class=\"single-option-radio\" data-option=\"option1\" id=\"ProductSelect_8910785347933-option-0\"\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Carte du monde fond bleu\"\n        name=\"Couleur\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_55293012083037 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8910785347933-option-0_1\"\n        checked\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8910785347933-option-0_1\"\u003eWorld map blue background\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Carte du monde fond marron\"\n        name=\"Couleur\"\n        data-varient_id=\"1_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_55293015359837 product_varient_radio_1_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8910785347933-option-1_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8910785347933-option-1_1\"\u003eWorld map brown background\u003c\/label\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Carte du monde rose\"\n        name=\"Couleur\"\n        data-varient_id=\"2_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_63499160584541 product_varient_radio_2_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_8910785347933-option-2_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_8910785347933-option-2_1\"\u003eWorld map pink background\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_8910785347933\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption\n      data-img=\"files\/housse_bleu.webp\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2395\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"23,95 EUR\"\n      selected=\"selected\"\n      data-sku=\"HOU-CMB-V2\"\n      value=\"55293012083037\"\n    \u003e\n      World map blue background — 23.95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/cartemondemarron.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2395\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"23,95 EUR\"\n      data-sku=\"HOU-CMM-V2\"\n      value=\"55293015359837\"\n    \u003e\n      World map brown background — 23.95 EUR\n    \u003c\/option\u003e\n    \u003coption\n      data-img=\"files\/housse_rose.jpg\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"2495\"\n      data-price_o=\"2395\"\n      data-compare_at_price=\"24,95 EUR\"\n      data-price=\"23,95 EUR\"\n      data-sku=\"HOU-CMR-V2\"\n      value=\"63499160584541\"\n    \u003e\n      World map pink background — 23.95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_8910785347933\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_8910785347933\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_8910785347933\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',2395,8910785347933)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"8910785347933\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_8910785347933 button\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_8910785347933\"\n      \u003eADD TO CART\u003c\/span\n    \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=\"8910785347933\" \/\u003e\n\u003c\/form\u003e\n\n\u003cscript\u003e\ndocument.addEventListener(\"DOMContentLoaded\", () =\u003e {\n  \/\/ --- CONSTANTES ET DONNÉES ---\n  const HOUSSE_PRICE = 23.95; \/\/ Prix fixe de la housse\n  const HOUSSE_ORIGINAL_PRICE = 24.95; \/\/ Prix barré de la housse\n  const PRODUCT_ID = \"8910785347933\";\n\n  \/\/ Couleurs disponibles pour la housse avec leurs images\n  const COLORS = [\n    {\n      id: \"bleu\",\n      name: \"World map blue background\",\n      variantID: \"55293012083037\",\n      available: true,\n      thumbnailImage:\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/housse_bleu_600x600.webp?v=1765194028\",\n      images: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mb.webp?v=1766161710\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mb_mif.webp?v=1766161710\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mb_2_en_1.webp?v=1766161711\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mb_dimensions.webp?v=1766161710\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mb_qualite.webp?v=1766161711\",\n      ],\n    },\n    {\n      id: \"marron\",\n      name: \"World map brown background\",\n      variantID: \"55293015359837\",\n      available: false,\n      thumbnailImage:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_marron.jpg?v=1771518450\",\n      images: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mm.webp?v=1771518311\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mm_mif.webp?v=1771518311\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mm_2_en_1.webp?v=1771518310\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mm_dimensions.webp?v=1771518311\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mm_qualite.webp?v=1771518310\",\n      ],\n    },\n    {\n      id: \"rose\",\n      name: \"World map pink background\",\n      variantID: \"63499160584541\",\n      available: true,\n      thumbnailImage:\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_rose.jpg?v=1771518196\",\n      images: [\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mr.webp?v=1771518310\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mr_mif.webp?v=1771518310\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mr_2_en_1.webp?v=1771853709\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mr_dimensions.webp?v=1771853710\",\n        \"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_mr_qualite.webp?v=1771518311\",\n      ],\n    },\n  ];\n\n  \/\/ Mapping des variants Shopify\n  const variantIDs = {\n    8910785347933: {\n      bleu: { normal: \"55293012083037\" },\n      marron: { normal: \"55293015359837\" },\n      rose: { normal: \"63499160584541\" },\n    },\n  };\n\n  \/\/ Reviews data\n  const reviewsData = [\n    {\n      firstName: \"Laura\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"An amazing discovery!\",\n      content: \"I received it and it's so cool!\",\n      verified: true,\n    },\n    {\n      firstName: \"Nathalie\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"Easy reading without disturbing others, perfect!\",\n      content:\n        \"I received my Lizia a few days ago. It's Christmas in September, what joy to be able to read in bed without disturbing my partner, on public transport...\",\n      verified: true,\n    },\n    {\n      firstName: \"Audrey\",\n      lastName: \"T\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"July\", year: 2025 },\n      title: \"Practical and reliable, I've been using it for a long time\",\n      content:\n        \"It's great for reading, I've had one for several months, it's great!\",\n      verified: true,\n    },\n    {\n      firstName: \"Sophie\",\n      lastName: \"D\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Essential for my reading evenings\",\n      content:\n        \"I can't do without it anymore! Perfect for reading in the evening without turning on the bedroom light.\",\n      verified: true,\n    },\n    {\n      firstName: \"Marc\",\n      lastName: \"L\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"June\", year: 2025 },\n      title: \"Perfect gift for readers\",\n      content:\n        \"Given to my wife, she loves it! The quality is there and the design is elegant.\",\n      verified: true,\n    },\n    {\n      firstName: \"Émilie\",\n      lastName: \"R\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"Lizia revolutionizes my reading moments\",\n      content: \"No more tired arms and light problems! I recommend it 100%.\",\n      verified: true,\n    },\n    {\n      firstName: \"Thomas\",\n      lastName: \"B\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"July\", year: 2025 },\n      title: \"Excellent product, meets my expectations\",\n      content:\n        \"Fast delivery, quality product. I now read everywhere without constraint.\",\n      verified: true,\n    },\n    {\n      firstName: \"Charlotte\",\n      lastName: \"V\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"A must-have for all readers\",\n      content:\n        \"Simple, effective and so practical. My children even asked for their own!\",\n      verified: true,\n    },\n    {\n      firstName: \"Pierre\",\n      lastName: \"G\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"May\", year: 2025 },\n      title: \"Top French quality\",\n      content:\n        \"Happy to support a French company. The product is robust and well thought out.\",\n      verified: true,\n    },\n    {\n      firstName: \"Isabelle\",\n      lastName: \"F\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"Perfect for reading while traveling\",\n      content:\n        \"I take it everywhere with me: train, plane, hotel. It has become my essential accessory.\",\n      verified: true,\n    },\n    {\n      firstName: \"Antoine\",\n      lastName: \"H\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"June\", year: 2025 },\n      title: \"Comfortable and discreet\",\n      content:\n        \"The lamp is powerful but doesn't disturb anyone. Ideal for nighttime reading.\",\n      verified: true,\n    },\n    {\n      firstName: \"Camille\",\n      lastName: \"P\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Elegant and functional design\",\n      content:\n        \"I love the clean design and available colors. Plus, it's super practical!\",\n      verified: true,\n    },\n    {\n      firstName: \"Julien\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"July\", year: 2025 },\n      title: \"Excellent value for money\",\n      content:\n        \"For the price, it's really an excellent investment. I recommend it without hesitation.\",\n      verified: true,\n    },\n    {\n      firstName: \"Léa\",\n      lastName: \"S\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"My children love it too\",\n      content:\n        \"My children use it every evening for their bedtime reading. It has become a ritual!\",\n      verified: true,\n    },\n    {\n      firstName: \"Maxime\",\n      lastName: \"C\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Innovation at the service of reading\",\n      content:\n        \"Finally a product that thinks about readers' real needs. Bravo for this innovation!\",\n      verified: true,\n    },\n  ];\n\n  \/\/ Images par couleur\/variante\n  const PRODUCT_IMAGES_BY_VARIANT = {\n    bleu: COLORS.find((c) =\u003e c.id === \"bleu\")?.images || [],\n    marron: COLORS.find((c) =\u003e c.id === \"marron\")?.images || [],\n    rose: COLORS.find((c) =\u003e c.id === \"rose\")?.images || [],\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 color = state.color || \"bleu\";\n    return (\n      PRODUCT_IMAGES_BY_VARIANT[color] || PRODUCT_IMAGES_BY_VARIANT[\"bleu\"]\n    );\n  }\n\n  \/\/ --- ÉTAT DE L'APPLICATION ---\n  let state = {\n    color: \"bleu\", \/\/ Couleur par défaut : bleu\n    quantity: 1,\n    currentImageIndex: 0,\n  };\n\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 preloadColorImages() {\n    COLORS.forEach((color) =\u003e {\n      if (color.available !== false) {\n        \/\/ Précharger l'image thumbnail\n        if (color.thumbnailImage \u0026\u0026 !imageCache.has(color.thumbnailImage)) {\n          const img = new Image();\n          img.src = color.thumbnailImage;\n          imageCache.set(color.thumbnailImage, img);\n        }\n        \/\/ Précharger les images de la galerie\n        color.images.forEach((url) =\u003e {\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\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(\"Image decoding error:\", 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  \/\/ --- 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 colorSelectorContainer = document.getElementById(\"colorSelector\");\n  const quantitySelectorContainer = document.getElementById(\"quantitySelector\");\n  const discountGaugeContainer = document.getElementById(\"discountGauge\");\n  const addToCartBtn = document.getElementById(\"addToCartBtn\");\n  const addToCartBtnMobile = document.getElementById(\"addToCartBtnMobile\");\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(\"Video playback error:\", 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(\"Video playback error:\", 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 couleur *\/\n  function renderColorSelector() {\n    if (!colorSelectorContainer) return;\n\n    const availableColors = COLORS.filter((color) =\u003e color.available !== false);\n\n    colorSelectorContainer.innerHTML = availableColors\n      .map(\n        (color) =\u003e `\n      \u003cbutton \n        class=\"colorOption ${state.color === color.id ? \"selected\" : \"\"}\" \n        data-color=\"${color.id}\"\n        aria-label=\"Select ${color.name}\"\n      \u003e\n        \u003cdiv class=\"colorOptionImage\"\u003e\n          \u003cimg \n            src=\"${color.thumbnailImage || color.images[0]}\" \n            alt=\"${color.name}\"\n            class=\"colorThumbnail\"\n          \/\u003e\n        \u003c\/div\u003e\n      \u003c\/button\u003e\n    `,\n      )\n      .join(\"\");\n  }\n\n  \/** Calcul de la réduction totale (Pochette n'a pas de réduction) *\/\n  function calculateTotalDiscount() {\n    \/\/ Pochette n'a aucune réduction, prix fixe à 4.95€ par unité\n    return {\n      packDiscount: 0,\n      quantityDiscount: 0,\n      total: 0,\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    \/\/ Pochette n'a pas de couleurs ni personnalisation, masquer la section\n    const liziaItemsSection = document.getElementById(\"liziaItemsSection\");\n    if (liziaItemsSection) {\n      liziaItemsSection.style.display = \"none\";\n    }\n    if (liziaItemsContainer) {\n      liziaItemsContainer.innerHTML = \"\";\n    }\n    return;\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(\n          () =\u003e {\n            renderLiziaItemsContent();\n            previousQuantity = state.quantity;\n            addEventListeners(); \/\/ Ré-attacher les écouteurs après le rendu\n          },\n          300 + itemsToAnimate.length * 50,\n        );\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    \/\/ Prix unitaire fixe : 23.95€ pour Housse\n    const unitPrice = HOUSSE_PRICE; \/\/ 23.95€\n    const originalUnitPrice = HOUSSE_ORIGINAL_PRICE; \/\/ 24.95€\n\n    \/\/ Calcul simple : prix unitaire × quantité\n    const totalPrice = unitPrice * state.quantity;\n\n    \/\/ Calculer l'économie réalisée (Prix Original * Qté - Prix Payé)\n    const totalReferencePrice = originalUnitPrice * state.quantity;\n    const savings = totalReferencePrice - totalPrice;\n\n    \/\/ Mettre à jour les deux boutons avec le prix\n    let buttonText = `ADD TO CART • ${totalPrice.toFixed(2)}€`;\n    if (savings \u003e 0.01) {\n      buttonText = `ADD TO CART • \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 (non utilisée pour housses) *\/\n  function showPackPopup(packId) {\n    \/\/ Non utilisé pour les housses\n    return;\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    renderImageGallery();\n    renderColorSelector();\n    renderQuantitySelector();\n    renderDiscountGauge();\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    \/\/ 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    renderColorSelector();\n    renderQuantitySelector();\n    renderDiscountGauge();\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(\"Video playback error:\", 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(\"Video playback error:\", 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\n   *\/\n  function getVariantID(productID, color, isPersonalized) {\n    \/\/ Housse : variant selon la couleur\n    if (productID === PRODUCT_ID) {\n      const colorKey = color || state.color || \"bleu\";\n      return variantIDs[productID]?.[colorKey]?.normal || null;\n    }\n\n    \/\/ Fallback\n    console.error(\n      `Variant not found for product ${productID} and color ${color}`,\n    );\n    return null;\n  }\n\n  function syncVariantToAllForms(variantID, colorName) {\n    if (!variantID) return;\n    document.querySelectorAll(\"select[name='id']\").forEach((select) =\u003e {\n      const opt = select.querySelector(`option[value=\"${variantID}\"]`);\n      if (opt) {\n        opt.selected = true;\n        select.value = variantID;\n      }\n    });\n    const form = document.getElementById(\"prodForm\");\n    if (form) {\n      form.querySelectorAll('input[name=\"Couleur\"]').forEach((radio) =\u003e {\n        radio.checked = radio.classList.contains(\n          `product_varient_sel_radio_${variantID}`,\n        );\n      });\n    }\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(\"Server response error\");\n        }\n        return response.json();\n      })\n      .then((data) =\u003e {\n        console.log(\"Products added to cart:\", data);\n        return data;\n      })\n      .catch((error) =\u003e {\n        console.error(\"Error adding to cart:\", 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 couleur\n    document.querySelectorAll(\".colorOption\").forEach((el) =\u003e {\n      el.onclick = (e) =\u003e {\n        const colorId = e.target.closest(\".colorOption\")?.dataset.color;\n        if (colorId \u0026\u0026 state.color !== colorId) {\n          state.color = colorId;\n          state.currentImageIndex = 0;\n          const variantID = getVariantID(PRODUCT_ID, colorId, false);\n          const colorName = COLORS.find((c) =\u003e c.id === colorId)?.name;\n          syncVariantToAllForms(variantID, colorName);\n          updateUIOptimized(true);\n        }\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    \/\/ Fermeture de la popup (si elle existe)\n    if (popupClose) {\n      popupClose.onclick = hidePackPopup;\n    }\n    if (packPopup) {\n      packPopup.onclick = (e) =\u003e {\n        if (e.target === packPopup) {\n          hidePackPopup();\n        }\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 couleurs pour éviter le lag\n  preloadColorImages();\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    \/\/ Housse : produit avec variantes de couleur\n    const productID = PRODUCT_ID; \/\/ \"8910785347933\"\n    const color = state.color || \"bleu\";\n    const productsToAdd = [];\n\n    \/\/ Gérer les produits 2 à X (si quantité \u003e 1)\n    if (state.quantity \u003e 1) {\n      const variantID = getVariantID(productID, color, false);\n      if (variantID) {\n        productsToAdd.push({\n          variantID: variantID,\n          quantity: state.quantity - 1, \/\/ Ajouter quantity - 1 via l'API\n          properties: {},\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(\"Error adding multiple items:\", 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    const firstVariantID = getVariantID(productID, color, false);\n\n    if (!firstVariantID) {\n      console.error(\"Unable to retrieve variant for first product\");\n      return;\n    }\n\n    \/\/ Mettre à jour TOUS les selects et radios (formulaire visible + caché)\n    const colorName = COLORS.find((c) =\u003e c.id === color)?.name;\n    syncVariantToAllForms(firstVariantID, colorName);\n\n    \/\/ Vérifier qu'au moins un formulaire a bien l'option\n    const anyOption = document.querySelector(\n      `select[name=\"id\"] option[value=\"${firstVariantID}\"]`,\n    );\n    if (!anyOption) {\n      console.error(\"Selection option not found for variant:\", firstVariantID);\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_${productID}`);\n    if (quantityInput) {\n      quantityInput.value = 1;\n      quantityInput.setAttribute(\"value\", \"1\");\n      quantityInput.dispatchEvent(new Event(\"input\", { bubbles: true }));\n      quantityInput.dispatchEvent(new Event(\"change\", { bubbles: true }));\n    }\n\n    const submitBtn = document.querySelector(`#prodForm .add_to_cart_btn_${productID}`);\n    if (submitBtn) {\n      submitBtn.removeAttribute(\"disabled\");\n      submitBtn.classList.remove(\"disabled\");\n      if (state.quantity \u003e 1) {\n        await new Promise((resolve) =\u003e setTimeout(resolve, 200));\n      }\n      submitBtn.click();\n    } else {\n      console.error(\"Add to cart button not found for product:\", productID);\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\n                ? '\u003cdiv class=\"reviewVerified\"\u003eVerified\u003c\/div\u003e'\n                : \"\"\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=\"Review ${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(\"Video playback 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":"Carte du monde fond bleu","offer_id":55293012083037,"sku":"HOU-CMB-V2","price":23.95,"currency_code":"EUR","in_stock":true},{"title":"Carte du monde fond marron","offer_id":55293015359837,"sku":"HOU-CMM-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Carte du monde fond rose","offer_id":63499160584541,"sku":"HOU-CMR-V2","price":23.95,"currency_code":"EUR","in_stock":true},{"title":"Black","offer_id":52001706672477,"sku":"HOU-NOI-V1","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"White","offer_id":52001706705245,"sku":"HOU-BLA-V1","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Red","offer_id":52001706738013,"sku":"HOU-ROU-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Jungle","offer_id":53615042920797,"sku":"HOU-JUN-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Exotic","offer_id":53615042953565,"sku":"HOU-EXO-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Aquatique","offer_id":54830777499997,"sku":"HOU-AQU-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Ananas","offer_id":54830777532765,"sku":"HOU-ANA-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Bleu florale","offer_id":54830777565533,"sku":"HOU-BLE-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Sauvage","offer_id":54830777598301,"sku":"HOU-SAU-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Vert pois blanc","offer_id":55293019586909,"sku":"HOU-VPB-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Étoiles","offer_id":55293022110045,"sku":"HOU-ETO-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Blanc et rouge","offer_id":55293022667101,"sku":"HOU-BER-V2","price":23.95,"currency_code":"EUR","in_stock":false},{"title":"Housse Coeur","offer_id":55579808006493,"sku":"HOU-COE-V2","price":24.95,"currency_code":"EUR","in_stock":false}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/housse_bleu.jpg?v=1770746563"},{"product_id":"markia","title":"Markia - 2-in-1 Page Holder","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 satisfied readers\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\n          src=\"..\/public\/placeholder.svg\"\n          alt=\"Markia\"\n          id=\"mainProductImage\"\n          class=\"mainImage\"\n        \/\u003e\n        \u003cbutton\n          id=\"prevImageBtn\"\n          class=\"galleryNavBtn prev\"\n          aria-label=\"Previous image\"\n        \u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n        \u003cbutton\n          id=\"nextImageBtn\"\n          class=\"galleryNavBtn next\"\n          aria-label=\"Next image\"\n        \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\"\n          \u003e⭐ +100,000 satisfied readers\u003c\/span\n        \u003e\n        \u003ch1\u003eMARKIA - 2-in-1 Bookmark\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 reviews)\u003c\/span\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"productSubtitle\"\u003e\n          \u003c!-- Markia • Porte-page 2-en-1 --\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              Quantity\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\"\u003eDiscount on your order\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          Personalization\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          ADD TO CART\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        ADD TO CART\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\"\u003eDelivery in 3 business days\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\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_fr.webp?v=1762266031\"\n              alt=\"Made In France\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eMADE IN FRANCE\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eFrench quality\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_garanti.webp?v=1762266032\"\n              alt=\"2 year warranty\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e2 YEARS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eWarranty\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_rembourser.webp?v=1762267150\"\n              alt=\"15 days satisfied or refunded\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e15 DAYS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eSatisfied or refunded\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_livraison.webp?v=1762266031\"\n              alt=\"Delivered in 1 to 3 days\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eDELIVERED\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003e1-5 days\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cdiv\n      id=\"configEndSentinel\"\n      class=\"configEndSentinel\"\n      style=\"height: 1px; width: 100%\"\n    \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      How does Lizia work?\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\"\u003eRead with one hand\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Lizia slides around your thumb and keeps the book perfectly open,\n              wherever you are.\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\"\u003eIlluminates\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Enjoy a soft and precise light that doesn't glare and doesn't\n              disturb those around you.\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\"\u003eBookmark\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Never lose your page again thanks to its integrated and practical\n              bookmark function.\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\n            class=\"liziaVideo\"\n            data-video-id=\"1\"\n            playsinline\n            muted\n            preload=\"metadata\"\n          \u003e\n            \u003csource\n              src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/6270dc5936c44a3a97d40e48209e8199.mp4\"\n              type=\"video\/mp4\"\n            \/\u003e\n          \u003c\/video\u003e\n          \u003cdiv class=\"videoControls\"\u003e\n            \u003cbutton\n              class=\"videoPlayBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Play\/Pause\"\n            \u003e\n              \u003csvg\n                class=\"playIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath d=\"M8 5v14l11-7z\" \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"pauseIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" \/\u003e\n              \u003c\/svg\u003e\n            \u003c\/button\u003e\n            \u003cbutton\n              class=\"videoMuteBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Mute\/Unmute\"\n            \u003e\n              \u003csvg\n                class=\"unmuteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"muteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\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          Reading has never been so comfortable\n        \u003c\/h2\u003e\n        \u003ch3 class=\"beforeAfterSubtitle\" data-scroll-reveal\u003e\n          Without fatigue, with one hand\n        \u003c\/h3\u003e\n        \u003cp class=\"beforeAfterDescription\" data-scroll-reveal\u003e\n          Adapted to all thumbs, gain flexibility wherever you are. Without\n          effort, fully enjoy your reading, including in the dark without\n          disturbing anyone.\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\n              id=\"lightToggleBtn\"\n              class=\"lightToggleBtn\"\n              aria-label=\"Toggle light\"\n            \u003e\n              \u003cspan class=\"lightToggleOn\"\u003e\n                \u003csvg\n                  xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"white\"\n                  width=\"16\"\n                  height=\"16\"\n                \u003e\n                  \u003cpath\n                    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\"\n                  \/\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\n            id=\"beforeAfterImageOff\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\"\n            alt=\"Lizia light off\"\n            class=\"beforeAfterImage beforeAfterImageOff\"\n          \/\u003e\n          \u003cimg\n            id=\"beforeAfterImageOn\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\"\n            alt=\"Lizia light on\"\n            class=\"beforeAfterImage beforeAfterImageOn\"\n          \/\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\n        href=\"https:\/\/www.europe1.fr\/emissions\/demain-au-bureau\/lizia-laccessoire-de-lecture-3-en-1-4163529\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_europe1.webp?v=1763465558\"\n          alt=\"Europe1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_quotidien.webp?v=1763465558\"\n          alt=\"Quotidien\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_bfm_business.webp?v=1763465558\"\n          alt=\"Bfm-tv-business\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.ouest-france.fr\/bretagne\/roudouallec-56110\/roudouallec-ils-creent-un-accessoire-de-lecture-innovant-16121898-f0a2-11ec-9262-0032434e6459\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_ouest_france.webp?v=1763465558\"\n          alt=\"Ouest-France\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tf1.webp?v=1763465558\"\n          alt=\"Tf1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.letelegramme.fr\/morbihan\/roudouallec-56110\/deux-jeunes-bretons-donnent-un-coup-de-pouce-aux-lecteurs-avec-lizia-281522.php\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_telegramme.webp?v=1763465558\"\n          alt=\"Le-telegramme\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.m6.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\"\n          alt=\"M6\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/entrepreneurs.lesechos.fr\/developpement-entreprise\/strategie\/de-linvention-futee-au-succes-commercial-lizia-a-la-conquete-des-fanas-de-lecture-2094778\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_les_echos.webp?v=1763465557\"\n          alt=\"Les-echos\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tmc.webp?v=1763465559\"\n          alt=\"TMC\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.nrj.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_nrj.webp?v=1763465558\"\n          alt=\"NRJ\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.7jours.fr\/actualites\/lizia-rennes-jeune-pousse-eclaire-lecture\/\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_7_jours.webp?v=1763465558\"\n          alt=\"7-jours\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.lejournaldesentreprises.com\/breve\/lizia-lance-son-accessoire-de-lecture-en-belgique-et-en-suisse-2129508\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_le_journal_des_entreprises.webp?v=1763465558\"\n          alt=\"le-journal-des-entreprises\"\n        \/\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\"\u003eLIZIA COMMITMENTS\u003c\/h2\u003e\n      \u003cp id=\"valuesSubtitle\" class=\"valuesSubtitle\"\u003e\n        Imagined in Rennes, Lizia is a French innovation designed to provide\n        lasting reading comfort, with local partners and responsible materials.\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\n          class=\"valueItem active\"\n          data-index=\"0\"\n          data-title=\"Made in France\"\n          data-desc=\"Our nylon parts are produced in Western France and then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, short supply chains and local industrial know-how, from design to shipping from Rennes.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- France Map Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_france.svg?v=1763826898\"\n              alt=\"France\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eMade in France\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003e\n              Production and assembly in the West\n            \u003c\/p\u003e\n            \u003c!-- Mobile Description (Hidden by default) --\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our nylon parts are produced in Western France and then assembled\n              in Brittany. By choosing Lizia, you support 100% French\n              manufacturing, short supply chains and local industrial know-how,\n              from design to shipping from Rennes.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 2 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"1\"\n          data-title=\"Assembled in ESAT\"\n          data-desc=\"Lizia is socially committed by entrusting the assembly of its products to ESAT (Establishments and Services for Assistance through Work) partners in Brittany, promoting the professional integration of people with disabilities.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Wheelchair\/Accessibility Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_esat.svg?v=1763826898\"\n              alt=\"ESAT\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAssembled in ESAT\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eLocal partners\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia is socially committed by entrusting the assembly of its\n              products to ESAT (Establishments and Services for Assistance\n              through Work) partners in Brittany, promoting the professional\n              integration of people with disabilities.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 3 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"2\"\n          data-title=\"Recyclable materials\"\n          data-desc=\"We use technical PA12 for its robustness and durability, as well as 100% recyclable cardboard packaging. Our eco-design approach aims to minimize our environmental impact.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Leaf\/Recycle Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_recyclable.svg?v=1763826898\"\n              alt=\"Recyclable\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eRecyclable materials\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eTechnical PA12, recyclable cardboard\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              We use technical PA12 for its robustness and durability, as well\n              as 100% recyclable cardboard packaging. Our eco-design approach\n              aims to minimize our environmental impact.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 4 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"3\"\n          data-title=\"Lépine Medal winner\"\n          data-desc=\"The Lizia innovation has been recognized and awarded with a medal at the prestigious Lépine Competition, a testament to its ingenuity and quality.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Medal Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_medaille.svg?v=1763826898\"\n              alt=\"Medal\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eLépine Medal winner\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eAwarded innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              The Lizia innovation has been recognized and awarded with a medal\n              at the prestigious Lépine Competition, a testament to its\n              ingenuity and quality.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 5 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"4\"\n          data-title=\"Internationally patented\"\n          data-desc=\"Our unique technology is protected by international patents, ensuring the exclusivity of our one-handed reading solution.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Globe\/Patent Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_brevet.svg?v=1763996122\"\n              alt=\"Patented\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eInternationally patented\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eProtected innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our unique technology is protected by international patents,\n              ensuring the exclusivity of our one-handed reading solution.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 6 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"5\"\n          data-title=\"Created by 2 students\"\n          data-desc=\"Lizia was born from the passion and entrepreneurship of two students, determined to improve readers' daily lives.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Users\/Students Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_etudiant.svg?v=1763996122\"\n              alt=\"Students\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eCreated by 2 students\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eInnovative young company\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia was born from the passion and entrepreneurship of two\n              students, determined to improve readers' daily lives.\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\"\u003eMade in France\u003c\/h3\u003e\n          \u003cp id=\"detailDesc\" class=\"detailDesc\"\u003e\n            Our nylon parts are produced in Western France and then assembled\n            in Brittany. By choosing Lizia, you support 100% French\n            manufacturing, short supply chains and local industrial know-how,\n            from design to shipping from 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        Adopted by many readers\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 reviews\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cbutton class=\"reviewsNavBtn prev\" aria-label=\"Previous reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \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=\"Next reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \u003e\n        \u003cpolyline points=\"9 18 15 12 9 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cdiv\n      id=\"reviewsContainer\"\n      class=\"reviewsContainer\"\n      data-scroll-reveal\n    \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\u0026times;\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 MARKIA --\u003e\n\u003cform\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_14760533721437\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"14760533721437\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \n  \u003cdiv class=\"radio-wrapper\" style=\"display: none;\"\u003e\n    \u003cdiv class=\"single-option-radio\" data-option=\"option1\" id=\"ProductSelect_14760533721437-option-0\"\u003e\n      \u003cinput\n        type=\"radio\"\n        value=\"Default Title\"\n        name=\"Title\"\n        data-varient_id=\"0_1\"\n        class=\"single-option-selector__radio product_varient_radio product_varient_sel_radio_54848339706205 product_varient_radio_0_1\"\n        data-option=\"option1\"\n        id=\"ProductSelect_14760533721437-option-0_1\"\n      \/\u003e\n      \u003clabel for=\"ProductSelect_14760533721437-option-0_1\"\u003eDefault Title\u003c\/label\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n  \n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_14760533721437\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption\n      data-img=\"\"\n      data-product_varation_type=\"buttons\"\n      data-compare_at_price_o=\"995\"\n      data-price_o=\"795\"\n      data-compare_at_price=\"9,95 EUR\"\n      data-price=\"7,95 EUR\"\n      selected=\"selected\"\n      data-sku=\"MARK-V1\"\n      value=\"54848339706205\"\n    \u003e\n      Default Title — 7,95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_14760533721437\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_14760533721437\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_14760533721437\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',795,14760533721437)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"14760533721437\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_14760533721437 button\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_14760533721437\"\n      \u003eADD TO CART\u003c\/span\n    \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=\"14760533721437\" \/\u003e\n\u003c\/form\u003e\n\n\u003cscript\u003e\ndocument.addEventListener(\"DOMContentLoaded\", () =\u003e {\n  \/\/ --- CONSTANTES ET DONNÉES ---\n  const MARKIA_PRICE = 7.95; \/\/ Prix fixe du Markia\n  const MARKIA_ORIGINAL_PRICE = 9.95; \/\/ Prix barré du Markia\n\n  const PACK_PRICES = {\n    \"markia-only\": 7.95, \/\/ Prix fixe\n  };\n  const PACK_ORIGINAL_PRICES = {\n    \"markia-only\": 9.95, \/\/ Prix barré\n  };\n\n  \/\/ Mapping des variants Shopify\n  const variantIDs = {\n    \/\/ Markia seul - un seul variant\n    14760533721437: {\n      Default: { normal: \"54848339706205\" },\n    },\n  };\n\n  \/\/ Mapping des noms de packs vers leurs IDs Shopify\n  const packIDMapping = {\n    \"markia-only\": \"14760533721437\",\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 (Markia n'a qu'un seul pack)\n  const PACK_IMAGES_BY_COLOR = {\n    \"markia-only\": {\n      default: \"https:\/\/lizia.fr\/cdn\/shop\/files\/markia-packaging_600x600.png?v=1732194868\",\n    },\n  };\n\n  const PACKS = [\n    {\n      id: \"markia-only\",\n      name: \"Markia\",\n      price: 7.95,\n      originalPrice: 9.95,\n      short: \"Markia\",\n      description:\n        \"The 2-in-1 bookmark Markia, practical and elegant for your reading.\",\n      features: [\n        \"2-in-1 bookmark\",\n        \"Compact design\",\n        \"Practical and light\",\n        \"Quality materials\",\n      ],\n      image:\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/markia-packaging_600x600.png?v=1732194868\",\n    },\n  ];\n\n  \/\/ Images par variante (Markia n'a qu'un seul pack)\n  const PRODUCT_IMAGES_BY_VARIANT = {\n    \"markia-only\": {\n      default: [\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/markia-livre_600x600.png?v=1732194867\",\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/markia-packaging_600x600.png?v=1732194868\",\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    \/\/ Markia n'a qu'un seul pack avec des images par défaut\n    if (pack === \"markia-only\") {\n      return (\n        PRODUCT_IMAGES_BY_VARIANT[pack]?.[\"default\"] ||\n        PRODUCT_IMAGES_BY_VARIANT[\"markia-only\"][\"default\"]\n      );\n    }\n\n    \/\/ Fallback\n    return PRODUCT_IMAGES_BY_VARIANT[\"markia-only\"][\"default\"];\n  }\n\n  \/\/ --- ÉTAT DE L'APPLICATION ---\n  let state = {\n    pack: \"markia-only\", \/\/ Pack par défaut : Markia seul\n    quantity: 1,\n    colors: [], \/\/ Non utilisé pour Markia\n    personalization: [], \/\/ Non utilisé pour Markia\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\n  function getPackImage(packId, colorId = null) {\n    \/\/ Markia n'a qu'un seul pack, retourner directement l'image\n    if (packId === \"markia-only\") {\n      return PACKS.find((p) =\u003e p.id === packId)?.image;\n    }\n\n    \/\/ Fallback\n    return PACKS.find((p) =\u003e p.id === \"markia-only\")?.image;\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 (Markia n'a pas de réduction) *\/\n  function calculateTotalDiscount() {\n    \/\/ Markia n'a aucune réduction, prix fixe à 7.95€ par unité\n    return {\n      packDiscount: 0,\n      quantityDiscount: 0,\n      total: 0,\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    \/\/ Markia n'a pas de couleurs ni personnalisation, masquer la section\n    const liziaItemsSection = document.getElementById(\"liziaItemsSection\");\n    if (liziaItemsSection) {\n      liziaItemsSection.style.display = \"none\";\n    }\n    if (liziaItemsContainer) {\n      liziaItemsContainer.innerHTML = \"\";\n    }\n    return;\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    \/\/ Prix unitaire fixe : 7.95€ pour Markia\n    const unitPrice = PACK_PRICES[state.pack]; \/\/ 7.95€\n    const originalUnitPrice = PACK_ORIGINAL_PRICES[state.pack] || unitPrice; \/\/ 9.95€\n\n    \/\/ Calcul simple : prix unitaire × quantité (pas de réduction)\n    const totalPrice = unitPrice * state.quantity;\n\n    \/\/ Calculer l'économie réalisée (Prix Original * Qté - Prix Payé)\n    const totalReferencePrice = originalUnitPrice * state.quantity;\n    const savings = totalReferencePrice - totalPrice;\n\n    \/\/ Mettre à jour les deux boutons avec le prix\n    let buttonText = `ADD TO CART • ${totalPrice.toFixed(2)}€`;\n    if (savings \u003e 0.01) {\n      buttonText = `ADD TO CART • \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    \/\/ Markia n'a pas de couleurs ni personnalisation, pas besoin de synchroniser\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    \/\/ Markia n'a pas de couleurs ni personnalisation, pas besoin de synchroniser\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\n   *\/\n  function getVariantID(packID, color, isPersonalized) {\n    \/\/ Markia : un seul variant, pas de couleur ni personnalisation\n    if (packID === \"14760533721437\") {\n      return variantIDs[packID].Default.normal; \/\/ Retourne le variant ID depuis le mapping\n    }\n\n    \/\/ Fallback\n    console.error(`Variant non trouvé pour pack ${packID}`);\n    return null;\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 et personnalisation : non utilisées pour Markia\n    \/\/ Les event listeners sont laissés pour compatibilité mais ne feront rien\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    \/\/ Markia : produit simple, pas de couleurs ni personnalisation\n    const packID = packIDMapping[state.pack]; \/\/ \"14760533721437\"\n    const productsToAdd = [];\n\n    \/\/ Gérer les produits 2 à X (si quantité \u003e 1)\n    if (state.quantity \u003e 1) {\n      const variantID = getVariantID(packID, \"Default\", false);\n      if (variantID) {\n        productsToAdd.push({\n          variantID: variantID,\n          quantity: state.quantity - 1, \/\/ Ajouter quantity - 1 via l'API\n          properties: {},\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    const firstVariantID = getVariantID(packID, \"Default\", false);\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    \/\/ Sélectionner le variant dans le formulaire caché\n    const optionSelector = `#ProductSelect_${packID} option[value=\"${firstVariantID}\"]`;\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:\",\n        firstVariantID\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_${packID}`);\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    }\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      \/\/ 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 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\"\u003eVerified\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":54848339706205,"sku":"MARK-V1","price":7.95,"currency_code":"EUR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/markia-livre.png?v=1732194867"},{"product_id":"coussin-de-lecture","title":"Reading pillow","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 satisfied readers\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\n          src=\"..\/public\/placeholder.svg\"\n          alt=\"Reading cushion\"\n          id=\"mainProductImage\"\n          class=\"mainImage\"\n        \/\u003e\n        \u003cbutton\n          id=\"prevImageBtn\"\n          class=\"galleryNavBtn prev\"\n          aria-label=\"Previous image\"\n        \u003e\n          \u003cspan class=\"galleryNavArrow\" aria-hidden=\"true\"\u003e\u003c\/span\u003e\n        \u003c\/button\u003e\n        \u003cbutton\n          id=\"nextImageBtn\"\n          class=\"galleryNavBtn next\"\n          aria-label=\"Next image\"\n        \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\"\n          \u003e⭐ +100,000 satisfied readers\u003c\/span\n        \u003e\n        \u003ch1\u003eREADING CUSHION\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 reviews)\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              Quantity\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\"\u003eDiscount on your order\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          Personalization\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          ADD TO CART\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        ADD TO CART\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\"\u003eDelivery in 3 business days\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\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_fr.webp?v=1762266031\"\n              alt=\"Made In France\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eMADE IN FRANCE\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eFrench quality\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_garanti.webp?v=1762266032\"\n              alt=\"2 ans de garantie\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e2 YEARS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eWarranty\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_rembourser.webp?v=1762267150\"\n              alt=\"15 jours satisfaits ou remboursé\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003e15 DAYS\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003eSatisfied or refunded\u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003cdiv class=\"guaranteeBadge\"\u003e\n          \u003cdiv class=\"guaranteeIcon\"\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/badge_livraison.webp?v=1762266031\"\n              alt=\"Livré en 1 à 3 jours\"\n              class=\"guaranteeIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"guaranteeTitle\"\u003eDELIVERED\u003c\/div\u003e\n          \u003cdiv class=\"guaranteeDesc\"\u003e1-5 days\u003c\/div\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cdiv\n      id=\"configEndSentinel\"\n      class=\"configEndSentinel\"\n      style=\"height: 1px; width: 100%\"\n    \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      How does the reading cushion work?\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\"\u003eOptimal comfort\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Enjoy ergonomic support that perfectly fits your\n              back and shoulders for an ideal reading position, wherever\n              you are.\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\"\u003eIdeal position\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Holds your book at the right height and angle to\n              avoid neck tension and wrist pain.\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\"\u003eExtended reading\u003c\/h3\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"itemBody\"\u003e\n            \u003cp class=\"itemDescription\"\u003e\n              Read for hours without fatigue thanks to its design made to offer\n              lasting comfort and total relaxation.\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\n            class=\"liziaVideo\"\n            data-video-id=\"1\"\n            playsinline\n            muted\n            preload=\"metadata\"\n          \u003e\n            \u003csource\n              src=\"https:\/\/cdn.shopify.com\/videos\/c\/o\/v\/333422d9fd34411cbaeda5b404b46544.mp4\"\n              type=\"video\/mp4\"\n            \/\u003e\n          \u003c\/video\u003e\n          \u003cdiv class=\"videoControls\"\u003e\n            \u003cbutton\n              class=\"videoPlayBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Play\/Pause\"\n            \u003e\n              \u003csvg\n                class=\"playIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath d=\"M8 5v14l11-7z\" \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"pauseIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" \/\u003e\n              \u003c\/svg\u003e\n            \u003c\/button\u003e\n            \u003cbutton\n              class=\"videoMuteBtn\"\n              data-video-id=\"1\"\n              aria-label=\"Mute\/Unmute\"\n            \u003e\n              \u003csvg\n                class=\"unmuteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\u003e\n              \u003c\/svg\u003e\n              \u003csvg\n                class=\"muteIcon\"\n                xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                viewBox=\"0 0 24 24\"\n                fill=\"white\"\n                width=\"24\"\n                height=\"24\"\n                style=\"display: none\"\n              \u003e\n                \u003cpath\n                  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\"\n                \/\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          Reading has never been so comfortable\n        \u003c\/h2\u003e\n        \u003ch3 class=\"beforeAfterSubtitle\" data-scroll-reveal\u003e\n          Without fatigue, with one hand\n        \u003c\/h3\u003e\n        \u003cp class=\"beforeAfterDescription\" data-scroll-reveal\u003e\n          Adapted to all thumbs, gain flexibility wherever you are.\n          Without effort, fully enjoy your reading, including in\n          the dark without disturbing anyone.\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\n              id=\"lightToggleBtn\"\n              class=\"lightToggleBtn\"\n              aria-label=\"Toggle light\"\n            \u003e\n              \u003cspan class=\"lightToggleOn\"\u003e\n                \u003csvg\n                  xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"white\"\n                  width=\"16\"\n                  height=\"16\"\n                \u003e\n                  \u003cpath\n                    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\"\n                  \/\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\n            id=\"beforeAfterImageOff\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_off_blue_V1.jpg?v=1692695272\"\n            alt=\"Lizia light off\"\n            class=\"beforeAfterImage beforeAfterImageOff\"\n          \/\u003e\n          \u003cimg\n            id=\"beforeAfterImageOn\"\n            src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/lizia_func2_blue_V2.jpg\"\n            alt=\"Lizia light on\"\n            class=\"beforeAfterImage beforeAfterImageOn\"\n          \/\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\n        href=\"https:\/\/www.europe1.fr\/emissions\/demain-au-bureau\/lizia-laccessoire-de-lecture-3-en-1-4163529\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_europe1.webp?v=1763465558\"\n          alt=\"Europe1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_quotidien.webp?v=1763465558\"\n          alt=\"Quotidien\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_bfm_business.webp?v=1763465558\"\n          alt=\"Bfm-tv-business\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.ouest-france.fr\/bretagne\/roudouallec-56110\/roudouallec-ils-creent-un-accessoire-de-lecture-innovant-16121898-f0a2-11ec-9262-0032434e6459\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_ouest_france.webp?v=1763465558\"\n          alt=\"Ouest-France\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tf1.webp?v=1763465558\"\n          alt=\"Tf1\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.letelegramme.fr\/morbihan\/roudouallec-56110\/deux-jeunes-bretons-donnent-un-coup-de-pouce-aux-lecteurs-avec-lizia-281522.php\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_telegramme.webp?v=1763465558\"\n          alt=\"Le-telegramme\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.m6.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_m6.webp?v=1763465558\"\n          alt=\"M6\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/entrepreneurs.lesechos.fr\/developpement-entreprise\/strategie\/de-linvention-futee-au-succes-commercial-lizia-a-la-conquete-des-fanas-de-lecture-2094778\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_les_echos.webp?v=1763465557\"\n          alt=\"Les-echos\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        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\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_tmc.webp?v=1763465559\"\n          alt=\"TMC\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca href=\"https:\/\/www.nrj.fr\/\" target=\"_blank\" class=\"mediaLogo\"\u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_nrj.webp?v=1763465558\"\n          alt=\"NRJ\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.7jours.fr\/actualites\/lizia-rennes-jeune-pousse-eclaire-lecture\/\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_7_jours.webp?v=1763465558\"\n          alt=\"7-jours\"\n        \/\u003e\n      \u003c\/a\u003e\n      \u003ca\n        href=\"https:\/\/www.lejournaldesentreprises.com\/breve\/lizia-lance-son-accessoire-de-lecture-en-belgique-et-en-suisse-2129508\"\n        target=\"_blank\"\n        class=\"mediaLogo\"\n      \u003e\n        \u003cimg\n          src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/logo_le_journal_des_entreprises.webp?v=1763465558\"\n          alt=\"le-journal-des-entreprises\"\n        \/\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\"\u003eLIZIA COMMITMENTS\u003c\/h2\u003e\n      \u003cp id=\"valuesSubtitle\" class=\"valuesSubtitle\"\u003e\n        Imagined in Rennes, Lizia is a French innovation designed to offer\n        lasting reading comfort, with local partners and\n        responsible materials.\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\n          class=\"valueItem active\"\n          data-index=\"0\"\n          data-title=\"Made in France\"\n          data-desc=\"Our nylon parts are produced in Western France and then assembled in Brittany. By choosing Lizia, you support 100% French manufacturing, a short supply chain and local industrial know-how, from design to shipping from Rennes.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- France Map Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_france.svg?v=1763826898\"\n              alt=\"France\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eMade in France\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003e\n              Production and assembly in the West\n            \u003c\/p\u003e\n            \u003c!-- Mobile Description (Hidden by default) --\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our nylon parts are produced in Western France and then\n              assembled in Brittany. By choosing Lizia, you support\n              100% French manufacturing, a short supply chain and local\n              industrial know-how, from design to shipping from Rennes.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 2 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"1\"\n          data-title=\"Assembled in ESAT\"\n          data-desc=\"Lizia is socially committed by entrusting the assembly of its products to partner ESAT (Establishments and Services for Support through Work) in Brittany, promoting the professional integration of people with disabilities.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Wheelchair\/Accessibility Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_esat.svg?v=1763826898\"\n              alt=\"ESAT\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eAssembled in ESAT\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eLocal partners\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia is socially committed by entrusting the assembly of its\n              products to partner ESAT (Establishments and Services for Support through\n              Work) in Brittany, promoting the professional\n              integration of people with disabilities.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 3 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"2\"\n          data-title=\"Recyclable materials\"\n          data-desc=\"We use technical PA12 for its robustness and durability, as well as 100% recyclable cardboard packaging. Our eco-design approach aims to minimize our environmental impact.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Leaf\/Recycle Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_recyclable.svg?v=1763826898\"\n              alt=\"Recyclable\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eRecyclable materials\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eTechnical PA12, recyclable cardboard\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              We use technical PA12 for its robustness and\n              durability, as well as 100% recyclable cardboard packaging.\n              Our eco-design approach aims to minimize our\n              environmental impact.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 4 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"3\"\n          data-title=\"Lépine Medal winner\"\n          data-desc=\"The Lizia innovation has been recognized and awarded with a medal at the prestigious Lépine Competition, a testament to its ingenuity and quality.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Medal Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_medaille.svg?v=1763826898\"\n              alt=\"Médaille\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eLépine Medal winner\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eAwarded innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              The Lizia innovation has been recognized and awarded with a medal\n              at the prestigious Lépine Competition, a testament to its ingenuity and\n              quality.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 5 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"4\"\n          data-title=\"Internationally patented\"\n          data-desc=\"Our unique technology is protected by international patents, ensuring the exclusivity of our one-handed reading solution.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Globe\/Patent Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_brevet.svg?v=1763996122\"\n              alt=\"Breveté\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eInternationally patented\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eProtected innovation\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Our unique technology is protected by international\n              patents, ensuring the exclusivity of our\n              one-handed reading solution.\n            \u003c\/p\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n\n        \u003c!-- Item 6 --\u003e\n        \u003cdiv\n          class=\"valueItem\"\n          data-index=\"5\"\n          data-title=\"Created by 2 students\"\n          data-desc=\"Lizia was born from the passion and entrepreneurship of two students, determined to improve readers' daily lives.\"\n        \u003e\n          \u003cdiv class=\"valueIcon\"\u003e\n            \u003c!-- Users\/Students Icon --\u003e\n            \u003cimg\n              src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0759\/3184\/4957\/files\/picto_etudiant.svg?v=1763996122\"\n              alt=\"Étudiants\"\n              class=\"valueIconImg\"\n            \/\u003e\n          \u003c\/div\u003e\n          \u003cdiv class=\"valueContent\"\u003e\n            \u003ch3 class=\"valueItemTitle\"\u003eCreated by 2 students\u003c\/h3\u003e\n            \u003cp class=\"valueItemShortDesc\"\u003eYoung innovative company\u003c\/p\u003e\n            \u003cp class=\"valueItemFullDescMobile\"\u003e\n              Lizia was born from the passion and entrepreneurship of two\n              students, determined to improve readers' daily lives.\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\"\u003eMade in France\u003c\/h3\u003e\n          \u003cp id=\"detailDesc\" class=\"detailDesc\"\u003e\n            Our nylon parts are produced in Western France and then\n            assembled in Brittany. By choosing Lizia, you support\n            100% French manufacturing, a short supply chain and local\n            industrial know-how, from design to shipping from 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        Adopted by many readers\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 reviews\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n    \u003cbutton class=\"reviewsNavBtn prev\" aria-label=\"Previous reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \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=\"Next reviews\"\u003e\n      \u003csvg\n        width=\"24\"\n        height=\"24\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n      \u003e\n        \u003cpolyline points=\"9 18 15 12 9 6\"\u003e\u003c\/polyline\u003e\n      \u003c\/svg\u003e\n    \u003c\/button\u003e\n    \u003cdiv\n      id=\"reviewsContainer\"\n      class=\"reviewsContainer\"\n      data-scroll-reveal\n    \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\u0026times;\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\n  method=\"post\"\n  action=\"\/cart\/add\"\n  id=\"prodForm\"\n  accept-charset=\"UTF-8\"\n  class=\"prod_form prod_form_header product_main_form_15775468388701\"\n  enctype=\"multipart\/form-data\"\n  novalidate=\"novalidate\"\n  data-product_id=\"15775468388701\"\n  data-other=\"prod_form_footer\"\n  data-product-form=\"true\"\n  style=\"display: none\"\n\u003e\n  \u003cinput type=\"hidden\" name=\"form_type\" value=\"product\" \/\u003e\n  \u003cinput type=\"hidden\" name=\"utf8\" value=\"✓\" \/\u003e\n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_15775468388701TEST\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption selected value=\"62707534889309\"\u003e\n      Reading cushion — 34.95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cselect\n    name=\"id\"\n    id=\"ProductSelect_15775468388701\"\n    class=\"product-single__variants\"\n    style=\"display: none\"\n  \u003e\n    \u003coption selected value=\"62707534889309\"\u003e\n      Reading cushion — 34.95 EUR\n    \u003c\/option\u003e\n  \u003c\/select\u003e\n  \u003cdiv class=\"cart-quantity\"\u003e\n    \u003cdiv class=\"cart-quantity-text\"\u003eQuantity\u003c\/div\u003e\n    \u003ca href=\"#\" field=\"updates_15775468388701\" class=\"qtyminus\"\n      \u003e\u003ci class=\"fa fa-minus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n    \u003cinput\n      type=\"text\"\n      name=\"quantity\"\n      id=\"updates_15775468388701\"\n      class=\"quantity\"\n      value=\"1\"\n    \/\u003e\n    \u003ca href=\"#\" field=\"updates_15775468388701\" class=\"qtyplus\"\n      \u003e\u003ci class=\"fa fa-plus\"\u003e\u003c\/i\n    \u003e\u003c\/a\u003e\n  \u003c\/div\u003e\n  \u003cbutton\n    onclick=\"snapAddToCart('EUR',3495,15775468388701)\"\n    type=\"submit\"\n    data-text=\"ADD TO CART\"\n    name=\"add\"\n    data-product_id=\"15775468388701\"\n    id=\"AddToCart\"\n    class=\"add_to_cart_btn add_to_cart_btn_15775468388701 button\"\n  \u003e\n    \u003csvg\n      version=\"1.1\"\n      id=\"Calque_1\"\n      xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n      xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 595.28 841.89\"\n      enable-background=\"new 0 0 595.28 841.89\"\n      xml:space=\"preserve\"\n    \u003e\n      \u003cpath\n        fill-rule=\"evenodd\"\n        clip-rule=\"evenodd\"\n        fill=\"#FFFFFF\"\n        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\"\n      \u003e\u003c\/path\u003e\n    \u003c\/svg\u003e\n    \u003cspan id=\"AddToCartText\" class=\"add_to_cart_txt_15775468388701\"\n      \u003eADD TO CART\u003c\/span\n    \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: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"An amazing discovery!\",\n      content: \"I received it and it's so cool!\",\n      verified: true,\n    },\n    {\n      firstName: \"Nathalie\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"Easy reading without disturbing others, perfect!\",\n      content:\n        \"I received my Lizia a few days ago. It's Christmas in September, what joy to be able to read in bed without disturbing my partner, on public transport...\",\n      verified: true,\n    },\n    {\n      firstName: \"Audrey\",\n      lastName: \"T\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"July\", year: 2025 },\n      title: \"Practical and reliable, I've been using it for a long time\",\n      content:\n        \"It's great for reading, I've had one for several months, it's top!\",\n      verified: true,\n    },\n    {\n      firstName: \"Sophie\",\n      lastName: \"D\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Essential for my reading evenings\",\n      content:\n        \"I can't do without it anymore! Perfect for reading in the evening without turning on the bedroom light.\",\n      verified: true,\n    },\n    {\n      firstName: \"Marc\",\n      lastName: \"L\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"June\", year: 2025 },\n      title: \"Perfect gift for readers\",\n      content:\n        \"Given to my wife, she loves it! The quality is there and the design is elegant.\",\n      verified: true,\n    },\n    {\n      firstName: \"Émilie\",\n      lastName: \"R\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"Lizia revolutionizes my reading moments\",\n      content:\n        \"No more tired arms and light problems! I recommend it 100%.\",\n      verified: true,\n    },\n    {\n      firstName: \"Thomas\",\n      lastName: \"B\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"July\", year: 2025 },\n      title: \"Excellent product, meets my expectations\",\n      content:\n        \"Fast delivery, quality product. I now read everywhere without constraints.\",\n      verified: true,\n    },\n    {\n      firstName: \"Charlotte\",\n      lastName: \"V\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"A must-have for all readers\",\n      content:\n        \"Simple, effective and so practical. My children even asked for their own!\",\n      verified: true,\n    },\n    {\n      firstName: \"Pierre\",\n      lastName: \"G\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"May\", year: 2025 },\n      title: \"French quality at its best\",\n      content:\n        \"Happy to support a French company. The product is robust and well thought out.\",\n      verified: true,\n    },\n    {\n      firstName: \"Isabelle\",\n      lastName: \"F\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"Perfect for reading while traveling\",\n      content:\n        \"I take it everywhere with me: train, plane, hotel. It has become my essential accessory.\",\n      verified: true,\n    },\n    {\n      firstName: \"Antoine\",\n      lastName: \"H\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"June\", year: 2025 },\n      title: \"Comfortable and discreet\",\n      content:\n        \"The lamp is powerful but doesn't disturb anyone. Ideal for nighttime reading.\",\n      verified: true,\n    },\n    {\n      firstName: \"Camille\",\n      lastName: \"P\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Elegant and functional design\",\n      content:\n        \"I love the clean design and available colors. Plus, it's super practical!\",\n      verified: true,\n    },\n    {\n      firstName: \"Julien\",\n      lastName: \"M\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"July\", year: 2025 },\n      title: \"Excellent value for money\",\n      content:\n        \"For the price, it's really an excellent investment. I recommend it without hesitation.\",\n      verified: true,\n    },\n    {\n      firstName: \"Léa\",\n      lastName: \"S\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Sept\", year: 2025 },\n      title: \"My children love it too\",\n      content:\n        \"My children use it every evening for their bedtime reading. It has become a ritual!\",\n      verified: true,\n    },\n    {\n      firstName: \"Maxime\",\n      lastName: \"C\",\n      gender: \"Reader\",\n      rating: 5,\n      date: { month: \"Aug\", year: 2025 },\n      title: \"Innovation at the service of reading\",\n      content:\n        \"Finally a product that thinks about readers' real needs. Bravo for this 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: \"Cushion only\",\n      price: 34.95,\n      short: \"Cushion only\",\n      description:\n        \"The reading cushion, perfect for optimal comfort during your reading moments.\",\n      features: [\n        \"Ergonomic cushion\",\n        \"Optimal comfort\",\n        \"Elegant design\",\n        \"Quality materials\",\n      ],\n      image:\n        \"https:\/\/lizia.fr\/cdn\/shop\/files\/coussin_pack_600x600.webp?v=1764085474\",\n    },\n    {\n      id: \"lizia-cushion\",\n      name: \"Lamp + Cushion\",\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        \"The Lizia pack with reading cushion, for optimal reading comfort.\",\n      features: [\n        \"Lizia reading lamp\",\n        \"Reading cushion\",\n        \"Integrated bookmark\",\n        \"One-handed reading\",\n        \"Long battery life\",\n        \"Compact and elegant design\",\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 Offer\" : 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=\"Learn more about ${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 Color';\n    } else if (colorsPersoLabel) {\n      colorsPersoLabel.innerHTML = \"Color\";\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\"\u003eEngraving (optional)\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=\"Add an engraving\" \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\u003eEngraving (optional)\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: Happy reading!\" \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 = `ADD TO CART • ${totalPrice.toFixed(2)}€`;\n    if (savings \u003e 0.01) {\n      buttonText = `ADD TO CART • \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\"\u003eVerified\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\/en\/collections\/accessories.oembed","provider":"Lizia","version":"1.0","type":"link"}