PageSourceSearch

https://whitehousestours.com/wp-content/plugins/wht-release-events/wht-release-events.js?ver=1.0.9

js whitehousestours.com collected 2026-09-25 23:05:55 UTC 22,380 bytes, 833 lines download raw bytes

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.