/* =========================================================
AZ PDP + MINI CART V82 (PRODUCT PAGE ONLY)
Runs ONLY when JS adds: body.az-pdp-v82
========================================================= */
/* --- 1. FORCE HIDE STOCK INDICATORS (PDP ONLY) --- */
body.az-pdp-v82 .circle_instock,
body.az-pdp-v82 .circle_outstock,
body.az-pdp-v82 .m_ecomm_inventory_icon,
body.az-pdp-v82 .m_ecomm_inventory,
body.az-pdp-v82 .m_ecomm_productorderbutton .col-12 > div:nth-child(3) {
display: none !important;
}
/* --- 2. STYLE THE DESKTOP HEADER SUBTOTAL TEXT (PDP ONLY) --- */
body.az-pdp-v82 > div:nth-child(7) > div > div > div > div > div.col-2.col-xl-4.d-none.d-xl-flex.flex-row.justify-content-end.headerhidden > div > div > a > span {
font-size: 16px !important;
font-weight: 700 !important;
color: #333333 !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
min-width: 80px;
display: inline-block;
text-align: right;
}
/* --- 3. MOBILE PRICE TEXT STYLING (PDP ONLY) --- */
@media (max-width: 992px) {
body.az-pdp-v82 span.mx-2.text-dark {
display: inline-block !important;
font-weight: 700 !important;
color: #333333 !important;
font-size: 12px !important;
}
}
/* --- 4. TOAST NOTIFICATION (only exists on PDP because JS only injects on PDP) --- */
#azAddedToastX65 {
position: fixed; right: 20px; bottom: 90px; z-index: 2147483647;
opacity: 0; visibility: hidden; transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
display: flex; align-items:center; gap:10px; padding: 12px 16px;
border-radius: 8px; background:#16a34a; color:#fff;
box-shadow: 0 10px 25px rgba(0,0,0,0.2); font-weight: 700; font-size: 14px;
pointer-events: none;
}
#azAddedToastX65.az-active { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }
#azAddedToastX65 .t { width:20px; height:20px; border-radius:50%; background: rgba(255,255,255,.25); display:flex; align-items:center; justify-content:center; font-weight: 900; font-size:12px; }
@media (max-width: 768px) {
#azAddedToastX65 {
right: auto !important; left: 50% !important; transform: translate(-50%, 10px) !important;
bottom: 20px !important; width: auto !important; min-width: 200px;
justify-content: center; border-radius: 50px !important; white-space: nowrap;
}
#azAddedToastX65.az-active { transform: translate(-50%, 0) !important; }
}
/* --- 5. MINI CART POPUP (only exists on PDP because JS only injects on PDP) --- */
#azMiniCartX62 {
position: fixed; width: 380px; max-width: 95vw;
background: #fff; border: 1px solid rgba(0,0,0,.1); border-radius: 12px;
box-shadow: 0 15px 40px rgba(0,0,0,.2); z-index: 2147483646;
display: none; overflow: hidden;
}
/* MOBILE FIX */
@media (max-width: 992px) {
#azMiniCartX62 {
top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important;
width: 90% !important; max-height: 80vh !important;
display: none;
flex-direction: column;
}
#azMiniCartX62[style*="display: block"] { display: flex !important; }
#azMiniCartX62 .azmc-body { overflow-y: auto !important; max-height: 50vh !important; }
}
/* Standard Hiding (PDP ONLY) */
body.az-pdp-v82 [id^="azMobileWrapV"],
body.az-pdp-v82 [id^="azTabsWrapV"],
body.az-pdp-v82 [id^="azMiniCartV"],
body.az-pdp-v82 [id^="azAddedToastV"],
body.az-pdp-v82 [id^="azTopBtnV"] { display:none !important; }
body.az-pdp-v82 .m_ecomm_inventory,
body.az-pdp-v82 .m_ecomm_inventory_icon,
body.az-pdp-v82 #showStockTable,
body.az-pdp-v82 span.m_ecomm_inventory.borderGreen,
body.az-pdp-v82 span.m_ecomm_inventory.borderGreen * { display:none !important; }
body.az-pdp-v82 .row.price-row span.floatLeft,
body.az-pdp-v82 .row.price-row span.floatLeft small,
body.az-pdp-v82 .row.price-row small { display:none !important; }
body.az-pdp-v82 .row.price-row .price{ display:flex !important; align-items:baseline !important; gap:12px !important; flex-wrap:wrap !important; width:100% !important; }
body.az-pdp-v82 .row.price-row .newPrice{ color:#16a34a !important; font-weight:950 !important; font-size:36px !important; line-height:1.05 !important; }
body.az-pdp-v82 .azpdp-rrp{ display:inline-flex; align-items:baseline; gap:8px; font-weight:900; }
body.az-pdp-v82 .azpdp-rrp-old{ color:#64748b; text-decoration:line-through; font-size:18px; font-weight:900; white-space:nowrap; }
body.az-pdp-v82 .azpdp-save{ background:#E11D2E; color:#fff; font-size:11px; padding:3px 7px; border-radius:5px; font-weight:950; white-space:nowrap; }
body.az-pdp-v82 .azpdp-vat{ width:100%; font-size:12px; font-weight:800; color:#94a3b8; margin-top:2px; }
body.az-pdp-v82 .azpdp-stock{ margin-top:10px; width:100%; display:inline-flex; align-items:center; gap:8px; padding:10px 12px; border-radius:8px; font-weight:950; text-transform:uppercase; font-size:12px; border:1px solid transparent; }
body.az-pdp-v82 .azpdp-in{ background:#f0fdf4; color:#16a34a; border-color:#dcfce7; }
body.az-pdp-v82 .azpdp-out{ background:#fff1f2; color:#e11d48; border-color:#fecdd3; }
body.az-pdp-v82 .azpdp-dot{ width:10px; height:10px; border-radius:50%; }
body.az-pdp-v82 .azpdp-dot-g{ background:#16a34a; }
body.az-pdp-v82 .azpdp-dot-r{ background:#e11d48; }
body.az-pdp-v82 .azpdp-pay{ width:100%; display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin-top:10px; }
body.az-pdp-v82 .azpdp-pay img{ height:34px; width:auto; display:block; object-fit:contain; border-radius:6px; background:#fff; }
body.az-pdp-v82 .azpdp-pay img[src*="23/35_image_upload.png"]{ height:60px !important; }
body.az-pdp-v82 .az-compat-msg{ background:#fff3cd; border:1px solid #ffe69c; color:#0f172a; border-radius:10px; padding:10px 12px; font-weight:800; font-size:13px; margin:10px 0; }
body.az-pdp-v82 ul.carsOeFit.nav.nav-tabs{ border:none !important; background:#1965FF !important; border-radius:14px 14px 0 0 !important; padding:10px 12px 0 12px !important; margin:0 !important; display:flex !important; flex-wrap:nowrap !important; gap:10px !important; overflow-x:auto !important; -webkit-overflow-scrolling:touch !important; }
body.az-pdp-v82 ul.carsOeFit .nav-item{ flex:0 0 auto !important; }
body.az-pdp-v82 ul.carsOeFit .nav-link{ border:none !important; border-radius:12px 12px 0 0 !important; padding:10px 12px !important; font-weight:950 !important; font-size:14px !important; white-space:nowrap !important; color:#fff !important; background: rgba(255,255,255,.16) !important; }
body.az-pdp-v82 ul.carsOeFit .nav-link:hover{ background:#000 !important; }
body.az-pdp-v82 ul.carsOeFit .nav-link.active, body.az-pdp-v82 ul.carsOeFit .nav-link[aria-selected="true"]{ background:#000 !important; }
body.az-pdp-v82 .tab-content.azpdp-tabcontent{ border:1px solid #e2e8f0 !important; border-top:none !important; border-bottom-left-radius:14px !important; border-bottom-right-radius:14px !important; background:#fff !important; padding:18px !important; margin:0 !important; }
body.az-pdp-v82 .azpdp-grid{ display:grid; grid-template-columns: 1.15fr .85fr; gap:18px; align-items:start; }
@media (max-width: 992px){ body.az-pdp-v82 .azpdp-grid{ grid-template-columns:1fr; } }
body.az-pdp-v82 .azpdp-card{ background:#fff; border:1px solid #e2e8f0; border-radius:14px; box-shadow: 0 10px 26px rgba(2,6,23,.06); overflow:hidden; }
body.az-pdp-v82 .azpdp-h{ padding:14px; border-bottom:1px solid #eef2f7; display:flex; justify-content:space-between; align-items:center; gap:10px; background: linear-gradient(180deg,#fff,#f8fafc); }
body.az-pdp-v82 .azpdp-h b{ font-weight:950; color:#0f172a; }
body.az-pdp-v82 .azpdp-pill{ font-size:12px; font-weight:950; letter-spacing:.6px; text-transform:uppercase; padding:6px 10px; border-radius:999px; background: rgba(25,101,255,.10); border: 1px solid rgba(25,101,255,.25); color:#1040a0; white-space:nowrap; }
body.az-pdp-v82 .azpdp-b{ padding:16px; color:#334155; line-height:1.75; font-size:14px; }
body.az-pdp-v82 .azpdp-title{ font-size:18px; font-weight:950; color:#0f172a; margin:0 0 10px 0; line-height:1.25; }
body.az-pdp-v82 .azpdp-sub{ font-size:13px; font-weight:850; color:#475569; margin:0 0 10px 0; }
body.az-pdp-v82 .azpdp-h4{ margin:14px 0 8px 0; font-weight:950; color:#0f172a; font-size:14px; }
body.az-pdp-v82 .azpdp-ul{ margin:10px 0 0 0; padding-left:18px; }
body.az-pdp-v82 .azpdp-ul li{ margin:6px 0; }
body.az-pdp-v82 .azpdp-note{ margin-top:12px; padding:12px; border-radius:12px; background:#f8fafc; border:1px dashed rgba(15,23,42,.18); color:#475569; font-weight:650; font-size:13px; }
body.az-pdp-v82 .azpdp-props table.table.prop.table-striped,
body.az-pdp-v82 .azpdp-props table.table.prop{ width:100% !important; border-collapse:separate !important; border-spacing:0 !important; border:1px solid #cbd5e1 !important; border-radius:12px !important; overflow:hidden !important; background:#fff !important; }
body.az-pdp-v82 .azpdp-props table.table.prop td{ padding:12px !important; border-bottom:1px solid #e2e8f0 !important; vertical-align:top !important; font-size:14px !important; word-break:break-word !important; overflow-wrap:anywhere !important; }
body.az-pdp-v82 .azpdp-props table.table.prop tr:nth-child(odd) td{ background:#f1f5f9 !important; }
body.az-pdp-v82 .azpdp-props table.table.prop tr:last-child td{ border-bottom:none !important; }
body.az-pdp-v82 .azpdp-props table.table.prop td:first-child{ width:48% !important; font-weight:950 !important; color:#0f172a !important; }
body.az-pdp-v82 .azpdp-props table.table.prop td:last-child{ width:52% !important; font-weight:750 !important; color:#334155 !important; }
#azMobileWrapX62{ display:none; }
@media (max-width: 992px){ body.az-pdp-v82 ul.carsOeFit.nav.nav-tabs, body.az-pdp-v82 .tab-content.azpdp-tabcontent{ display:none !important; } #azMobileWrapX62{ display:block !important; } }
#azMobileWrapX62{ border:1px solid #e2e8f0; border-radius:16px; background:#fff; overflow:hidden; }
#azMobileWrapX62 .azm-open{ padding:12px; }
#azMobileWrapX62 .azm-divider{ height:1px; background:#eef2f7; margin:12px 0; }
#azMobileWrapX62 details{ border-top:1px solid #eef2f7; }
#azMobileWrapX62 summary{ list-style:none; cursor:pointer; padding:12px 12px; font-weight:950; color:#0f172a; display:flex; align-items:center; justify-content:space-between; gap:10px; }
#azMobileWrapX62 summary::-webkit-details-marker{ display:none; }
#azMobileWrapX62 summary:after{ content:"▾"; font-size:16px; opacity:.75; }
#azMobileWrapX62 details[open] summary:after{ content:"▴"; }
#azMobileWrapX62 .azm-body{ padding: 0 12px 12px 12px; color:#334155; line-height:1.65; font-size:14px; }
body.az-pdp-v82 .az-expanded-list { max-height: none !important; overflow: visible !important; }
#azTopBtnX62{ position:fixed; right:16px; bottom:16px; width:46px; height:46px; border:none; border-radius:999px; background:#1965FF; color:#fff; display:none; align-items:center; justify-content:center; font-weight:950; font-size:18px; box-shadow: 0 14px 30px rgba(2,6,23,.18); z-index:2147483646; cursor:pointer; }
#azMiniCartX62 .azmc-h{ padding: 10px 12px; border-bottom: 1px solid rgba(15,23,42,.08); display:flex; align-items:center; justify-content:space-between; gap:10px; background: #f8fafc; }
#azMiniCartX62 .azmc-title{ font-weight: 950; font-size: 13px; color: #0f172a; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#azMiniCartX62 .azmc-right{ display:flex;align-items:center;gap:8px; }
#azMiniCartX62 .azmc-pill{ font-size: 11px; font-weight: 950; padding: 6px 10px; border-radius: 999px; background: rgba(22,163,74,.12); border: 1px solid rgba(22,163,74,.25); color:#166534; white-space:nowrap; display:none; }
/* LOGIN BUTTON HIDDEN VIA CSS */
#azMiniCartX62 .azmc-login { display: none !important; }
#azMiniCartX62 .azmc-close{ border:none; background:transparent; font-weight:950; font-size:18px; line-height:1; color:#0f172a; padding:4px 6px; cursor:pointer; opacity:.7; }
#azMiniCartX62 .azmc-close:hover{ opacity:1; }
#azMiniCartX62 .azmc-added{ display:none; padding:10px 12px; background:#16a34a; color:#fff; font-weight:950; font-size:13px; border-bottom:1px solid rgba(0,0,0,.06); }
#azMiniCartX62.azmc-mode-added .azmc-added{ display:block; }
#azMiniCartX62 .azmc-body{ padding: 10px 12px; max-height: 340px; overflow:auto; }
#azMiniCartX62 .azmc-item{ display:flex; gap:10px; border-radius: 12px; padding: 10px 0; border-bottom: 1px solid rgba(15,23,42,.06); cursor:pointer; }
#azMiniCartX62 .azmc-img{ width: 42px; height: 42px; border-radius: 10px; border: 1px solid rgba(15,23,42,.10); object-fit: cover; background: #fff; flex: 0 0 auto; }
#azMiniCartX62 .azmc-mid{ min-width:0; flex:1; }
#azMiniCartX62 .azmc-name{ font-size: 13px; font-weight: 850; color: #0f172a; line-height: 1.2; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
#azMiniCartX62 .azmc-qty{ margin-top: 4px; font-size: 12px; font-weight: 800; color: #64748b; }
#azMiniCartX62 .azmc-price{ font-size: 13px; font-weight: 950; color: #0f172a; white-space:nowrap; margin-left: 10px; }
#azMiniCartX62 .azmc-empty{ padding: 10px 0; font-weight: 850; color:#475569; }
#azMiniCartX62 .azmc-progress{ margin: 8px 0 0 0; padding: 10px 12px; border-radius: 10px; background:#f8fafc; border:1px solid rgba(15,23,42,.06); }
#azMiniCartX62 .azmc-progress-top{ display:flex; justify-content:space-between; gap:10px; font-size:12px; font-weight:900; color:#0f172a; margin-bottom:8px; }
#azMiniCartX62 .azmc-bar{ height:10px; border-radius:999px; background:#e2e8f0; overflow:hidden; }
#azMiniCartX62 .azmc-bar > i{ display:block; height:100%; width:0%; background:#16a34a; }
#azMiniCartX62 .azmc-progress-note{ margin-top:8px; font-size:12px; font-weight:850; color:#475569; }
#azMiniCartX62 .azmc-f{ padding: 12px; border-top: 1px solid rgba(15,23,42,.08); background: #fff; }
#azMiniCartX62 .azmc-sub{ display:flex; justify-content:space-between; align-items:baseline; margin-bottom: 10px; }
#azMiniCartX62 .azmc-sub b{ font-weight: 950; color:#0f172a; }
#azMiniCartX62 .azmc-sub small{ color:#64748b; font-weight:800; }
#azMiniCartX62 .azmc-btn{ width:100%; display:block; border:none; border-radius: 10px; padding: 11px 12px; font-weight: 950; cursor:pointer; text-align:center; text-decoration:none; }
#azMiniCartX62 .azmc-btn-checkout{ background:#1965FF; color:#fff; margin-bottom:8px; }
#azMiniCartX62 .azmc-btn-basket{ background: transparent; color:#1965FF; border:1px solid rgba(25,101,255,.25); margin-bottom:8px; }
#azMiniCartX62 .azmc-btn-continue{ background: #f8fafc; color:#0f172a; border:1px solid rgba(15,23,42,.10); }
body.az-silent-modal #shoppingcartModal,
body.az-silent-modal .modal-backdrop{ opacity:0 !important; pointer-events:none !important; }
(function(){
"use strict";
const CORE_KEY = "__AZ_CORE_V82_STATE__";
const PDP_CLASS = "az-pdp-v82";
// Kill any previous instance (important for SPA/back button)
try{ if (window[CORE_KEY] && typeof window[CORE_KEY].destroy === "function") window[CORE_KEY].destroy(); }catch(_){}
const qs = (s, r=document) => r.querySelector(s);
const qsa = (s, r=document) => Array.from(r.querySelectorAll(s));
const txt = (el) => (el && el.textContent ? el.textContent : "").trim();
const sleep = (ms) => new Promise(res => setTimeout(res, ms));
const CONFIG = {
updateEveryMs: 90000,
savePctMin: 15,
savePctMax: 40,
freeDeliveryThreshold: 25,
hoverHideDelayMs: 400,
addedAutoHideMs: 18000,
refreshFastMs: 450,
refreshSlowMs: 1600,
fastWindowMs: 12000,
silentTries: 3,
afterAddTTL: 20000
};
const DESKTOP_PRICE_SELECTOR = "body > div:nth-child(7) > div > div > div > div > div.col-2.col-xl-4.d-none.d-xl-flex.flex-row.justify-content-end.headerhidden > div > div > a > span";
const MOBILE_PRICE_SELECTOR = "span.mx-2.text-dark";
const controller = ("AbortController" in window) ? new AbortController() : null;
const signal = controller ? controller.signal : null;
function on(el, type, fn, opts){
if (!el) return;
try{
if (signal) el.addEventListener(type, fn, Object.assign({}, (opts||{}), { signal }));
else el.addEventListener(type, fn, opts||false);
}catch(_){
el.addEventListener(type, fn, opts||false);
}
}
function safe(fn){ try{ return fn(); }catch(e){ console.error("[AZ V82]", e); return null; } }
/* -----------------------------
PDP ONLY GUARD (IMPORTANT)
----------------------------- */
function looksLikeProductURL(){
// Primary: product URL pattern
if (/\/product\//i.test(location.pathname)) return true;
// Backup: common PDP markers
if (qs("h1.prodtitle") || qs(".row.price-row") || qs("table.table.prop") || qs("table.prop")) return true;
return false;
}
function hasBuyNowButton(){
const candidates = qsa('a.buynow, a.atc-hover, a[title="Buy Now"], button[title="Buy Now"], button.add-to-basket, button.btn-success');
if (candidates.length) return true;
// Fallback: scan text for “buy now / add to basket / add to cart”
const btn = qsa("a,button").find(el => {
const t = (txt(el)||"").toLowerCase();
if (!t) return false;
if (!(t.includes("buy now") || t.includes("add to basket") || t.includes("add to cart"))) return false;
const st = window.getComputedStyle(el);
if (st.display === "none" || st.visibility === "hidden" || st.opacity === "0") return false;
if (el.disabled || el.getAttribute("aria-disabled") === "true") return false;
if (el.classList && el.classList.contains("disabled")) return false;
return true;
});
return !!btn;
}
function hasFAQOrFitment(){
// “FAQ etc” can be tabs/accordion/faq anchors, or the properties table
return !!(
qs("ul.carsOeFit.nav.nav-tabs") ||
qs(".tab-content") ||
qs("#faq, [id*='faq'], .faq") ||
qs(".accordion, .accordion-item") ||
qs("table.table.prop, table.prop")
);
}
function isTargetPDP(){
return looksLikeProductURL() && hasBuyNowButton() && hasFAQOrFitment();
}
/* -----------------------------
Cleanup (safe on any page)
----------------------------- */
function purgeOld(){
const killIds = [
"azMobileWrapX62","azTabsWrapX62","azMiniCartX62","azAddedToastX62","azTopBtnX62",
"azAddedToastX65",
"az_tab_descfit_x62","az_tab_delivery_x62","az_tab_collection_x62","az_tab_returns_x62","az_tab_warranty_x62",
"azMobileWrap","azTabsWrap","azMiniCart","azAddedToast",
"az_tab_descfit","az_tab_delivery","az_tab_collection","az_tab_returns","az_tab_warranty"
];
killIds.forEach(id => { const el = document.getElementById(id); if (el) el.remove(); });
qsa('[id^="azMobileWrapV"],[id^="azTabsWrapV"],[id^="azMiniCartV"],[id^="azAddedToastV"],[id^="azTopBtnV"]').forEach(el => el.remove());
qsa(".az-compat-msg").forEach((el,i)=>{ if(i>0) try{el.remove();}catch(_){ } });
try{ document.body.classList.remove(PDP_CLASS, "az-silent-modal"); }catch(_){}
}
/* -----------------------------
If NOT target PDP: do nothing
----------------------------- */
if (!looksLikeProductURL()){
// definitely not a product URL/markup: clean & exit
purgeOld();
window[CORE_KEY] = { destroy: function(){ try{ if (controller) controller.abort(); }catch(_){ } try{ purgeOld(); }catch(_){ } } };
return;
}
/* =========================================================
Your original logic below (unchanged as much as possible)
========================================================= */
function fixShowMoreButtons(container) {
if (!container) return;
const buttons = Array.from(container.querySelectorAll('.show-more-btn'));
buttons.forEach(btn => {
btn.removeAttribute('onclick');
let list = btn.previousElementSibling;
while (list && !list.classList.contains('number-list')) { list = list.previousElementSibling; }
if (!list) return;
btn.addEventListener('click', (e) => {
e.preventDefault(); e.stopPropagation();
const isExpanded = list.classList.contains('az-expanded-list');
if (isExpanded) {
list.classList.remove('az-expanded-list');
btn.innerHTML = 'Show More ';
} else {
list.classList.add('az-expanded-list');
btn.innerHTML = 'Show Less ';
}
});
});
}
function hashString(s){ s = String(s || ""); let h = 2166136261; for (let i=0;i>> 0); }
function parsePrice(raw){ const hasComma = raw.includes(","); const cleaned = raw.replace(/[^\d,\.]/g,""); let num = NaN; if (hasComma) num = parseFloat(cleaned.replace(/\./g,"").replace(",",".")); else num = parseFloat(cleaned); return { num, hasComma }; }
function formatGBP(n, useComma){ const fixed = (Math.round(n * 100) / 100).toFixed(2); return useComma ? fixed.replace(".",",") : fixed; }
function findTitle(){ return txt(qs("h1.prodtitle")) || txt(qs("h1")) || "Product"; }
function getSavePct(){ const seed = hashString(findTitle()); const span = (CONFIG.savePctMax - CONFIG.savePctMin + 1); return CONFIG.savePctMin + (seed % span); }
function isVisible(el){
if (!el) return false;
const r = el.getBoundingClientRect();
if (r.width < 2 || r.height < 2) return false;
const st = window.getComputedStyle(el);
return st.display !== "none" && st.visibility !== "hidden" && st.opacity !== "0";
}
function isOutOfStock(){
const notifyBtn = qsa("button, a").find(el => /notify me when back in stock/i.test(txt(el) || "") && isVisible(el));
if (notifyBtn) return true;
const buy = qsa('a.buynow, a.atc-hover, button').find(el => {
const t = (txt(el)||"").toLowerCase();
if (!isVisible(el)) return false;
if (!(t.includes("buy now") || t.includes("add to basket") || t.includes("add to cart"))) return false;
if (el.disabled) return false;
if (el.getAttribute("aria-disabled") === "true") return false;
if (el.classList && el.classList.contains("disabled")) return false;
return true;
});
return !buy;
}
function injectPaymentIcons(where){
if (!where) return;
qsa(".azpdp-pay", where).forEach(el=>el.remove());
const icons = [
"https://azmotorfactors.co.uk/assets/blocks/23/11_image_upload.webp",
"https://azmotorfactors.co.uk/assets/blocks/23/13_image_upload.png",
"https://azmotorfactors.co.uk/assets/blocks/23/14_image_upload.png",
"https://azmotorfactors.co.uk/assets/blocks/23/16_image_upload.png",
"https://azmotorfactors.co.uk/assets/blocks/23/35_image_upload.png",
"https://azmotorfactors.co.uk/assets/blocks/23/17_image_upload.png",
"https://azmotorfactors.co.uk/assets/blocks/23/12_image_upload.png"
];
const pay = document.createElement("div");
pay.className = "azpdp-pay";
pay.innerHTML = icons.map(src => ``).join("");
where.appendChild(pay);
}
function updatePriceUI(force){
const row = qs(".row.price-row");
const newP = row ? qs(".newPrice", row) : qs(".newPrice");
if (!newP) return false;
const p = parsePrice(txt(newP));
if (!isFinite(p.num) || p.num el.remove());
const savePct = getSavePct();
const rrp = p.num / (1 - (savePct/100));
const rrpEl = document.createElement("span");
rrpEl.className = "azpdp-rrp";
rrpEl.innerHTML = `£${formatGBP(rrp, p.hasComma)}SAVE ${savePct}%`;
holder.appendChild(rrpEl);
const vat = document.createElement("div");
vat.className = "azpdp-vat";
vat.textContent = "Price incl. VAT";
holder.appendChild(vat);
const out = isOutOfStock();
const st = document.createElement("div");
st.className = "azpdp-stock " + (out ? "azpdp-out" : "azpdp-in");
st.innerHTML = out ? ` OUT OF STOCK` : ` IN STOCK`;
holder.appendChild(st);
injectPaymentIcons(holder);
return true;
}
function injectCompatibilityMessage(){
const span = qs(".borderbigselectedcar .spanfont");
const car = span ? txt(span) : "";
if (!car) return;
const target = qs(".row .col-12.marginTop") || qs(".col-12.marginTop");
if (!target) return;
if (qs(".az-compat-msg", target)) return;
const msg = document.createElement("div");
msg.className = "az-compat-msg";
msg.textContent = "This product is compatible with your " + car + ". Please confirm using OE / Usage numbers and Fitment Info.";
target.prepend(msg);
}
function ensureTopButton(){
if (qs("#azTopBtnX62")) return;
const btn = document.createElement("button");
btn.id = "azTopBtnX62";
btn.type = "button";
btn.textContent = "↑";
btn.addEventListener("click", () => window.scrollTo({ top: 0, behavior: "smooth" }));
document.body.appendChild(btn);
const onScroll = () => { btn.style.display = (window.scrollY > 650) ? "flex" : "none"; };
window.addEventListener("scroll", onScroll, { passive:true });
onScroll();
}
function getPropTable(){
return qs("table.table.prop.table-striped") || qs("table.prop.table-striped") || qs("table.table.prop") || qs("table.prop") || null;
}
function parseProps(){
const t = getPropTable();
if (!t) return [];
return qsa("tbody tr", t).map(r => {
const tds = qsa("td", r);
if (tds.length < 2) return null;
return { k: (tds[0].textContent||"").replace(/\s+/g," ").trim(), v: (tds[1].textContent||"").replace(/\s+/g," ").trim() };
}).filter(Boolean);
}
function getPaneByTabLinkClass(cls){
const a = qs("a." + cls) || qs("." + cls);
if (!a) return null;
const href = a.getAttribute("href") || "";
if (!href.startsWith("#")) return null;
return qs(href) || null;
}
function extractOEPairsFromPane(pane){
if (!pane) return [];
const pairs = [];
const seen = new Set();
qsa("table tr", pane).forEach(tr => {
const tds = qsa("td,th", tr).map(x => (x.textContent||"").replace(/\s+/g," ").trim()).filter(Boolean);
if (tds.length >= 2){
const make = tds[0];
const num = tds.slice(1).join(" ").trim();
const key = (make + "|" + num).toLowerCase();
if (!seen.has(key) && /[0-9]/.test(num)){
seen.add(key);
pairs.push({ make, num });
}
}
});
return pairs;
}
const PARTS = [
{ type:"Tie Rod", aka:["Track Rod","Track Rod End","Steering Tie Rod","Rack End","Outer Tie Rod End","Inner Tie Rod","Steering Track Rod"], re:[/\btie\s*rod\b/i, /\btrack\s*rod\b/i, /\brack\s*end\b/i] },
{ type:"Control Arm", aka:["Wishbone","Suspension Arm","Track Control Arm","Lower Arm","Upper Arm"], re:[/\bcontrol\s*arm\b/i, /\bwishbone\b/i, /\bsuspension\s*arm\b/i] },
{ type:"Ball Joint", aka:["Lower Ball Joint","Upper Ball Joint","Suspension Joint"], re:[/\bball\s*joint\b/i] },
{ type:"Anti Roll Bar Link", aka:["Drop Link","Stabiliser Link","Sway Bar Link","ARB Link"], re:[/\bdrop\s*link\b/i, /\bstabilis(er|or)\s*link\b/i, /\banti[\s-]*roll\s*bar\s*link\b/i] },
{ type:"Brake Pads", aka:["Pad Set","Front Pads","Rear Pads","Disc Pads","Brake Pad Set"], re:[/\bbrake\s*pad(s)?\b/i, /\bpad\s*set\b/i] },
{ type:"Brake Discs", aka:["Brake Rotors","Disc Rotors","Front Discs","Rear Discs","Disc Set"], re:[/\bbrake\s*disc(s)?\b/i, /\bbrake\s*rotor(s)?\b/i] },
{ type:"Oil Filter", aka:["Oil Cartridge","Spin-on Filter","Filter Element","Engine Oil Filter"], re:[/\boil\s*filter\b/i] },
{ type:"Air Filter", aka:["Engine Air Filter","Air Cleaner Filter","Intake Filter"], re:[/\bair\s*filter\b/i] },
{ type:"Cabin Filter", aka:["Pollen Filter","HVAC Filter","Interior Filter","Cabin Air Filter"], re:[/\bcabin\s*filter\b/i, /\bpollen\b/i] },
{ type:"Fuel Filter", aka:["Diesel Filter","Petrol Filter","In-line Fuel Filter"], re:[/\bfuel\s*filter\b/i] },
{ type:"Gas Spring", aka:["Boot Strut","Tailgate Strut","Lift Support","Gas Strut","Tailgate Lift"], re:[/\bgas\s*spring\b/i, /\bboot\s*strut\b/i, /\btailgate\s*strut\b/i] }
];
function matchPart(title){
for (const p of PARTS){ if (p.re.some(rx => rx.test(title||""))) return p; }
return { type:"Vehicle Part", aka:["Replacement Part","OE Quality Part","Aftermarket Part","Automotive Part","Spare Part","Car Part"] };
}
function getBrandFromTitle(title){
title = String(title||"").trim();
const w = title.split(/\s+/).filter(Boolean);
if (!w.length) return "";
let brand = w[0];
const w2 = w[1] || "";
if (w2 && w2 === w2.toUpperCase() && w2.length /product number|article number|part number|paired article number|mpn/i.test(p.k||""));
return cand ? (cand.v||"").trim() : "";
}
function buildSEO(title, partType, aka, pn, props, oePairs){
const brand = getBrandFromTitle(title);
const fits = extractFitsVehiclesFromTitle(title);
const fitting = props.filter(p => /fitting position|position|axle|side|front|rear|left|right/i.test(p.k||"")).slice(0, 10);
const oeTop = (oePairs||[]).slice(0, 14);
const oeMakes = Array.from(new Set(oeTop.map(x => (x.make||"").trim()).filter(Boolean))).slice(0, 12);
const names = Array.from(new Set([partType, ...(aka||[])])).map(s => String(s||"").trim()).filter(Boolean).slice(0, 26);
const ul = (arr) => `${arr.map(x=>`${x}`).join("")}`;
return `${title}
${brand ? `${brand} • ` : ""}${partType}${pn ? ` • Part number: ${pn}` : ""}${fits ? ` • Fits: ${fits}` : ""}
What this part does${ul([
"Designed to replace a worn or damaged component and restore correct function.",
"Helps maintain reliable performance when fitted to the correct vehicle specification."
])}
When to replace${ul([
"Replace if worn, leaking, noisy, damaged, or if fitment checks show incorrect performance.",
"Always confirm using reference numbers and your vehicle details before ordering."
])}
Common symptoms of wear${ul(["Noise","Vibration","Reduced performance","Visible wear/damage"])}
Key features${ul(["OE-style fitment (where applicable)","Durable construction","Designed for dependable performance"])}
Other names for this part${ul(names)}
${fitting.length ? `Fitment highlights (from properties)${ul(fitting.map(p=>`${p.k}: ${p.v}`))}` : ""}
${oeTop.length ? `OE / OEM reference numbers (from OE numbers tab)
Manufacturers found in OE numbers: ${oeMakes.join(", ")}${ul(oeTop.map(x=>`${x.make}: ${x.num}`))}` : ""}
Always confirm compatibility using OE numbers, Usage numbers and the Fitment Info properties table.`;
}
const TAB_ID = "az_tab_descfit_x62";
const LINK_ID = "az_tab_descfit_link_x62";
function addDescFitTab(nav, content){
if (qs("#"+LINK_ID, nav) && qs("#"+TAB_ID, content)) return;
const li = document.createElement("li");
li.className = "nav-item";
li.setAttribute("role","presentation");
const a = document.createElement("a");
a.id = LINK_ID;
a.href = "#"+TAB_ID;
a.className = "nav-link";
a.setAttribute("data-bs-toggle","tab");
a.setAttribute("role","tab");
a.setAttribute("aria-selected","false");
a.textContent = "Description & Fitment Info";
li.appendChild(a);
nav.insertBefore(li, nav.firstChild);
const pane = document.createElement("div");
pane.className = "tab-pane";
pane.id = TAB_ID;
pane.setAttribute("role","tabpanel");
pane.innerHTML = `
Description
Fitment InfoPROPERTIES
`;
const firstPane = qs(".tab-pane", content);
if (firstPane) content.insertBefore(pane, firstPane);
else content.appendChild(pane);
}
function tabText(a){ return (a ? (a.textContent||"") : "").replace(/\s+/g," ").trim().toLowerCase(); }
function hasTab(nav, names){
const n = names.map(x => String(x).toLowerCase());
return qsa(".nav-link", nav).some(a => n.includes(tabText(a)));
}
function addTab(nav, content, id, label, html){
if (qs('a[href="#'+id+'"]', nav) && qs('#'+id, content)) return;
const li = document.createElement("li");
li.className = "nav-item";
li.setAttribute("role","presentation");
const a = document.createElement("a");
a.href = "#"+id;
a.className = "nav-link";
a.setAttribute("data-bs-toggle","tab");
a.setAttribute("role","tab");
a.setAttribute("aria-selected","false");
a.textContent = label;
li.appendChild(a);
nav.appendChild(li);
const pane = document.createElement("div");
pane.className = "tab-pane";
pane.id = id;
pane.setAttribute("role","tabpanel");
pane.innerHTML = html;
content.appendChild(pane);
}
function ensureStandardTabs(nav, content){
qsa(".nav-link", nav).forEach(a => {
const t = tabText(a);
if (t === "click & collect" || t === "click and collect") a.textContent = "Collection";
});
if (!hasTab(nav, ["delivery"])) {
addTab(nav, content, "az_tab_delivery_x62", "Delivery",
`Delivery
2–3 working days standard delivery (where available)
Delivery options and pricing shown at checkout
`);
}
if (!hasTab(nav, ["collection"])) {
addTab(nav, content, "az_tab_collection_x62", "Collection",
`Collection
Email: info@azmotorfactors.co.uk
Phone: +44 7779 454554
`);
}
if (!hasTab(nav, ["returns"])) {
addTab(nav, content, "az_tab_returns_x62", "Returns",
`Returns
30 days return (subject to condition)
Items must be unused and resalable (unless faulty)
`);
}
if (!hasTab(nav, ["warranty"])) {
addTab(nav, content, "az_tab_warranty_x62", "Warranty",
`Warranty
Warranty depends on brand and product type
Keep your invoice as proof of purchase
`);
}
}
function showTabByLink(linkEl){
if (!linkEl) return;
if (window.bootstrap && window.bootstrap.Tab) {
try { new window.bootstrap.Tab(linkEl).show(); return; } catch(e){}
}
const nav = linkEl.closest("ul.nav-tabs");
const content = qs(".tab-content.azpdp-tabcontent") || qs(".tab-content");
if (!nav || !content) return;
qsa(".nav-link", nav).forEach(l => { l.classList.remove("active"); l.setAttribute("aria-selected","false"); });
qsa(".tab-pane", content).forEach(p => p.classList.remove("active","show"));
linkEl.classList.add("active");
linkEl.setAttribute("aria-selected","true");
const targetSel = linkEl.getAttribute("href");
const pane = targetSel ? qs(targetSel, content) : null;
if (pane) pane.classList.add("active","show");
}
function fillDescFit(){
const content = qs(".tab-content.azpdp-tabcontent") || qs(".tab-content");
if (!content) return false;
const pane = qs("#"+TAB_ID, content);
if (!pane) return false;
const title = findTitle();
const props = parseProps();
const part = matchPart(title);
const oePane = getPaneByTabLinkClass("oe-numbers-tab") || qs("#tab1");
const oePairs = extractOEPairsFromPane(oePane);
const pn = findPartNumberFromProps(props);
const descBox = qs("#azDescBoxX62", pane);
const fitBox = qs("#azFitBoxX62", pane);
if (descBox) descBox.innerHTML = buildSEO(title, part.type, part.aka, pn, props, oePairs);
const t = getPropTable();
if (fitBox){
fitBox.innerHTML = t ? t.outerHTML : `Fitment properties were not found on this page.`;
}
return true;
}
const WRAP_ID = "azTabsWrapX62";
function moveTabsIntoGreyBox(nav, content){
const descH3 = qsa("h3.text-red, h3").find(h => txt(h).toLowerCase() === "description");
if (!descH3) return false;
const greyBox = descH3.closest('div[style*="background-color"]') || descH3.closest(".col-12.px-5") || descH3.closest(".col-12") || descH3.parentElement;
if (!greyBox) return false;
let wrap = qs("#"+WRAP_ID);
if (!wrap){ wrap = document.createElement("div"); wrap.id = WRAP_ID; }
wrap.innerHTML = "";
wrap.appendChild(nav);
wrap.appendChild(content);
greyBox.insertBefore(wrap, greyBox.firstChild);
const hr = descH3.nextElementSibling && descH3.nextElementSibling.tagName === "HR" ? descH3.nextElementSibling : null;
descH3.style.setProperty("display","none","important");
if (hr) hr.style.setProperty("display","none","important");
return true;
}
function buildMobileWrap(nav, content){
let wrap = qs("#azMobileWrapX62");
if (!wrap){
wrap = document.createElement("div");
wrap.id = "azMobileWrapX62";
content.parentElement.insertBefore(wrap, content);
}
wrap.innerHTML = "";
const title = findTitle();
const props = parseProps();
const part = matchPart(title);
const pn = findPartNumberFromProps(props);
const oePane = getPaneByTabLinkClass("oe-numbers-tab") || qs("#tab1");
const usagePane = getPaneByTabLinkClass("usage-numbers-tab") || qs("#tab2");
const compPane = getPaneByTabLinkClass("compatibility-tab") || qs("#tab3");
const oePairs = extractOEPairsFromPane(oePane);
const t = getPropTable();
const open = document.createElement("div");
open.className = "azm-open azpdp-props";
open.innerHTML = `${buildSEO(title, part.type, part.aka, pn, props, oePairs)}
Fitment Info
${t ? t.outerHTML : `Fitment properties were not found on this page.`}`;
wrap.appendChild(open);
function addDrop(label, html, fallback){
const d = document.createElement("details");
d.innerHTML = `${label}${(html && html.trim()) ? html : (fallback || `No data found.`)}`;
wrap.appendChild(d);
}
addDrop("OE numbers", oePane ? oePane.innerHTML : "", `OE numbers tab content not found.`);
addDrop("Usage numbers", usagePane ? usagePane.innerHTML : "", `Usage numbers tab content not found.`);
addDrop("Compatibility", compPane ? compPane.innerHTML : "", `Compatibility tab content not found.`);
const extra = [
{ label:"Delivery", id:"#az_tab_delivery_x62" },
{ label:"Collection", id:"#az_tab_collection_x62" },
{ label:"Returns", id:"#az_tab_returns_x62" },
{ label:"Warranty", id:"#az_tab_warranty_x62" }
];
extra.forEach(x=>{
const pane = qs(x.id, content);
addDrop(x.label, pane ? pane.innerHTML : "", `${x.label} content not found.`);
});
fixShowMoreButtons(wrap);
}
/* -----------------------------
MINI CART + TOAST (unchanged)
----------------------------- */
const MINI_ID = "azMiniCartX62";
const TOAST_ID = "azAddedToastX65";
const STORE_KEY = "az_mc_lastgood_x62";
const AFTER_ADD_KEY = "az_after_add_x62";
const CART_CANDIDATE_SELECTORS = [
'a[data-bs-target="#shoppingcartModal"]',
'[data-bs-target="#shoppingcartModal"]',
'a[href="#shoppingcartModal"]',
'.m_ecomm_cart_animation a',
'.m_ecomm_cart_animation',
'a[href*="/my-cart"]',
'a[href*="my-cart"]',
'a[href*="/cart"]',
'a[href*="cart"]',
'a[aria-label*="cart"]',
'a[aria-label*="basket"]',
'a[title*="cart"]',
'a[title*="basket"]'
].join(",");
let miniEl=null, toastEl=null, toastTimer=null;
let isPinned=false, overAnchor=false;
let hideTimer=null, refreshTimer=null, refreshFastUntil=0, autoHideAddedTimer=null;
let modalObserver=null, lastQtySeen=-1;
let intervalId=null;
function loadLastGood(){
try{
const raw = sessionStorage.getItem(STORE_KEY);
if (!raw) return { items: [], totalText: "", totalNum: 0 };
const d = JSON.parse(raw);
if (!d || !Array.isArray(d.items)) return { items: [], totalText: "", totalNum: 0 };
return d;
}catch(_){
return { items: [], totalText: "", totalNum: 0 };
}
}
function saveLastGood(d){ try{ sessionStorage.setItem(STORE_KEY, JSON.stringify(d)); }catch(_){ } }
let lastGoodData = loadLastGood();
function ensureToast(){
if (toastEl) return toastEl;
toastEl = document.createElement("div");
toastEl.id = TOAST_ID;
toastEl.innerHTML = `✓Added to basket`;
document.body.appendChild(toastEl);
return toastEl;
}
function showToast(msg){
ensureToast();
const m = toastEl.querySelector(".m");
if (m) m.textContent = msg || "Added to basket";
toastEl.classList.add('az-active');
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(()=>{ if (toastEl) toastEl.classList.remove('az-active'); }, 2400);
}
function isVisibleNode(el){
if (!el) return false;
const r = el.getBoundingClientRect();
if (r.width < 2 || r.height < 2) return false;
const st = window.getComputedStyle(el);
if (st.display === "none" || st.visibility === "hidden" || st.opacity === "0") return false;
return true;
}
function normalizeClickable(el){ return el ? (el.closest("a,button") || el) : null; }
function findCartAnchor(){
const candidates = qsa(CART_CANDIDATE_SELECTORS).map(normalizeClickable).filter(Boolean);
if (!candidates.length) return null;
let best = null, bestScore = -Infinity;
for (const el of candidates){
if (!isVisibleNode(el)) continue;
const r = el.getBoundingClientRect();
const topScore = Math.max(0, 240 - r.top);
const rightScore = Math.max(0, r.left - window.innerWidth * 0.45);
const inHeader = !!el.closest("header, .header, .site-header, .navbar, #header, .topbar, .top-header");
const headerScore = inHeader ? 260 : 0;
const attr = (el.getAttribute && (el.getAttribute("data-bs-target") || el.getAttribute("href") || "")) || "";
const modalScore = /shoppingcartModal/i.test(attr) ? 240 : 0;
const s = topScore + rightScore + headerScore + modalScore;
if (s > bestScore){ bestScore = s; best = el; }
}
return best;
}
function ensureMiniCart(){
if (miniEl) return miniEl;
miniEl = document.createElement("div");
miniEl.id = MINI_ID;
miniEl.innerHTML = `
Cart
Qualified for Free Delivery
×
✓ Added to basket
Updating…
Subtotal
—incl. VAT
Free Delivery—
Updating…
Proceed to Checkout
Go to basket
Continue shopping
`;
document.body.appendChild(miniEl);
on(miniEl.querySelector(".azmc-close"), "click", closeMiniCart);
on(miniEl.querySelector(".azmc-btn-continue"), "click", closeMiniCart);
on(miniEl, "mouseenter", ()=>{ isPinned = true; clearTimeout(hideTimer); clearTimeout(autoHideAddedTimer); });
on(miniEl, "mouseleave", ()=>{ isPinned = false; scheduleHide(CONFIG.hoverHideDelayMs); });
on(miniEl, "click", (e)=>{
const item = e.target.closest(".azmc-item");
if (!item) return;
const href = item.getAttribute("data-href");
if (href) window.location.href = href;
});
return miniEl;
}
function positionMiniUnderCart(){
if (window.matchMedia("(max-width: 992px)").matches) return;
const a = findCartAnchor();
ensureMiniCart();
const width = 380;
miniEl.style.width = width + "px";
if (!a){
miniEl.style.left = Math.max(10, window.innerWidth - width - 10) + "px";
miniEl.style.top = "70px";
return;
}
const r = a.getBoundingClientRect();
const pad = 8;
const left = Math.min(window.innerWidth - width - 10, Math.max(10, r.right - width));
const top = Math.max(10, r.bottom + pad);
miniEl.style.left = left + "px";
miniEl.style.top = top + "px";
}
function parseMoneyToNumber(str){
str = String(str || "");
const cleaned = str.replace(/[^\d,\.]/g,"").trim();
if (!cleaned) return NaN;
if (cleaned.includes(",")) return parseFloat(cleaned.replace(/\./g,"").replace(",","."));
return parseFloat(cleaned);
}
function formatMoneyEU(n){
const v = (Math.round((Number(n)||0) * 100) / 100).toFixed(2);
const parts = v.split(".");
const intPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ".");
return "£ " + intPart + "," + parts[1];
}
function getLiveHeaderQty(){
const el = qs(".cartquantity");
const n = el ? parseInt((txt(el)||"0").replace(/[^\d]/g,"")||"0",10) : 0;
return Number.isFinite(n) ? n : 0;
}
function parseCartFromModalDOM(modalEl){
const root =
modalEl.querySelector('.modal-body.overflow-auto .m_ecomm_cartexpand_items') ||
modalEl.querySelector('.m_ecomm_cartexpand_items') ||
modalEl.querySelector('.cart_items');
if (!root) return null;
const rows = Array.from(root.querySelectorAll(".row"));
const items = [];
rows.forEach(row => {
const imgEl = row.querySelector('a[href*="/product/"] img') || row.querySelector("img");
const links = Array.from(row.querySelectorAll('a[href*="/product/"]'));
const linkEl = links.find(a => /\(\s*\d+\s*[x×]\s*\)/i.test(a.textContent||"")) || links[0] || null;
if (!imgEl || !linkEl) return;
const href = linkEl.getAttribute("href") || "";
const img = imgEl.getAttribute("src") || "";
const rawText = (linkEl.textContent || linkEl.getAttribute("title") || "").replace(/\s+/g," ").trim();
const m = rawText.match(/^\((\d+)\s*[x×]\)\s*/i);
const qty = m ? parseInt(m[1],10) : 1;
let name = rawText.replace(/^\(\d+\s*[x×]\)\s*/i, "").trim();
if (!name) name = (linkEl.getAttribute("title") || "").trim();
let priceText = "";
const priceCandidate = Array.from(row.querySelectorAll("*")).reverse().find(el => (el.textContent||"").includes("£") && /\d/.test(el.textContent||""));
if (priceCandidate) priceText = (priceCandidate.textContent||"").replace(/\s+/g," ").trim();
items.push({ href, img, name, qty, price: priceText, priceNum: parseMoneyToNumber(priceText) });
});
let totalText = "";
const totalEl = modalEl.querySelector(".cart_totalprice");
if (totalEl) totalText = (totalEl.textContent||"").replace(/\s+/g," ").trim();
let totalNum = parseMoneyToNumber(totalText) || 0;
if (!totalNum && items.length) totalNum = items.reduce((a,it)=>a+(Number.isFinite(it.priceNum)?it.priceNum:0),0);
return { items, totalText: totalText || formatMoneyEU(totalNum), totalNum };
}
function hideBootstrapModal(modal){
try{
if (window.bootstrap && window.bootstrap.Modal){
const inst = window.bootstrap.Modal.getInstance(modal) || window.bootstrap.Modal.getOrCreateInstance(modal);
inst.hide();
} else {
const closeBtn = modal.querySelector('[data-bs-dismiss="modal"], .btn-close, button.close');
if (closeBtn) closeBtn.click();
}
}catch(_){}
}
async function ensureModusCartLoaded(){
const modal = qs("#shoppingcartModal");
const a = findCartAnchor();
if (!modal || !a) return false;
for (let i=0; iparseCartFromModalDOM(modal));
if (p0 && (p0.items.length || p0.totalNum > 0)) return true;
document.body.classList.add("az-silent-modal");
a.dispatchEvent(new MouseEvent("click", { bubbles:true, cancelable:true, view:window }));
await sleep(220);
hideBootstrapModal(modal);
await sleep(220);
document.body.classList.remove("az-silent-modal");
}
return false;
}
async function getFreshCartData(){
const modal = qs("#shoppingcartModal");
let d = safe(()=>parseCartFromModalDOM(modal));
if (d && (d.items.length || d.totalNum > 0)) return d;
if (getLiveHeaderQty() > 0){
await ensureModusCartLoaded();
d = safe(()=>parseCartFromModalDOM(modal));
if (d && (d.items.length || d.totalNum > 0)) return d;
}
if (getLiveHeaderQty() > 0 && (lastGoodData.items.length || lastGoodData.totalText)) return lastGoodData;
return { items: [], totalText: "£ 0,00", totalNum: 0 };
}
function renderProgress(totalNum){
const threshold = CONFIG.freeDeliveryThreshold;
const pct = Math.max(0, Math.min(100, (totalNum / threshold) * 100));
const qualified = totalNum >= threshold;
const pill = miniEl.querySelector(".azmc-pill");
const right = miniEl.querySelector(".azmc-progress-right");
const bar = miniEl.querySelector(".azmc-bar > i");
const note = miniEl.querySelector(".azmc-progress-note");
if (pill) pill.style.display = qualified ? "inline-flex" : "none";
if (right) right.textContent = `${formatMoneyEU(totalNum)} / ${formatMoneyEU(threshold)}`;
if (bar) bar.style.width = pct + "%";
if (note){
note.textContent = qualified
? "You qualify for free delivery."
: `Add ${formatMoneyEU(Math.max(0, threshold - totalNum))} more to qualify for free delivery.`;
}
}
function renderMiniCart(data, mode){
ensureMiniCart();
if (mode === "added") miniEl.classList.add("azmc-mode-added");
else miniEl.classList.remove("azmc-mode-added");
const headerTitle = miniEl.querySelector(".azmc-title");
const body = miniEl.querySelector(".azmc-body");
const total = miniEl.querySelector(".azmc-subtotal");
const qty = getLiveHeaderQty();
headerTitle.textContent = qty ? `Cart (${qty})` : "Cart";
const subtotalText = (data.totalText && data.totalText.includes("£")) ? data.totalText : "£ 0,00";
total.textContent = subtotalText;
// UPDATE DESKTOP PRICE
const desktopPriceSpan = qs(DESKTOP_PRICE_SELECTOR);
if (desktopPriceSpan) desktopPriceSpan.textContent = subtotalText;
// UPDATE MOBILE PRICE
const mobilePriceSpan = qs(MOBILE_PRICE_SELECTOR);
if (mobilePriceSpan) mobilePriceSpan.textContent = subtotalText;
renderProgress(data.totalNum || 0);
if (qty === 0){
body.innerHTML = `Your basket is empty.`;
return;
}
if (!data.items || !data.items.length){
body.innerHTML = `Updating basket…`;
return;
}
body.innerHTML = data.items.slice(0, 8).map(it => `
${it.name || ""}
Quantity x ${it.qty || 1}
${it.price || ""}
`).join("");
}
async function refreshMiniCart(mode){
ensureMiniCart();
if (getLiveHeaderQty() > 0 && (lastGoodData.items.length || lastGoodData.totalText)) {
renderMiniCart(lastGoodData, mode);
} else {
renderMiniCart({ items:[], totalText:"", totalNum:0 }, mode);
}
const fresh = await getFreshCartData();
if (getLiveHeaderQty() === 0){
lastGoodData = { items: [], totalText: "£ 0,00", totalNum: 0 };
saveLastGood(lastGoodData);
renderMiniCart(lastGoodData, mode);
return;
}
if (fresh && (fresh.items.length || fresh.totalNum > 0)){
lastGoodData = { items: fresh.items || [], totalText: fresh.totalText || lastGoodData.totalText || "", totalNum: fresh.totalNum || 0 };
saveLastGood(lastGoodData);
renderMiniCart(lastGoodData, mode);
}
}
function startOpenRefreshLoop(mode){
clearTimeout(refreshTimer);
refreshFastUntil = Date.now() + CONFIG.fastWindowMs;
const tick = async () => {
const open = miniEl && miniEl.style.display === "block";
if (!open) return;
const q = getLiveHeaderQty();
if (q !== lastQtySeen){
lastQtySeen = q;
refreshFastUntil = Date.now() + CONFIG.fastWindowMs;
}
await refreshMiniCart(mode);
const now = Date.now();
const fast = now < refreshFastUntil;
refreshTimer = setTimeout(tick, fast ? CONFIG.refreshFastMs : CONFIG.refreshSlowMs);
};
tick();
}
function openMiniCart(mode){
ensureMiniCart();
positionMiniUnderCart();
miniEl.style.display = "block";
startOpenRefreshLoop(mode);
clearTimeout(autoHideAddedTimer);
if (mode === "added"){
autoHideAddedTimer = setTimeout(() => {
if (!isPinned && !overAnchor) closeMiniCart();
}, CONFIG.addedAutoHideMs);
}
}
function closeMiniCart(){
if (!miniEl) return;
miniEl.style.display = "none";
miniEl.classList.remove("azmc-mode-added");
clearTimeout(refreshTimer);
clearTimeout(autoHideAddedTimer);
}
function scheduleHide(ms){
clearTimeout(hideTimer);
hideTimer = setTimeout(() => {
if (!isPinned && !overAnchor) closeMiniCart();
}, ms);
}
function bindMiniCartHover(){
if (window.matchMedia("(max-width: 992px)").matches) return;
on(document, "mouseover", (e) => {
const hit = normalizeClickable(e.target.closest ? e.target.closest(CART_CANDIDATE_SELECTORS) : null);
if (!hit) return;
const best = findCartAnchor();
if (!best || (hit !== best && !best.contains(hit) && !hit.contains(best))) return;
overAnchor = true; clearTimeout(hideTimer); clearTimeout(autoHideAddedTimer);
openMiniCart("hover");
}, { capture:true });
on(document, "mouseout", (e) => {
const from = normalizeClickable(e.target.closest ? e.target.closest(CART_CANDIDATE_SELECTORS) : null);
if (!from) return;
const best = findCartAnchor();
if (!best || (from !== best && !best.contains(from) && !from.contains(best))) return;
const to = e.relatedTarget && e.relatedTarget.closest ? normalizeClickable(e.relatedTarget.closest(CART_CANDIDATE_SELECTORS)) : null;
if (to) return;
overAnchor = false;
scheduleHide(CONFIG.hoverHideDelayMs);
}, { capture:true });
on(window, "resize", () => { if (miniEl && miniEl.style.display==="block") positionMiniUnderCart(); }, { passive:true });
on(window, "scroll", () => { if (miniEl && miniEl.style.display==="block") positionMiniUnderCart(); }, { passive:true });
}
function bindHeaderCartClick(){
const desktopAnchor = qs(DESKTOP_PRICE_SELECTOR)?.closest('a');
const mobileAnchor = qs(MOBILE_PRICE_SELECTOR)?.closest('a');
[desktopAnchor, mobileAnchor].filter(Boolean).forEach(a => {
on(a, "click", () => closeMiniCart());
});
}
function watchShoppingCartModal(){
const modal = qs("#shoppingcartModal");
if (!modal) return;
if (modalObserver) modalObserver.disconnect();
modalObserver = new MutationObserver(() => {
refreshFastUntil = Date.now() + CONFIG.fastWindowMs;
if (miniEl && miniEl.style.display === "block") refreshMiniCart("hover");
});
modalObserver.observe(modal, { childList:true, subtree:true, characterData:true });
}
function observeCartBadgeChanges(){
const badge = qs(".cartquantity");
if (!badge) return;
let last = txt(badge);
const mo = new MutationObserver(() => {
const now = txt(badge);
if (!now || now === last) return;
last = now;
openMiniCart("added");
});
mo.observe(badge, { childList:true, characterData:true, subtree:true });
}
function getPDPQty(){
const inp =
qs('input[id^="orderamount_"].cEcommProductCalculator') ||
qs('input[id^="orderamount_"].result') ||
qs('input[id^="orderamount_"]') ||
qs('input.cEcommProductCalculator.result');
if (!inp) return 1;
const v = parseInt(inp.value || inp.getAttribute("value") || "1", 10);
return (Number.isFinite(v) && v > 0) ? v : 1;
}
function markAfterAddInfo(title, qty){
try{ sessionStorage.setItem(AFTER_ADD_KEY, JSON.stringify({ ts: Date.now(), title: title || "", qty: qty || 1 })); }catch(_){}
}
function setAddedLine(title, qty){
const el = miniEl ? miniEl.querySelector(".azmc-added") : null;
if (!el) return;
const t = String(title || "").trim();
const q = Number(qty||1);
el.textContent = t ? `✓ Added: ${t} (x${q})` : `✓ Added to basket (x${q})`;
}
function showAfterAddIfNeeded(){
try{
const raw = sessionStorage.getItem(AFTER_ADD_KEY);
if (!raw) return;
const d = JSON.parse(raw);
if (!d || !d.ts) return;
if ((Date.now() - d.ts) < CONFIG.afterAddTTL){
sessionStorage.removeItem(AFTER_ADD_KEY);
setTimeout(() => {
ensureMiniCart();
miniEl.classList.add("azmc-mode-added");
setAddedLine(d.title, d.qty);
openMiniCart("added");
}, 650);
}
}catch(_){}
}
function bindAddToCartPopup(){
on(document, "click", async (e) => {
const el = e.target.closest('a.buynow, a.atc-hover, a[title="Buy Now"], button[title="Buy Now"], button.add-to-basket, button.btn-success');
if (!el || el.closest('#shoppingcartModal, #'+MINI_ID)) return;
const title = findTitle();
const qty = getPDPQty();
showToast(`Added: ${title} (x${qty})`);
markAfterAddInfo(title, qty);
ensureMiniCart();
miniEl.classList.add("azmc-mode-added");
setAddedLine(title, qty);
await ensureModusCartLoaded();
setTimeout(() => openMiniCart("added"), 120);
}, { capture:true });
}
/* -----------------------------
INIT (ONLY when target PDP)
----------------------------- */
function init(){
// If we somehow stop being on a target PDP (SPA navigation), destroy.
if (!isTargetPDP()){
purgeOld();
return false;
}
// Mark PDP mode so CSS activates only here
document.body.classList.add(PDP_CLASS);
safe(ensureToast);
safe(ensureMiniCart);
safe(bindMiniCartHover);
safe(watchShoppingCartModal);
safe(observeCartBadgeChanges);
safe(showAfterAddIfNeeded);
safe(bindAddToCartPopup);
safe(bindHeaderCartClick);
safe(()=>refreshMiniCart("init"));
// PDP enhancements
safe(()=>updatePriceUI(true));
safe(injectCompatibilityMessage);
safe(ensureTopButton);
const nav = qs("ul.carsOeFit.nav.nav-tabs");
const content = qs(".tab-content");
if (nav && content){
content.classList.add("azpdp-tabcontent");
safe(()=>addDescFitTab(nav, content));
safe(()=>ensureStandardTabs(nav, content));
safe(fillDescFit);
safe(()=>moveTabsIntoGreyBox(nav, content));
const link = qs("#"+LINK_ID, nav);
if (link){
showTabByLink(link);
setTimeout(()=>showTabByLink(link),120);
}
safe(()=>buildMobileWrap(nav, content));
safe(()=>fixShowMoreButtons(content));
}
intervalId = setInterval(() => {
// If user navigated away in SPA, stop.
if (!isTargetPDP()){
window[CORE_KEY]?.destroy?.();
return;
}
safe(()=>refreshMiniCart("interval"));
safe(()=>updatePriceUI(true));
safe(injectCompatibilityMessage);
const nav2 = qs("ul.carsOeFit.nav.nav-tabs");
const content2 = qs(".tab-content.azpdp-tabcontent") || qs(".tab-content");
if (nav2 && content2){
safe(fillDescFit);
safe(()=>buildMobileWrap(nav2, content2));
}
}, CONFIG.updateEveryMs);
return true;
}
function boot(){
let tries = 0;
const tick = () => {
tries++;
// If after a few checks it doesn't look like the correct product page, stop and clean.
if (tries > 6 && (!hasBuyNowButton() || !hasFAQOrFitment())){
purgeOld();
return;
}
// When the page qualifies, start.
if (isTargetPDP()){
purgeOld(); // clean any leftovers first
init();
return;
}
// Keep waiting briefly for PDP DOM to load
if (tries < 120) return setTimeout(tick, 250);
// Timeout: clean and stop
purgeOld();
};
tick();
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot, { once:true });
else boot();
on(window, "pageshow", ()=> safe(showAfterAddIfNeeded), { passive:true });
window[CORE_KEY] = {
destroy: function(){
try{ if (controller) controller.abort(); }catch(_){}
try{ clearInterval(intervalId); }catch(_){}
try{ clearTimeout(hideTimer); clearTimeout(refreshTimer); clearTimeout(autoHideAddedTimer); }catch(_){}
try{ if (modalObserver) modalObserver.disconnect(); }catch(_){}
try{ purgeOld(); }catch(_){}
}
};
})();