amazon-modern-widgets
Version:
Amazon Modern Widgets for Amazon affiliate websites based on Amazon PAAPI v5
2 lines • 18.2 kB
JavaScript
/* Amazon Modern Widgets - https://github.com/ltoinel/amw */
!function(){"use strict";const n={apiBaseUrl:window.location.origin+"/item",performance:{debounceDelay:300,maxConcurrentRequests:5,cacheExpiry:3e5,preloadImages:!0},lazyLoading:{enabled:!0,rootMargin:"50px",threshold:.1},api:{timeout:1e4,retryAttempts:2,retryDelay:1e3},languages:{fr:{buy:"➕ Plus d'infos",update:"↻ Dernière mise à jour : ",not_available:"Non disponible",loading:"Chargement...",error_not_found:"Produit non trouvé",error_network:"Erreur de connexion",error_timeout:"Délai dépassé",error_retry_message:"Vérifiez votre connexion ou réessayez plus tard"},es:{buy:"🚀 Yo voy !",update:"↻ Ultima actualización : ",not_available:"No disponible",loading:"Cargando...",error_not_found:"Producto no encontrado",error_network:"Error de conexión",error_timeout:"Tiempo agotado",error_retry_message:"Verifique su conexión o inténtelo de nuevo más tarde"},en:{buy:"🚀 Let's go !",update:"↻ Last product update : ",not_available:"Not available",loading:"Loading...",error_not_found:"Product not found",error_network:"Connection error",error_timeout:"Request timeout",error_retry_message:"Check your connection or try again later"},de:{buy:"🚀 Los geht's!",update:"↻ Letzte Aktualisierung : ",not_available:"Nicht verfügbar",loading:"Wird geladen...",error_not_found:"Produkt nicht gefunden",error_network:"Verbindungsfehler",error_timeout:"Zeitüberschreitung",error_retry_message:"Überprüfen Sie Ihre Verbindung oder versuchen Sie es später erneut"},it:{buy:"🚀 Andiamo!",update:"↻ Ultimo aggiornamento : ",not_available:"Non disponibile",loading:"Caricamento...",error_not_found:"Prodotto non trovato",error_network:"Errore di connessione",error_timeout:"Timeout della richiesta",error_retry_message:"Controlla la tua connessione o riprova più tardi"},pt:{buy:"🚀 Vamos lá!",update:"↻ Última atualização : ",not_available:"Indisponível",loading:"Carregando...",error_not_found:"Produto não encontrado",error_network:"Erro de conexão",error_timeout:"Tempo limite esgotado",error_retry_message:"Verifique sua conexão ou tente novamente mais tarde"},nl:{buy:"🚀 Laten we gaan!",update:"↻ Laatste update : ",not_available:"Niet beschikbaar",loading:"Laden...",error_not_found:"Product niet gevonden",error_network:"Verbindingsfout",error_timeout:"Verzoek time-out",error_retry_message:"Controleer uw verbinding of probeer het later opnieuw"}}};function e(e,t,o="not_found"){const r=n.languages[t]||n.languages.fr;let i;switch(o){case"not_found":default:i=`${r.error_not_found}: ${e}`;break;case"network_error":i=r.error_network;break;case"timeout":i=r.error_timeout}return`\n <div class="amw-widget-error">\n <span class="amw-widget-error-icon">⚠️</span>\n <div>${i}</div>\n <div class="amw-widget-error-retry-message">\n ${r.error_retry_message}\n </div>\n </div>\n `}function t(e,t){const o=n.languages[t]||n.languages.fr;return`\n <a href="#" class="amw-widget-main-link" target="_blank" rel="nofollow">\n <div class="amw-widget-img-container">\n <img src="" alt="Amazon product" class="amw-widget-img"/>\n </div>\n\n <div class="amw-widget-content">\n <div class="amw-widget-body">\n <h1 class="amw-widget-title">\n <span class="amw-widget-caption"></span>\n </h1>\n \n <div class="amw-widget-buttons">\n <span class="amw-widget-btn amw-widget-btn-success amw-widget-price" role="button"></span>\n <span class="amw-widget-btn amw-widget-btn-warning" role="button">${o.buy}</span>\n </div>\n\n <p class="amw-widget-text">\n <span class="amw-widget-timestamp"></span>\n </p>\n </div>\n\n <div class="amw-widget-banner">\n Amazon \n </div>\n </div>\n </a>\n\n <div class="amw-widget-spinner">\n <div class="amw-widget-spinner-grow" role="status">\n <span class="amw-widget-visually-hidden">${o.loading}</span>\n </div>\n </div>\n `}function o(t,i,s,l=1){n.languages[s]||n.languages.fr;const c=`${t}_${s}`,g=a.get(c);if(g&&Date.now()-g.timestamp<n.performance.cacheExpiry)return void r(g.data,i,s);if(d.has(c))return;d.add(c);const m=`${n.apiBaseUrl}/product?id=${t}`,u=setTimeout(()=>{console.error(`Timeout loading product ${t} (attempt ${l})`),l<n.api.retryAttempts?setTimeout(()=>{o(t,i,s,l+1)},n.api.retryDelay):i.innerHTML=e(t,s,"timeout")},n.api.timeout);fetch(m).then(n=>{if(clearTimeout(u),!n.ok)throw new Error(`HTTP ${n.status}`);return n.json()}).then(n=>{if(d.delete(c),null===n)return i.innerHTML=e(t,s,"not_found"),void console.log(`Amazon product not found: ${t}`);a.set(c,{data:n,timestamp:Date.now()}),r(n,i,s)}).catch(r=>{d.delete(c),clearTimeout(u),console.error(`Error loading product ${t} (attempt ${l}):`,r),l<n.api.retryAttempts?setTimeout(()=>{o(t,i,s,l+1)},n.api.retryDelay):i.innerHTML=e(t,s,"network_error")})}function r(e,t,o){const r=n.languages[o]||n.languages.fr,i=t.querySelector(".amw-widget-img"),a=t.querySelector(".amw-widget-caption"),d=t.querySelector(".amw-widget-price"),s=t.querySelector(".amw-widget-timestamp"),l=(t.querySelectorAll(".amw-widget-link"),t.querySelector(".amw-widget-spinner")),c=t.querySelector(".amw-widget-content"),g=t.querySelector(".amw-widget-body");i.src=e.image||"",i.onerror=function(){this.style.display="none"};const m=e.title||"Produit Amazon";a.textContent=m.length>70?m.slice(0,67)+"...":m;const u=t.querySelector(".amw-widget-main-link");var w;u&&(u.href=e.url||"#"),s.textContent=r.update+new Date(e.timestamp).toLocaleString(),-1===e.price?d.textContent=r.not_available:d.textContent=e.price,e.savings&&0!==e.savings&&(d.innerHTML=("string"!=typeof(w=e.price)?"":w.replace(/[<>&"']/g,function(n){return{"<":"<",">":">","&":"&",'"':""","'":"'"}[n]}).slice(0,1e3))+`<span class="amw-widget-badge">-${e.savings}%<span class="amw-widget-visually-hidden">Savings</span></span>`),l.style.display="none",i.style.display="block",c.style.display="block",g.style.display="block"}function i(n,e,t){o(n,e,t,1)}const a=new Map,d=new Set;let s=null;function l(){if(!window.IntersectionObserver)return console.warn("IntersectionObserver not supported, falling back to immediate loading"),null;const e={root:null,rootMargin:n.lazyLoading.rootMargin,threshold:n.lazyLoading.threshold};return new IntersectionObserver(n=>{n.forEach(n=>{if(n.isIntersecting){const e=n.target,t=e.getAttribute("data-product-id"),o=e.getAttribute("data-language");c(e,t,o),s.unobserve(e)}})},e)}function c(n,e,o){n.innerHTML=t(0,o),n.className="amw-widget",i(e,n,o)}function g(e){const t=e.id;if(!t)return void console.warn("Amazon widget element missing id attribute");const o=document.documentElement.getAttribute("lang")||"fr";e.setAttribute("data-product-id",t),e.setAttribute("data-language",o),e.className="amw-widget amw-widget-lazy",e.innerHTML=function(e){return`\n <div class="amw-widget-lazy-placeholder">\n <div class="amw-widget-lazy-icon"></div>\n <span>${(n.languages[e]||n.languages.fr).loading}</span>\n </div>\n `}(o),s?s.observe(e):c(e,t,o)}function m(n){const e=n.id;if(!e)return void console.warn("Amazon widget element missing id attribute");const o=document.documentElement.getAttribute("lang")||"fr";n.className="amw-widget",n.innerHTML=t(0,o),i(e,n,o)}function u(){!function(){if(document.getElementById("amw-widget-styles"))return;const n=document.createElement("style");n.id="amw-widget-styles",n.textContent="\n .amw-widget {\n unset: all;\n max-width: 100%;\n min-height: 100px;\n background-color: #333;\n display: flex;\n border-radius: 12px;\n overflow: hidden;\n margin: 10px 0;\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n font-family: ubuntu, sans-serif;\n border: 2px solid #8b5cf6;\n background: linear-gradient(135deg, #333 0%, #444 100%);\n position: relative;\n cursor: pointer;\n transition: transform 0.2s ease;\n }\n\n .amw-widget::after {\n content: '';\n position: absolute;\n top: 0;\n left: -100%;\n width: 100%;\n height: 100%;\n background: linear-gradient(\n 90deg,\n transparent,\n rgba(255, 255, 255, 0.4),\n transparent\n );\n transition: left 0.5s ease-in-out;\n z-index: 1;\n pointer-events: none;\n }\n\n .amw-widget:hover::after {\n left: 100%;\n }\n\n .amw-widget:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 25px rgba(139, 92, 246, 0.3);\n }\n\n .amw-widget-main-link {\n color: #FFF;\n text-decoration: none;\n display: flex;\n width: 100%;\n height: 100%;\n position: relative;\n }\n\n .amw-widget-main-link:hover {\n color: #FFF;\n }\n\n .amw-widget a {\n color: #FFF;\n text-decoration: none;\n }\n\n .amw-widget a:hover {\n color: #CCC;\n }\n\n .amw-widget-img-container {\n overflow: hidden;\n flex: 0 0 24%;\n max-width: 24%;\n height: 100%;\n display: flex;\n align-items: stretch;\n }\n\n .amw-widget-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n border-radius: 8px 0 0 8px;\n transform-origin: 0%;\n transition: transform 0.3s ease-out, filter 0.3s ease-in-out;\n display: none;\n will-change: transform;\n loading: lazy;\n background-color: #444;\n }\n\n .amw-widget-img:hover {\n transform: scale(1.05);\n }\n\n .amw-widget-content {\n flex: 1;\n display: none;\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n }\n\n .amw-widget-body {\n display: none;\n padding: 16px 20px;\n }\n\n .amw-widget-title {\n font-size: 0.8em;\n font-weight: bold;\n margin-bottom: 15px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #FFF;\n }\n\n .amw-widget-text {\n margin-top: 10px;\n color: #FFF;\n font-size: 0.6em;\n }\n\n .amw-widget-btn {\n display: inline-block;\n font-weight: bold;\n margin-right: 15px;\n padding: 4px 10px;\n border: none;\n border-radius: 6px;\n text-decoration: none;\n cursor: pointer;\n font-size: 0.8em;\n transition: background-color 0.2s;\n pointer-events: none;\n }\n\n .amw-widget-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n }\n\n .amw-widget-btn-success {\n background-color: #3e8d4d;\n color: white;\n }\n\n .amw-widget-btn-warning {\n background-color: #eccf00;\n color: #684a98;\n }\n\n .amw-widget-btn-warning:hover {\n background-color: #eccf00;\n color: #684a98;\n }\n \n .amw-widget-badge {\n padding: 0.25em 0.4em;\n font-size: 0.75em;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.375rem;\n transform: translate(-50%, -50%);\n background-color: #dc3545;\n color: white;\n border-radius: 50rem;\n animation: blinker 1s linear infinite;\n }\n\n .amw-widget-spinner {\n text-align: center;\n margin: 3rem;\n }\n\n .amw-widget-spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid transparent;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spinner-grow 0.75s linear infinite;\n color: #ffc107;\n }\n\n .amw-widget-visually-hidden {\n position: absolute !important;\n width: 1px !important;\n height: 1px !important;\n padding: 0 !important;\n margin: -1px !important;\n overflow: hidden !important;\n clip: rect(0, 0, 0, 0) !important;\n white-space: nowrap !important;\n border: 0 !important;\n }\n\n .amw-widget-lazy {\n background: linear-gradient(90deg, #333 25%, #444 50%, #333 75%);\n background-size: 200% 100%;\n animation: shimmer 2s infinite;\n min-height: 120px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #888;\n font-size: 0.9em;\n will-change: background-position;\n }\n\n .amw-widget-lazy-placeholder {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .amw-widget-lazy-icon {\n width: 20px;\n height: 20px;\n border: 2px solid #666;\n border-top: 2px solid #fff;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n will-change: transform;\n }\n\n .amw-widget-error {\n background-color: #2c1810;\n border: 1px solid #d32f2f;\n color: #ff6b6b;\n padding: 20px;\n text-align: center;\n border-radius: 8px;\n font-size: 0.9em;\n }\n\n .amw-widget-error-icon {\n font-size: 1.5em;\n margin-bottom: 10px;\n display: block;\n }\n\n .amw-widget-timestamp{\n margin-top: 10px;\n display: block; \n font-size: 0.7em;\n color: #bbb;\n }\n \n .amw-widget-banner {\n position: absolute;\n bottom: 0;\n right: 0;\n background: #222;\n color: #fff;\n font-weight: bold;\n padding: 2px 40px;\n transform: rotate(-45deg) translate(50%, -130%);\n transform-origin: bottom right;\n pointer-events: none;\n box-shadow: 0 4px 10px rgba(0,0,0,0.35);\n font-size: 0.7em;\n }\n\n @keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n transform: none;\n }\n }\n\n @keyframes blinker {\n 50% {\n opacity: 0;\n }\n }\n\n @keyframes shimmer {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n }\n\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n\n /* Responsive design */\n @media (max-width: 576px) {\n .amw-widget-img-container {\n flex: 0 0 25%;\n max-width: 25%;\n }\n \n .amw-widget-btn-warning {\n display: none !important;\n }\n \n .amw-widget-text {\n display: none !important;\n }\n }\n ",document.head.appendChild(n)}(),n.lazyLoading.enabled&&(s=l());const e=document.querySelectorAll("div.amw");n.lazyLoading.enabled?(e.forEach(g),console.log(`Initialized ${e.length} AMW widgets with lazy loading`)):(e.forEach(m),console.log(`Initialized ${e.length} AMW widgets with immediate loading`))}function w(){new MutationObserver(function(e){e.forEach(function(e){e.addedNodes.forEach(function(e){if(e.nodeType===Node.ELEMENT_NODE){e.matches&&e.matches("div.amw")&&(n.lazyLoading.enabled?g(e):m(e));const t=e.querySelectorAll&&e.querySelectorAll("div.amw");t&&(n.lazyLoading.enabled?t.forEach(g):t.forEach(m))}})})}).observe(document.body,{childList:!0,subtree:!0})}function p(){"loading"===document.readyState?document.addEventListener("DOMContentLoaded",function(){u(),w()}):(u(),w())}window.AmwWidget={init:p,transformElement:m,transformLazyElement:g,config:n,enableLazyLoading:function(){n.lazyLoading.enabled=!0,s||(s=l())},disableLazyLoading:function(){n.lazyLoading.enabled=!1,s&&(s.disconnect(),s=null)},loadAllWidgets:function(){document.querySelectorAll(".amw-widget-lazy").forEach(n=>{const e=n.getAttribute("data-product-id"),t=n.getAttribute("data-language");e&&t&&c(n,e,t)})},destroy:function(){s&&(s.disconnect(),s=null)},isLazyLoadingSupported:function(){return!!window.IntersectionObserver},setApiConfig:function(e){void 0!==e.timeout&&(n.api.timeout=e.timeout),void 0!==e.retryAttempts&&(n.api.retryAttempts=e.retryAttempts),void 0!==e.retryDelay&&(n.api.retryDelay=e.retryDelay)},getCacheStats:function(){return{cacheSize:a.size,activeRequests:d.size,cacheHitRate:a.size>0?a.size/(a.size+d.size):0}},clearCache:function(){a.clear(),d.clear()}},p()}();