1(function () { 2 'use strict'; 3 4 if (window.__whtApprovedAnalyticsEventsLoaded) { 5 return; 6 } 7 window.__whtApprovedAnalyticsEventsLoaded = true; 8 9 var config = window.whtApprovedAnalyticsEvents || {}; 10 var eventSource = 'wht_release_events_1_0_7'; 11 var ecommerce = config.ecommerce || {}; 12 var startedForms = typeof WeakSet === 'function' ? new WeakSet() : null; 13 var recentFormErrors = typeof WeakMap === 'function' ? new WeakMap() : null; 14 var pendingAjaxAdds = {}; 15 var pendingAjaxAdd = null; 16 var pendingAjaxRemoves = {}; 17 var pendingAjaxRemove = null; 18 var pageEvents = {}; 19 var promotionObserver = null; 20 var promotionTimers = new Map(); 21 var promotionRatios = typeof WeakMap === 'function' ? new WeakMap() : null; 22 var viewedPromotions = typeof WeakSet === 'function' ? new WeakSet() : null; 23 var promotionSelector = '[data-wht-promotion-card="true"]'; 24 var shopPromotionExclusions = [ 25 '.wht-wine-shop-card', 26 '.wht-wine-shop-related-card', 27 '.wht-wine-shop-page', 28 '.wht-wine-shop-archive', 29 '.wht-wine-shop-product', 30 'li.product' 31 ].join(', '); 32 var itemKeys = [ 33 'item_id', 34 'item_name', 35 'item_category', 36 'price', 37 'quantity' 38 ]; 39 40 function push(payload) { 41 window.dataLayer = window.dataLayer || []; 42 window.dataLayer.push(payload); 43 } 44 45 function pushEvent(name, parameters) { 46 var payload = { 47 event: name, 48 wht_event_source: eventSource 49 }; 50 51 Object.keys(parameters || {}).forEach(function (key) { 52 if (typeof parameters[key] !== 'undefined' && parameters[key] !== '') { 53 payload[key] = parameters[key]; 54 } 55 }); 56 57 push(payload); 58 /* 59 * A GTM data-layer variable can retain its previous value. Clear the 60 * source immediately after the marked event so a later homonymous 61 * event from another component cannot inherit this emitter's marker. 62 * This object has no `event` key and therefore creates no GA4 event. 63 */ 64 push({ wht_event_source: null }); 65 } 66 67 function pushEcommerceEvent(name, parameters) { 68 push({ ecommerce: null }); 69 pushEvent(name, { ecommerce: parameters }); 70 } 71 72 function promotionAttribute(element, name) { 73 return String(element.getAttribute('data-wht-' + name) || '').trim(); 74 } 75 76 function promotionCardFor(element) { 77 return element && element.closest ? element.closest(promotionSelector) : null; 78 } 79 80 function isShopPromotionCard(card) { 81 return !!( 82 card && 83 (card.matches(shopPromotionExclusions) || card.closest(shopPromotionExclusions)) 84 ); 85 } 86 87 function promotionData(card) { 88 var promotionId; 89 var promotionName; 90 var creativeName; 91 var creativeSlot; 92 var locationId; 93 var itemId; 94 var itemName; 95 var itemCategory; 96 var itemIndex; 97 98 if (!card || isShopPromotionCard(card)) { 99 return null; 100 } 101 102 promotionId = promotionAttribute(card, 'promotion-id'); 103 promotionName = promotionAttribute(card, 'promotion-name'); 104 creativeName = promotionAttribute(card, 'creative-name'); 105 creativeSlot = promotionAttribute(card, 'creative-slot'); 106 locationId = promotionAttribute(card, 'location-id'); 107 itemId = promotionAttribute(card, 'item-id'); 108 itemName = promotionAttribute(card, 'item-name'); 109 itemCategory = promotionAttribute(card, 'item-category'); 110 itemIndex = parseInt(promotionAttribute(card, 'item-index'), 10); 111 112 if ( 113 !promotionId || 114 !promotionName || 115 !creativeName || 116 !creativeSlot || 117 !locationId || 118 !itemId || 119 !itemName || 120 !itemCategory || 121 !isFinite(itemIndex) 122 ) { 123 return null; 124 } 125 126 return { 127 page_type: safeToken(promotionAttribute(card, 'page-type'), 'other'), 128 experience_type: safeToken(promotionAttribute(card, 'experience-type'), 'sitewide'), 129 cta_location: safeToken(promotionAttribute(card, 'cta-location'), 'content_card'), 130 link_type: safeToken(promotionAttribute(card, 'link-type'), 'commercial_detail'), 131 ecommerce: { 132 promotion_id: promotionId, 133 promotion_name: promotionName, 134 creative_name: creativeName, 135 creative_slot: creativeSlot, 136 location_id: locationId, 137 items: [{ 138 item_id: itemId, 139 item_name: itemName, 140 item_category: itemCategory, 141 index: itemIndex 142 }] 143 } 144 }; 145 } 146 147 function pushPromotionEvent(name, card) { 148 var data = promotionData(card); 149 150 if (!data) { 151 return false;
152 } 153 154 push({ ecommerce: null }); 155 pushEvent(name, data); 156 return true; 157 } 158 159 function promotionWasViewed(card) { 160 if (viewedPromotions) { 161 return viewedPromotions.has(card); 162 } 163 return card.getAttribute('data-wht-promotion-viewed') === 'true'; 164 } 165 166 function markPromotionViewed(card) { 167 if (viewedPromotions) { 168 viewedPromotions.add(card); 169 return; 170 } 171 card.setAttribute('data-wht-promotion-viewed', 'true'); 172 } 173 174 function promotionRatio(card) { 175 return promotionRatios ? number(promotionRatios.get(card), 0) : number(card.getAttribute('data-wht-promotion-ratio'), 0); 176 } 177 178 function setPromotionRatio(card, ratio) { 179 if (promotionRatios) { 180 promotionRatios.set(card, ratio); 181 return; 182 } 183 card.setAttribute('data-wht-promotion-ratio', String(ratio)); 184 } 185 186 function cancelPromotionTimer(card) { 187 if (!promotionTimers.has(card)) { 188 return; 189 } 190 window.clearTimeout(promotionTimers.get(card)); 191 promotionTimers.delete(card); 192 } 193 194 function schedulePromotionView(card) { 195 if ( 196 promotionWasViewed(card) || 197 promotionTimers.has(card) || 198 promotionRatio(card) < 0.5 || 199 document.visibilityState !== 'visible' 200 ) { 201 return; 202 } 203 204 promotionTimers.set(card, window.setTimeout(function () { 205 promotionTimers.delete(card); 206 if ( 207 promotionWasViewed(card) || 208 promotionRatio(card) < 0.5 || 209 document.visibilityState !== 'visible' 210 ) { 211 return; 212 } 213 if (pushPromotionEvent('view_promotion', card)) { 214 markPromotionViewed(card); 215 if (promotionObserver) { 216 promotionObserver.unobserve(card); 217 } 218 } 219 }, 1000)); 220 } 221 222 function promotionIntersection(entries) { 223 entries.forEach(function (entry) { 224 var card = entry.target; 225 var ratio = entry.isIntersecting ? entry.intersectionRatio : 0; 226 227 setPromotionRatio(card, ratio); 228 if (ratio >= 0.5) { 229 schedulePromotionView(card); 230 } else { 231 cancelPromotionTimer(card); 232 } 233 }); 234 } 235 236 function registerPromotionCards(root) { 237 var cards = []; 238 239 if (!promotionObserver || !root) { 240 return; 241 } 242 243 if (root.matches && root.matches(promotionSelector)) { 244 cards.push(root); 245 } 246 if (root.querySelectorAll) { 247 cards = cards.concat(Array.prototype.slice.call(root.querySelectorAll(promotionSelector))); 248 } 249 250 cards.forEach(function (card) { 251 if (!promotionData(card) || promotionWasViewed(card) || card.getAttribute('data-wht-promotion-observed') === 'true') { 252 return; 253 } 254 card.setAttribute('data-wht-promotion-observed', 'true'); 255 promotionObserver.observe(card); 256 }); 257 } 258 259 function initPromotionTracking() { 260 if (!('IntersectionObserver' in window)) { 261 return; 262 } 263 264 promotionObserver = new window.IntersectionObserver(promotionIntersection, { 265 threshold: [0, 0.5, 1] 266 }); 267 registerPromotionCards(document); 268 window.whtRegisterPromotionCards = registerPromotionCards; 269 } 270 271 function emitSelectPromotion(link) { 272 var card = promotionCardFor(link); 273 274 if (!card || isShopPromotionCard(card)) { 275 return; 276 } 277 pushPromotionEvent('select_promotion', card); 278 } 279 280 function safeToken(value, fallback) { 281 var token = String(value || '') 282 .toLowerCase() 283 .replace(/[^a-z0-9_-]+/g, '_') 284 .replace(/^_+|_+$/g, '') 285 .slice(0, 80); 286 287 return token || fallback || 'unknown'; 288 } 289 290 function number(value, fallback) { 291 var result = parseFloat(value); 292 return isFinite(result) ? result : (fallback || 0); 293 } 294 295 function cleanItem(source, quantity) { 296 var item = {}; 297 298 itemKeys.forEach(function (key) { 299 if (source && typeof source[key] !== 'undefined' && source[key] !== '') { 300 item[key] = source[key]; 301 } 302 }); 303 304 if (quantity > 0) { 305 item.quantity = quantity; 306 } 307 308 return item; 309 } 310 311 function itemValue(items) { 312 return (items || []).reduce(function (total, item) { 313 return total + (number(item.price, 0) * Math.max(1, parseInt(item.quantity || 1, 10))); 314 }, 0); 315 } 316 317 function currency() { 318 return safeToken(ecommerce.currency || 'EUR', 'eur').toUpperCase(); 319 } 320 321 function pageType(element) { 322 if (element && element.closest('.wht-home-code-build-root, .wht-home-code')) { 323 return 'home'; 324 } 325 if (element && element.closest('.wht-oil-wine-page')) { 326 return 'tour'; 327 } 328 if (element && element.closest('.wht-city-tasting-page, .wht-mwt-page, .wht-vineyard-page')) { 329 return 'experience_detail'; 330 } 331 return safeToken(ecommerce.page_type || 'other', 'other'); 332 } 333 334 function itemById(productId) { 335 var id = String(productId || ''); 336 var items = ecommerce.items || []; 337 var found = null; 338 339 items.some(function (item) {
340 if (String(item.item_id || '') === id) { 341 found = item; 342 return true; 343 } 344 return false; 345 }); 346 347 return found; 348 } 349 350 function productIdFromElement(element) { 351 var product; 352 var match; 353 var classes; 354 355 if (!element) { 356 return ''; 357 } 358 359 product = element.closest('[data-product_id], [data-product-id]'); 360 if (product) { 361 return product.getAttribute('data-product_id') || product.getAttribute('data-product-id') || ''; 362 } 363 364 product = element.closest('[class*="post-"], [class*="product-"]'); 365 classes = product ? product.className : ''; 366 match = String(classes).match(/(?:post|product)-(\d+)/); 367 if (match) { 368 return match[1]; 369 } 370 371 product = element.closest('form.cart'); 372 if (product) { 373 element = product.querySelector('[name="add-to-cart"]'); 374 return element ? element.value : ''; 375 } 376 377 return ''; 378 } 379 380 function quantityFromElement(element, fallback) { 381 var wrapper = element ? element.closest('form.cart, .wht-wine-shop-card__purchase, .cart_item') : null; 382 var field = wrapper ? wrapper.querySelector('[name="quantity"], select, .qty') : null; 383 var value = element ? element.getAttribute('data-quantity') : ''; 384 385 return Math.max(1, parseInt(value || (field ? field.value : fallback) || fallback || 1, 10)); 386 } 387 388 function experienceTypeFor(element) { 389 if (element && element.closest('.wht-oil-wine-page')) { 390 return 'olive_oil_wine_tour'; 391 } 392 if (element && element.closest('.wht-city-tasting-page')) { 393 return 'malaga_city_wine_tasting'; 394 } 395 if (element && element.closest('.wht-mwt-page')) { 396 return 'malaga_wine_tour'; 397 } 398 if (element && element.closest('.wht-vineyard-page')) { 399 return 'vineyard_tasting'; 400 } 401 if (element && element.closest('.wht-wine-shop-page')) { 402 return 'wine_shop'; 403 } 404 if (element && element.closest('.wht-home-code-build-root, .wht-home-code')) { 405 return 'home'; 406 } 407 return 'sitewide'; 408 } 409 410 function locationFor(element) { 411 if (!element) { 412 return 'content'; 413 } 414 if (element.closest('header, [role="banner"]')) { 415 return 'header'; 416 } 417 if (element.closest('footer, [role="contentinfo"]')) { 418 return 'footer'; 419 } 420 if (element.closest('aside, [class*="sidebar"]')) { 421 return 'sidebar'; 422 } 423 if (element.closest('[class*="sticky"]')) { 424 return 'sticky'; 425 } 426 if (element.closest('[class*="hero"]')) { 427 return 'hero'; 428 } 429 return 'content'; 430 } 431 432 function bookingLocation(element) { 433 return safeToken( 434 element.getAttribute('data-wht-cta-location') || 435 element.getAttribute('data-cta-location') || 436 locationFor(element), 437 'content' 438 ); 439 } 440 441 function bookingParameters(element) { 442 var session = safeToken(element.getAttribute('data-wht-booking-session'), ''); 443 var experienceType = experienceTypeFor(element); 444 var ctaLocation = bookingLocation(element); 445 446 if (experienceType === 'malaga_city_wine_tasting' && session === 'noon') { 447 experienceType = 'malaga_city_wine_tasting_p18'; 448 ctaLocation = 'session_noon'; 449 } else if (experienceType === 'malaga_city_wine_tasting' && session === 'evening') { 450 experienceType = 'malaga_city_wine_tasting_p19'; 451 ctaLocation = 'session_evening'; 452 } 453 454 return { 455 page_type: pageType(element), 456 experience_type: experienceType, 457 cta_location: ctaLocation, 458 link_type: 'booking' 459 }; 460 } 461 462 function isBookingLink(link) { 463 var href = String(link.getAttribute('href') || '').toLowerCase(); 464 465 return link.hasAttribute('data-wht-booking-cta') || 466 href.indexOf('#wht-city-tasting-booking') === 0 || 467 href.indexOf('#wht-oil-wine-booking') === 0 || 468 href.indexOf('#wht-mwt-booking') === 0 || 469 href.indexOf('#wht-malaga-booking') === 0 || 470 href.indexOf('#wht-vineyard-booking') === 0 || 471 href.indexOf('fareharbor.com') !== -1 || 472 href.indexOf('turitop.com') !== -1; 473 } 474 475 function isWhatsAppLink(link) { 476 var href = String(link.getAttribute('href') || '').toLowerCase(); 477 478 return href.indexOf('whatsapp:') === 0 || 479 href.indexOf('https://wa.me/') === 0 || 480 href.indexOf('https://api.whatsapp.com/') === 0; 481 } 482 483 function formId(form) { 484 if (form.closest('.wht-city-tasting-enquiry__form')) { 485 return 'city_tasting_enquiry'; 486 } 487 if (form.closest('.wht-mwt-enquiry__form')) { 488 return 'malaga_wine_tour_enquiry'; 489 }
490 if (form.closest('.wht-vineyard-enquiry__form')) { 491 return 'vineyard_enquiry'; 492 } 493 if (form.closest('.wht-wine-shop-page')) { 494 return 'wine_shop_enquiry'; 495 } 496 if (form.closest('.wht-home-code__request-form')) { 497 return 'home_enquiry'; 498 } 499 if (form.closest('[class*="business"]')) { 500 return 'business_tours_enquiry'; 501 } 502 if (form.closest('[class*="private"]')) { 503 return 'private_tours_enquiry'; 504 } 505 return 'contact_enquiry'; 506 } 507 508 function isTrackableForm(form) { 509 return !!( 510 form && 511 !form.closest('.woocommerce-checkout, .woocommerce-cart-form') && 512 !form.querySelector('input[type="password"]') && 513 (form.classList.contains('wpcf7-form') || 514 form.hasAttribute('data-wht-request-form') || 515 form.closest('.wpcf7, [class*="enquiry"], [class*="contact-form"], .wht-home-code__request-form')) 516 ); 517 } 518 519 function isRelevantFormField(element) { 520 var type; 521 522 if (!element || !element.matches || !element.matches('input, select, textarea')) { 523 return false; 524 } 525 if (!element.matches('input')) { 526 return true; 527 } 528 529 type = String(element.getAttribute('type') || 'text').toLowerCase(); 530 return ['hidden', 'submit', 'button', 'reset', 'file'].indexOf(type) === -1; 531 } 532 533 function markFormStarted(form) { 534 if (!isTrackableForm(form)) { 535 return; 536 } 537 if (startedForms && startedForms.has(form)) { 538 return; 539 } 540 if (startedForms) { 541 startedForms.add(form); 542 } else if (form.getAttribute('data-wht-approved-form-started') === 'true') { 543 return; 544 } else { 545 form.setAttribute('data-wht-approved-form-started', 'true'); 546 } 547 pushEvent('form_start', { 548 page_type: pageType(form), 549 form_id: formId(form) 550 }); 551 } 552 553 function pushFormValidationError(form) { 554 var now = Date.now(); 555 var recent = recentFormErrors ? recentFormErrors.get(form) : null; 556 557 if (!isTrackableForm(form)) { 558 return; 559 } 560 if (recent && now - recent.time < 750) { 561 return; 562 } 563 if (recentFormErrors) { 564 recentFormErrors.set(form, { time: now }); 565 } 566 pushEvent('form_error', { 567 page_type: pageType(form), 568 form_id: formId(form) 569 }); 570 } 571 572 function cf7Form(event) { 573 var target = event && event.target; 574 575 if (!target) { 576 return null; 577 } 578 if (target.matches && target.matches('form')) { 579 return target; 580 } 581 return target.closest ? (target.closest('form') || target.querySelector('form')) : null; 582 } 583 584 function emitPageEcommerce() { 585 var items = (ecommerce.items || []).map(function (item) { 586 return cleanItem(item); 587 }); 588 var shopPage = document.querySelector('.wht-wine-shop-page, .wht-wine-shop-archive, .wht-wine-shop-product'); 589 var eventName = ''; 590 591 if (ecommerce.page_type === 'item_list' && shopPage && items.length) { 592 eventName = 'view_item_list'; 593 } else if (ecommerce.page_type === 'item' && shopPage && items.length) { 594 eventName = 'view_item'; 595 } else if (ecommerce.page_type === 'cart' && items.length) { 596 eventName = 'view_cart'; 597 } else if (ecommerce.page_type === 'checkout' && items.length) { 598 eventName = 'begin_checkout'; 599 } 600 601 if (!eventName || pageEvents[eventName]) { 602 return; 603 } 604 605 pageEvents[eventName] = true; 606 pushEcommerceEvent(eventName, { 607 currency: currency(), 608 value: itemValue(items), 609 items: items 610 }); 611 } 612 613 function emitSelectItem(link) { 614 var card = link.closest('.wht-wine-shop-card, li.product'); 615 var item = itemById(productIdFromElement(link)); 616 617 if (!card || !item || link.closest('.add_to_cart_button')) { 618 return; 619 } 620 621 pushEcommerceEvent('select_item', { 622 items: [cleanItem(item)] 623 }); 624 } 625 626 function emitAddToCart(button) { 627 var productId = productIdFromElement(button); 628 var quantity = quantityFromElement(button); 629 var item = itemById(productId); 630 631 if (!item) { 632 return; 633 } 634 635 item = cleanItem(item, quantity); 636 pushEcommerceEvent('add_to_cart', { 637 currency: currency(), 638 value: itemValue([item]), 639 items: [item] 640 }); 641 } 642 643 function emitRemoveFromCart(button) { 644 var productId = productIdFromElement(button); 645 var item = itemById(productId); 646 var quantity; 647 648 if (!item) { 649 return; 650 } 651 652 quantity = quantityFromElement(button, item.quantity || 1); 653 item = cleanItem(item, quantity); 654 pushEcommerceEvent('remove_from_cart', { 655 currency: currency(), 656 value: itemValue([item]), 657 items: [item] 658 }); 659 } 660 661 document.addEventListener('click', function (event) { 662 var target = event.target && event.target.closest ? event.target : null; 663 var link = target ? target.closest('a[href]') : null; 664 var addButton = target ? target.closest('.add_to_cart_button, button.single_add_to_cart_button') : null; 665 var removeButton = target ? target.closest('.remove_from_cart_button, a.remove') : null; 666 var href; 667 var productId; 668 669 if (link) { 670 href = String(link.getAttribute('href') || '').toLowerCase(); 671 672 if (isWhatsAppLink(link)) { 673 pushEvent('click_whatsapp', { 674 page_type: pageType(link), 675 experience_type: experienceTypeFor(link), 676 cta_location: locationFor(link), 677 link_type: 'whatsapp' 678 }); 679 } else if (href.indexOf('mailto:') === 0) { 680 pushEvent('click_email', { 681 page_type: pageType(link), 682 experience_type: experienceTypeFor(link), 683 cta_location: locationFor(link), 684 link_type: 'email' 685 }); 686 } else if (href.indexOf('tel:') === 0) { 687 pushEvent('click_phone', { 688 page_type: pageType(link), 689 experience_type: experienceTypeFor(link), 690 cta_location: locationFor(link), 691 link_type: 'phone' 692 }); 693 } 694 695 if (isBookingLink(link)) { 696 pushEvent('click_booking', bookingParameters(link)); 697 } 698 699 emitSelectPromotion(link); 700 emitSelectItem(link); 701 } 702 703 if (addButton) { 704 productId = productIdFromElement(addButton); 705 if (addButton.classList.contains('ajax_add_to_cart')) { 706 pendingAjaxAdds[productId] = addButton; 707 pendingAjaxAdd = addButton; 708 } 709 } 710 711 if (removeButton) { 712 productId = productIdFromElement(removeButton); 713 if (removeButton.classList.contains('remove_from_cart_button')) { 714 pendingAjaxRemoves[productId] = removeButton; 715 pendingAjaxRemove = removeButton; 716 } else if (removeButton.matches('a.remove')) { 717 /* 718 * The standard WooCommerce cart remove link navigates with a 719 * nonce-protected GET and does not emit removed_from_cart. 720 * Record the explicit user action before that navigation. 721 * AJAX removals remain gated to WooCommerce's success event. 722 */ 723 emitRemoveFromCart(removeButton); 724 } 725 } 726 }, true); 727 728 document.addEventListener('focusin', function (event) { 729 var field = event.target; 730 var form = isRelevantFormField(field) && field.closest ? field.closest('form') : null; 731 markFormStarted(form); 732 }, true); 733 734 document.addEventListener('invalid', function (event) { 735 var form = event.target && event.target.form ? event.target.form : null; 736 pushFormValidationError(form); 737 }, true); 738 739 document.addEventListener('wpcf7mailsent', function (event) { 740 var form = cf7Form(event); 741 if (isTrackableForm(form)) { 742 pushEvent('form_submit', { 743 page_type: pageType(form), 744 form_id: formId(form) 745 }); 746 } 747 }); 748 749 document.addEventListener('wpcf7invalid', function (event) { 750 pushFormValidationError(cf7Form(event)); 751 }); 752 753 document.addEventListener('change', function (event) { 754 var target = event.target; 755 var items; 756 if (ecommerce.page_type !== 'checkout' || !target) { 757 return; 758 } 759 760 items = (ecommerce.items || []).map(function (item) { 761 return cleanItem(item); 762 }); 763 764 if (target.matches('input[name^="shipping_method"]') && target.checked) { 765 pushEcommerceEvent('add_shipping_info', { 766 currency: currency(), 767 value: itemValue(items), 768 items: items 769 }); 770 } 771 772 if (target.matches('input[name="payment_method"]') && target.checked) { 773 pushEcommerceEvent('add_payment_info', { 774 currency: currency(), 775 value: itemValue(items), 776 items: items 777 }); 778 } 779 }, true); 780 781 if (window.jQuery) { 782 window.jQuery(document.body).on('added_to_cart', function (event, fragments, cartHash, button) { 783 var element = button && button[0] ? button[0] : button; 784 var productId = productIdFromElement(element); 785 786 element = element || pendingAjaxAdds[productId] || pendingAjaxAdd; 787 if (element) { 788 productId = productIdFromElement(element); 789 emitAddToCart(element);
790 delete pendingAjaxAdds[productId]; 791 pendingAjaxAdd = null; 792 } 793 }); 794 795 window.jQuery(document.body).on('removed_from_cart', function (event, fragments, cartHash, button) { 796 var element = button && button[0] ? button[0] : button; 797 var productId = productIdFromElement(element); 798 799 element = element || pendingAjaxRemoves[productId] || pendingAjaxRemove; 800 if (element) { 801 productId = productIdFromElement(element); 802 emitRemoveFromCart(element); 803 delete pendingAjaxRemoves[productId]; 804 pendingAjaxRemove = null; 805 } 806 }); 807 } 808 809 document.addEventListener('visibilitychange', function () { 810 document.querySelectorAll(promotionSelector).forEach(function (card) { 811 if (document.visibilityState !== 'visible') { 812 cancelPromotionTimer(card); 813 } else if (promotionRatio(card) >= 0.5) { 814 schedulePromotionView(card); 815 } 816 }); 817 }); 818 819 document.addEventListener('wht:promotion-cards-updated', function (event) { 820 var root = event.detail && event.detail.root ? event.detail.root : document; 821 registerPromotionCards(root); 822 }); 823 824 if (document.readyState === 'loading') { 825 document.addEventListener('DOMContentLoaded', function () { 826 emitPageEcommerce(); 827 initPromotionTracking(); 828 }); 829 } else { 830 emitPageEcommerce(); 831 initPromotionTracking(); 832 } 833}());
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.