(function(){ const zoovuId = 'rjeHqUvdquWv3Nd6RMELUbJbvCLUqlTvxeeR/QTTDSs='; const domainId = '5ae48472-9b89-4299-a9c1-2d91641791ee'; const variables = [{"id":"1eec2be6-dc91-4c4e-a443-da2f051baf5b","name":"Add to cart","type":"CONSTANT","valueType":"TEXT","value":"Add to cart","scope":"GLOBAL"},{"id":"05e7a8c4-ec2a-486f-bb77-24f051f024c0","name":"addToCartBestSellersTrigger","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){return ( Boolean(event.target.closest('[data-aue-resource*="homepage-bestsellers"]')) && Boolean(event.target.closest('[data-testid*="-addToCart"]')) ); /* */},"scope":"LOCAL"},{"id":"17544614-8761-4994-999e-0846fa6ef15f","name":"addToCartCategory","type":"FUNCTION","valueType":"TEXT","function":function(event){// Optional CSS.escape fallback (kept tiny) const esc = window.CSS && CSS.escape ? CSS.escape : (s) => String(s).replace(/[^a-zA-Z0-9_-]/g, "\\$&"); // Safely get last UI event target const evt = (window.__evt && window.__evt.target) || window.event?.target || null; // 1) If we can reach the card directly from the click, use it let card = evt?.closest?.(".stg-trackable-product-card") || null; // Helper: extract SKU from common data-testids near Qty/Add buttons function skuFromTestId(id) { if (!id) return ""; const m = id.match(/^product-button-(.+)-addToCart$/) || id.match(/^shop-button-(.+)-product-quantity-(?:input|increment|decrement)$/) || id.match(/^product-link-(.+)-product-card$/); return m ? m[1] : ""; } // 2) If no card yet, try to infer it by SKU found on the clicked control if (!card && evt && evt.closest) { const needle = evt.closest('[data-testid^="product-button-"][data-testid$="-addToCart"]') || evt.closest('[data-testid^="shop-button-"][data-testid$="product-quantity-input"]') || evt.closest('[data-testid^="shop-button-"][data-testid$="product-quantity-increment"]') || evt.closest('[data-testid^="shop-button-"][data-testid$="product-quantity-decrement"]') || evt.closest('[data-testid^="product-link-"][data-testid$="-product-card"]'); const sku = skuFromTestId(needle?.getAttribute("data-testid") || ""); if (sku) { // (a) Card where data-tracking-id equals the SKU (common on SERP/suggestions) card = document.querySelector(`.stg-trackable-product-card[data-tracking-id="${esc(sku)}"]`) || card; // (b) Or locate the product-link for this SKU and climb to its card if (!card) { const link = document.querySelector(`[data-testid="product-link-${esc(sku)}-product-card"]`); card = link?.closest?.(".stg-trackable-product-card") || card; } } } // 3) Final read (prefer the more specific cat2 when present) const cat2 = card?.getAttribute("data-tracking-category2") || ""; const cat1 = card?.getAttribute("data-tracking-category") || ""; return cat2 || cat1 || ""; /* */},"scope":"LOCAL"},{"id":"c6abe1c2-0962-4813-8a13-d4f6771c890f","name":"addToCartPrice","type":"FUNCTION","valueType":"DECIMAL","function":function(event){function numberFromPrice(txt) { if (!txt) return 0; const raw = String(txt).replace(/[\u00A0\u202F\s]/g, " ").trim(); let keep = raw.replace(/[^\d.,']/g, ""); const m = keep.match(/([.,])(\d{2})$/); if (m) { const decSep = m[1]; keep = keep.replace(/[.,'](?=\d{3}(?:[^\d]|$))/g, ""); keep = keep.replace(decSep, "."); } else { keep = keep.replace(/[^\d]/g, ""); } const n = Number(keep); return Number.isFinite(n) ? n : 0; } const t = (window.__evt && window.__evt.target) || window.event?.target; // 1) PDP add-to-cart context let priceEl = document.querySelector('[data-testid="product-section-information"] [data-testid^="component-price-"][data-testid$="-current-price"]'); if (priceEl) return numberFromPrice(priceEl.textContent); // 2) Within a product card (PLP/SERP/suggestions) const card = t?.closest?.('.stg-trackable-product-card'); priceEl = card?.querySelector('label.MuiTypography-labelM span,[data-testid^="component-price-"][data-testid$="-current-price"]'); if (priceEl) return numberFromPrice(priceEl.textContent); // 3) Fallback: any visible “current price” priceEl = document.querySelector('[data-testid^="component-price-"][data-testid$="-current-price"]'); return priceEl ? numberFromPrice(priceEl.textContent) : 0; /* */},"scope":"LOCAL"},{"id":"e13c5676-bb2c-43c9-be42-938b221632b4","name":"addToCartQty","type":"FUNCTION","valueType":"INTEGER","function":function(event){ const evt = (window.__evt && window.__evt.target) || window.event?.target || null; const target = event.target?.closest('[data-testid*="-addToCart"]') // --- 1) If we are on PDP, use the main PDP quantity input const pdp = document.querySelector('[data-testid="product-section-information"]'); if (pdp) { if (target && target?.closest('[data-testid="product-section-oftencombined"]')) { const parent = target?.parentElement; const inputValue = parent?.querySelector('.stg-counter-input input').value ?? 1; return parseInt(inputValue, 10) } const pdpInput = pdp.querySelector('[data-testid$="product-quantity-input"] input'); if (pdpInput && pdpInput.value) { const n = parseInt(pdpInput.value, 10); return Number.isFinite(n) && n > 0 ? n : 1; } } // Helper: parse SKU from a data-testid like "product-button--addToCart" function skuFromDataTestId(id) { if (!id) return ""; const m = id.match(/^product-button-(.+)-addToCart$/) || id.match(/^shop-button-(.+)-product-quantity-(?:input|increment|decrement)$/); return m ? m[1] : ""; } // --- 2) Try local context around the clicked element (PLP / SERP / suggestions) if (evt && evt.closest) { // (a) direct local input near the click const localInput = evt.closest('.stg-counter-input')?.querySelector('input') || evt.closest('[data-testid$="product-quantity-input"]')?.querySelector('input'); if (localInput && localInput.value) { const n = parseInt(localInput.value, 10); if (Number.isFinite(n) && n > 0) return n; } // (b) derive SKU from the addToCart button then address the input by SKU const addBtn = evt.closest('[data-testid^="product-button-"][data-testid$="-addToCart"]') || evt.closest('[data-testid^="shop-button-"][data-testid$="product-quantity-increment"]') || evt.closest('[data-testid^="shop-button-"][data-testid$="product-quantity-decrement"]') || evt.closest('[data-testid^="shop-button-"][data-testid$="product-quantity-input"]'); const sku = skuFromDataTestId(addBtn?.getAttribute('data-testid') || ""); if (sku) { const skuInput = document.querySelector( `[data-testid="shop-button-${CSS.escape(sku)}-product-quantity-input"] input` ); if (skuInput && skuInput.value) { const n = parseInt(skuInput.value, 10); if (Number.isFinite(n) && n > 0) return n; } } } // --- 3) Final fallback return 1; /* */},"scope":"LOCAL"},{"id":"9bcc6c18-d60d-4f77-b7d3-8f9d8dc4a217","name":"addToCartSku","type":"FUNCTION","valueType":"TEXT","function":function(event){const evt = window.__evt || window.event; const el = evt?.target || evt?.srcElement || document.activeElement; const addBtn = el?.closest?.( 'button[data-testid^="product-button-"][data-testid$="-addToCart"]' ); if (addBtn) { const dt = addBtn.getAttribute("data-testid") || ""; const m = dt.match(/^product-button-(.+)-addToCart$/i); return m?.[1] || ""; } return ""; /* */},"scope":"LOCAL"},{"id":"521ed7c6-7510-45cc-b206-4d9380418fee","name":"cartDeltaMinusOne","type":"CONSTANT","valueType":"INTEGER","value":"-1","scope":"LOCAL"},{"id":"dafc253c-b1ae-45db-81fd-4d274f7104aa","name":"cartDeltaPlusOne","type":"CONSTANT","valueType":"INTEGER","value":"1","scope":"LOCAL"},{"id":"2c79bdb1-b595-407b-9055-dc8afdff4c17","name":"cartSkuFromClick","type":"FUNCTION","valueType":"TEXT","function":function(event){const el = (window.__evt && window.__evt.target) || window.event?.target || document.activeElement; // Nearest list item carries the SKU in its data-testid: shop-listItem- const li = el?.closest?.('li[data-testid^="shop-listItem-"]'); if (li) { const id = li.getAttribute("data-testid") || ""; const m = id.match(/^shop-listItem-([A-Z0-9.]+)/i); if (m) return m[1]; } // Fallbacks inside the card const card = el?.closest?.('.MuiCardContent-root, [data-testid^="cart-card-"]'); // data-testid="cart-card-product-name-" const nameId = card?.querySelector?.('[data-testid^="cart-card-product-name-"]')?.getAttribute("data-testid") || ""; let m = nameId.match(/^cart-card-product-name-([A-Z0-9.]+)/i); if (m) return m[1]; // data-testid="cart-card-product-label--REF-" const refId = card?.querySelector?.('[data-testid^="cart-card-product-label-"]')?.getAttribute("data-testid") || ""; m = refId.match(/^cart-card-product-label-([A-Z0-9.]+)-REF-/i); if (m) return m[1]; // Absolute last resort: any attribute that looks like a SKU const txt = card?.textContent || ""; m = txt.match(/\b\d{3}\.\d{4}[A-Z]?\b/); return m ? m[0] : ""; /* */},"scope":"LOCAL"},{"id":"794148b9-d30c-4fa9-ab0f-d1e903691954","name":"cartUnitPriceFromCard","type":"FUNCTION","valueType":"DECIMAL","function":function(event){const t = (window.__evt && window.__evt.target) || window.event?.target || null; const card = t?.closest?.('li[data-testid^="shop-listItem-"], .MuiCardContent-root') || document.querySelector('li[data-testid^="shop-listItem-"], .MuiCardContent-root'); if (!card) return 0; // Try to read SKU to target price elements function skuFromCard(c) { const li = c.closest?.('li[data-testid^="shop-listItem-"]'); if (li) { const id = li.getAttribute("data-testid") || ""; const m = id.match(/^shop-listItem-([A-Z0-9.]+)/i); if (m) return m[1]; } const nameId = c.querySelector?.('[data-testid^="cart-card-product-name-"]')?.getAttribute("data-testid") || ""; let m = nameId.match(/^cart-card-product-name-([A-Z0-9.]+)/i); if (m) return m[1]; const refId = c.querySelector?.('[data-testid^="cart-card-product-label-"]')?.getAttribute("data-testid") || ""; m = refId.match(/^cart-card-product-label-([A-Z0-9.]+)-REF-/i); return m ? m[1] : ""; } const sku = skuFromCard(card); const baseEl = sku ? card.querySelector(`[data-testid="component-price-${CSS.escape(sku)}-base-price"]`) : null; const txt = (baseEl?.textContent || "").trim(); function readPrice(s) { const keep = (s || "").replace(/[^\d.,']/g, ""); if (!keep) return 0; const last = Math.max(keep.lastIndexOf(","), keep.lastIndexOf(".")); let ip, dp; if (last !== -1 && keep.length - last - 1 <= 2) { ip = keep.slice(0, last).replace(/[.,\s']/g, ""); dp = keep.slice(last + 1); } else { ip = keep.replace(/[.,\s']/g, ""); dp = ""; } dp = (dp + "00").slice(0, 2); const n = Number(`${ip}.${dp}`); return Number.isFinite(n) ? n : 0; } // 1) Direct unit/base price if available const unit = readPrice(txt); if (unit > 0) return unit; // 2) Compute: total price / quantity const totalTxt = card.querySelector('[data-testid^="component-price-"][data-testid$="-total-price"]')?.textContent || ""; const total = readPrice(totalTxt); // qty from input or xN label const inputVal = card.querySelector(".stg-counter-input input")?.value || ""; let qty = parseInt(inputVal, 10); if (!Number.isFinite(qty) || qty <= 0) { const qlbl = card.querySelector('[data-testid^="cart-card-product-quantity-"]')?.textContent || ""; qty = parseInt(qlbl.replace(/[^\d]/g, ""), 10); } if (total > 0 && Number.isFinite(qty) && qty > 0) { return +(total / qty).toFixed(2); } return 0; /* */},"scope":"LOCAL"},{"id":"e421893f-c0f4-435d-922d-3809963e986e","name":"categoryTargetUrl","type":"FUNCTION","valueType":"TEXT","function":function(event){const el = event.target; const closestHref = el?.closest('a'); return closestHref?.getAttribute('href'); /* */},"scope":"LOCAL"},{"id":"904c3ff0-8e69-48ca-a868-c61c5260d9e7","name":"checkoutLineItemsJson","type":"FUNCTION","valueType":"LIST","function":function(event){// Returns [{ sku, name, quantity, pricePerUnit }] function readPrice(txt) { const keep = (txt || "").replace(/[^\d.,']/g, ""); if (!keep) return 0; const last = Math.max(keep.lastIndexOf(","), keep.lastIndexOf(".")); let ip, dp; if (last !== -1 && keep.length - last - 1 <= 2) { ip = keep.slice(0, last).replace(/[.,\s']/g, ""); dp = keep.slice(last + 1); } else { ip = keep.replace(/[.,\s']/g, ""); dp = ""; } dp = (dp + "00").slice(0, 2); const n = Number(`${ip}.${dp}`); return Number.isFinite(n) ? n : 0; } function escapeSKU(s){ return window.CSS && CSS.escape ? CSS.escape(s) : String(s).replace(/[^a-zA-Z0-9_-]/g,"\\$&"); } // one card per line item (works on cart + checkout + confirmation) const cards = Array.from(document.querySelectorAll('[data-testid^="checkout-card-cart-entry-"]')); const items = cards.map(card => { // SKU from card testid const cardId = card.getAttribute("data-testid") || ""; const sku = (cardId.match(/^checkout-card-cart-entry-(.+)$/) || [, ""])[1]; // Name const name = card.querySelector(`[data-testid="cart-link-product-name-${escapeSKU(sku)}"]`)?.textContent?.trim() || ""; // Quantity: N const qtyTxt = card.querySelector(`[data-testid="checkout-typography-unit-quantity-${escapeSKU(sku)}"]`)?.textContent || ""; const qty = parseInt(qtyTxt.replace(/[^\d]/g, ""), 10); const quantity = Number.isFinite(qty) && qty > 0 ? qty : 1; // Price candidates const base = card.querySelector(`[data-testid="component-price-${escapeSKU(sku)}-base-price"]`)?.textContent || ""; const unit = card.querySelector(`[data-testid="component-price-${escapeSKU(sku)}-unit-price"]`)?.textContent || ""; const total= card.querySelector(`[data-testid="component-price-${escapeSKU(sku)}-total-price"]`)?.textContent || ""; let pricePerUnit = 0; if (base) pricePerUnit = readPrice(base); else if (unit) pricePerUnit = readPrice(unit); else if (total) { const t = readPrice(total); pricePerUnit = quantity ? +(t / quantity).toFixed(2) : 0; } return { sku, name, quantity, pricePerUnit }; }).filter(it => it && it.sku); return items; /* */},"scope":"LOCAL"},{"id":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","name":"clickDataTestId","type":"FUNCTION","valueType":"TEXT","function":function(event){const el = event.target; const withId = el?.closest?.("[data-testid]"); return withId ? withId.getAttribute("data-testid") : ""; /* */},"scope":"LOCAL"},{"id":"b73b1d5c-2232-410e-be49-48ec6609f4d7","name":"Clickout, add product from search result","type":"CONSTANT","valueType":"TEXT","value":"Clickout, add product from search result","scope":"GLOBAL"},{"id":"3b881070-bf08-4869-be93-2b5d94a99c5b","name":"Clickout, category","type":"CONSTANT","valueType":"TEXT","value":"Clickout, category","scope":"GLOBAL"},{"id":"d2d8bad9-06f7-4f78-9184-8c38a9a469a1","name":"Clickout, view all products","type":"CONSTANT","valueType":"TEXT","value":"Clickout, view all products","scope":"GLOBAL"},{"id":"50d1794c-965c-4d73-bb7e-558080b33281","name":"clickParentTestId","type":"FUNCTION","valueType":"TEXT","function":function(event){// clickParentTestId (hardened) const el = event.target; if (!el) return ""; // Prefer the popup root if we are inside it (more stable targeting) const popupRoot = el.closest?.('[data-testid="product-search-suggestions-popup"]'); if (popupRoot) return 'product-search-suggestions-popup'; // Otherwise: nearest ancestor with data-testid (one hop above the first hit) const first = el.closest?.('[data-testid]'); const parent = first?.parentElement?.closest?.('[data-testid]') || first; return parent?.getAttribute?.('data-testid') || ""; /* */},"scope":"LOCAL"},{"id":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","name":"currencyCode","type":"FUNCTION","valueType":"TEXT","function":function(event){return document.documentElement.getAttribute("data-currency-iso-code") || ""; /* */},"scope":"LOCAL"},{"id":"bfa64fe3-c31a-4550-bc41-39abf7f7360b","name":"emptySearchInput","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const skus = (function () { const root = document.querySelector('[data-testid="product-search-suggestions-popup"]'); if (!root) return ""; const cards = root.querySelectorAll( '[data-testid="product-search-product-list"] .stg-trackable-product-card' ); return [...cards] .map(el => el.dataset.trackingId || el.getAttribute('data-tracking-id')) .filter(Boolean) .join(","); })(); return skus.length === 0; /* */},"scope":"LOCAL"},{"id":"c58a7312-7b27-4ff7-804e-546e99c03152","name":"Empty search result popup","type":"CONSTANT","valueType":"TEXT","value":"Empty search result popup","scope":"GLOBAL"},{"id":"ad6080ea-719b-4732-952f-ef4f1ce45e02","name":"Event target","type":"FUNCTION","valueType":"EVENT","function":function(event){return event.target /* */},"scope":"GLOBAL"},{"id":"1e327f21-f8ab-4ffc-afbd-61bb59ee3728","name":"Event target - parent element","type":"FUNCTION","valueType":"EVENT","function":function(event){return event.target.parentElement; /* */},"scope":"GLOBAL"},{"id":"2e141436-b43c-40d6-bbed-64cfdca7f222","name":"Go to PDP - search clickout","type":"CONSTANT","valueType":"TEXT","value":"Go to PDP - search clickout","scope":"GLOBAL"},{"id":"8f616b9d-a538-4dca-a88f-8105e10c6965","name":"hasAutosuggestionWrapper","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const wrapper = event.target?.closest('div[data-testid="product-search-product-list"]'); return !!wrapper; /* */},"scope":"LOCAL"},{"id":"aaebc7bd-ec9d-48cc-a482-e61bd809fa3f","name":"hasCategorySearchSuggestionsWrapper","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const el = event.target; const hasWrapper = el.closest('div[data-testid="product-search-category-list"]'); return !!hasWrapper; /* */},"scope":"LOCAL"},{"id":"fc7e128a-8061-492a-bc9b-4bccdf013ab6","name":"identifierHref","type":"FUNCTION","valueType":"TEXT","function":function(event){ const el = (window.__evt?.target) || window.event?.target || document.activeElement; // Brand base (keeps sub-brand + locale prefix intact) const brandBase = (location.pathname.match(/^(\/(?:[^/]+\/)?[a-z]{2}\/[a-z]{2}_[a-z]{2})\b/i) || [""])[0]; // A solid "root" to search within: // - suggestions product card button // - any element carrying tracking id // - the general product card const root = el?.closest?.('[data-testid^="product-link-"]') || el?.closest?.('[data-tracking-id]') || el?.closest?.('.stg-trackable-product-card') || el; // now look for the PDP inside that root const link = root?.querySelector?.('a[href*="/p/"]') || el?.closest?.('a[href*="/p/"]'); // fallback: direct nearest if (link) { const href = link.getAttribute('href') || ''; if (!href) return ''; if (/^https?:\/\//i.test(href)) return href; // absolute // root-relative vs bare path (prefix with brandBase) return new URL(href, href.startsWith('/') ? location.origin : (location.origin + brandBase + '/')).href; } // Final fallback: build from SKU on the nearest trackable card const card = el?.closest?.('[data-tracking-id], .stg-trackable-product-card'); const sku = card?.getAttribute?.('data-tracking-id'); return sku ? new URL(`/p/${encodeURIComponent(sku)}`, location.origin + brandBase + '/').href : ''; /* */},"scope":"LOCAL"},{"id":"891ea5f6-cc29-4787-be9f-351372112e69","name":"isCategoryClickout","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const el = event.target; const closestHref = el.closest('a'); return !!closestHref?.getAttribute('href')?.includes('/c/cat_'); /* */},"scope":"LOCAL"},{"id":"2dc7bb1d-4a0f-4e0c-ab74-de088744557e","name":"isProductPdpClick","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const el = event.target; const productWrapper = el?.closest('button[data-testid*="product-link-"]'); return !!productWrapper; /* */},"scope":"LOCAL"},{"id":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","name":"locale","type":"FUNCTION","valueType":"TEXT","function":function(event){function getLocaleFromPath() { // Example paths: // /us/en_us/BLX // /de/de_de/iexcel_assistant // /no/en_no/ // /medentika/de/de_de/search/... // /neodent/us/en_us const path = (window.location.pathname || "/").replace(/^\/+|\/+$/g, ""); const parts = path ? path.split("/") : []; // Decide if the first segment is a sub-brand. // If the first segment is 2 letters, treat it as country, otherwise treat it as brand. const first = (parts[0] || "").toLowerCase(); const hasBrandPrefix = first && !/^[a-z]{2}$/.test(first); const idx = hasBrandPrefix ? 1 : 0; const country = (parts[idx] || "").toLowerCase(); const localePart = (parts[idx + 1] || "").toLowerCase(); // country should be 2 letters if (!/^[a-z]{2}$/.test(country)) return false; // localePart should be like en_us const m = localePart.match(/^([a-z]{2})_([a-z]{2})$/i); if (!m) return false; // Optional sanity check: if locale says *_xx, xx should match country // If you want to be strict, keep this check, otherwise remove it. const localeCountry = m[2].toLowerCase(); if (localeCountry !== country) { // If you prefer not to fail hard, return localePart anyway: // return localePart; return false; } return localePart; // already in en_us format } // If you still want a fallback to HTML lang: function getStrictLocale() { const fromPath = getLocaleFromPath(); if (fromPath) return fromPath; const lang = document.documentElement.getAttribute("lang"); if (!lang) return false; const match = lang.match(/^([a-z]{2})[-_]([a-z]{2})$/i); if (!match) return false; const language = match[1].toLowerCase(); const country = match[2].toLowerCase(); return `${language}_${country}`; } return getStrictLocale(); /* */},"scope":"LOCAL"},{"id":"6c77635c-e95a-4eca-8086-f6be1b1193e8","name":"Named referral: Search","type":"CONSTANT","valueType":"TEXT","value":"Search","scope":"GLOBAL"},{"id":"fda8e609-5410-4707-b86b-01184d2af597","name":"No permission to track","type":"CONSTANT","valueType":"TEXT","value":"No permission to track","scope":"GLOBAL"},{"id":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","name":"Page URL","type":"FUNCTION","valueType":"TEXT","function":function(event){return window.location.href; /* */},"scope":"GLOBAL"},{"id":"82d3f082-6cb8-453e-8f80-567200c2a4da","name":"Page visit","type":"CONSTANT","valueType":"TEXT","value":"Page visit","scope":"GLOBAL"},{"id":"f383aa5f-c209-4b43-b01b-9035658ac777","name":"pdpAddToCartTrigger","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){return ( window.location.pathname.includes('/p/') && Boolean(event.target.closest('[data-testid*="-addToCart"]')) ); /* */},"scope":"LOCAL"},{"id":"7c90d4b8-ddf0-4166-b43a-2ce18c2f4755","name":"pdpSkuFromUrl","type":"FUNCTION","valueType":"TEXT","function":function(event){ const path = location.pathname; // Match anything after "/p/" up to a slash, question mark, or end of string const match = path.match(/\/p\/([^/?#]+)/i); if (match && match[1]) { return decodeURIComponent(match[1]); } return ""; /* */},"scope":"LOCAL"},{"id":"b12f3744-de4e-487e-b45c-494d0ecd4b3b","name":"Product details page visit","type":"CONSTANT","valueType":"TEXT","value":"Product details page visit","scope":"GLOBAL"},{"id":"42c22679-b3f7-4003-8975-c3e179e21d90","name":"Remove from cart","type":"CONSTANT","valueType":"TEXT","value":"Remove from cart","scope":"GLOBAL"},{"id":"f5260072-b60f-4a97-aad1-2950c7246db8","name":"searchInputSkusCsv","type":"FUNCTION","valueType":"LIST","function":function(event){const root = document.querySelector('[data-testid="product-search-suggestions-popup"]'); if (!root) return []; const cards = root.querySelectorAll( '[data-testid="product-search-product-list"] .stg-trackable-product-card' ); const skus = [...cards] .map(el => el.dataset.trackingId || el.getAttribute('data-tracking-id')) .filter(Boolean); console.log('skus,', skus) return skus; /* */},"scope":"LOCAL"},{"id":"effc6cd0-1b1d-4e33-b913-526ade59cf0a","name":"searchIsEmpty","type":"FUNCTION","valueType":"BOOLEAN","function":function(event){const main = document.querySelector("main"); if (!main) return false; // Look for the explicit empty search banner inside
const hasEmptyBanner = !!main.querySelector( '[data-testid="shop-section-empty-search-section"]' ); // True only if the banner is present return hasEmptyBanner; /* */},"scope":"LOCAL"},{"id":"ca544c79-ff6e-412a-a0b5-2bf963c493fd","name":"Search phrase typed","type":"CONSTANT","valueType":"TEXT","value":"Search phrase typed","scope":"GLOBAL"},{"id":"65acdc55-be24-40cf-ad19-de3d1ba2c697","name":"searchResultSkusCsv","type":"FUNCTION","valueType":"LIST","function":function(event){const cards = Array.from(document.querySelectorAll('main .stg-trackable-product-card')); return cards.map(c => c.getAttribute('data-tracking-id')).filter(Boolean); /* */},"scope":"LOCAL"},{"id":"17b79a43-d18f-4de9-b56c-eeb7a3e50200","name":"Search Results label (page)","type":"FUNCTION","valueType":"TEXT","function":function(event){const main = document.querySelector("main"); if (!main) return false; // Look for the explicit empty search banner inside
const hasEmptyBanner = !!main.querySelector( '[data-testid="shop-section-empty-search-section"]' ); // True only if the banner is present return hasEmptyBanner ? 'Empty search result page' : 'Search result page'; /* */},"scope":"LOCAL"},{"id":"c1fd0e80-cc66-4d38-b43f-6a20c5352a09","name":"transactionId","type":"FUNCTION","valueType":"TEXT","function":function(event){// /checkout/confirmation?order=05234000 (primary) const qs = new URLSearchParams(location.search); const qid = qs.get("order") || qs.get("orderId") || qs.get("transactionId"); if (qid) return qid; // Older pattern: /orderConfirmation/ const m = location.pathname.match(/\/orderConfirmation\/([^/?#]+)/i); return m ? m[1] : ""; /* */},"scope":"LOCAL"},{"id":"9ec04551-6b11-4101-b769-b08ee5148b1d","name":"Update cart","type":"CONSTANT","valueType":"TEXT","value":"Update cart","scope":"GLOBAL"},{"id":"16f42a89-ecfb-4b52-9363-20e91e064db8","name":"viewAllTargetUrl","type":"FUNCTION","valueType":"TEXT","function":function(event){const q = (document.querySelector('input[aria-label="search input"]')?.value || "").trim() || (new URLSearchParams(location.search).get("q") || "").trim(); const m = location.pathname.match(/^(\/(?:[^/]+\/)?[a-z]{2}\/[a-z]{2}_[a-z]{2})\b/i); const base = m ? m[1] : ""; return q ? `${base}/search?q=${encodeURIComponent(q)}` : (location.pathname + location.search); /* */},"scope":"LOCAL"}]; const script = {"id":"ea56e241-999a-49a6-a020-4edbc090064a","rows":[{"id":"28be889a-4580-4f02-8585-0d04887e7473","rowType":"EVENT","trigger":{"id":"d16c687f-b914-4000-a280-fdd03c19d6fc","name":"Search input (fixed)","type":"INPUT","delayMs":3000,"groups":[{"id":"bc294b8d-237d-4d1b-b22e-55f8fe914841","rows":[{"valueType":"EVENT","id":"9ff68741-c086-45a4-99b2-53c21806ed43","variableId":"ad6080ea-719b-4732-952f-ef4f1ce45e02","value":"input[aria-label=\"search input\"]","operator":"MATCHES_CSS_SELECTOR"},{"valueType":"BOOLEAN","id":"efa4b4b5-894e-4afa-8419-40ef77bc93f4","variableId":"bfa64fe3-c31a-4550-bc41-39abf7f7360b","value":"true","operator":"EQUALS"}]}]},"action":{"type":"SEARCH_RESULT"},"fields":[{"id":"369095c0-0d67-44c9-b9d9-53273ee5a728","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"26278e99-e2d7-47fa-a13c-4f063cdcb5d0","variableId":"bfa64fe3-c31a-4550-bc41-39abf7f7360b","fieldName":"isEmpty"},{"id":"99eb7f69-3a83-48c7-bbf9-9e5f203c410f","variableId":"f5260072-b60f-4a97-aad1-2950c7246db8","fieldName":"skus"},{"id":"ddfcad79-b358-44de-92fa-c16c581c1bdc","variableId":"c58a7312-7b27-4ff7-804e-546e99c03152","fieldName":"eventLabel"}]},{"id":"82f9108d-693c-4176-9a00-6f201a99cbc8","rowType":"EVENT","trigger":{"id":"b8fdeb95-b9c6-42cd-81f2-c33c21cbd26b","name":"Add To Cart (Best Sellers)","type":"CLICK","groups":[{"id":"aa519e21-2b9b-4faf-9849-ebf606f0c606","rows":[{"valueType":"BOOLEAN","id":"c284f36e-adf4-405a-81d2-0763dc0cf339","variableId":"05e7a8c4-ec2a-486f-bb77-24f051f024c0","value":"true","operator":"EQUALS"}]}]},"action":{"type":"ADD_TO_CART"},"fields":[{"id":"0499d460-ce35-4137-b58f-3b5541de5db5","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"562ee8c1-d2cf-4f60-9e2a-7ceac23cca87","variableId":"9bcc6c18-d60d-4f77-b7d3-8f9d8dc4a217","fieldName":"sku"},{"id":"1cc9b118-5d2f-4a90-8542-6cb07d5dbf02","variableId":"c6abe1c2-0962-4813-8a13-d4f6771c890f","fieldName":"price"},{"id":"a0e6bd85-b112-4d27-8fb8-377a020e84d4","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"18e109ab-dea5-4714-8918-7c6b2c88d813","variableId":"e13c5676-bb2c-43c9-be42-938b221632b4","fieldName":"quantity"},{"id":"8782f5b8-9efc-419f-afea-0ea82f502a63","variableId":"17544614-8761-4994-999e-0846fa6ef15f","fieldName":"category"}]},{"id":"11f70e48-67c8-4050-aa46-a7f4acb751ab","rowType":"EVENT","trigger":{"id":"336ce078-02f8-4f8a-9b0c-de83ab677678","name":"Reject All Cookies Click","type":"CLICK","groups":[{"id":"b12412a6-85a6-4523-94bd-70bc432f6b28","rows":[{"valueType":"EVENT","id":"1cccd1cd-8509-4c99-97b6-60975c3c0333","variableId":"ad6080ea-719b-4732-952f-ef4f1ce45e02","value":"onetrust-close-btn-handler","operator":"CONTAINS_CLASS"}]}]},"action":{"type":"DECLINE_TRACKING"},"fields":[{"id":"3d62f2ec-4622-45c2-b34c-3cd41519866e","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"4f1a5796-229f-470c-b438-0a14cf8160ec","variableId":"fda8e609-5410-4707-b86b-01184d2af597","fieldName":"eventLabel"}]},{"id":"711188da-f101-428f-9b4e-63e2cad2086c","rowType":"EVENT","trigger":{"id":"d98ba2a4-9d32-4d67-ae05-d7719b5c2f41","name":"Search Result (fixed)","type":"PAGE_LOAD","delayMs":3000,"groups":[{"id":"baee1304-5dc6-45f4-b764-d1b63c746642","rows":[{"valueType":"TEXT","id":"74d40c2f-f08e-4064-92a0-f5a47e40e21b","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/search","operator":"CONTAINS"}]}]},"action":{"type":"SEARCH_RESULT"},"fields":[{"id":"06ec823e-fa03-4706-939b-a3ef65b468bc","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"2a6dc581-2069-4980-af35-680605afb7e9","variableId":"effc6cd0-1b1d-4e33-b913-526ade59cf0a","fieldName":"isEmpty"},{"id":"ba06f185-f91c-4fbe-a8f7-25f98d8caf57","variableId":"65acdc55-be24-40cf-ad19-de3d1ba2c697","fieldName":"skus"},{"id":"abe605ec-c889-4712-a87c-8667d32da9a3","variableId":"17b79a43-d18f-4de9-b56c-eeb7a3e50200","fieldName":"eventLabel"}]},{"id":"bc281650-7d27-4923-8d83-a63a31c0e448","rowType":"EVENT","trigger":{"id":"cd689443-f8f6-48f8-83b6-a71af6a5a94c","name":"CLICKOUT – Add product (search plp and suggestions - fixed)","type":"CLICK","groups":[{"id":"864b5221-955a-4452-9383-25522e1ae742","rows":[{"valueType":"TEXT","id":"329c1477-389b-448e-87bf-322335aa4051","variableId":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","value":"product-button-","operator":"CONTAINS"},{"valueType":"TEXT","id":"391ef57b-52fd-4034-8552-d78ebcec8282","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/search","operator":"CONTAINS"},{"valueType":"BOOLEAN","id":"8861869c-cb1a-4538-aefd-2ccabcc6c755","variableId":"8f616b9d-a538-4dca-a88f-8105e10c6965","value":"false","operator":"EQUALS"}]},{"id":"c4c64cc4-74b1-41f9-98f3-e1ffa996a21e","rows":[{"valueType":"TEXT","id":"1b363d65-ffdd-4cac-bb05-260955e7fbc4","variableId":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","value":"product-button-","operator":"CONTAINS"},{"valueType":"BOOLEAN","id":"b2951763-0136-47e7-8144-a1f5b6cc49cb","variableId":"8f616b9d-a538-4dca-a88f-8105e10c6965","value":"true","operator":"EQUALS"}]}]},"action":{"type":"CLICKOUT"},"fields":[{"id":"1658cef0-6411-424f-afa3-c7c1fd013296","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"9cf906e4-15e4-4a29-97ba-8f222f503f89","variableId":"fc7e128a-8061-492a-bc9b-4bccdf013ab6","fieldName":"targetUrl"},{"id":"4bd22f8b-9d16-45e5-b6f9-6f8793fd62ff","variableId":"6c77635c-e95a-4eca-8086-f6be1b1193e8","fieldName":"namedReferral"},{"id":"ce8efdf4-0fae-46b9-9b3e-661abb51996f","variableId":"b73b1d5c-2232-410e-be49-48ec6609f4d7","fieldName":"eventLabel"}]},{"id":"584b32ff-6a5c-4ea3-9a76-86593f04c737","rowType":"EVENT","trigger":{"id":"86246192-4b8b-4fb4-b588-e696ec596d2f","name":"CLICKOUT – Go to PDP (plp and suggestions - fixed)","type":"CLICK","groups":[{"id":"88e1d6bb-eb62-4f40-b727-da152166f3e0","rows":[{"valueType":"TEXT","id":"df74bc80-94da-4637-9bd2-f9d55402df02","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/search","operator":"CONTAINS"},{"valueType":"BOOLEAN","id":"f40371f6-13a8-4a0e-867e-8ce1293085f5","variableId":"2dc7bb1d-4a0f-4e0c-ab74-de088744557e","value":"true","operator":"EQUALS"},{"valueType":"BOOLEAN","id":"e3298a13-6d62-4f59-b76e-da10b2038d43","variableId":"8f616b9d-a538-4dca-a88f-8105e10c6965","value":"false","operator":"EQUALS"}]},{"id":"808a6df1-93d4-472a-be17-c579674dd114","rows":[{"valueType":"BOOLEAN","id":"9c8a7137-6922-4542-9df5-ff5a80afc42b","variableId":"8f616b9d-a538-4dca-a88f-8105e10c6965","value":"true","operator":"EQUALS"},{"valueType":"BOOLEAN","id":"9ab5b60e-506a-4a4d-92c4-27f95c848951","variableId":"2dc7bb1d-4a0f-4e0c-ab74-de088744557e","value":"true","operator":"EQUALS"}]}]},"action":{"type":"CLICKOUT"},"fields":[{"id":"80274f63-79ce-432e-a029-aaea8c7b29e6","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"e9b7a7e5-4817-423d-b3d6-f3ee923411df","variableId":"fc7e128a-8061-492a-bc9b-4bccdf013ab6","fieldName":"targetUrl"},{"id":"c6cec676-c4a0-4848-b6ef-0f3bea37c7c7","variableId":"6c77635c-e95a-4eca-8086-f6be1b1193e8","fieldName":"namedReferral"},{"id":"86b8cde4-4240-400a-842b-0dba107222cb","variableId":"2e141436-b43c-40d6-bbed-64cfdca7f222","fieldName":"eventLabel"},{"id":"82dc2366-702f-44be-bd6a-189874b8cc74","variableId":"17544614-8761-4994-999e-0846fa6ef15f","fieldName":"category"}]},{"id":"720afc7c-a35b-4b74-9b38-91b67017dfe4","rowType":"EVENT","trigger":{"id":"257f9167-ec37-4ee9-a872-c50202668526","name":"CLICKOUT – Go to Category page (fixed)","type":"CLICK","groups":[{"id":"9381a3f0-a37b-47bb-9406-3e5b03f35ed4","rows":[{"valueType":"TEXT","id":"10a2bfb2-9581-4915-92a6-77d0f827b5cf","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/search","operator":"CONTAINS"},{"valueType":"BOOLEAN","id":"31d22566-9bdf-4ea4-bdca-051c0fcd8de1","variableId":"891ea5f6-cc29-4787-be9f-351372112e69","value":"true","operator":"EQUALS"}]},{"id":"9e3bdaee-ca5e-4de4-84d0-d4e246d6673f","rows":[{"valueType":"BOOLEAN","id":"50d13080-77ba-42a9-8f0f-3f26aefaae06","variableId":"891ea5f6-cc29-4787-be9f-351372112e69","value":"true","operator":"EQUALS"},{"valueType":"BOOLEAN","id":"79e2ddd5-9d7b-4a37-8bd2-b200e1ed016c","variableId":"aaebc7bd-ec9d-48cc-a482-e61bd809fa3f","value":"true","operator":"EQUALS"}]}]},"action":{"type":"CLICKOUT"},"fields":[{"id":"c838b3fb-384d-4992-9eea-e81c6cffea38","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"d9a11ed1-e53b-4065-96c4-e1342fe712d4","variableId":"e421893f-c0f4-435d-922d-3809963e986e","fieldName":"targetUrl"},{"id":"8b60c021-4e8b-4477-8121-3c420537110c","variableId":"6c77635c-e95a-4eca-8086-f6be1b1193e8","fieldName":"namedReferral"},{"id":"515bfd60-69bd-4d91-89e3-19eea57ef405","variableId":"3b881070-bf08-4869-be93-2b5d94a99c5b","fieldName":"eventLabel"},{"id":"669cbc22-cc92-49a1-92a0-3da2d3272ec3","variableId":"17544614-8761-4994-999e-0846fa6ef15f","fieldName":"category"}]},{"id":"9d64c00a-a8f6-47e1-a477-53d6c8f9988c","rowType":"EVENT","trigger":{"id":"572961b5-d08d-4e97-a558-e577a2d5ddbd","name":"Checkout Confirmation","type":"PAGE_LOAD","groups":[{"id":"620711b8-2556-4ad4-aa27-7dbf881eb109","rows":[{"valueType":"TEXT","id":"0d1d23d8-7ecd-4b37-bcba-a528f714685f","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/checkout/confirmation","operator":"CONTAINS"}]}]},"action":{"type":"PURCHASED"},"fields":[{"id":"3c9f3d52-5811-419d-9153-0babe5981ede","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"32b3a369-6c43-44a6-96c6-99e83d0882ee","variableId":"904c3ff0-8e69-48ca-a868-c61c5260d9e7","fieldName":"products"},{"id":"b6a9a001-07ed-409c-951b-781897dde920","variableId":"c1fd0e80-cc66-4d38-b43f-6a20c5352a09","fieldName":"transactionId"}]},{"id":"8e9f14aa-d298-40a0-b263-48673d220f5b","rowType":"EVENT","trigger":{"id":"a2fc863c-49f8-4fea-b842-bc378b676ad2","name":"Remove from cart – Single Item","type":"CLICK","groups":[{"id":"4bf88d2b-28e0-437e-8945-0b51742c715e","rows":[{"valueType":"TEXT","id":"b26cef14-21b9-424c-89fa-11ef74ee18d3","variableId":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","value":"cart-button-cart-entry-delete","operator":"CONTAINS"}]}]},"action":{"type":"REMOVE_FROM_CART"},"fields":[{"id":"a7020a13-5503-419c-b50f-2b45c0a89214","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"27667c0a-937a-4fec-bfc1-216c436bf62f","variableId":"2c79bdb1-b595-407b-9055-dc8afdff4c17","fieldName":"sku"},{"id":"66ee9156-8b69-421d-abfa-e6840b09fe04","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"0a2ba11b-42c3-4ae8-bdce-8089ba483d10","variableId":"42c22679-b3f7-4003-8975-c3e179e21d90","fieldName":"eventLabel"},{"id":"c3beb278-a4cd-4449-8f5f-5f3265013740","variableId":"794148b9-d30c-4fa9-ab0f-d1e903691954","fieldName":"price"}]},{"id":"4c683d78-9762-47ad-864e-96875fa84289","rowType":"EVENT","trigger":{"id":"fe70154b-7eb2-4e2c-a341-56659ae0e723","name":"Quantity Decrement (fixed)","type":"CLICK","groups":[{"id":"63a68bb9-eee6-4701-91b3-4b6d697298c9","rows":[{"valueType":"TEXT","id":"54871943-edc4-4c48-8385-94ad91c8811c","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/cart","operator":"CONTAINS"},{"valueType":"EVENT","id":"c6a26863-ebc0-4e17-aebf-dbaa878c4bdc","variableId":"1e327f21-f8ab-4ffc-afbd-61bb59ee3728","value":"button[data-testid*=\"-cart-entry-quantity-decrement\"]","operator":"MATCHES_CSS_SELECTOR"}]}]},"action":{"type":"UPDATE_CART"},"fields":[{"id":"dfa3e3c7-9d91-453d-bffe-ee91d6c68fbc","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"abf96d32-eac9-4b6f-a47f-64f0db62148c","variableId":"2c79bdb1-b595-407b-9055-dc8afdff4c17","fieldName":"sku"},{"id":"4da8d2e6-0f89-4b53-bed5-c382223237a9","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"2c2b8892-cd34-4be9-9bba-865f3c43cb40","variableId":"9ec04551-6b11-4101-b769-b08ee5148b1d","fieldName":"eventLabel"},{"id":"05f222a3-8483-45b5-84cf-aee2fbc4d9c0","variableId":"794148b9-d30c-4fa9-ab0f-d1e903691954","fieldName":"price"},{"id":"8ab25397-f7e6-4331-ac90-d13ba414a539","variableId":"521ed7c6-7510-45cc-b206-4d9380418fee","fieldName":"quantity"}]},{"id":"c11f0bbb-3557-4c03-9973-fef95ff426b4","rowType":"EVENT","trigger":{"id":"3eae7a8d-b2bd-4518-9490-d3ce2496687a","name":"Quantity increment (fixed)","type":"CLICK","groups":[{"id":"33170df7-3820-438d-9a03-94afa9308185","rows":[{"valueType":"EVENT","id":"949fc82e-23e1-45ee-9ef6-99f38a0e48bc","variableId":"1e327f21-f8ab-4ffc-afbd-61bb59ee3728","value":"button[data-testid*=\"-cart-entry-quantity-increment\"]","operator":"MATCHES_CSS_SELECTOR"},{"valueType":"TEXT","id":"dd89b3c6-e632-4dd6-aaf9-6982e7c7a4c0","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/cart","operator":"CONTAINS"}]}]},"action":{"type":"UPDATE_CART"},"fields":[{"id":"73084afd-c5fd-440e-a660-84c028065bff","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"30539f96-6ccc-42a6-8afc-4c2ccce3126c","variableId":"9ec04551-6b11-4101-b769-b08ee5148b1d","fieldName":"eventLabel"},{"id":"2b6779d0-8751-4f09-8a01-6348d4e2db00","variableId":"dafc253c-b1ae-45db-81fd-4d274f7104aa","fieldName":"quantity"},{"id":"c2670218-5ae2-424d-856b-f10cd0dd9afa","variableId":"794148b9-d30c-4fa9-ab0f-d1e903691954","fieldName":"price"},{"id":"d1fb5ff5-a540-4692-ae36-8373d5719380","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"95ae5b64-ac12-4f5e-bf8e-774b3a6259dd","variableId":"2c79bdb1-b595-407b-9055-dc8afdff4c17","fieldName":"sku"}]},{"id":"eea835f1-8622-4642-92da-f772d9b8c08a","rowType":"EVENT","trigger":{"id":"325a6bfb-1a56-4433-acd6-5767f44c75de","name":"Search Input","type":"INPUT","delayMs":1500,"groups":[{"id":"36c53dfb-0893-484f-864e-68607755b9c9","rows":[{"valueType":"EVENT","id":"a4c980b2-7477-495d-ad24-0d08efb85f80","variableId":"ad6080ea-719b-4732-952f-ef4f1ce45e02","value":"input[aria-label=\"search input\"]","operator":"MATCHES_CSS_SELECTOR"}]}]},"action":{"type":"SEARCH"},"fields":[{"id":"f6291f1c-29b8-4895-8bcc-49387639b37c","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"bb93de40-97dd-4122-8c5c-942857a6890f","variableId":"ca544c79-ff6e-412a-a0b5-2bf963c493fd","fieldName":"eventLabel"}]},{"id":"6b67114f-855e-4152-8b64-eac658002f7d","rowType":"EVENT","trigger":{"id":"05ba7d0d-1728-4931-8a35-5a9591d39875","name":"PDP Page Load","type":"PAGE_LOAD","groups":[{"id":"b369c75d-cfc3-4a4a-9a01-1f3292d5c63d","rows":[{"valueType":"TEXT","id":"1cfb92df-357b-4ae9-8ec3-bfe8a9b0e36b","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/p/","operator":"CONTAINS"},{"valueType":"TEXT","id":"515b0040-51ee-4314-a240-9f1b2da77d22","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/p/base_","operator":"DOES_NOT_CONTAIN"}]}]},"action":{"type":"PDP_VISITED"},"fields":[{"id":"b0a0010b-0f3c-4411-850f-209bbf1ce934","variableId":"b12f3744-de4e-487e-b45c-494d0ecd4b3b","fieldName":"eventLabel"},{"id":"7b8cd737-2083-402c-aa0c-ca3d34bc50f7","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"837d732c-6aa7-44d8-b2cc-43e581a6b8a0","variableId":"7c90d4b8-ddf0-4166-b43a-2ce18c2f4755","fieldName":"sku"}]},{"id":"a96040b9-2527-48fd-b854-edcb6cb27e52","rowType":"EVENT","trigger":{"id":"25fe6abf-7b99-4b98-ae52-9af63be84944","name":"Page Load","type":"PAGE_LOAD","groups":[{"id":"b3979ed2-1c8c-4aae-b66d-d23034939365","rows":[{"valueType":"TEXT","id":"5886b8c9-814b-4db3-9c9f-48c57d565af1","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/p/","operator":"DOES_NOT_CONTAIN"}]},{"id":"514f76db-aa87-45f9-9c24-c28460e924f0","rows":[{"valueType":"TEXT","id":"e6230de6-a1cc-4942-b1a4-6a84952e0bb8","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"\\/p\\/base_[^/?#]+","operator":"MATCHES_REGEX"}]}]},"action":{"type":"PAGE_VISITED"},"fields":[{"id":"8600bf4a-166d-4697-ad0c-3aac7c1a0d32","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"a8e4c320-27d2-43cb-bb70-aaa997710193","variableId":"82d3f082-6cb8-453e-8f80-567200c2a4da","fieldName":"eventLabel"}]},{"id":"30e0f5cf-ffe9-46c6-891d-ea8b78fab334","rowType":"EVENT","trigger":{"id":"4bc664b8-13bf-4374-abbf-508223ffb79c","name":"Add to cart (Search Suggestions)","type":"CLICK","groups":[{"id":"fbef9d81-1f45-468d-8768-16cb1cdec22b","rows":[{"valueType":"TEXT","id":"2eb786d9-06b7-49fb-8136-f6eb8a892056","variableId":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","value":"-addToCart","operator":"CONTAINS"},{"valueType":"TEXT","id":"111e189a-3482-43b4-9a7f-8ae14402961c","variableId":"50d1794c-965c-4d73-bb7e-558080b33281","value":"product-search-suggestions-popup","operator":"EQUALS"}]}]},"action":{"type":"ADD_TO_CART"},"fields":[{"id":"22d3f562-85d6-49ba-b3cf-302dcbfc822e","variableId":"9bcc6c18-d60d-4f77-b7d3-8f9d8dc4a217","fieldName":"sku"},{"id":"2fd1491e-5175-442d-a52b-476e2b855185","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"fc1fe890-a633-4de7-881b-da47f89726b1","variableId":"17544614-8761-4994-999e-0846fa6ef15f","fieldName":"category"},{"id":"3017f199-5ac8-4dd3-8686-83949710a2d4","variableId":"1eec2be6-dc91-4c4e-a443-da2f051baf5b","fieldName":"eventLabel"},{"id":"e8fc8730-7ae6-45b3-a03d-14f966663ca0","variableId":"c6abe1c2-0962-4813-8a13-d4f6771c890f","fieldName":"price"},{"id":"4c628858-d4fe-4479-a91c-306135ea003e","variableId":"e13c5676-bb2c-43c9-be42-938b221632b4","fieldName":"quantity"},{"id":"b6064fdf-6fcb-43c4-8a4e-3aad166a90c2","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"}]},{"id":"ed5c58db-28c7-41eb-873f-9c4486c62dac","rowType":"EVENT","trigger":{"id":"3044de02-59a8-47ef-b07e-fc24187bc690","name":"Add to cart (Search Results Page - fixed)","type":"CLICK","groups":[{"id":"b3d14ef0-7d6a-4035-9221-c3dfecc8f498","rows":[{"valueType":"TEXT","id":"f5304d17-e1b1-4402-b694-ac5457fedb02","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/search","operator":"CONTAINS"},{"valueType":"TEXT","id":"612b42c3-f3ee-4e50-9636-21e5555b511a","variableId":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","value":"-addToCart","operator":"CONTAINS"},{"valueType":"BOOLEAN","id":"2de9931a-14c7-4e94-906e-cd44453d796a","variableId":"8f616b9d-a538-4dca-a88f-8105e10c6965","value":"false","operator":"EQUALS"}]}]},"action":{"type":"ADD_TO_CART"},"fields":[{"id":"59a185b8-c5a4-4964-b848-135d63618e82","variableId":"c6abe1c2-0962-4813-8a13-d4f6771c890f","fieldName":"price"},{"id":"d6fd6c0a-6c0f-4eea-b3db-cc7409c95252","variableId":"e13c5676-bb2c-43c9-be42-938b221632b4","fieldName":"quantity"},{"id":"a98adf25-05f8-4a77-bc4e-36b9ce6345d6","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"a157b3f4-2ab6-42bb-99d5-43d8e695dfcc","variableId":"9bcc6c18-d60d-4f77-b7d3-8f9d8dc4a217","fieldName":"sku"},{"id":"db02d167-4e07-4749-b166-5d3d7fffbb0c","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"3427fe85-4da2-4d99-90f3-0a985f90d370","variableId":"17544614-8761-4994-999e-0846fa6ef15f","fieldName":"category"},{"id":"4b657135-eea2-49d2-a6f4-9e605f97b849","variableId":"1eec2be6-dc91-4c4e-a443-da2f051baf5b","fieldName":"eventLabel"}]},{"id":"995c5807-4862-47dd-8877-6ed213f8d615","rowType":"EVENT","trigger":{"id":"61a4b0c3-8b77-4aef-88f8-6a8641601238","name":"Add to Cart (PDP)","type":"CLICK","groups":[{"id":"1aebc98b-7b11-4759-87a7-d5f11ec19efd","rows":[{"valueType":"BOOLEAN","id":"86e84ac5-65bb-46a4-9b10-66eacc6fc3f8","variableId":"f383aa5f-c209-4b43-b01b-9035658ac777","value":"true","operator":"EQUALS"}]},{"id":"3b48f94d-733a-4e14-b4cf-1dcc3a9adbd9","rows":[{"valueType":"TEXT","id":"5c2c9617-5cb0-47df-bae3-5d81c73b6623","variableId":"75cf8fd1-fafe-44be-9806-23ff3017c9c1","value":"/p/","operator":"CONTAINS"},{"valueType":"TEXT","id":"f8b68af3-35da-4048-bf70-34723424baf9","variableId":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","value":"-addToCart","operator":"CONTAINS"}]}]},"action":{"type":"ADD_TO_CART"},"fields":[{"id":"4cdbd204-b287-4889-929a-94243eddd778","variableId":"c6abe1c2-0962-4813-8a13-d4f6771c890f","fieldName":"price"},{"id":"456cc9b3-3ee0-4163-af6b-4785c85eae05","variableId":"e13c5676-bb2c-43c9-be42-938b221632b4","fieldName":"quantity"},{"id":"08d66307-6688-4211-88b8-682379ccb19b","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"6b41659c-c6e1-4894-b025-34031ef2f025","variableId":"9bcc6c18-d60d-4f77-b7d3-8f9d8dc4a217","fieldName":"sku"},{"id":"7c585e07-1622-4d7a-92e2-3286db70e7f3","variableId":"17544614-8761-4994-999e-0846fa6ef15f","fieldName":"category"},{"id":"f483d947-f19a-46ca-92fb-8fb75c41b526","variableId":"b69c433b-e98f-43bf-a3fa-80774c6b54fb","fieldName":"currencyCode"},{"id":"bbe9aa66-bb07-47bc-81eb-c4b9428fdee9","variableId":"1eec2be6-dc91-4c4e-a443-da2f051baf5b","fieldName":"eventLabel"}]},{"id":"52bb897a-eb6a-4369-ab72-438f8761549f","rowType":"EVENT","trigger":{"id":"61080a23-b451-4afd-8954-8d72011a93ea","name":"View all products (suggestions)","type":"CLICK","groups":[{"id":"c2b71cbd-98b4-4218-9d77-d7c9c0204afa","rows":[{"valueType":"TEXT","id":"8ec34604-4979-4fe7-abad-d7180fab39d3","variableId":"fe8767b5-7afa-45f8-ae72-0ed18dda9f49","value":"product-search-suggestions-view-all-button","operator":"EQUALS"}]}]},"action":{"type":"CLICKOUT"},"fields":[{"id":"190dc5b5-55ce-4978-b721-7680a6493258","variableId":"d2d8bad9-06f7-4f78-9184-8c38a9a469a1","fieldName":"eventLabel"},{"id":"3f0f54b2-fc38-4da5-b4da-351d2b376d4f","variableId":"59dfcac1-9388-4dd0-9382-8ec21d1d0033","fieldName":"locale"},{"id":"c44e12cf-a65a-43f2-a432-bdc9b3b5dea5","variableId":"16f42a89-ecfb-4b52-9363-20e91e064db8","fieldName":"targetUrl"},{"id":"58274880-b9c6-4c5c-91c3-855bcca62fb9","variableId":"6c77635c-e95a-4eca-8086-f6be1b1193e8","fieldName":"namedReferral"}]}]}; const advancedCode = function(){/* ========================================================= Zoovu external tracking (SPA safe) - Consent gate via OneTrust OptanonConsent (C0002) - Uses Zoovu built-ins: enableTracking() / disableTracking() - Loads zoovu-tracker.min.js only when consent is true - Fully SPA: observer strategies for all page types - SPA locale switching: re-resolve projectId from URL and re-init tracker - Network guard: 1) blocks Zoovu requests whose projectId != current locale projectId 2) sanitizes query + referrer in Zoovu payloads to strip suffix noise (prevents polluted query reporting/newsletters) - SERP show: Removed custom trackSerpShow workaround Uses Zoovu built-in rebindStrategy: ["conditionChange"] ========================================================= */ (function () { // ---- SINGLETON GUARD (prevents double injection / double execution) ---- if (window.__ZOOVU_TRACKING_BOOTSTRAP_ACTIVE__) return; window.__ZOOVU_TRACKING_BOOTSTRAP_ACTIVE__ = true; /* ========================= 0) Network guard (installed once) ========================= */ (function installZoovuNetworkGuardOnce() { if (window.__ZOOVU_NETWORK_GUARD_INSTALLED__) return; window.__ZOOVU_NETWORK_GUARD_INSTALLED__ = true; function getCurrentProjectId() { return String(window.__ZOOVU_TRACKING_CURRENT_PROJECT_ID__ || ""); } function isZoovuInsightsUrl(url) { try { return String(url || "").indexOf("https://api.search.zoovu.com/insights") === 0; } catch (e) { return false; } } function getProjectIdFromUrl(url) { try { const u = new URL(url, window.location.href); return String(u.searchParams.get("projectId") || ""); } catch (e) { return ""; } } function shouldBlockZoovuRequest(url) { if (!isZoovuInsightsUrl(url)) return false; const reqProjectId = getProjectIdFromUrl(url); const currentProjectId = getCurrentProjectId(); // If we cannot determine either side, do not block. if (!reqProjectId || !currentProjectId) return false; return reqProjectId !== currentProjectId; } // ---- SERP SHOW de-dupe (prevents multiple /serp/show calls for same state) ---- const __zoovuSerpShowDedupe = { lastKey: "", lastTs: 0, windowMs: 1500, // adjust if needed: 800-2000ms usually fine }; function isZoovuSerpShowUrl(url) { try { return String(url || "").indexOf("https://api.search.zoovu.com/insights/serp/show") === 0; } catch (e) { return false; } } function getFormValue(body, name) { try { const params = new URLSearchParams(String(body || "")); return String(params.get(name) || ""); } catch (e) { return ""; } } function shouldDedupeSerpShow(url, body) { if (!isZoovuSerpShowUrl(url)) return false; const reqProjectId = getProjectIdFromUrl(url); if (!reqProjectId) return false; // Zoovu sends x-www-form-urlencoded body const query = getFormValue(body, "query"); const resultCount = getFormValue(body, "resultCount"); // If query is missing, do not dedupe, let it pass if (!query) return false; const key = reqProjectId + "|" + query + "|" + resultCount; const now = Date.now(); if (key && key === __zoovuSerpShowDedupe.lastKey && now - __zoovuSerpShowDedupe.lastTs < __zoovuSerpShowDedupe.windowMs) { return true; } __zoovuSerpShowDedupe.lastKey = key; __zoovuSerpShowDedupe.lastTs = now; return false; } // NEW: sanitize Zoovu application/x-www-form-urlencoded payload function sanitizeZoovuFormBody(body) { if (typeof body !== "string") return body; // Fast bailout if (body.indexOf("referrer=") === -1 && body.indexOf("query=") === -1) return body; // Same rule as your getter: // remove everything after the first ":" IF that ":" begins a suffix pattern: // - relevance-(asc|desc) // - price-(asc|desc) // - digits (e.g. :263428486:263432071) function stripQuerySuffixes(raw) { const s = String(raw || ""); const normalized = s.replace(/\+/g, " ").trim(); return normalized.replace(/:(?=(?:relevance-(?:asc|desc)|price-(?:asc|desc)|\d)).*$/i, "").trim(); } // Decode 1-2 times, clean q=, re-encode function stripSuffixesInUrlEncodedValue(encodedValue) { let v = String(encodedValue || ""); for (let i = 0; i < 2; i++) { try { v = decodeURIComponent(v); } catch (e) {} try { const u = new URL(v, window.location.href); if (u.searchParams.has("q")) { const q = u.searchParams.get("q") || ""; u.searchParams.set("q", stripQuerySuffixes(q)); v = u.toString(); } else { v = stripQuerySuffixes(v); } } catch (e) { v = stripQuerySuffixes(v); } } try { return encodeURIComponent(v); } catch (e) { return v; } } try { const params = new URLSearchParams(body); if (params.has("query")) { const q = String(params.get("query") || ""); params.set("query", stripQuerySuffixes(q)); } if (params.has("referrer")) { const r = String(params.get("referrer") || ""); params.set("referrer", stripSuffixesInUrlEncodedValue(r)); } return params.toString(); } catch (e) { return body; } } // ---- fetch guard ---- if (typeof window.fetch === "function") { const _fetch = window.fetch; window.fetch = function (input, init) { const url = typeof input === "string" ? input : (input && input.url) || ""; if (shouldBlockZoovuRequest(url)) { try { return Promise.resolve(new Response("", { status: 204 })); } catch (e) { return Promise.resolve({ ok: true, status: 204, text: function () { return Promise.resolve(""); }, }); } } try { if (isZoovuInsightsUrl(url) && init && typeof init === "object" && typeof init.body === "string") { init.body = sanitizeZoovuFormBody(init.body); // De-dupe serp/show if (shouldDedupeSerpShow(url, init.body)) { try { return Promise.resolve(new Response("", { status: 204 })); } catch (e) { return Promise.resolve({ ok: true, status: 204, text: function () { return Promise.resolve(""); }, }); } } } } catch (e) {} return _fetch.apply(this, arguments); }; } // ---- XHR guard ---- if (typeof window.XMLHttpRequest === "function") { const XHRProto = window.XMLHttpRequest.prototype; const _open = XHRProto.open; const _send = XHRProto.send; XHRProto.open = function (method, url) { try { this.__zoovu_guard_url__ = url; } catch (e) {} return _open.apply(this, arguments); }; XHRProto.send = function (body) { const url = this.__zoovu_guard_url__ || ""; if (shouldBlockZoovuRequest(url)) { try { this.abort(); } catch (e) {} return; } try { if (isZoovuInsightsUrl(url) && typeof body === "string") { body = sanitizeZoovuFormBody(body); // De-dupe serp/show if (shouldDedupeSerpShow(url, body)) { try { this.abort(); } catch (e) {} return; } } } catch (e) {} return _send.call(this, body); }; } })(); /* ========================= 1) Consent + tracker load ========================= */ function getCookie(name) { const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)")); return match ? decodeURIComponent(match[2]) : null; } function hasPerformanceConsent() { const consentCookie = getCookie("OptanonConsent"); if (!consentCookie) return false; const groupsMatch = consentCookie.match(/groups=([^&]+)/); if (!groupsMatch) return false; const groups = groupsMatch[1].split(","); const performanceGroup = groups.find((g) => g.startsWith("C0002:")); if (!performanceGroup) return false; return Boolean(Number(performanceGroup.split(":")[1])); } function callEnableTrackingSafely() { try { if (typeof window.enableTracking === "function") window.enableTracking(); else if (typeof enableTracking === "function") enableTracking(); } catch (e) {} } function callDisableTrackingSafely() { try { if (typeof window.disableTracking === "function") window.disableTracking(); else if (typeof disableTracking === "function") disableTracking(); } catch (e) {} } function isZoovuTrackerScriptPresent() { return Boolean(document.querySelector('script[src*="zoovu-tracker.min.js"]')); } function loadZoovuTrackerOnce(onReady) { const existing = document.querySelector('script[src*="zoovu-tracker.min.js"]'); if (existing) { if (typeof onReady === "function") onReady(); return; } const s = document.createElement("script"); s.src = "https://cdn.search.zoovu.com/zoovu-tracker.min.js"; s.defer = true; s.onload = function () { if (typeof onReady === "function") onReady(); }; document.head.appendChild(s); } function main() { const consent = hasPerformanceConsent(); if (consent) { if (isZoovuTrackerScriptPresent()) { callEnableTrackingSafely(); return; } loadZoovuTrackerOnce(function () { callEnableTrackingSafely(); }); } else { callDisableTrackingSafely(); } } function onOneTrustClick(e) { const t = e && e.target; if (!t || !t.closest) return; const acceptBanner = t.closest("#onetrust-accept-btn-handler"); const rejectBanner = t.closest("#onetrust-reject-all-handler"); const allowAllPc = t.closest("#accept-recommended-btn-handler"); const confirmChoices = t.closest(".save-preference-btn-handler.onetrust-close-btn-handler"); if (acceptBanner || rejectBanner || allowAllPc || confirmChoices) { setTimeout(main, 150); } } /* ========================= 2) URL helpers + projectId ========================= */ function detectEnv(host) { if (!host) return "prod"; const h = host.toLowerCase(); return h.startsWith("test-") || h.startsWith("test.") || h.includes("test-shop") || h.includes(".stg.") || h.includes("-stg.") || h.includes(".stage.") ? "test" : "prod"; } function detectBrand(pathname) { const parts = (pathname || "/").split("/").filter(Boolean); const seg = (parts[0] || "").toLowerCase(); if (seg === "medentika") return "medentika"; if (seg === "neodent") return "neodent"; return "straumann"; } function detectLocale(pathname) { const parts = (pathname || "/").split("/").filter(Boolean); let i = 0; const seg0 = (parts[0] || "").toLowerCase(); if (seg0 === "medentika" || seg0 === "neodent") i = 1; const maybeDist = (parts[i] || "").toLowerCase(); if (maybeDist === "dist") { const a = (parts[i] || "").toLowerCase(); const b = (parts[i + 1] || "").toLowerCase(); const c = (parts[i + 2] || "").toLowerCase(); const distLocale = [a, b, c].filter(Boolean).join("_"); return distLocale || null; } const loc = (parts[i + 1] || "").toLowerCase(); if (/^[a-z]{2}_[a-z]{2}$/i.test(loc)) return loc; for (let p of parts) { const s = (p || "").toLowerCase(); if (/^[a-z]{2}_[a-z]{2}$/i.test(s)) return s; } return null; } function makeKey(env, brand, locale) { return `${(env || "").toLowerCase()}|${(brand || "").toLowerCase()}|${(locale || "").toLowerCase()}`; } const PROJECT_MAP = { // ---------- Neodent Test ---------- "test|neodent|fr_ca": 46115, "test|neodent|en_za": 46117, "test|neodent|en_us": 46119, "test|neodent|de_de": 46121, "test|neodent|en_ca": 46123, "test|neodent|en_be": 46125, "test|neodent|fr_fr": 46127, "test|neodent|fr_be": 46128, "test|neodent|de_ch": 46129, "test|neodent|en_fi": 46130, "test|neodent|en_nz": 46131, "test|neodent|en_se": 46132, "test|neodent|de_at": 46133, "test|neodent|en_uk": 46136, "test|neodent|en_dk": 46139, "test|neodent|fr_ch": 46141, "test|neodent|en_nl": 46143, "test|neodent|en_au": 46144, "test|neodent|en_no": 46146, "test|neodent|en_hu": 46148, "test|neodent|en_ro": 46150, "test|neodent|es_pe": 46152, "test|neodent|pt_pt": 46154, "test|neodent|es_es": 46156, "test|neodent|it_it": 46158, "test|neodent|es_ar": 46160, "test|neodent|es_mx": 46162, "test|neodent|it_ch": 46164, "test|neodent|es_cl": 46166, "test|neodent|es_co": 46168, "test|neodent|pt_br": 46170, "test|neodent|hu_hu": 53982, // ---------- Straumann Test ---------- "test|straumann|fr_ca": 29029, "test|straumann|en_za": 29672, "test|straumann|en_us": 29026, "test|straumann|de_de": 29027, "test|straumann|en_ca": 29028, "test|straumann|en_be": 29835, "test|straumann|fr_fr": 29825, "test|straumann|fr_be": 29836, "test|straumann|de_ch": 29830, "test|straumann|en_fi": 29839, "test|straumann|en_kr": 29840, "test|straumann|en_nz": 29841, "test|straumann|en_se": 29833, "test|straumann|de_at": 29837, "test|straumann|en_uk": 29829, "test|straumann|en_dk": 29838, "test|straumann|en_jp": 29824, "test|straumann|fr_ch": 29831, "test|straumann|en_nl": 29832, "test|straumann|en_au": 30796, "test|straumann|en_no": 30803, "test|straumann|en_ie": 30798, "test|straumann|en_hu": 30804, "test|straumann|en_ro": 30795, "test|straumann|en_sg": 30797, "test|straumann|es_pe": 31871, "test|straumann|pt_pt": 31865, "test|straumann|es_es": 31863, "test|straumann|it_it": 31864, "test|straumann|es_ar": 31867, "test|straumann|es_mx": 31870, "test|straumann|it_ch": 31862, "test|straumann|es_cl": 31868, "test|straumann|es_co": 31869, "test|straumann|pt_br": 31872, "test|straumann|ko_kr": 31866, "test|straumann|jp_jp": 41000, "test|straumann|hu_hu": 53980, // ---------- Neodent Production ---------- "prod|neodent|es_ar": 46089, "prod|neodent|de_at": 46090, "prod|neodent|en_au": 46091, "prod|neodent|en_be": 46092, "prod|neodent|fr_be": 46093, "prod|neodent|pt_br": 46094, "prod|neodent|en_ca": 46095, "prod|neodent|fr_ca": 46096, "prod|neodent|es_cl": 46097, "prod|neodent|es_co": 46098, "prod|neodent|en_dk": 46099, "prod|neodent|en_fi": 46100, "prod|neodent|fr_fr": 46101, "prod|neodent|de_de": 46102, "prod|neodent|en_hu": 46103, "prod|neodent|it_it": 46104, "prod|neodent|es_mx": 46105, "prod|neodent|en_nz": 46106, "prod|neodent|en_no": 46107, "prod|neodent|es_pe": 46108, "prod|neodent|pt_pt": 46109, "prod|neodent|en_ro": 46110, "prod|neodent|es_es": 46111, "prod|neodent|en_za": 46112, "prod|neodent|en_se": 46113, "prod|neodent|de_ch": 46116, "prod|neodent|fr_ch": 46118, "prod|neodent|it_ch": 46120, "prod|neodent|en_nl": 46122, "prod|neodent|en_uk": 46124, "prod|neodent|en_us": 46126, "prod|neodent|hu_hu": 53981, // ---------- Medentika Test ---------- "test|medentika|de_at": 46138, "test|medentika|en_au": 46140, "test|medentika|en_be": 46142, "test|medentika|fr_be": 46145, "test|medentika|en_dk": 46147, "test|medentika|en_fi": 46149, "test|medentika|fr_fr": 46151, "test|medentika|de_de": 46153, "test|medentika|it_it": 46155, "test|medentika|en_nz": 46157, "test|medentika|en_no": 46159, "test|medentika|pt_pt": 46161, "test|medentika|es_es": 46163, "test|medentika|en_za": 46165, "test|medentika|en_se": 46167, "test|medentika|en_nl": 46169, "test|medentika|en_uk": 46171, "test|medentika|en_us": 46172, "test|medentika|dist_en_di": 52689, "test|medentika|dist_es_di": 52690, "test|medentika|dist_fr_di": 52691, // ---------- Straumann Production ---------- "prod|straumann|de_de": 29354, "prod|straumann|en_ca": 29355, "prod|straumann|fr_ca": 29356, "prod|straumann|en_us": 29353, "prod|straumann|en_be": 29851, "prod|straumann|fr_be": 29852, "prod|straumann|en_kr": 29856, "prod|straumann|en_za": 29858, "prod|straumann|fr_fr": 29843, "prod|straumann|en_uk": 29844, "prod|straumann|de_at": 29853, "prod|straumann|en_se": 29850, "prod|straumann|en_nz": 29857, "prod|straumann|de_ch": 29845, "prod|straumann|en_nl": 29849, "prod|straumann|en_dk": 29854, "prod|straumann|en_fi": 29855, "prod|straumann|fr_ch": 29847, "prod|straumann|en_jp": 29842, "prod|straumann|en_ro": 30799, "prod|straumann|en_sg": 30801, "prod|straumann|en_ie": 30802, "prod|straumann|en_no": 30743, "prod|straumann|en_hu": 30744, "prod|straumann|en_au": 30800, "prod|straumann|es_co": 31858, "prod|straumann|es_mx": 31859, "prod|straumann|it_ch": 31851, "prod|straumann|es_es": 31852, "prod|straumann|es_pe": 31860, "prod|straumann|pt_pt": 31854, "prod|straumann|ko_kr": 31855, "prod|straumann|es_ar": 31856, "prod|straumann|es_cl": 31857, "prod|straumann|pt_br": 31861, "prod|straumann|it_it": 31853, "prod|straumann|hu_hu": 53979, // ---------- Medentika Production ---------- "prod|medentika|de_at": 46176, "prod|medentika|en_au": 46177, "prod|medentika|en_be": 46178, "prod|medentika|fr_be": 46179, "prod|medentika|en_dk": 46180, "prod|medentika|en_nl": 46181, "prod|medentika|en_fi": 46182, "prod|medentika|fr_fr": 46183, "prod|medentika|de_de": 46184, "prod|medentika|it_it": 46185, "prod|medentika|en_nz": 46186, "prod|medentika|en_no": 46187, "prod|medentika|pt_pt": 46188, "prod|medentika|es_es": 46189, "prod|medentika|en_za": 46190, "prod|medentika|en_se": 46191, "prod|medentika|en_uk": 46192, "prod|medentika|en_us": 46193, "prod|medentika|dist_en_di": 52697, "prod|medentika|dist_fr_di": 52698, "prod|medentika|dist_es_di": 52699, }; function resolveProjectIdFromUrl() { const env = detectEnv(window.location.hostname); const brand = detectBrand(window.location.pathname); const locale = detectLocale(window.location.pathname); const key = makeKey(env, brand, locale); const resolved = PROJECT_MAP[key]; const projectId = String(resolved || "30743"); if (!resolved) { try { console.warn("[ZoovuTracking] No projectId for key:", key, { env, brand, locale, href: location.href }); } catch (e) {} } return { key, projectId }; } /* ========================================================= 3) Config builder (siteId is dynamic) - Unified identifier + articleNumber - Normalized counts to numbers - Added rebindStrategy: ["conditionChange"] for searchResults ========================================================= */ function getCurrencyIso() { try { return String(document.documentElement.getAttribute("data-currency-iso-code") || "").trim() || "USD"; } catch (e) { return "USD"; } } function ensureInterfaceConfig() { const cfg = window.zoovuSearchTrackingConfiguration || (window.zoovuSearchTrackingConfiguration = {}); cfg.interface = cfg.interface || {}; cfg.baseUrl = "https://api.search.zoovu.com/insights"; cfg.interface.searchBox = cfg.interface.searchBox || { selector: 'input[aria-label="search input"]', initializationStrategy: { type: "observer", wrapper: "body" }, }; cfg.interface.searchSuggestions = cfg.interface.searchSuggestions || { blockSelector: '[data-testid="product-search-suggestions-popup"]', itemSelector: ".stg-trackable-product-card", activeItemSelector: ".stg-trackable-product-card", initializationStrategy: { type: "observer", wrapper: "body" }, addToCartTrigger: 'button[data-testid^="product-button-"][data-testid$="-addToCart"]', articleNumberSource: { type: "getter", getter: function (result) { return result && result.getAttribute ? result.getAttribute("data-tracking-id") : ""; }, }, identifierSource: { type: "getter", getter: function (result) { return result && result.getAttribute ? result.getAttribute("data-tracking-id") : ""; }, }, linkSource: { type: "getter", getter: function (result) { const a = result && result.querySelector ? result.querySelector('a[href*="/p/"]') : null; return a ? a.getAttribute("href") : ""; }, }, priceSource: { type: "getter", getter: function (result) { const el = result && result.querySelector ? result.querySelector('label[data-testid^="component-price-"][data-testid$="-current-price"]') : null; return el ? (el.textContent || "").trim() : ""; }, }, priceUnitSource: { type: "getter", getter: function () { return getCurrencyIso(); }, }, countSource: { type: "getter", getter: function (result) { const input = result && result.querySelector ? result.querySelector('div[data-testid$="-product-quantity-input"] input') : null; const raw = input ? input.value || input.getAttribute("value") || "1" : "1"; const n = parseInt(String(raw || "1"), 10); return Number.isFinite(n) && n > 0 ? n : 1; }, }, }; cfg.interface.searchResults = cfg.interface.searchResults || { blockSelector: "main", rebindStrategy: ["conditionChange"], itemSelector: ".stg-trackable-product-card", initializationStrategy: { type: "observer", wrapper: "body" }, expectedConditions: { urlRegex: "/search", searchParam: "q", domElement: '*[data-testid="shop-section-empty-search-section"],main .stg-trackable-product-card a[href*="/p/"]', }, querySource: { type: "getter", getter: function () { let q = new URLSearchParams(window.location.search).get("q") || ""; q = q.replace(/\+/g, " ").trim(); q = q.replace(/:(?=(?:relevance-(?:asc|desc)|price-(?:asc|desc)|\d)).*$/i, ""); return q.trim(); }, }, addToCartTrigger: 'button[data-testid^="product-button-"][data-testid$="-addToCart"]', articleNumberSource: { type: "getter", getter: function (result) { return result && result.getAttribute ? result.getAttribute("data-tracking-id") : ""; }, }, identifierSource: { type: "getter", getter: function (result) { return result && result.getAttribute ? result.getAttribute("data-tracking-id") : ""; }, }, linkSource: { type: "getter", getter: function (result) { const a = result && result.querySelector ? result.querySelector('a[href*="/p/"]') : null; return a ? a.getAttribute("href") : ""; }, }, priceSource: { type: "getter", getter: function (result) { const el = result && result.querySelector ? result.querySelector('label[data-testid^="component-price-"][data-testid$="-current-price"]') : null; return el ? (el.textContent || "").trim() : ""; }, }, priceUnitSource: { type: "getter", getter: function () { return getCurrencyIso(); }, }, countSource: { type: "getter", getter: function (result) { const input = result && result.querySelector ? result.querySelector('div[data-testid$="-product-quantity-input"] input') : null; const raw = input ? input.value || input.getAttribute("value") || "1" : "1"; const n = parseInt(String(raw || "1"), 10); return Number.isFinite(n) && n > 0 ? n : 1; }, }, }; cfg.interface.productDetailPage = cfg.interface.productDetailPage || { initializationStrategy: { type: "observer", wrapper: "main" }, trigger: '[data-testid="product-section-information"] button[data-testid^="product-button-"][data-testid$="-addToCart"]', expectedConditions: { urlRegex: "/p/", domElement: '[data-testid="product-section-information"] [data-testid="product-typography-product-code"]', }, articleNumberSource: { type: "domElement", selector: '[data-testid="product-section-information"] label[data-testid="product-typography-product-code"]', source: "text", }, identifierSource: { type: "domElement", selector: '[data-testid="product-section-information"] label[data-testid="product-typography-product-code"]', source: "text", }, priceSource: { type: "domElement", selector: '[data-testid="product-section-information"] [data-testid^="component-price-"][data-testid$="-current-price"]', source: "text", }, priceUnitSource: { type: "getter", getter: function () { return getCurrencyIso(); }, }, countSource: { type: "domElement", selector: '[data-testid="product-section-information"] [data-testid$="-product-quantity-input"] input', source: "@value", }, }; cfg.interface.checkout = { initializationStrategy: { type: "observer", wrapper: "body" }, expectedConditions: { urlRegex: "\\/(cart|checkout\\/(payment-type|confirmation|orderConfirmation))(\\/|\\?|$)" }, trigger: '[data-testid="checkout-section-main-payment-type"] button[data-testid="checkout-button-next"]', itemSource: { selector: '[data-testid^="checkout-card-cart-entry-"]', articleNumberSource: { type: "domElement", selector: 'label[data-testid*="checkout-card-product-label-"]', source: "text", }, identifierSource: { type: "domElement", selector: 'label[data-testid*="checkout-card-product-label-"]', source: "text", }, linkSource: { type: "getter", getter: function (root) { var a = root.querySelector('a[data-testid^="cart-link-product-name-"]'); return a ? a.href : ""; }, }, priceSource: { type: "domElement", selector: 'label[data-testid$="-total-price"]', source: "text", }, priceUnitSource: { type: "getter", getter: function () { return getCurrencyIso(); }, }, countSource: { type: "getter", getter: function (root) { try { var el = root && root.querySelector ? root.querySelector('[data-testid^="checkout-typography-unit-quantity-"]') : null; var txt = el ? (el.textContent || "").trim() : ""; var n = parseInt(txt, 10); return Number.isFinite(n) && n > 0 ? n : 1; } catch (e) { return 1; } }, }, }, }; } /* ========================================================= 4) Re-init on projectId change (SPA locale switch) ========================================================= */ function removeZoovuTrackerScriptAndGlobals() { const scripts = document.querySelectorAll('script[src*="zoovu-tracker.min.js"]'); scripts.forEach((s) => { try { if (s && s.parentNode) s.parentNode.removeChild(s); } catch (e) {} }); try { delete window.ZoovuSearchInsightsTracker; } catch (e) { window.ZoovuSearchInsightsTracker = undefined; } try { delete window.zoovuSearchInsightsTracker; } catch (e) { window.zoovuSearchInsightsTracker = undefined; } } function loadZoovuTrackerFresh(onReady) { const s = document.createElement("script"); s.src = "https://cdn.search-studio.zoovu.com/zoovu-tracker.min.js"; s.defer = true; s.onload = function () { if (typeof onReady === "function") onReady(); }; document.head.appendChild(s); } let __currentProjectId = null; let __reinitInProgress = false; function applyProjectIdAndReinitIfChanged() { if (__reinitInProgress) return; ensureInterfaceConfig(); const cfg = window.zoovuSearchTrackingConfiguration || (window.zoovuSearchTrackingConfiguration = {}); const resolved = resolveProjectIdFromUrl(); const nextProjectId = resolved.projectId; cfg.addSiteToCookie = true; // Update the global used by the network guard window.__ZOOVU_TRACKING_CURRENT_PROJECT_ID__ = nextProjectId; cfg.siteId = nextProjectId; if (__currentProjectId === null) __currentProjectId = nextProjectId; if (__currentProjectId === nextProjectId) return; __currentProjectId = nextProjectId; if (!hasPerformanceConsent()) { callDisableTrackingSafely(); return; } __reinitInProgress = true; callDisableTrackingSafely(); setTimeout(function () { removeZoovuTrackerScriptAndGlobals(); loadZoovuTrackerFresh(function () { callEnableTrackingSafely(); __reinitInProgress = false; }); }, 50); } function onSpaUrlChange() { applyProjectIdAndReinitIfChanged(); } // Init once ensureInterfaceConfig(); applyProjectIdAndReinitIfChanged(); main(); // OneTrust hooks window.addEventListener("OneTrustGroupsUpdated", main); document.addEventListener("OneTrustGroupsUpdated", main); const __existingOptanonWrapper = window.OptanonWrapper; window.OptanonWrapper = function () { try { if (typeof __existingOptanonWrapper === "function") __existingOptanonWrapper(); } catch (e) {} main(); }; window.addEventListener("click", onOneTrustClick, true); // SPA URL change detection (history + popstate) plus a poll fallback try { if (!history.__zoovuWrapped__) { history.__zoovuWrapped__ = true; const _pushState = history.pushState; history.pushState = function () { const ret = _pushState.apply(this, arguments); onSpaUrlChange(); return ret; }; const _replaceState = history.replaceState; history.replaceState = function () { const ret = _replaceState.apply(this, arguments); onSpaUrlChange(); return ret; }; window.addEventListener("popstate", onSpaUrlChange); } } catch (e) {} let __lastHref = location.href; setInterval(function () { if (location.href === __lastHref) return; __lastHref = location.href; onSpaUrlChange(); }, 1000); })();}; const url = 'https://queue-propagator.zoovu.com'; const currentEnvironment = 'orca'; const currentAccountId = 250000008; const currencies = ['FJD', 'STD', 'MXN', 'LVL', 'SCR', 'CDF', 'BBD', 'UGX', 'HNL', 'MXV', 'ZAR', 'STN', 'CUC', 'BSD', 'SDD', 'SDG', 'IQD', 'CUP', 'GMD', 'TWD', 'RSD', 'UYI', 'MYR', 'FKP', 'XOF', 'UYU', 'CVE', 'OMR', 'SEK', 'KES', 'BTN', 'GNF', 'MZN', 'MZM', 'SVC', 'ARS', 'QAR', 'NLG', 'IRR', 'XPD', 'THB', 'XPF', 'UZS', 'BDT', 'LYD', 'KWD', 'XPT', 'RUB', 'ISK', 'BEF', 'MKD', 'RUR', 'DZD', 'PAB', 'SGD', 'KGS', 'XAD', 'XAF', 'XAG', 'ITL', 'CHF', 'HRK', 'ATS', 'DJF', 'CHE', 'TZS', 'ADP', 'XAU', 'VND', 'AUD', 'CHW', 'KHR', 'IDR', 'XBA', 'KYD', 'XBC', 'XBB', 'SHP', 'BWP', 'XBD', 'CYP', 'TJS', 'RWF', 'AED', 'DKK', 'BGL', 'ZWD', 'BGN', 'MMK', 'NOK', 'ZWG', 'SYP', 'ZWL', 'YUM', 'ZWN', 'LKR', 'ZWR', 'CZK', 'IEP', 'GRD', 'XCD', 'HTG', 'XSU', 'AFA', 'XCG', 'BHD', 'SIT', 'PTE', 'SZL', 'KZT', 'YER', 'AFN', 'BYB', 'AWG', 'NPR', 'MNT', 'GBP', 'XTS', 'BYN', 'HUF', 'BYR', 'BIF', 'XUA', 'XDR', 'BZD', 'MOP', 'NAD', 'SKK', 'TMM', 'PEN', 'WST', 'TMT', 'FRF', 'CLF', 'GTQ', 'CLP', 'TND', 'SLE', 'SLL', 'AYM', 'XFO', 'DOP', 'KMF', 'XFU', 'GEL', 'MAD', 'AZM', 'TOP', 'PGK', 'AZN', 'UAH', 'ERN', 'TPE', 'MRO', 'CNY', 'MRU', 'BMD', 'PHP', 'XXX', 'PYG', 'JMD', 'GWP', 'ESP', 'COP', 'USD', 'COU', 'USN', 'ETB', 'VEB', 'USS', 'VED', 'VUV', 'SOS', 'VEF', 'LAK', 'ZMK', 'BND', 'LRD', 'ALL', 'GHC', 'MTL', 'VES', 'ZMW', 'TRL', 'ILS', 'KPW', 'GHS', 'GYD', 'MDL', 'BOB', 'AMD', 'TRY', 'LBP', 'JOD', 'HKD', 'EUR', 'LSL', 'CAD', 'BOV', 'EEK', 'MUR', 'ROL', 'GIP', 'RON', 'NGN', 'CRC', 'PKR', 'ANG', 'SRD', 'SAR', 'LTL', 'TTD', 'MVR', 'SRG', 'INR', 'KRW', 'JPY', 'PLN', 'AOA', 'SBD', 'CSD', 'LUF', 'MWK', 'MGA', 'FIM', 'MGF', 'DEM', 'BAM', 'EGP', 'SSP', 'NIO', 'NZD', 'BRL'] // ------------------------- API ------------------------- let trackingEnabled = true; let hasLauncher = typeof advancedCode === 'function' && advancedCode.toString().includes('/behavioral-launchers-script'); let signalReady = false; let signalReadyPromise = null; let signalCheckAttempts = 0; const MAX_SIGNAL_CHECK_ATTEMPTS = 20; // 20 attempts const SIGNAL_CHECK_INTERVAL_MS = 200; // Check every 200ms (total 4 seconds max wait) let eventQueue = []; function disableTracking() { trackingEnabled = false; } function enableTracking() { trackingEnabled = true; } function getNumericPriceFromString(stringPrice) { const parsedPrice = stringPrice .replace(/\.$/, '') .replace(/([^.',\s\d])*/g, '') .replace(/([.',\s](?=\d{3}))/g, '') .replace(/([.',](?=\d{2}))/g, '.'); return Number(parsedPrice); } // helper for loading launcher from advancedmode function loadLauncher(launcherUrl, callback, id) { if (launcherUrl) { const script = document.createElement('script'); script.type = 'text/javascript'; script.src = launcherUrl; script.id = id || 'zv-launcher'; script.onload = callback; document.head.appendChild(script); } } function setSignalReady() { signalReady = true; if (eventQueue.length > 0) { console.debug(`Signal ready - flushing ${eventQueue.length} queued events`); const eventsToFlush = [...eventQueue]; eventQueue = []; eventsToFlush.forEach(queuedEvent => { sendEventImmediately(queuedEvent); }); } } if (typeof window !== 'undefined') { if (typeof window.ZoovuTrackingManager === 'undefined') { window.ZoovuTrackingManager = {}; } window.ZoovuTrackingManager.setSignalReady = setSignalReady; } class TrackingExecutionError extends Error { constructor(message, trackingEntityType, variableId, triggerId, scriptId, trackingErrorType) { super(message); this.trackingEntityType = trackingEntityType; this.variableId = variableId; this.triggerId = triggerId; this.scriptId = scriptId; this.trackingErrorType = trackingErrorType; } } // ------------------------- helpers ------------------------- const eventTypes = Object.freeze({ PAGE_VISITED: "PAGE_VISITED", ADD_TO_CART: "ADD_TO_CART", REMOVE_FROM_CART: "REMOVE_FROM_CART", PDP_VISITED: "PDP_VISITED", PURCHASED: "PURCHASED", UPDATE_CART: "UPDATE_CART", DECLINE_TRACKING: "DECLINE_TRACKING", SEARCH: "SEARCH", SEARCH_RESULT: "SEARCH_RESULT", CLICKOUT: "CLICKOUT", LEAD_GEN: "LEAD_GEN" }); const trackingEventTypes = Object.freeze({ TRACKING_SUCCESSFUL_EXECUTION: 'TRACKING_SUCCESSFUL_EXECUTION', TRACKING_EXECUTION_FAILURE: 'TRACKING_EXECUTION_FAILURE' }) const actionTypes = Object.freeze({ ...eventTypes, CUSTOM: 'CUSTOM' }) const triggerTypes = Object.freeze({ CLICK: 'CLICK', INPUT: 'INPUT', PAGE_LOAD: 'PAGE_LOAD' }); const variableTypes = Object.freeze({ FUNCTION: 'FUNCTION', CONSTANT: 'CONSTANT' }); const trackingEntityTypes = Object.freeze({ VARIABLE: 'VARIABLE', TRIGGER: 'TRIGGER', CUSTOM_ACTION: 'CUSTOM_ACTION' }); const trackingErrorTypes = Object.freeze({ MISSING_VALUE: 'MISSING_VALUE', TYPE_MISMATCH: 'TYPE_MISMATCH', UNSUPPORTED_VARIABLE: 'UNSUPPORTED_VARIABLE', MISSING_VARIABLE: 'MISSING_VARIABLE', OTHER: 'OTHER' }); const trackingVariableScopes = Object.freeze({ GLOBAL: 'GLOBAL', LOCAL: 'LOCAL' }); const namedReferrals = Object.freeze({ SEARCH: 'SEARCH' }) const getElementAttribute = (target, attributeName) => { const attribute = target[attributeName]; if (typeof attribute === 'string') { return attribute; } if (attribute && attribute.baseVal !== undefined) { return attribute.baseVal; } return target.getAttribute(attributeName === 'className' ? 'class' : attributeName) || ''; }; const getElementId = (target) => { return getElementAttribute(target, 'id'); }; const getElementClassName = (target) => { return getElementAttribute(target, 'className'); }; const matchers = Object.freeze({ EQUALS: (a, b) => a === b, DOES_NOT_EQUAL: (a, b) => a !== b, CONTAINS: (a, b) => a.indexOf(b) >= 0, DOES_NOT_CONTAIN: (a, b) => a.indexOf(b) === -1, GREATER_THAN: (a, b) => a > b, LESS_THAN: (a, b) => a < b, GREATER_THAN_OR_EQUAL: (a, b) => a >= b, LESS_THAN_OR_EQUAL: (a, b) => a <= b, MATCHES_CSS_SELECTOR: (target, test) => target.matches(test), MATCHES_ID: (target, test) => getElementId(target) === test, CONTAINS_ID: (target, test) => getElementId(target).includes(test), DOES_NOT_CONTAIN_ID: (target, test) => !getElementId(target).includes(test), MATCHES_CLASS: (target, test) => getElementClassName(target) === test, CONTAINS_CLASS: (target, test) => getElementClassName(target).includes(test), DOES_NOT_CONTAIN_CLASS: (target, test) => !getElementClassName(target).includes(test), MATCHES_REGEX: (a, b) => parseRegexString(b).test(a), }); const trackingFieldName = `${domainId}_${zoovuId}_trackingExecutions` const MAX_RECORDS = 100 // helper to retrieve variableId later let eventFields = [] if (!JSON.parse(localStorage.getItem(trackingFieldName))) { localStorage.setItem(trackingFieldName, JSON.stringify([])) } function parseRegexString(input) { const trimmed = input.trim(); // check if it starts and ends with slashes (e.g., /^abc$/gi) const match = trimmed.match(/^\/(.+)\/([a-z]*)$/i); if (match) { const pattern = match[1]; const flags = match[2]; return new RegExp(pattern, flags); } // no flags return new RegExp(trimmed); } function checkValue(type, value) { switch (type) { case 'BOOLEAN': return value === true || value === 'true'; case 'TEXT': return value; case 'DECIMAL': return typeof value === 'string' ? parseFloat(value) : value; case 'INTEGER': return typeof value === 'string' ? parseInt(value) : value; default: return value; } } function cast(type, value) { switch (type) { case 'BOOLEAN': return value === true || value === 'true'; case 'TEXT': return value; case 'DECIMAL': return typeof value === 'string' ? parseFloat(value) : value; case 'INTEGER': return typeof value === 'string' ? parseInt(value) : value; default: return value; } } function debounce(fn, delay, useTrailing = false) { if (useTrailing) { // Trailing debounce for INPUT triggers - only fires once after delay using requestAnimationFrame let lastCallTime = 0; let animationFrameId = null; let hasScheduledExecution = false; function checkAndExecute(context, args) { const currentTime = performance.now(); const timeSinceLastCall = currentTime - lastCallTime; if (timeSinceLastCall >= delay) { // Delay has passed, execute the function fn.apply(context, args); hasScheduledExecution = false; } else { // Keep checking until delay has passed animationFrameId = requestAnimationFrame(() => { checkAndExecute(context, args); }); } } return function () { const context = this, args = arguments; lastCallTime = performance.now(); if (!hasScheduledExecution) { hasScheduledExecution = true; animationFrameId = requestAnimationFrame(() => { checkAndExecute(context, args); }); } }; } // Original requestAnimationFrame-based debounce for other triggers let lastCallTime = 0; let animationFrameId = null; let isScheduled = false; function execute(context, args, currentTime) { if (currentTime - lastCallTime >= delay) { fn.apply(context, args); isScheduled = false; } else { animationFrameId = requestAnimationFrame((newTime) => { execute(context, args, newTime); }); } } return function () { const context = this, args = arguments; lastCallTime = performance.now(); if (!isScheduled) { isScheduled = true; animationFrameId = requestAnimationFrame((currentTime) => { execute(context, args, currentTime); }); } }; } function getOrganization() { return zoovuId; } function getDomainId() { return domainId; } function getTimezone() { return Intl.DateTimeFormat().resolvedOptions().timeZone; } function getReferrer() { const referrer = document.referrer; if (/^(https?|android-app):\/\//i.test(referrer)) { return referrer; } else { return undefined; } } function getPath() { return window.location.href.replace(window.location.origin, ''); } function getCookieValue(cookieName) { const cookies = document.cookie.split('; '); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].split('='); if (cookie[0] === cookieName) { return cookie[1]; } } return null; } function generateUuid() { return self.crypto.randomUUID(); } function addZoovuCidToCookies() { const uuid = generateUuid(); const hostname = window.location.hostname; const domain = hostname.split('.').slice(-2).join('.'); const zoovuCid = `zoovu-cid=${uuid}; path=/; domain=.${domain}`; document.cookie = zoovuCid; return uuid; } function getCID() { return getCookieValue(`zoovu-cid`) !== null ? getCookieValue(`zoovu-cid`) : addZoovuCidToCookies(); } function getEnvironment() { return currentEnvironment; } function getAccountId() { return currentAccountId; } function getPropertyTypeError(propertyName, expectedType, currentType) { return `TYPE MISMATCH: ${propertyName}: should be ${expectedType} but is ${currentType}` } function typeCheckValue(value, variableType, propertyName, variableId, triggerId) { let errorMessage = ''; if (value === undefined || value === null) { throw new TrackingExecutionError(`${propertyName}: value is not defined.`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.MISSING_VALUE) } switch (variableType) { case "TEXT": if (typeof value !== 'string') errorMessage = getPropertyTypeError(propertyName, 'string', typeof value); break; case "INTEGER": case "DECIMAL": if (typeof value !== 'number') errorMessage = getPropertyTypeError(propertyName, 'number', typeof value); break; case "BOOLEAN": if (typeof value !== 'boolean') errorMessage = getPropertyTypeError(propertyName, 'boolean', typeof value); break; case 'LIST': if (!Array.isArray(value)) errorMessage = getPropertyTypeError(propertyName, 'list', typeof value); break; case 'EVENT': break; default: throw new TrackingExecutionError(`Not supported variable type: ${variableType}`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.UNSUPPORTED_VARIABLE) } if (errorMessage) { throw new TrackingExecutionError(errorMessage, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.TYPE_MISMATCH) } } function ensureRequiredFieldsPresent(fields, eventType) { return Object.entries(fields).every(([key, value]) => { if (!value) { const variableId = eventFields.find(field => field.fieldName === key).variableId; // empty eventFields eventFields = []; throw new TrackingExecutionError( `Required property ${key} missing value for ${eventType}.`, trackingEntityTypes.VARIABLE, variableId, null, script.id, trackingErrorTypes.MISSING_VALUE ) } return true; }); } function getBaseEventBody(eventType) { return { origin: 'CLIENT', queryParams: {}, organization: getOrganization(), domainId: getDomainId(), path: getPath(), referrer: getReferrer(), cid: getCID(), timezone: getTimezone(), // dynamic eventType: eventType ?? '', eventLabel: '', }; } function getEventExecutable(actionType) { switch (actionType) { case actionTypes.PAGE_VISITED: return sendPageVisitedEvent; case actionTypes.ADD_TO_CART: return sendAddToCartEvent; case actionTypes.CLICKOUT: return sendClickoutEvent; case actionTypes.REMOVE_FROM_CART: return sendRemoveFromCartEvent; case actionTypes.PDP_VISITED: return sendPdpVisitedEvent; case actionTypes.PURCHASED: return sendPurchaseEvent; case actionTypes.DECLINE_TRACKING: return sendDeclineTrackingEvent; case actionTypes.UPDATE_CART: return sendUpdateCartEvent; case actionTypes.SEARCH: return sendSearchEvent; case actionTypes.SEARCH_RESULT: return sendSearchResultEvent; case actionTypes.LEAD_GEN: return sendLeadGenEvent; } } function checkSpecificFieldValues(fieldName, value, variableId) { if (fieldName === 'currencyCode' && !currencies.includes(value)) { throw new TrackingExecutionError( `Value ${value} is not compatible with currencyCode.`, trackingEntityTypes.VARIABLE, variableId, null, script.id, trackingErrorTypes.OTHER ) } if (fieldName === 'namedReferral' && !namedReferrals[value.toUpperCase()]) { throw new TrackingExecutionError( `Value ${value} is not compatible with namedReferral.`, trackingEntityTypes.VARIABLE, variableId, null, script.id, trackingErrorTypes.OTHER ) } } function getVariableValueById(variableId, event, fieldName, triggerId) { const foundVariable = variables.find(v => v.id === variableId); let variableValue = null; if (foundVariable) { if (foundVariable.type === variableTypes.CONSTANT) { variableValue = cast(foundVariable.valueType, foundVariable.value); } else if (foundVariable.type === variableTypes.FUNCTION && typeof foundVariable.function === 'function') { try { variableValue = foundVariable.function(event); } catch (e) { throw new TrackingExecutionError( e.message, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.OTHER ) } } else { throw new TrackingExecutionError( `${foundVariable.type} is not supported.`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.UNSUPPORTED_VARIABLE ) } typeCheckValue(variableValue, foundVariable.valueType, fieldName ? fieldName : foundVariable.name, variableId, triggerId); if (fieldName === 'currencyCode' || fieldName === 'namedReferral') { checkSpecificFieldValues(fieldName, variableValue, variableId) } return variableValue; } else { throw new TrackingExecutionError( `No variable with ID ${variableId}`, trackingEntityTypes.VARIABLE, variableId, triggerId, script.id, trackingErrorTypes.MISSING_VARIABLE ) } } function evaluateSingleTriggerRow(row, event, triggerId) { const target = getVariableValueById(row.variableId, event, undefined, triggerId); const matcher = matchers[row.operator]; checkValue(row.valueType, row.value); const value = cast(row.valueType, row.value); if (matcher && typeof matcher === 'function') { return matcher(target, value); } return false; } function preparePayloadFromFields(fields, event) { const payload = {}; fields.forEach(f => { payload[f.fieldName] = getVariableValueById(f.variableId, event, f.fieldName) }) return payload; } function evaluateTriggerConditions(trigger, event) { // OR between groups return trigger.groups.some(group => // AND between rows group.rows.every(row => evaluateSingleTriggerRow(row, event, trigger.id))) } function runAction(action, fields, event, trigger) { const trackingExecutions = jsonParser(trackingFieldName) if (action.type === actionTypes.CUSTOM && typeof action.code === 'function') { // add script custom action trackingExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.CUSTOM_ACTION, action.id)) try { action.code(event); } catch (e) { throw new TrackingExecutionError( e.message, trackingEntityTypes.CUSTOM_ACTION, action.id, null, script.id, trackingErrorTypes.OTHER ) } localStorage.setItem(trackingFieldName, JSON.stringify(trackingExecutions)); } else { const sendEventFunction = getEventExecutable(action.type); eventFields = [...fields]; if (sendEventFunction && typeof sendEventFunction === 'function') { const payload = preparePayloadFromFields(fields, event) sendEventFunction(payload); addSuccessfulExecutions(trigger, fields) } } } // Store debounced functions per rule to reuse them const debouncedRuleActions = new Map(); let ruleCounter = 0; function evaluateSingleRule(rule, event) { // Assign unique ID to rule if it doesn't have one if (!rule._uniqueId) { rule._uniqueId = ruleCounter++; } if (evaluateTriggerConditions(rule.trigger, event)) { // Set default delay for INPUT triggers if delayMs is undefined const delay = rule.trigger.type === triggerTypes.INPUT && rule.trigger.delayMs === undefined ? 1500 : rule.trigger.delayMs; if (delay) { // Get or create debounced function for this rule using unique rule ID const ruleKey = rule._uniqueId; if (!debouncedRuleActions.has(ruleKey)) { const useTrailing = rule.trigger.type === triggerTypes.INPUT; const debounced = debounce(function (event) { runAction(rule.action, rule.fields, event, rule.trigger); }, delay, useTrailing); debouncedRuleActions.set(ruleKey, debounced); } debouncedRuleActions.get(ruleKey)(event); } else { runAction(rule.action, rule.fields, event, rule.trigger); } } } function evaluateRules(rules, event) { try { rules.forEach(rule => { evaluateSingleRule(rule, event) }); } catch (error) { const trackingExecutions = jsonParser(trackingFieldName); const {message, trackingEntityType, variableId, triggerId, scriptId, trackingErrorType} = error; const failedExecution = createTrackingFailedExecutionRecord( trackingEntityType, variableId, triggerId, scriptId, trackingErrorType, message ) trackingExecutions.push(failedExecution) localStorage.setItem(trackingFieldName, JSON.stringify(trackingExecutions)); console.debug(error) } } function observeAndReactOnPageChange(callback) { let oldHref; if (oldHref === undefined) { callback(); oldHref = document.location.href; } const body = document.querySelector('body'); const observer = new MutationObserver(() => { if (oldHref !== document.location.href) { oldHref = document.location.href; callback(); } }); observer.observe(body, {childList: true, subtree: true}); } function executeAdvancedCode() { if (typeof advancedCode === 'function') { advancedCode(); } } function jsonParser(key) { const foundValue = JSON.parse(localStorage.getItem(key)) if (!Array.isArray(foundValue)) { throw Error('Invalid value in localStorage for key ' + key) } return foundValue } function createTrackingSuccessfulExecutionRecord(entityType, entityId) { const executionTime = Date.now(); return { organization: getOrganization(), domainId: getDomainId(), eventType: trackingEventTypes.TRACKING_SUCCESSFUL_EXECUTION, trackingEntityType: entityType, trackingEntityId: entityId, executionTime } } function createTrackingFailedExecutionRecord(entityType, entityId, triggerId, scriptId, errorType, message) { const executionTime = Date.now(); return { organization: getOrganization(), domainId: getDomainId(), eventType: trackingEventTypes.TRACKING_EXECUTION_FAILURE, trackingEntityType: entityType, trackingEntityId: entityId, executionTime, triggerId: triggerId || null, scriptId: scriptId || null, error: { type: errorType, message } } } function addSuccessfulExecutions(trigger, fields) { const successfulExecutions = jsonParser(trackingFieldName) // add script trigger successfulExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.TRIGGER, trigger.id)) // add script function variables fields.forEach(field => { const foundVariableInField = variables.find(variable => variable.id === field.variableId) if (foundVariableInField.type === variableTypes.FUNCTION && foundVariableInField.scope === trackingVariableScopes.LOCAL) { successfulExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.VARIABLE, foundVariableInField.id)) } }) // add trigger function variables trigger.groups.forEach(groups => { groups.rows.forEach(row => { const foundVariable = variables.find(variable => variable.id === row.variableId) if (foundVariable.type === variableTypes.FUNCTION && foundVariable.scope === trackingVariableScopes.LOCAL) { successfulExecutions.push(createTrackingSuccessfulExecutionRecord(trackingEntityTypes.VARIABLE, foundVariable.id)) } }) }) localStorage.setItem(trackingFieldName, JSON.stringify(successfulExecutions)); } // ------------------------- events ------------------------- async function sendEventImmediately(body) { if (trackingEnabled) { await fetch(`${url}/fact`, { method: 'POST', mode: 'cors', cache: 'no-cache', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(body), }); } else { console.debug("Tracking disabled - no permission to track"); } } function getSignalReadyPromise() { // Return existing promise if already polling if (signalReadyPromise) { return signalReadyPromise; } // Signal already ready, return resolved promise if (signalReady) { return Promise.resolve(); } // Create new polling promise signalReadyPromise = new Promise((resolve) => { signalCheckAttempts = 0; const checkInterval = setInterval(() => { signalCheckAttempts++; if (signalReady) { clearInterval(checkInterval); signalReadyPromise = null; resolve(); } else if (signalCheckAttempts >= MAX_SIGNAL_CHECK_ATTEMPTS) { clearInterval(checkInterval); signalReadyPromise = null; console.debug('Signal connection timeout - proceeding without signal'); resolve(); } }, SIGNAL_CHECK_INTERVAL_MS); }); return signalReadyPromise; } async function sendEvent(body) { if (trackingEnabled) { // Check if we should wait for signal connection // Only wait if launcher exists (hasLauncher) and signal is not ready yet if (hasLauncher && !signalReady && signalCheckAttempts < MAX_SIGNAL_CHECK_ATTEMPTS) { // Queue the event - don't block, just queue it eventQueue.push(body); // Trigger polling in background (fire and forget) getSignalReadyPromise().then(() => { // Flush happens in setSignalReady, but handle timeout case if (!signalReady && eventQueue.length > 0) { const eventsToFlush = [...eventQueue]; eventQueue = []; eventsToFlush.forEach(queuedEvent => { sendEventImmediately(queuedEvent); }); } }); } else { // Send immediately if no launcher or signal already ready await sendEventImmediately(body); } } else { console.debug("Tracking disabled - no permission to track"); } } async function sendSSTRPurchaseEvent(body) { if (trackingEnabled) { const purchaseUrl = 'https://ev-co.zoovu.com/v1/CollectSalesEvent'; await fetch(purchaseUrl, { method: 'POST', mode: 'cors', cache: 'no-cache', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(body), }); } else { console.debug("Tracking disabled - no permission to track"); } } async function sendTrackingSuccessfulExecution(body) { if (trackingEnabled) { await fetch(`${url}/tracking/executions`, { method: 'POST', mode: 'cors', cache: 'no-cache', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(body), }); } else { console.debug("Tracking disabled - no permission to track"); } } function sendFactEventBase(eventType, eventSpecificBody) { const body = { ...getBaseEventBody(eventType), // event label + custom per-event fields ...eventSpecificBody, }; sendEvent(body); }; function sendPageVisitedEvent({locale, category, eventLabel = 'Page visit'}) { const body = { locale, eventLabel, // optional category, }; if (ensureRequiredFieldsPresent({locale}, eventTypes.PAGE_VISITED)) { sendFactEventBase(eventTypes.PAGE_VISITED, body); } }; function sendPdpVisitedEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Product details page visit", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.PDP_VISITED)) { sendFactEventBase(eventTypes.PDP_VISITED, body); } }; function sendAddToCartEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Add to cart", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.ADD_TO_CART)) { sendFactEventBase(eventTypes.ADD_TO_CART, body); } }; function sendRemoveFromCartEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Remove from cart", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.REMOVE_FROM_CART)) { sendFactEventBase(eventTypes.REMOVE_FROM_CART, body); } }; function sendUpdateCartEvent({ locale, sku, quantity = 1, currencyCode, eventLabel = "Update cart", price, category }) { const body = { locale, sku, eventLabel, // optional category, price, currencyCode, quantity, }; if (ensureRequiredFieldsPresent({locale, sku}, eventTypes.UPDATE_CART)) { sendFactEventBase(eventTypes.UPDATE_CART, body); } }; function sendDeclineTrackingEvent({ locale, eventLabel = "No permission to track", category }) { const body = { locale, eventLabel, // optional category }; if (ensureRequiredFieldsPresent({locale}, eventTypes.DECLINE_TRACKING)) { sendFactEventBase(eventTypes.DECLINE_TRACKING, body); disableTracking(); } }; function sendSearchEvent({ locale, eventLabel = "Search phrase typed", category }) { const body = { locale, eventLabel, // optional category, }; if (ensureRequiredFieldsPresent({locale}, eventTypes.SEARCH)) { sendFactEventBase(eventTypes.SEARCH, body); } }; function sendSearchResultEvent({ locale, isEmpty = false, eventLabel = "Search results", skus, category }) { const body = { locale, isEmpty, skus, eventLabel, // optional category }; if (ensureRequiredFieldsPresent({locale, skus}, eventTypes.SEARCH_RESULT)) { sendFactEventBase(eventTypes.SEARCH_RESULT, body); } }; function sendClickoutEvent({ locale, targetUrl, namedReferral, eventLabel = "Clickout", category }) { const body = { locale, targetUrl, namedReferral, eventLabel, // optional category }; if (ensureRequiredFieldsPresent({locale, targetUrl, namedReferral}, eventTypes.CLICKOUT)) { sendFactEventBase(eventTypes.CLICKOUT, body); } }; function sendPurchaseEvent({ currencyCode, transactionId, products }) { const sstrBody = { transactionId, products, currency: currencyCode, browserTimestamp: Date.now(), env: getEnvironment(), accountId: getAccountId(), clientId: getCID(), }; if (ensureRequiredFieldsPresent({products, currencyCode,}, eventTypes.PURCHASED)) { sendSSTRPurchaseEvent(sstrBody); } }; function sendLeadGenEvent({ locale, currencyCode, products, leadId, leadType, category, eventLabel = "Lead gen sent" }) { const body = { locale, eventLabel, // optional currencyCode, products, leadId, leadType, category }; if (ensureRequiredFieldsPresent({locale}, eventTypes.LEAD_GEN)) { sendFactEventBase(eventTypes.LEAD_GEN, body); } }; function sendSuccessfulExecutionEvent(forceSend) { const successfulExecutions = jsonParser(trackingFieldName) if (successfulExecutions.length === MAX_RECORDS || (forceSend && successfulExecutions.length > 0)) { sendTrackingSuccessfulExecution(successfulExecutions) localStorage.setItem(trackingFieldName, JSON.stringify([])) } } // ------------------------- core ------------------------- function reactOnLoad() { const pageLoadRules = script.rows.filter(row => row.trigger.type === triggerTypes.PAGE_LOAD); const handler = (event) => { observeAndReactOnPageChange(() => { evaluateRules(pageLoadRules, event); sendSuccessfulExecutionEvent(); }); }; // Check if page already loaded if (document.readyState === "complete") { handler(new Event('load')); } else { window.addEventListener("load", handler, {capture: true}); } } function reactOnClick() { const clickRules = script.rows.filter(row => row.trigger.type === triggerTypes.CLICK); // click via mouse document.querySelector('body').addEventListener('mousedown', (event) => { evaluateRules(clickRules, event); sendSuccessfulExecutionEvent() }, {capture: true}); // click via enter button document.querySelector('body').addEventListener('keydown', (event) => { if (event.code === "Enter" || event.code === "NumpadEnter") { evaluateRules(clickRules, event); sendSuccessfulExecutionEvent() } }, {capture: true}); } function reactOnInput() { const inputRules = script.rows.filter(row => row.trigger.type === triggerTypes.INPUT); document.querySelector('body').addEventListener('input', function (event) { evaluateRules(inputRules, event); sendSuccessfulExecutionEvent() }, {capture: true}); } function reactOnMouseLeave() { document.addEventListener("mouseleave", function (event) { if (event.clientY <= 0 || event.clientX <= 0 || (event.clientX >= window.innerWidth || event.clientY >= window.innerHeight)) { sendSuccessfulExecutionEvent(true) } }); } function track() { try { executeAdvancedCode(); reactOnLoad(); reactOnClick(); reactOnInput(); reactOnMouseLeave(); } catch (error) { console.debug("Tracking disabled", error); } } // RUN SCRIPT AFTER PAGE LOAD if (document.readyState === "loading" || document.readyState === "interactive") { // Loading hasn't finished yet document.addEventListener("readystatechange", (event) => { if (event.target.readyState === "complete") { track(); } }); } else { track(); } })();