1document.addEventListener('DOMContentLoaded', function () { 2 const accordion = document.querySelector('.shop-details-accordion .accordion-inner'); 3 if (!accordion) return; 4 5 const firstImage = accordion.querySelector('img'); 6 const title = accordion.querySelector('h2[style*="text-align: center"], h2') || document.querySelector('.product_title'); 7 if (!firstImage || !title) return; 8 9 if (accordion.querySelector('.aw-product-reassurance-rating')) return; 10 11 function hashString(str) { 12 let hash = 0; 13 for (let i = 0; i < str.length; i++) { 14 hash = ((hash << 5) - hash) + str.charCodeAt(i); 15 hash |= 0; 16 } 17 return Math.abs(hash); 18 } 19 20 function seededValue(seed, min, max) { 21 return min + (seed % (max - min + 1)); 22 } 23 24 function hasType(node, type) { 25 if (!node || !node['@type']) return false; 26 return Array.isArray(node['@type']) 27 ? node['@type'].includes(type) 28 : node['@type'] === type; 29 } 30 31 const seedBase = window.location.pathname + '|' + title.textContent.trim(); 32 const seed = hashString(seedBase); 33 34 const ratingTenths = seededValue(seed, 43, 49); 35 const rating = (ratingTenths / 10).toFixed(1).replace('.', ','); 36 const schemaRatingValue = (ratingTenths / 10).toFixed(1); 37 const reviews = seededValue(seed * 7, 38, 97); 38 39 const fullStars = Math.floor(ratingTenths / 10); 40 const decimal = ratingTenths % 10; 41 const starPercent = (decimal / 10) * 100; 42 43 const ratingBlock = document.createElement('div'); 44 ratingBlock.className = 'aw-product-reassurance-rating'; 45 ratingBlock.innerHTML = ` 46 <div class="aw-rating-wrap" aria-label="Note client ${rating} sur 5 basée sur ${reviews} avis"> 47 <div class="aw-stars" aria-hidden="true"> 48 <span class="aw-stars-base">â â â â â </span> 49 <span class="aw-stars-fill" style="width:${((fullStars * 20) + (starPercent * 0.2))}%">â â â â â </span> 50 </div> 51 <div class="aw-rating-text"> 52 <strong>${rating}/5</strong> 53 <span>sur ${reviews} avis clients</span> 54 </div> 55 </div> 56 `; 57 58 firstImage.insertAdjacentElement('afterend', ratingBlock); 59 60 const ratingWrap = ratingBlock.querySelector('.aw-rating-wrap'); 61 const cart = document.querySelector('.cart'); 62 63 if (ratingWrap && cart) { 64 ratingWrap.style.cursor = 'pointer'; 65 ratingWrap.addEventListener('click', function () { 66 cart.scrollIntoView({ 67 behavior: 'smooth', 68 block: 'center' 69 }); 70 }); 71 } 72 73 const aggregateRating = { 74 "@type": "AggregateRating", 75 "ratingValue": schemaRatingValue, 76 "bestRating": "5", 77 "worstRating": "1", 78 "ratingCount": reviews 79 }; 80 81 const scripts = document.querySelectorAll('script[type="application/ld+json"]'); 82 let schemaInjected = false; 83 84 scripts.forEach(script => { 85 try { 86 const data = JSON.parse(script.textContent); 87 88 if (Array.isArray(data)) { 89 data.forEach(node => { 90 if ((hasType(node, 'Product') || hasType(node, 'SoftwareApplication')) && !node.aggregateRating) { 91 node.aggregateRating = aggregateRating; 92 schemaInjected = true; 93 } 94 }); 95 if (schemaInjected) { 96 script.textContent = JSON.stringify(data); 97 } 98 return; 99 } 100 101 if (data['@graph'] && Array.isArray(data['@graph'])) { 102 let changed = false; 103 data['@graph'].forEach(node => { 104 if ((hasType(node, 'Product') || hasType(node, 'SoftwareApplication')) && !node.aggregateRating) { 105 node.aggregateRating = aggregateRating; 106 changed = true; 107 schemaInjected = true; 108 } 109 }); 110 if (changed) { 111 script.textContent = JSON.stringify(data); 112 } 113 return; 114 } 115 116 if ((hasType(data, 'Product') || hasType(data, 'SoftwareApplication')) && !data.aggregateRating) { 117 data.aggregateRating = aggregateRating; 118 script.textContent = JSON.stringify(data); 119 schemaInjected = true; 120 } 121 } catch (e) {} 122 }); 123 124 if (!schemaInjected) { 125 const schema = { 126 "@context": "https://schema.org", 127 "@type": "Product", 128 "name": title.textContent.trim(), 129 "aggregateRating": aggregateRating 130 }; 131 132 const newScript = document.createElement('script'); 133 newScript.type = 'application/ld+json'; 134 newScript.textContent = JSON.stringify(schema); 135 document.head.appendChild(newScript); 136 } 137}); 138 139document.addEventListener("DOMContentLoaded", function() { 140 // 1. Conversion des tableaux en galerie Masonry 141 // On cible les tableaux (vous pouvez affiner le sélecteur, ex: 'table.ma-classe') 142 const tables = document.querySelectorAll('.single-product table'); 143
144 tables.forEach(table => { 145 // On cherche les liens contenant des images dans ce tableau 146 const linksWithImages = table.querySelectorAll('a > img'); 147 148 // Si le tableau ne contient pas d'images, on l'ignore 149 if (linksWithImages.length === 0) return; 150 151 // Création du conteneur de la galerie 152 const galleryContainer = document.createElement('div'); 153 galleryContainer.className = 'masonry-gallery'; 154 155 linksWithImages.forEach(img => { 156 const link = img.closest('a'); 157 158 // Création de l'élément parent de l'image 159 const wrapper = document.createElement('div'); 160 wrapper.className = 'gallery-item'; 161 162 // Clonage de l'image pour la nettoyer des classes inutiles du tableau 163 const newImg = document.createElement('img'); 164 newImg.src = img.src; 165 newImg.alt = img.alt || ''; 166 // On stocke l'URL du lien (l'image en haute résolution) dans un data-attribute 167 newImg.dataset.fullSrc = link.href; 168 169 wrapper.appendChild(newImg); 170 galleryContainer.appendChild(wrapper); 171 }); 172 173 // Remplacement du tableau original par la nouvelle galerie dans le DOM 174 table.parentNode.replaceChild(galleryContainer, table); 175 }); 176 177 // 2. Création et gestion de la Lightbox (Agrandissement + Zoom) 178 const lightbox = document.createElement('div'); 179 lightbox.className = 'vanilla-lightbox'; 180 lightbox.innerHTML = ` 181 <span class="vanilla-lightbox-close">Ã</span> 182 <img src="" alt="Aperçu"> 183 `; 184 document.body.appendChild(lightbox); 185 186 const lightboxImg = lightbox.querySelector('img'); 187 188 // Gestionnaire de clics global 189 document.body.addEventListener('click', function(e) { 190 191 // A. Ouvrir la lightbox 192 if (e.target.matches('.masonry-gallery img')) { 193 e.preventDefault(); 194 lightboxImg.src = e.target.dataset.fullSrc || e.target.src; 195 lightbox.classList.add('active'); 196 } 197 198 // B. Gérer le Zoom si on clique sur l'image dans la lightbox 199 else if (e.target === lightboxImg) { 200 lightboxImg.classList.toggle('zoomed'); 201 // Si on dézoome, on recentre l'origine 202 if (!lightboxImg.classList.contains('zoomed')) { 203 lightboxImg.style.transformOrigin = 'center center'; 204 } 205 } 206 207 // C. Fermer la lightbox (clic sur le fond ou la croix) 208 else if (e.target.matches('.vanilla-lightbox') || e.target.matches('.vanilla-lightbox-close')) { 209 lightbox.classList.remove('active'); 210 lightboxImg.classList.remove('zoomed'); // Réinitialiser le zoom à la fermeture 211 lightboxImg.style.transformOrigin = 'center center'; 212 213 setTimeout(() => { 214 if(!lightbox.classList.contains('active')) lightboxImg.src = ''; 215 }, 300); 216 } 217 }); 218 219 // 3. Suivre la souris pour "naviguer" dans l'image zoomée 220 lightboxImg.addEventListener('mousemove', function(e) { 221 if (this.classList.contains('zoomed')) { 222 // Récupère les dimensions et la position de l'image 223 const rect = this.getBoundingClientRect(); 224 225 // Calcule la position de la souris en pourcentage par rapport à l'image 226 const x = ((e.clientX - rect.left) / rect.width) * 100; 227 const y = ((e.clientY - rect.top) / rect.height) * 100; 228 229 // Déplace le point focal du zoom là où se trouve la souris 230 this.style.transformOrigin = `${x}% ${y}%`; 231 } 232 }); 233});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.