(function() { (function () {
  var COOKIE_NAME = "_cb_testgroup";
  var COOKIE_MAX_AGE = 7 * 24 * 60 * 60; // 7 days
  var VALUES = ["cartbooster_a", "cartbooster_b"]; 
  var isHTTPS = location.protocol === "https:";
  var cookieAttrs = [
    "path=/",
    "max-age=" + COOKIE_MAX_AGE,
    "SameSite=Lax"
  ];
  if (isHTTPS) cookieAttrs.push("Secure");

  function getCookie(name) {
    var m = document.cookie.match(
      new RegExp("(?:^|; )" + name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1") + "=([^;]*)")
    );
    return m ? decodeURIComponent(m[1]) : null;
  }

  function setCookie(name, value, attrs) {
    document.cookie = name + "=" + value + "; " + attrs.join("; ");
  }

  // Check for an existing assignment
  var existing = getCookie(COOKIE_NAME);

  if (!existing) {
    // Pure 50/50 assignment
    var pick = VALUES[Math.floor(Math.random() * VALUES.length)];
    setCookie(COOKIE_NAME, pick, cookieAttrs);

    // Optional LS mirror
    try { localStorage.setItem("_cb_testgroup", pick); } catch (e) {}
  } else {
    try {
      if (localStorage.getItem("_cb_testgroup") !== existing) {
        localStorage.setItem("_cb_testgroup", existing);
      }
    } catch (e) {}
  }
})(); })();
(function () {
	const STORAGE_KEYS = {
		referral: "cb_ref_id",
		demoMode: "cb_demo_mode",
		conversionTracked: "cb_conversion_tracked",
		abGroupPrefix: "cb_ab_group_",
		sessionTrackedPrefix: "cb_session_tracked_",
		pendingDiscountCode: "cb_discount_code_pending",
		pendingDiscountCampaignId: "cb_discount_campaign_id_pending",
		orderConfirmationPinged: "cb_order_confirmation_pinged",
		orderConfirmationReported: "cb_order_confirmation_reported",
		badgeDismissedPrefix: "cb_badge_dismissed_",
		forceReveal: "cb_force_reveal",
		popupSuppressedPrefix: "cb_popup_suppressed_",
	};
	const API_BASE_FALLBACK = "https://api.cartbooster.io";
	const ORDER_CONFIRM_ENDPOINT = "/ambassador/order-confirm";
	const ORDER_CONFIRM_PING_ENDPOINT = "/sites/order-confirmation/ping";
	const SESSION_TRACKING_ENDPOINT = "/sessions/track";
	const CONVERSION_TRACKING_ENDPOINT = "/conversions/track";
	const TAB_RECOVERY_TRACKING_ENDPOINT = "/tab-recovery/track";
	const TAB_RECOVERY_EVENTS = {
		inactive: "tab_inactive",
		reactivated: "tab_reactivated",
	};
	var hasReportedOrderConfirmation = false;
	var hasPingedOrderConfirmationVisit = false;

	// Function to process URL parameters (demo mode, referral tracking)
	function initializeUrlFlags() {
		const urlParams = new URLSearchParams(window.location.search);
		if (urlParams.has("cb_demo")) {
			sessionStorage.setItem(STORAGE_KEYS.demoMode, "true");
			console.log(
				"%cDemo mode enabled",
				"color: #00A36C; font-weight: bold; font-size: 16px;",
			);
		}

		const referralId = urlParams.get("cb_ref_id");
		if (referralId) {
			try {
				localStorage.setItem(STORAGE_KEYS.referral, referralId);
			} catch (error) {
				console.warn("[AMBASSADOR] Failed to persist cb_ref_id", error);
			}
		}

		const discountCode = urlParams.get("cb_discount_code");
		const campaignId = urlParams.get("cb_campaign_id");
		if (discountCode) {
			try {
				const trimmedDiscountCode = discountCode.trim();
				const trimmedCampaignId = campaignId ? campaignId.trim() : "";
				if (trimmedDiscountCode) {
					if (trimmedCampaignId) {
						sessionStorage.setItem(
							`cb_discount_code_${trimmedCampaignId}`,
							trimmedDiscountCode,
						);
						sessionStorage.setItem(
							STORAGE_KEYS.pendingDiscountCode,
							trimmedDiscountCode,
						);
						sessionStorage.setItem(
							STORAGE_KEYS.pendingDiscountCampaignId,
							trimmedCampaignId,
						);
					}
					document.cookie =
						"discount_code=" +
						encodeURIComponent(trimmedDiscountCode) +
						"; path=/";
				}
			} catch (error) {
				console.warn(
					"[AMBASSADOR] Failed to persist cb_discount_code from URL",
					error,
				);
			}
		}

		// split-tab: keep a campaign popup closed in this tab
		const suppressCampaignId = urlParams.get("cb_suppress");
		if (suppressCampaignId && suppressCampaignId.trim()) {
			try {
				sessionStorage.setItem(
					STORAGE_KEYS.popupSuppressedPrefix + suppressCampaignId.trim(),
					"true",
				);
			} catch (error) {
				console.warn(
					"[SPLIT-TAB] Failed to persist cb_suppress from URL",
					error,
				);
			}
		}

		// cb_reveal is handled in track() once campaign config is available
	}

	initializeUrlFlags();

	try {
		if (!window.sessionStorage) return false;
	} catch (err) {
		return false;
	}

	function getBadgeDismissedStorageKey(cpm) {
		return STORAGE_KEYS.badgeDismissedPrefix + cpm.objectId;
	}

	function getAbGroupStorageKey(siteId) {
		return STORAGE_KEYS.abGroupPrefix + siteId;
	}

	function getSessionTrackedStorageKey(siteId) {
		return STORAGE_KEYS.sessionTrackedPrefix + siteId;
	}

	function normalizeControlGroupSize(value) {
		var numericValue = Number(value);
		if (!isFinite(numericValue)) return 0;
		return Math.max(0, Math.min(100, numericValue));
	}

	function hasAbHoldoutEnabled(config) {
		return normalizeControlGroupSize(config?.abHoldoutControlGroupSize) > 0;
	}

	function assignAbGroup(config) {
		if (!config || !config.siteId) {
			return "treatment";
		}

		if (config.abGroup) {
			return config.abGroup;
		}

		var storageKey = getAbGroupStorageKey(config.siteId);
		var storedGroup = null;

		try {
			storedGroup = localStorage.getItem(storageKey);
		} catch (error) {
			log("[AB] Failed to read stored group", error);
		}

		if (storedGroup === "control" || storedGroup === "treatment") {
			config.abGroup = storedGroup;
			return storedGroup;
		}

		var controlGroupSize = normalizeControlGroupSize(
			config.abHoldoutControlGroupSize,
		);
		var assignedGroup =
			Math.random() * 100 < controlGroupSize ? "control" : "treatment";

		try {
			localStorage.setItem(storageKey, assignedGroup);
		} catch (error) {
			log("[AB] Failed to persist assigned group", error);
		}

		config.abGroup = assignedGroup;
		return assignedGroup;
	}

	function isControlGroup(config) {
		return assignAbGroup(config) === "control";
	}

	const FREQUENCY_CAP = 604800000; // 7 days in milliseconds
	const VISIT_FREQUENCY_CAP = 86400000; // 24 hours in milliseconds

	var TAG_PREFIX = "CV",
		TAG_VISITOR = TAG_PREFIX + "_VISITOR",
		listeners = [],
		config = {"device":2,"campaigns":[{"frequencyCap":7,"objectId":"q6p4egg1SpqSWozy9w6i","type":"INTERACTIVE_TAB_HEADING","fonts":["Oswald:wght@600","Cabin:wght@600","Oswald:wght@700","Open+Sans:wght@400"],"codesRunOut":"template","submitURL":"https://www.oneworldobservatory.com/tickets/?keyword","impressionURLs":[""],"blurhash":"UfHVVYSgtRxu.To0WAbH*0RPt7kCx^ofWBof","badge":null,"clickURLs":["https://clk.tradedoubler.com/click?p=328779&a=3296794&epi=Beacon1"],"trafficSources":{"include":[[{"value":"cartbooster_a","prop":"cookieValue","type":"contains"}]],"exclude":[]},"trigger":{"type":"tab","value":"5","title":"🌆 Elevate Your View—Book Now!"},"conditions":[[{"type":"contains","prop":"url","value":"/buy-tickets/"},{"value":"/plan-your-visit/","prop":"url","type":"contains"},{"prop":"url","type":"contains","value":"/manage-my-booking/"}]],"template":"<div id=\"fc46233c-e5ef-48a6-9dea-498c8b1e1320\" style=\"position:relative;background-color:#ffffff;letter-spacing:normal;max-width:800px;font-size:20px;height:500px;display:flex;width:100%;\"><div id=\"a0c8befb-fb0e-4a8c-b5bf-bc8f5e465c06\" data-type=\"Image\" style=\"background-image:url(https://firebasestorage.googleapis.com/v0/b/conversingjs.appspot.com/o/popup-images%2Fq6p4egg1SpqSWozy9w6i.jpeg?alt=media&token=7bc5d5ff-bbd9-45c5-8afc-d5b9b63eba9b);background-position:center;width:400px;background-size:cover;\"></div><div id=\"1ce2aa07-9dc3-470c-b1c5-7bf20afb69f2\" style=\"gap:24px;padding:32px;flex-direction:column;justify-content:center;flex:1;display:flex;\"><span id=\"8678b20b-b085-4161-a6fe-d6152b5132bf\" cv-close data-type=\"Close button\" style=\"line-height:14px;right:12px;font-size:24px;content:x;position:absolute;cursor:pointer;top:12px;\">×</span><p id=\"e9e46e1e-05b6-479d-bbc6-90db87a6aa82\" data-type=\"Heading\" style=\"margin:0;color:#000;font-family:Oswald;height:auto;font-size:36px;line-height:1.2;padding:0;text-align:center;font-weight:600;\">Rise above it all</p><p id=\"ccbea963-4b1d-425d-b2e1-29240bdbdd6c\" data-type=\"Body text\" style=\"text-align:center;font-weight:600;padding:0;height:auto;font-size:20px;color:#000;font-family:Cabin;margin:0;\">Experience jaw-dropping 360-degree views from New York's highest point. Secure your tickets today!</p><div id=\"6cfeced6-1c24-40e1-8782-39204bed29d7\" data-type=\"Discount code\" style=\"display:none\" style=\"font-family:Cabin;border-style:dotted;padding:10px 8px;color:#000;margin:0;text-align:center;border-width:1px;position:relative;border-radius:0;font-size:18px;background-color:#f5f5f5;border-color:#000;font-weight:600;\">DISCOUNT_CODE<div id=\"92bfc293-1cab-4574-a1d2-41e3a181b510\" data-type=\"Reveal text\" style=\"background-color:#f5f5f5;margin:0;justify-content:center;padding:0;inset:0px;cursor:pointer;position:absolute;z-index:1;text-align:center;height:auto;display:flex;border-width:0;align-items:center;\">Click to reveal code<div id=\"3f6c5def-6948-4af8-a646-9baca7521528\" data-type=\"Decoration\" style=\"box-shadow:rgba(0, 0, 0, 0.15) -2px 2px 4px;right:0;border-style:solid;z-index:2;border-color:transparent rgba(0, 0, 0, 0.2) white transparent;height:0;position:absolute;border-width:0 20px 20px 0;top:0;width:0;\"></div></div></div><button id=\"b06b0828-42cd-4d2a-ba66-7bc7b29b461a\" cv-submit data-type=\"Button\" style=\"border-radius:2px;font-size:20px;cursor:pointer;border-color:#44b9d2;height:auto;border-width:4px;background-color:#000;font-family:Oswald;line-height:1.5;font-weight:700;margin:0;color:#ffffff;border-style:solid;padding:10px 12px;\">BUY TICKETS</button><div id=\"c81d6e3b-284c-4d65-a88f-38304913fcf1\" data-type=\"Terms and conditions\" style=\"display:none\" style=\"font-family:Open Sans;font-size:12px;font-weight:400;text-align:center;color:#000000;\">View terms and conditions</div></div></div>"}],"siteId":"J1jfk6XBaNoxVCLz0FeG","debug":false,"country":"US","shouldPing":false,"trafficSources":{"include":[[{"prop":"queryParamName","type":"contains","value":"utm_source"}],[{"prop":"queryParamValue","type":"contains","value":"tradedoubler"}]],"exclude":[]},"autoApplyDiscountCodeCSSSelector":"","defaults":{"heading":{"fontFamily":"Oswald","fontWeight":"600","fontSize":"36","color":"#000"},"body":{"fontFamily":"Cabin","fontWeight":"600","fontSize":"20","color":"#000"},"discount":{"fontFamily":"Cabin","fontWeight":"600","fontSize":"18","color":"#000","borderStyle":"dotted","borderColor":"#000","borderWidth":"1","borderRadius":"0","backgroundColor":"#f5f5f5"},"button":{"fontFamily":"Oswald","fontWeight":"700","fontSize":"20","color":"#ffffff","borderStyle":"solid","borderColor":"#44b9d2","borderWidth":"4","borderRadius":"2","backgroundColor":"#000"}},"orderConfirmationPath":{"type":"contains","value":""}},
		fontsURL = "fonts.googleapis.com/css2?family=";

	/**
	 * Logger methods
	 */
	var log = function () {
		config.debug && console.log.apply(this, arguments);
	};

	function maybeReportAmbassadorOrder(config) {
		if (hasReportedOrderConfirmation || !config.orderConfirmationPath) {
			log(
				"[AMBASSADOR] Order confirmation already reported or no orderConfirmationPath",
			);
			return;
		}

		let pathMatches = false;
		const currentHref = window.location.href;

		if (
			typeof config.orderConfirmationPath === "object" &&
			config.orderConfirmationPath.type &&
			config.orderConfirmationPath.value
		) {
			// New format with type and value
			const { type, value } = config.orderConfirmationPath;

			switch (type) {
				case "equals":
					pathMatches = currentHref === value;
					break;
				case "contains":
					pathMatches = currentHref.includes(value);
					break;
				case "startsWith":
					pathMatches = currentHref.startsWith(value);
					break;
				case "endsWith":
					pathMatches = currentHref.endsWith(value);
					break;
				default:
					console.warn(
						"[AMBASSADOR] Unknown orderConfirmationPath type:",
						type,
					);
					pathMatches = false;
			}
		} else {
			console.warn(
				"[AMBASSADOR] Invalid orderConfirmationPath format:",
				config.orderConfirmationPath,
			);
			return;
		}

		if (!pathMatches) {
			log(
				"[AMBASSADOR] Order confirmation path does not match current href",
				currentHref,
				config.orderConfirmationPath,
			);
			return;
		}

		var referralId = null;
		try {
			referralId = localStorage.getItem(STORAGE_KEYS.referral);
		} catch (error) {
			log("[AMBASSADOR] Unable to access referral storage", error);
			return;
		}

		if (!referralId) return;

		// Persistent dedup: if this referral was already reported (e.g. on a
		// previous full page load or redirect within the same checkout flow),
		// don't fire again. The in-memory flag alone is reset on every load.
		try {
			if (localStorage.getItem(STORAGE_KEYS.orderConfirmationReported) === referralId) {
				log("[AMBASSADOR] Order confirmation already reported for referral", referralId);
				hasReportedOrderConfirmation = true;
				return;
			}
		} catch (error) {
			log("[AMBASSADOR] Unable to read order confirmation report flag", error);
		}

		hasReportedOrderConfirmation = true;

		// Claim the report persistently BEFORE sending so a concurrent page
		// load can't fire a second request before this one resolves.
		try {
			localStorage.setItem(STORAGE_KEYS.orderConfirmationReported, referralId);
		} catch (error) {
			log("[AMBASSADOR] Failed to persist order confirmation report flag", error);
		}

		var apiBase = (config.api || API_BASE_FALLBACK).replace(/\/$/, "");
		var url = apiBase + ORDER_CONFIRM_ENDPOINT;

		fetch(url, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({ referral: referralId }),
		})
			.then(function (response) {
				if (response && response.ok) {
					// Remove cb_ref_id from storage after a confirmed success
					try {
						localStorage.removeItem(STORAGE_KEYS.referral);
					} catch (error) {
						log("[AMBASSADOR] Failed to remove cb_ref_id from storage", error);
					}
					return;
				}

				// Non-OK response: release the claim so it can be retried later.
				log("[AMBASSADOR] Order confirmation request returned non-OK status");
				hasReportedOrderConfirmation = false;
				try {
					localStorage.removeItem(STORAGE_KEYS.orderConfirmationReported);
				} catch (error) {
					log("[AMBASSADOR] Failed to clear order confirmation report flag", error);
				}
			})
			.catch(function (error) {
				console.error(
					"[AMBASSADOR] Failed to report order confirmation",
					error,
				);
				hasReportedOrderConfirmation = false;
				try {
					localStorage.removeItem(STORAGE_KEYS.orderConfirmationReported);
				} catch (cleanupError) {
					log(
						"[AMBASSADOR] Failed to clear order confirmation report flag",
						cleanupError,
					);
				}
			});
	}

	function maybeTrackConversion(config) {
		if (!config.orderConfirmationPath || !config.siteId) {
			log("[CONVERSION] No orderConfirmationPath or siteId", config);
			return;
		}

		// Handle different orderConfirmationPath formats
		var pathMatches = false;
		var currentHref = window.location.href;

		if (
			typeof config.orderConfirmationPath === "object" &&
			config.orderConfirmationPath.type &&
			config.orderConfirmationPath.value
		) {
			// New format with type and value
			var type = config.orderConfirmationPath.type;
			var value = config.orderConfirmationPath.value;

			switch (type) {
				case "equals":
					pathMatches = currentHref === value;
					break;
				case "contains":
					pathMatches = currentHref.includes(value);
					break;
				case "startsWith":
					pathMatches = currentHref.startsWith(value);
					break;
				case "endsWith":
					pathMatches = currentHref.endsWith(value);
					break;
				default:
					console.warn(
						"[CONVERSION] Invalid orderConfirmationPath type:",
						config.orderConfirmationPath,
					);
					pathMatches = false;
			}
		} else if (typeof config.orderConfirmationPath === "object") {
			console.warn(
				"[CONVERSION] Invalid orderConfirmationPath format:",
				config.orderConfirmationPath,
			);
			return;
		}

		if (!pathMatches) {
			return;
		}

		// Check if conversion was already tracked within 24 hours
		try {
			var lastTracked = localStorage.getItem(STORAGE_KEYS.conversionTracked);
			if (
				lastTracked &&
				Date.now() - parseInt(lastTracked, 10) < VISIT_FREQUENCY_CAP
			) {
				log("[CONVERSION] Already tracked within 24 hours");
				return;
			}
		} catch (error) {
			log("[CONVERSION] Unable to access localStorage", error);
		}

		var abGroup = assignAbGroup(config);

		// Find campaignId by checking for click tracking in localStorage
		var campaignId = null;
		if (abGroup === "treatment" && config.campaigns && config.campaigns.length) {
			for (var i = 0; i < config.campaigns.length; i++) {
				var cpm = config.campaigns[i];
				try {
					if (localStorage.getItem(cpm.objectId + "_click")) {
						campaignId = cpm.objectId;
						break;
					}
				} catch (error) {
					log("[CONVERSION] Unable to check campaign click", error);
				}
			}
		}

		// Map device type: 0=mobile, 1=tablet, 2=desktop
		var deviceTypes = ["mobile", "tablet", "desktop"];
		var deviceType = deviceTypes[config.device] || "desktop";

		var apiBase = (config.api || API_BASE_FALLBACK).replace(/\/$/, "");
		var url = apiBase + CONVERSION_TRACKING_ENDPOINT;

		var payload = {
			siteId: config.siteId,
			deviceType: deviceType,
		};

		if (hasAbHoldoutEnabled(config)) {
			payload.abGroup = abGroup;
		}

		if (campaignId) {
			payload.campaignId = campaignId;
		}

		fetch(url, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify(payload),
		})
			.then(function (response) {
				if (response.ok) {
					// Set localStorage timestamp to prevent duplicate tracking
					try {
						localStorage.setItem(
							STORAGE_KEYS.conversionTracked,
							Date.now().toString(),
						);
						log("[CONVERSION] Successfully tracked conversion");
					} catch (error) {
						log("[CONVERSION] Failed to set tracking timestamp", error);
					}
				}
			})
			.catch(function (error) {
				console.error("[CONVERSION] Failed to track conversion", error);
			});
	}

	function maybeTrackSession(config) {
		if (!config.siteId) {
			log("[SESSION] No siteId", config);
			return;
		}

		if (!hasAbHoldoutEnabled(config)) {
			return;
		}

		var sessionTrackedStorageKey = getSessionTrackedStorageKey(config.siteId);

		try {
			if (sessionStorage.getItem(sessionTrackedStorageKey) === "true") {
				return;
			}
		} catch (error) {
			log("[SESSION] Unable to access sessionStorage", error);
		}

		var apiBase = (config.api || API_BASE_FALLBACK).replace(/\/$/, "");
		var url = apiBase + SESSION_TRACKING_ENDPOINT;
		var payload = {
			event: "Session",
			siteId: config.siteId,
			abGroup: assignAbGroup(config),
		};

		fetch(url, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify(payload),
			keepalive: true,
		})
			.then(function (response) {
				if (!response.ok) {
					return;
				}

				try {
					sessionStorage.setItem(sessionTrackedStorageKey, "true");
				} catch (error) {
					log("[SESSION] Failed to persist session tracking state", error);
				}
			})
			.catch(function (error) {
				console.error("[SESSION] Failed to track session", error);
			});
	}

	function maybeTrackTabRecoveryEvent(config, cpm, eventType) {
		if (
			!config ||
			!config.siteId ||
			!cpm ||
			!cpm.objectId ||
			!hasAbHoldoutEnabled(config)
		) {
			return;
		}

		if (
			eventType !== TAB_RECOVERY_EVENTS.inactive &&
			eventType !== TAB_RECOVERY_EVENTS.reactivated
		) {
			return;
		}

		var apiBase = (config.api || API_BASE_FALLBACK).replace(/\/$/, "");
		var url = apiBase + TAB_RECOVERY_TRACKING_ENDPOINT;
		var payload = {
			siteId: config.siteId,
			campaignId: cpm.objectId,
			abGroup: assignAbGroup(config),
			eventType: eventType,
		};

		fetch(url, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify(payload),
			keepalive: true,
		}).catch(function (error) {
			console.error("[TAB RECOVERY] Failed to track event", error);
		});
	}

	function maybePingOrderConfirmationVisit(config) {
		if (hasPingedOrderConfirmationVisit || !config.orderConfirmationPath) {
			return;
		}

		try {
			if (
				sessionStorage.getItem(STORAGE_KEYS.orderConfirmationPinged) === "true"
			) {
				hasPingedOrderConfirmationVisit = true;
				return;
			}
		} catch (error) {
			log(
				"[ORDER_CONFIRMATION] Unable to access sessionStorage for ping state",
				error,
			);
		}

		var pathMatches = false;
		var currentHref = window.location.href;

		if (
			typeof config.orderConfirmationPath === "object" &&
			config.orderConfirmationPath.type &&
			config.orderConfirmationPath.value
		) {
			var type = config.orderConfirmationPath.type;
			var value = config.orderConfirmationPath.value;

			switch (type) {
				case "equals":
					pathMatches = currentHref === value;
					break;
				case "contains":
					pathMatches = currentHref.includes(value);
					break;
				case "startsWith":
					pathMatches = currentHref.startsWith(value);
					break;
				case "endsWith":
					pathMatches = currentHref.endsWith(value);
					break;
				default:
					pathMatches = false;
			}
		}

		if (!pathMatches) {
			return;
		}

		hasPingedOrderConfirmationVisit = true;

		var apiBase = (config.api || API_BASE_FALLBACK).replace(/\/$/, "");
		var url = apiBase + ORDER_CONFIRM_PING_ENDPOINT;

		fetch(url, {
			method: "POST",
			keepalive: true,
		})
			.then(function (response) {
				if (!response.ok) {
					hasPingedOrderConfirmationVisit = false;
					return;
				}

				try {
					sessionStorage.setItem(STORAGE_KEYS.orderConfirmationPinged, "true");
				} catch (error) {
					log(
						"[ORDER_CONFIRMATION] Failed to persist ping session state",
						error,
					);
				}
			})
			.catch(function (error) {
				console.error(
					"[ORDER_CONFIRMATION] Failed to ping order confirmation visit",
					error,
				);
				hasPingedOrderConfirmationVisit = false;
			});
	}

	// BlurHash decoder
	// Copyright (c) 2021, Alexey Gusev mad.gooze@gmail.com
	var BlurHash = {
		digit:
			"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~",
		decode83: function (str, start, end) {
			var value = 0;
			while (start < end) {
				value *= 83;
				value += BlurHash.digit.indexOf(str.charAt(start++));
			}
			return value;
		},

		sRGBToLinear: function (value) {
			var d = 3294.6;
			var e = 269.025;
			return value > 10.31475 ? Math.pow(value / e + 0.052132, 2.4) : value / d;
		},

		linearTosRGB: function (v) {
			var d = 3294.6;
			var e = 269.025;
			return ~~(v > 0.00001227
				? e * Math.pow(v, 0.416666) - 13.025
				: v * d + 1);
		},

		signSqr: function (x) {
			return (x < 0 ? -1 : 1) * x * x;
		},

		fastCos: function (x) {
			var PI = Math.PI;
			var PI2 = PI * 2;
			x += PI / 2;
			while (x > PI) {
				x -= PI2;
			}
			var cos = 1.27323954 * x - 0.405284735 * BlurHash.signSqr(x);
			return 0.225 * (BlurHash.signSqr(cos) - cos) + cos;
		},

		getAverageColor: function (blurHash) {
			var val = BlurHash.decode83(blurHash, 2, 6);
			return [val >> 16, (val >> 8) & 255, val & 255];
		},

		decode: function (blurHash, width, height, punch) {
			var sizeFlag = BlurHash.decode83(blurHash, 0, 1);
			var numX = (sizeFlag % 9) + 1;
			var numY = ~~(sizeFlag / 9) + 1;
			var size = numX * numY;

			var i = 0,
				j = 0,
				x = 0,
				y = 0,
				r = 0,
				g = 0,
				b = 0,
				basis = 0,
				basisY = 0,
				colorIndex = 0,
				pixelIndex = 0,
				yh = 0,
				xw = 0,
				value = 0;

			var maximumValue =
				((BlurHash.decode83(blurHash, 1, 2) + 1) / 13446) * (punch || 1);

			var colors = new Float64Array(size * 3);

			var averageColor = BlurHash.getAverageColor(blurHash);
			for (i = 0; i < 3; i++) {
				colors[i] = BlurHash.sRGBToLinear(averageColor[i]);
			}

			for (i = 1; i < size; i++) {
				value = BlurHash.decode83(blurHash, 4 + i * 2, 6 + i * 2);
				colors[i * 3] =
					BlurHash.signSqr(~~(value / (19 * 19)) - 9) * maximumValue;
				colors[i * 3 + 1] =
					BlurHash.signSqr((~~(value / 19) % 19) - 9) * maximumValue;
				colors[i * 3 + 2] = BlurHash.signSqr((value % 19) - 9) * maximumValue;
			}

			var bytesPerRow = width * 4;
			var pixels = new Uint8ClampedArray(bytesPerRow * height);

			for (y = 0; y < height; y++) {
				yh = (Math.PI * y) / height;
				for (x = 0; x < width; x++) {
					r = 0;
					g = 0;
					b = 0;
					xw = (Math.PI * x) / width;

					for (j = 0; j < numY; j++) {
						basisY = BlurHash.fastCos(yh * j);
						for (i = 0; i < numX; i++) {
							basis = BlurHash.fastCos(xw * i) * basisY;
							colorIndex = (i + j * numX) * 3;
							r += colors[colorIndex] * basis;
							g += colors[colorIndex + 1] * basis;
							b += colors[colorIndex + 2] * basis;
						}
					}

					pixelIndex = 4 * x + y * bytesPerRow;
					pixels[pixelIndex] = BlurHash.linearTosRGB(r);
					pixels[pixelIndex + 1] = BlurHash.linearTosRGB(g);
					pixels[pixelIndex + 2] = BlurHash.linearTosRGB(b);
					pixels[pixelIndex + 3] = 255; // alpha
				}
			}
			return pixels;
		},
	};

	/**
	 * Countdown timer functionality
	 */
	var countdown = {
		// Parse duration string like "15m", "1h30m", "2d4h15m"
		parseDuration: function (durationStr) {
			var total = 0;
			var matches = durationStr.match(/(\d+)([dhms])/g);

			if (!matches) return null;

			matches.forEach(function (match) {
				var value = parseInt(match.slice(0, -1));
				var unit = match.slice(-1);

				switch (unit) {
					case "d":
						total += value * 24 * 60 * 60 * 1000;
						break;
					case "h":
						total += value * 60 * 60 * 1000;
						break;
					case "m":
						total += value * 60 * 1000;
						break;
					case "s":
						total += value * 1000;
						break;
				}
			});

			return total;
		},

		// Parse absolute datetime in ISO 8601 format
		parseAbsoluteTime: function (dateTimeStr) {
			try {
				return new Date(dateTimeStr).getTime();
			} catch (e) {
				return null;
			}
		},

		// Format countdown display based on time remaining
		formatTime: function (milliseconds) {
			if (milliseconds <= 0) return "00:00";

			var totalSeconds = Math.floor(milliseconds / 1000);
			var days = Math.floor(totalSeconds / (24 * 60 * 60));
			var hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
			var minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
			var seconds = totalSeconds % 60;

			// Pad with zeros
			var pad = function (num) {
				return num.toString().padStart(2, "0");
			};

			if (days > 0) {
				// D:HH:MM:SS
				return (
					days + ":" + pad(hours) + ":" + pad(minutes) + ":" + pad(seconds)
				);
			} else if (hours > 0) {
				// HH:MM:SS
				return pad(hours) + ":" + pad(minutes) + ":" + pad(seconds);
			} else {
				// MM:SS
				return pad(minutes) + ":" + pad(seconds);
			}
		},

		// Create and manage countdown timers
		create: function (targetTime, elements) {
			var intervalId = setInterval(function () {
				var now = Date.now();
				var remaining = targetTime - now;

				var formattedTime = countdown.formatTime(remaining);

				// Update all elements with this countdown
				elements.forEach(function (element) {
					if (remaining <= 0) {
						// Hide element when countdown reaches zero
						element.style.display = "none";
					} else {
						element.textContent = formattedTime;
					}
				});

				// Clear interval when countdown reaches zero
				if (remaining <= 0) {
					clearInterval(intervalId);
				}
			}, 1000);

			// Initial update
			var remaining = targetTime - Date.now();
			var formattedTime = countdown.formatTime(remaining);
			elements.forEach(function (element) {
				if (remaining <= 0) {
					element.style.display = "none";
				} else {
					element.textContent = formattedTime;
				}
			});

			return intervalId;
		},
	};

	/**
	 * Helper methods
	 */
	var utils = {
		copyToClipboard: function (text) {
			var copied = false;

			try {
				var textarea = document.createElement("textarea");
				textarea.value = text;
				textarea.setAttribute("readonly", "");
				textarea.style.position = "fixed";
				textarea.style.top = "-9999px";
				textarea.style.left = "-9999px";
				textarea.style.opacity = "0";
				textarea.style.pointerEvents = "none";
				document.body.appendChild(textarea);
				textarea.select();
				textarea.setSelectionRange(0, textarea.value.length);
				copied = document.execCommand("copy");
				document.body.removeChild(textarea);
			} catch (error) {
				// execCommand may be unavailable or blocked
			}

			if (copied) {
				log("Text copied to clipboard");
				return;
			}

			if (navigator.clipboard && navigator.clipboard.writeText) {
				navigator.clipboard
					.writeText(text)
					.then(function () {
						log("Text copied to clipboard");
					})
					.catch(function (error) {
						console.error("Failed to copy text: ", error);
					});
			} else {
				console.error("Failed to copy text: clipboard not available");
			}
		},
		// Tab activity tracking
		tabTracking: {
			TAB_ID: Date.now().toString() + Math.random().toString().substring(2, 8),
			ACTIVE_TAB_KEY: "cb_active_tab",

			// Initialize tab tracking
			init: function () {
				// Generate a truly unique tab ID to avoid collisions
				try {
					// Try to make the ID more unique by adding browser-specific identifiers
					const browserInfo =
						navigator.userAgent + window.screenX + window.screenY;
					const uniqueHash = this.hashString(browserInfo);
					this.TAB_ID =
						Date.now().toString() +
						uniqueHash +
						Math.random().toString().substring(2, 8);
					log("[TAB TRACKING] Generated tab ID:", this.TAB_ID);
				} catch (e) {
					// Fallback if the above fails
					this.TAB_ID =
						Date.now().toString() + Math.random().toString().substring(2, 8);
					log("[TAB TRACKING] Using fallback tab ID:", this.TAB_ID);
				}

				// Set up event listener for tab closing
				window.addEventListener("beforeunload", () => {
					log("[TAB TRACKING] Tab closing (beforeunload)");
					this.markTabAsInactive();
				});

				window.addEventListener("unload", () => {
					log("[TAB TRACKING] Tab closing (unload)");
					this.markTabAsInactive();
				});

				// Handle visibility changes
				document.addEventListener("visibilitychange", () => {
					const isVisible = !document.hidden;
					log("[TAB TRACKING] Visibility changed:", isVisible);

					if (isVisible) {
						// Mark this tab as active when it becomes visible
						this.markTabAsActive();
					}
				});

				// Handle focus events - these fire when user comes back to this tab
				window.addEventListener("focus", () => {
					log("[TAB TRACKING] Tab gained focus");
					this.markTabAsActive();
				});

				// Listen for changes from other tabs
				window.addEventListener("storage", (e) => {
					if (e.key === this.ACTIVE_TAB_KEY) {
						log("[TAB TRACKING] Active tab changed in another tab");
					}
				});

				// Mark this tab as active initially if it's visible
				if (!document.hidden) {
					this.markTabAsActive();
				}

				// Regular ping to keep this tab marked as active (handles cases where events might be missed)
				setInterval(() => {
					if (!document.hidden) {
						this.markTabAsActive();
					}
				}, 30000); // Every 30 seconds
			},

			// Simple string hashing function for creating unique IDs
			hashString: function (str) {
				let hash = 0;
				for (let i = 0; i < str.length; i++) {
					const char = str.charCodeAt(i);
					hash = (hash << 5) - hash + char;
					hash = hash & hash; // Convert to 32bit integer
				}
				return Math.abs(hash).toString(36); // Convert to base 36 (alphanumeric)
			},

			// Mark this tab as the active tab
			markTabAsActive: function () {
				try {
					localStorage.setItem(this.ACTIVE_TAB_KEY, this.TAB_ID);
					log("[TAB TRACKING] Marked as active tab:", this.TAB_ID);
				} catch (e) {
					log("[TAB TRACKING] Error marking as active tab:", e);
				}
			},

			// Mark this tab as inactive (when closing)
			markTabAsInactive: function () {
				try {
					// Only remove if this tab is the active one
					if (localStorage.getItem(this.ACTIVE_TAB_KEY) === this.TAB_ID) {
						localStorage.removeItem(this.ACTIVE_TAB_KEY);
						log("[TAB TRACKING] Removed active tab status");
					}
				} catch (e) {
					log("[TAB TRACKING] Error removing active tab status:", e);
				}
			},

			// Check if user is active in any other tab of the same site
			isUserActiveInOtherTab: function () {
				try {
					const activeTabId = localStorage.getItem(this.ACTIVE_TAB_KEY);

					// If there's no active tab or this is the active tab, user is not in another tab
					if (!activeTabId || activeTabId === this.TAB_ID) {
						log(
							"[TAB TRACKING] User not active in other tabs, or current tab is the active one",
						);
						return false;
					}

					// If active tab is different from this one, user is in another tab
					log("[TAB TRACKING] User active in another tab:", activeTabId);
					return true;
				} catch (e) {
					log("[TAB TRACKING] Error checking active tab:", e);
					return false;
				}
			},
		},
		scrollCheck: (function () {
			var lastPosition,
				newPosition,
				timer,
				delta,
				delay = 50;

			function clear() {
				lastPosition = null;
				delta = 0;
			}

			clear();

			return function () {
				newPosition = window.scrollY;

				if (lastPosition != null) delta = newPosition - lastPosition;

				lastPosition = newPosition;
				clearTimeout(timer);
				timer = setTimeout(clear, delay);

				return delta;
			};
		})(),
		getScrollPercent: function () {
			var h = document.documentElement,
				b = document.body,
				st = "scrollTop",
				sh = "scrollHeight";

			return ((h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight)) * 100;
		},
		lead: function (objectId, input) {
			var xhr = new XMLHttpRequest();
			xhr.open("POST", `${config.api}/campaigns/lead`, true);
			xhr.setRequestHeader("Content-Type", "application/json");
			xhr.send(JSON.stringify({ objectId, input }));
		},
		ambassadorSubmit: async function (objectId, name, email) {
			return new Promise((resolve, reject) => {
				var xhr = new XMLHttpRequest();
				xhr.open("POST", `${config.api}/ambassador/submit`, true);
				xhr.setRequestHeader("Content-Type", "application/json");

				xhr.onload = function () {
					if (xhr.status === 200) {
						try {
							const responseData = JSON.parse(xhr.responseText);
							resolve(responseData);
						} catch (error) {
							resolve({ success: true });
						}
					} else {
						reject(new Error(`HTTP error! status: ${xhr.status}`));
					}
				};

				xhr.onerror = function () {
					reject(new Error("Network error occurred"));
				};

				xhr.send(JSON.stringify({ objectId, name, email }));
			});
		},
		proxy: async function proxy(url, type) {
			return new Promise((resolve, reject) => {
				var xhr = new XMLHttpRequest();
				xhr.open("POST", `${config.api}/proxy/${type}`, true);
				xhr.setRequestHeader("Content-Type", "application/json");

				xhr.onload = function () {
					if (xhr.status === 200) {
						try {
							const responseData = JSON.parse(xhr.responseText);
							if (responseData.type && responseData.key && responseData.value) {
								resolve(responseData);
							} else {
								reject(new Error("Response does not match expected format"));
							}
						} catch (error) {
							reject(new Error("Failed to parse JSON response"));
						}
					} else {
						reject(new Error(`HTTP error! status: ${xhr.status}`));
					}
				};

				xhr.onerror = function () {
					reject(new Error("Network error occurred"));
				};

				xhr.send(JSON.stringify({ url: url }));
			});
		},
		ping: async function (cpm, type) {
			// Skip ping in demo mode
			if (sessionStorage.getItem(STORAGE_KEYS.demoMode) === "true")
				return false;

			// Skip all pings for popups replayed in a split-tab reveal tab
			// (the originating tab already counted the click and fired callbacks)
			if (cpm.suppressPings) return false;

			// For visits type, check daily frequency cap
			if (type === "visits") {
				var visitTrackedAt = window.localStorage.getItem(
					`${cpm.objectId}_visit`,
				);
				if (
					visitTrackedAt &&
					Date.now() - visitTrackedAt < VISIT_FREQUENCY_CAP
				) {
					log("[PING] Visit already tracked today");
					return false;
				}

				// Set timestamp for visits immediately after frequency check
				window.localStorage.setItem(`${cpm.objectId}_visit`, Date.now());
			}

			// For clicks type, check weekly frequency cap
			if (type === "clicks") {
				var clickTrackedAt = window.localStorage.getItem(
					`${cpm.objectId}_click`,
				);
				var frequencyCap =
					cpm.frequencyCap * 1000 * 60 * 60 * 24 || FREQUENCY_CAP;

				if (clickTrackedAt && Date.now() - clickTrackedAt < frequencyCap) {
					log("[PING] Click already tracked");
					return false;
				}

				// Set timestamp for clicks immediately after frequency check
				window.localStorage.setItem(`${cpm.objectId}_click`, Date.now());
			}

			var fields = {
				clicks: "clickURLs",
				impressions: "impressionURLs",
			};

			// add ref tag
			var tag = document.createElement("meta");
			tag.name = "referrer";
			tag.content = "origin";
			document.head.appendChild(tag);

			// ping counter
			var cF = document.createElement("iframe");
			cF.referrerPolicy = "origin";
			cF.style.width = "0px";
			cF.style.height = "0px";

			let url = `${config.api}/hits/${type}/${cpm.objectId}?device=${config.device}`;

			// add nested parameter if campaign is nested
			if (cpm.isNested) {
				url += `&nested=${cpm.isNested}`;
			}

			cF.setAttribute("src", url);

			document.body.appendChild(cF);

			// skip ping if cpm has nested property
			if (cpm.hasOwnProperty("nested")) return false;

			// ping third party
			if (cpm.hasOwnProperty(fields[type])) {
				const pingURLs = cpm[fields[type]];

				// trigger proxy request
				if (config.shouldProxy && type == "clicks") {
					try {
						const data = await utils.proxy(pingURLs[0], config.shouldProxy);

						if (data.type && window[data.type])
							window[data.type].setItem(data.key, data.value);
					} catch (err) {
						log("[PROXY] Failed fetching data", err.message);
					}
				} else {
					pingURLs.forEach((url) => {
						const iF = document.createElement("iframe");
						iF.referrerPolicy = "origin";
						iF.style.width = "0px";
						iF.style.height = "0px";
						iF.setAttribute("src", url);
						document.body.appendChild(iF);

						log(`[PING] ${type} ${url}`);

						// remove iframe after 10 seconds
						setTimeout(function () {
							iF.remove();
						}, 10000);
					});
				}
			}

			setTimeout(function () {
				cF.remove();
				tag.remove();
			}, 10000);
		},
		shouldSplitTab: function (cpm) {
			// split-tab method is opt-in via campaign setting (default: background iframe)
			if (cpm.clickCallbackInBackground !== false) return false;

			// keep the merchant page intact in demo mode
			if (sessionStorage.getItem(STORAGE_KEYS.demoMode) === "true")
				return false;

			// out of scope: ambassador popups and nested refer-a-friend campaigns
			if (cpm.type === "AMBASSADOR" || cpm.isNested) return false;

			// without a click callback URL there is nothing to redirect to
			var clickURLs = (cpm.clickURLs || []).filter(Boolean);
			return !!clickURLs.length;
		},
		campaignHasDiscountCode: function (cpm) {
			if (cpm.customDiscountCodesAvailable) return true;

			if (
				config.customDiscountCodesAvailable &&
				cpm.codesRunOut !== "template"
			) {
				return true;
			}

			if (!cpm.template) return false;

			// templates hide the discount element when no code is configured
			if (
				/data-type=["']Discount code["'][^>]*style=["'][^"']*display\s*:\s*none/i.test(
					cpm.template,
				)
			) {
				return false;
			}

			return /data-type=["']Discount code["']/i.test(cpm.template);
		},
		buildSplitTabURL: function (baseUrl, options) {
			options = options || {};
			var url = new URL(baseUrl, window.location.href);

			url.searchParams.delete("cb_reveal");
			url.searchParams.delete("cb_suppress");

			if (options.revealCampaignId) {
				url.searchParams.set("cb_reveal", options.revealCampaignId);
			}

			if (options.suppressCampaignId) {
				url.searchParams.set("cb_suppress", options.suppressCampaignId);
			}

			return url.toString();
		},
		splitTabRedirect: function (cpm, newTabURL) {
			// the new tab must open synchronously within the user gesture,
			// otherwise popup blockers will kill it
			window.open(newTabURL, "_blank", "noopener,noreferrer");

			// record the internal click stat once, from this tab;
			// keepalive lets the request survive the redirect below
			// (an iframe would be cancelled by the navigation)
			try {
				window.localStorage.setItem(`${cpm.objectId}_click`, Date.now());
			} catch (err) {
				log("[SPLIT-TAB] Failed to persist click timestamp", err);
			}
			try {
				fetch(
					`${config.api}/hits/clicks/${cpm.objectId}?device=${config.device}`,
					{ mode: "no-cors", keepalive: true },
				);
			} catch (err) {
				log("[SPLIT-TAB] Failed to send click hit", err);
			}

			var clickURLs = (cpm.clickURLs || []).filter(Boolean);

			// fire secondary click callbacks via background iframes
			clickURLs.slice(1).forEach(function (url) {
				var iF = document.createElement("iframe");
				iF.referrerPolicy = "origin";
				iF.style.width = "0px";
				iF.style.height = "0px";
				iF.setAttribute("src", url);
				document.body.appendChild(iF);

				log(`[SPLIT-TAB] secondary click callback ${url}`);
			});

			log("[SPLIT-TAB] Redirecting to click callback", clickURLs[0]);

			// give secondary iframes and pending requests (e.g. lead) a moment
			// to fire before navigating away; the new tab is already in focus
			setTimeout(function () {
				window.location.href = clickURLs[0];
			}, 500);
		},
		splitTabReveal: function (cpm) {
			if (!utils.campaignHasDiscountCode(cpm)) {
				log(
					"[SPLIT-TAB] Reveal skipped — no discount code, using non-discount flow",
				);

				var fallbackURL;
				if (cpm.submitURL) {
					fallbackURL = cpm.submitURL;
					if (fallbackURL.includes("{{current_page}}")) {
						var currentPageUrl = utils.buildSplitTabURL(window.location.href);
						fallbackURL = fallbackURL.replace(
							"{{current_page}}",
							encodeURIComponent(currentPageUrl),
						);
					}
				} else {
					fallbackURL = utils.buildSplitTabURL(window.location.href, {
						suppressCampaignId: cpm.objectId,
					});
				}

				utils.splitTabRedirect(cpm, fallbackURL);
				return;
			}

			// new tab reopens the current page with a flag that force-shows
			// this campaign's popup with the discount code revealed
			var newTabURL = utils.buildSplitTabURL(window.location.href, {
				revealCampaignId: cpm.objectId,
			});
			utils.splitTabRedirect(cpm, newTabURL);
		},
		pingSite: function () {
			var xhr = new XMLHttpRequest();
			xhr.open("POST", `${config.api}/sites/ping`, true);
			xhr.setRequestHeader("Content-Type", "");
			xhr.send();
		},
		loadFont: function (fontName) {
			// check if font is already loaded
			var fonts = document.head.querySelectorAll(
				'link[href*="' + fontsURL + fontName + '"]',
			);

			// exit if font was already loaded
			if (fonts.length) return false;

			// proceed to loading font
			var link = document.createElement("link");
			link.href = "https://" + fontsURL + fontName + "&display=swap";
			link.type = "text/css";
			link.rel = "stylesheet";
			document.head.appendChild(link);

			return true;
		},
		getDiscountCode: async function (objectId) {
			try {
				const response = await fetch(`${config.api}/discount/${objectId}`, {
					method: "POST",
				});
				const data = await response.json();
				return data;
			} catch (err) {
				log(err);
			}
		},
		checkIfShown: function (cpm) {
			// Skip frequency cap in demo mode
			if (sessionStorage.getItem(STORAGE_KEYS.demoMode) === "true")
				return false;

			// check if frequency cap is disabled
			if (cpm.skipFrequencyCap) return false;

			// check if frequency cap is set to 0
			if (cpm.frequencyCap === "0") return false;

			var frequencyCap =
				cpm.frequencyCap * 1000 * 60 * 60 * 24 || FREQUENCY_CAP;

			// check if popup was already shown
			if (window.localStorage.getItem(cpm.objectId)) {
				var lastShown = window.localStorage.getItem(cpm.objectId);

				// check if popup was shown in the last week
				if (Date.now() - lastShown < frequencyCap) {
					log("[POPUP] Already shown in the last week");
					return true;
				}
			}

			// check if campaign type was already shown
			if (window.localStorage.getItem(cpm.type)) {
				var lastShown = window.localStorage.getItem(cpm.type);

				// check if campaign type was shown in the last week
				if (Date.now() - lastShown < frequencyCap) {
					log("[POPUP] Campaign type already shown in the last week");
					return true;
				}
			}

			return false;
		},
		copyElementText: function (element, text, callback) {
			element.addEventListener("click", function () {
				// copy text to clipboard
				utils.copyToClipboard(text);

				// create container element
				var container = document.createElement("div");
				container.style.position = "absolute";
				container.style.width = "100%";
				container.style.height = "100%";
				container.style.top = "0";
				container.style.right = "0";
				container.style.display = "flex";
				container.style.justifyContent = "center";
				container.style.alignItems = "center";
				container.style.backgroundColor = element.style.backgroundColor;

				// create icon element
				var icon = document.createElement("svg");
				icon.style.display = "block";
				icon.style.height = "24px";
				icon.style.width = "24px";
				icon.style.fill = "currentColor";
				icon.style.transform = "scale(1.5)";
				icon.innerHTML = `
				<svg
					fill="none"
					viewBox="0 0 24 24"
					stroke-width="1"
					stroke="currentColor"
				>
					<path
						stroke-linecap="round"
						stroke-linejoin="round"
						d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75"
					/>
				</svg>
			`;

				// append icon to container
				container.appendChild(icon);

				// append container to element
				element.appendChild(container);

				// call callback function if provided
				if (callback) callback();

				setTimeout(function () {
					// remove icon
					container.remove();
				}, 1000);
			});
		},
		handleSocialShares: function (shadow, cpm) {
			try {
				const shareMessage = shadow.querySelector(
					"[cv-share-link-message]",
				)?.textContent;

				const socialShareButtons = [
					"facebook",
					"whatsapp",
					"telegram",
					"twitter",
				];

				socialShareButtons.forEach((platform) => {
					const shareButton = shadow.querySelector(`[cv-social-${platform}]`);

					if (shareButton) {
						shareButton.addEventListener("click", () => {
							// Trigger click event for tracking
							utils.ping(cpm, "clicks");

							// Construct the appropriate share URL based on platform
							const shareUrl = getShareUrl(
								platform,
								encodeURIComponent(shareMessage),
								cpm.referralLink,
							);

							// Open the share dialog in a new window
							window.open(
								shareUrl,
								`${platform}-share-dialog`,
								"width=626,height=436",
							);
						});
					}
				});
			} catch (error) {
				log("[SHARE]", error);
			}

			function getShareUrl(platform, shareMessage, referralLink) {
				switch (platform) {
					case "facebook":
						return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
							referralLink,
						)}`;
					case "whatsapp":
						return `https://api.whatsapp.com/send?text=${shareMessage}%20${encodeURIComponent(
							referralLink,
						)}`;
					case "telegram":
						return `https://t.me/share/url?url=${encodeURIComponent(
							referralLink,
						)}&text=${shareMessage}`;
					case "twitter":
						return `https://twitter.com/intent/tweet?text=${shareMessage}%20${encodeURIComponent(
							referralLink,
						)}`;
					default:
						return ""; // Handle unsupported platforms
				}
			}
		},
		queryDeepSelector: function (selector) {
			// Check if selector contains a pseudo-element
			const pseudoMatch = selector.match(/(.*?)(::[a-zA-Z-]+)$/);
			const baseSelector = pseudoMatch ? pseudoMatch[1] : selector;

			// Split selector by @ to get path through shadow roots
			const parts = baseSelector.split("@");
			let element = document;

			for (let i = 0; i < parts.length; i++) {
				const part = parts[i].trim();
				if (!part) continue;

				if (i === parts.length - 1) {
					// Last part - regular querySelector
					element = element.querySelector(part);
				} else {
					// Get element and its shadow root or document fragment
					const nextElement = element.querySelector(part);
					element = nextElement?.shadowRoot || nextElement?.content || null;
				}

				if (!element) return null;
			}

			// Store pseudo-element info on the element if it exists
			if (pseudoMatch) {
				element._pseudoElement = pseudoMatch[2];
				element._pseudoStyle = window.getComputedStyle(
					element,
					element._pseudoElement,
				);
			}

			return element;
		},
		prefillDiscountCode: function (cpm) {
			var storedDiscountCode = sessionStorage.getItem(
				`cb_discount_code_${cpm.objectId}`,
			);
			if (!storedDiscountCode) {
				var pendingDiscountCode = sessionStorage.getItem(
					STORAGE_KEYS.pendingDiscountCode,
				);
				var pendingDiscountCampaignId = sessionStorage.getItem(
					STORAGE_KEYS.pendingDiscountCampaignId,
				);

				if (
					pendingDiscountCode &&
					pendingDiscountCampaignId &&
					pendingDiscountCampaignId === cpm.objectId
				) {
					storedDiscountCode = pendingDiscountCode;
					sessionStorage.setItem(
						`cb_discount_code_${cpm.objectId}`,
						storedDiscountCode,
					);
					sessionStorage.removeItem(STORAGE_KEYS.pendingDiscountCode);
					sessionStorage.removeItem(STORAGE_KEYS.pendingDiscountCampaignId);
				}
			}

			if (config.autoApplyDiscountCodeCSSSelector && storedDiscountCode) {
				const discountCodeInput = document.querySelector(
					config.autoApplyDiscountCodeCSSSelector,
				);
				if (discountCodeInput) {
					discountCodeInput.value = storedDiscountCode;
				} else {
					log("[PREFILL] Discount code input not found ");
				}
			} else {
				log(
					"[PREFILL] Discount code not found",
					config.autoApplyDiscountCodeCSSSelector,
					storedDiscountCode,
				);
			}
		},
		processCountdowns: function (shadow) {
			// Find all countdown placeholders in the template
			var walker = document.createTreeWalker(
				shadow,
				NodeFilter.SHOW_TEXT,
				null,
				false,
			);

			var countdownGroups = {}; // Group identical placeholders
			var countdownIntervals = [];
			var textNodes = [];

			// Collect all text nodes that contain countdown placeholders
			var node;
			while ((node = walker.nextNode())) {
				if (node.textContent && node.textContent.includes("{{countdown:")) {
					textNodes.push(node);
				}
			}

			// Process each text node
			textNodes.forEach(function (textNode) {
				var content = textNode.textContent;
				var countdownMatches = content.match(/\{\{countdown:([^}]+)\}\}/g);

				if (!countdownMatches) return;

				countdownMatches.forEach(function (match) {
					var target = match.replace(/\{\{countdown:([^}]+)\}\}/, "$1");

					if (!countdownGroups[target]) {
						countdownGroups[target] = [];
					}

					countdownGroups[target].push({
						textNode: textNode,
						placeholder: match,
						target: target,
					});
				});
			});

			// Second pass: process each unique countdown target
			Object.keys(countdownGroups).forEach(function (target) {
				var group = countdownGroups[target];
				var targetTime = null;
				var now = Date.now();

				// Parse target time
				if (target.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?$/)) {
					// Absolute datetime
					targetTime = countdown.parseAbsoluteTime(target);
				} else if (target.match(/^(\d+[dhms])+$/)) {
					// Duration format
					var duration = countdown.parseDuration(target);
					if (duration !== null) {
						targetTime = now + duration;
					}
				}

				if (targetTime === null) {
					log("[COUNTDOWN] Invalid target format:", target);
					return;
				}

				// Create placeholder elements for countdown display
				var countdownElements = [];

				group.forEach(function (item) {
					var textNode = item.textNode;
					var placeholder = item.placeholder;

					// Create a span element to hold the countdown
					var countdownSpan = document.createElement("span");
					countdownSpan.style.fontFamily = "monospace";
					countdownSpan.style.display = "inline";

					// Split the text content around the placeholder
					var content = textNode.textContent;
					var placeholderIndex = content.indexOf(placeholder);

					if (placeholderIndex === -1) return; // Placeholder not found

					var beforeText = content.substring(0, placeholderIndex);
					var afterText = content.substring(
						placeholderIndex + placeholder.length,
					);

					// Create document fragments
					var parentNode = textNode.parentNode;

					// Replace the text node with split parts and countdown span
					if (beforeText) {
						var beforeTextNode = document.createTextNode(beforeText);
						parentNode.insertBefore(beforeTextNode, textNode);
					}

					parentNode.insertBefore(countdownSpan, textNode);

					if (afterText) {
						var afterTextNode = document.createTextNode(afterText);
						parentNode.insertBefore(afterTextNode, textNode);
					}

					// Remove the original text node
					parentNode.removeChild(textNode);

					countdownElements.push(countdownSpan);
				});

				// Start countdown for this group
				if (countdownElements.length > 0) {
					var intervalId = countdown.create(targetTime, countdownElements);
					countdownIntervals.push(intervalId);
					log(
						"[COUNTDOWN] Started countdown for target:",
						target,
						"with",
						countdownElements.length,
						"elements",
					);
				}
			});

			return countdownIntervals;
		},
	};

	/**
	 * Popup
	 */
	var popup = {
		show: async function (cpm, options) {
			options = options || { minimized: false };

			// check if any popup is currently showing (prevent multiple popups)
			if (
				!options.minimized &&
				document.querySelector('[id^="cv-popup-container-"]')
			) {
				log("[POPUP] Another popup is already showing");
				return false;
			}

			// check if a popup for this campaign is already open or minimized
			if (
				document.querySelector("#cv-popup-container-" + cpm.objectId) &&
				!options.minimized
			) {
				log("[POPUP] Already open/minimized");
				return false;
			}

			// keep popup closed in tabs opened by the split-tab flow
			if (
				!options.forceReveal &&
				sessionStorage.getItem(
					STORAGE_KEYS.popupSuppressedPrefix + cpm.objectId,
				) === "true"
			) {
				log("[POPUP] Suppressed in this tab (split-tab flow)");
				return false;
			}

			// forced reveal bypasses traffic source and frequency cap gates
			if (!options.forceReveal) {
				// check site-level traffic sources
				if (
					sessionStorage.getItem("cb_traffic_sources_include_site") === "false"
				) {
					log("[POPUP] Site-level traffic sources include condition not met");
					return false;
				}
				if (
					sessionStorage.getItem("cb_traffic_sources_exclude_site") === "true"
				) {
					log("[POPUP] Site-level traffic sources exclude condition met");
					return false;
				}

				// check campaign-level traffic sources
				if (
					sessionStorage.getItem(
						`cb_traffic_sources_include_${cpm.objectId}`,
					) === "false"
				) {
					log("[POPUP] Traffic sources include condition not met");
					return false;
				}
				if (
					sessionStorage.getItem(
						`cb_traffic_sources_exclude_${cpm.objectId}`,
					) === "true"
				) {
					log("[POPUP] Traffic sources exclude condition met");
					return false;
				}
			}

			// check if template is defined
			if (!cpm.template) {
				log("[POPUP] Missing campaign template");
				return false;
			}

			if (utils.checkIfShown(cpm) && !options.minimized && !options.forceReveal)
				return false;

			log("[POPUP] Show");

			// preload fonts
			if (cpm.fonts.length) {
				log("[FONTS] Loading", cpm.fonts);

				for (var j = 0; j < cpm.fonts.length; j++)
					utils.loadFont(cpm.fonts[j].replace(/\s/g, "+"));
			}

			if (sessionStorage.getItem(STORAGE_KEYS.demoMode) !== "true") {
				// prevent further popups
				window.localStorage.setItem(cpm.objectId, Date.now());

				// prevent further popups of the same type
				window.localStorage.setItem(cpm.type, Date.now());
			}

			// hide overflow scrollbar
			document.body.style.overflow = "hidden";

			// hide margin (if any)
			document.body.style.margin = "0px";

			// Create a container element for the popup content
			popup.content = document.createElement("div");

			// Set the popup content id to be unique
			popup.content.id = "cv-popup-container-" + cpm.objectId;

			// Override the display property
			popup.content.style.display = "block";

			// Create a shadow DOM for the popup content
			const shadow = popup.content.attachShadow({ mode: "open" });

			// Create a container for the popup content inside the shadow DOM
			const popupContainer = document.createElement("div");
			popupContainer.innerHTML = cpm.template;
			popupContainer.id = cpm.objectId;
			popupContainer.setAttribute("cv-popup", "");

			// Apply styles to the shadow DOM
			const style = document.createElement("style");
			style.textContent = `
				[cv-popup] {
					position: fixed;
					background-color: rgba(0, 0, 0, 0.6);
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					z-index: 99999999;
					display: flex;
					justify-content: center;
					align-items: center;
					transition: background-color 0.3s ease;
				}
				[cv-popup] > * {
					transition: opacity 0.3s ease, transform 0.3s ease;
				}
				[cv-badge] {
					display: none;
					position: fixed;
					bottom: 20px;
					${cpm.badge === "left" ? "left: 20px;" : "right: 20px;"}
					background-color: ${config.defaults?.button?.backgroundColor || "#4539F6"};
					color: ${config.defaults?.button?.color || "#ffffff"};
					padding: 10px 15px;
					border-radius: 25px;
					cursor: pointer;
					z-index: 999999999;
					font-family: Inter, sans-serif;
					font-size: 14px;
					font-weight: bold;
					box-shadow: 0 4px 12px rgba(0,0,0,0.15);
					transition: transform 0.3s ease, opacity 0.3s ease;
					transform: translateY(100px);
					opacity: 0;
					pointer-events: none;
				}
				[cv-badge].visible {
					transform: translateY(0);
					opacity: 1;
					pointer-events: auto;
				}
				
				/* Metallic effect for discount boxes - overlay approach */
				.metallic-effect {
					position: relative;
					overflow: hidden;
				}

				.metallic-effect::before {
					content: '';
					position: absolute;
					top: 0;
					left: 0;
					right: 0;
					bottom: 0;
					background: linear-gradient(
						45deg,
						rgba(0, 0, 0, 0.1) -1.5%,
						rgba(255, 255, 255, 0.15) 9.11%,
						rgba(0, 0, 0, 0.07) 30.33%,
						rgba(255, 255, 255, 0.2) 51.55%,
						rgba(0, 0, 0, 0.07) 72.77%,
						rgba(255, 255, 255, 0.15) 83.38%,
						rgba(0, 0, 0, 0.1) 104.6%
					);
					pointer-events: none;
					z-index: 1;
					border-radius: inherit;
				}

				.metallic-effect::after {
					content: '';
					position: absolute;
					top: 0;
					left: 0;
					width: 100%;
					height: 100%;
					background: linear-gradient(
						120deg,
						rgba(255, 255, 255, 0) 0%,
						rgba(255, 255, 255, 0) 40%,
						rgba(255, 255, 255, 0.6) 50%,
						rgba(255, 255, 255, 0) 60%,
						rgba(255, 255, 255, 0) 100%
					);
					background-size: 300% 100%;
					transform: skewX(-20deg);
					animation: shine-contained 5s infinite;
					pointer-events: none;
					z-index: 2;
				}

				@keyframes shine-contained {
					0% {
						background-position: -50% 0%;
					}
					20% {
						background-position: 100% 0%;
					}
					100% {
						background-position: 100% 0%;
					}
				}

				/* Success message styles */
				.cv-success-message {
					position: absolute;
					top: 16px;
					left: 50%;
					transform: translateX(-50%) translateY(-50px);
					background-color: #00A36C;
					color: white;
					padding: 10px 20px;
					text-align: center;
					font-family: Inter, sans-serif;
					font-size: 14px;
					font-weight: 500;
					z-index: 1000;
					opacity: 0;
					transition: all 0.3s ease;
					border-radius: 25px;
					box-shadow: 0 4px 12px rgba(0, 163, 108, 0.3);
					white-space: nowrap;
				}

				.cv-success-message.show {
					opacity: 1;
					transform: translateX(-50%) translateY(0);
				}

				.cv-success-message.fade-out {
					opacity: 0;
					transform: translateX(-50%) translateY(-20px);
				}
			`;
			shadow.appendChild(style);

			// Append the popup container to the shadow DOM
			shadow.appendChild(popupContainer);

			// find close button
			popup.close = shadow.querySelector("[cv-close]");

			if (popup.close) {
				// remove attribute
				popup.close.removeAttribute("cv-close");

				// set close button color
				popup.close.style.color = config.defaults?.body?.color || "#000000";
			} else {
				// create a close button
				popup.close = document.createElement("div");
				popup.close.innerHTML = "&times;";
				popup.close.setAttribute(
					"style",
					"cursor:pointer;font-size:24px;line-height:14px;content:x;top:12px;position:absolute;right:12px;",
				);
				popupContainer.appendChild(popup.close);
			}

			// make first child relative
			popupContainer.firstChild.style.position = "relative";

			// Create and handle minimize/badge logic
			if (cpm.badge) {
				const badgeEl = document.createElement("div");
				badgeEl.setAttribute("cv-badge", "");
				badgeEl.innerHTML = `
				<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24" fill="none">
					<path fill="#fff"d="M19.5 7.75H18.1C18.5 7.27 18.75 6.67 18.75 6C18.75 4.48 17.52 3.25 16 3.25C14.32 3.25 12.84 4.14 12 5.46C11.16 4.14 9.68 3.25 8 3.25C6.48 3.25 5.25 4.48 5.25 6C5.25 6.67 5.5 7.27 5.9 7.75H4.5C3.81 7.75 3.25 8.31 3.25 9V11.5C3.25 12.1 3.68 12.58 4.25 12.7V19.5C4.25 20.19 4.81 20.75 5.5 20.75H18.5C19.19 20.75 19.75 20.19 19.75 19.5V12.7C20.32 12.58 20.75 12.1 20.75 11.5V9C20.75 8.31 20.19 7.75 19.5 7.75ZM19.25 11.25H12.75V9.25H19.25V11.25ZM16 4.75C16.69 4.75 17.25 5.31 17.25 6C17.25 6.69 16.69 7.25 16 7.25H12.84C13.18 5.82 14.47 4.75 16 4.75ZM8 4.75C9.53 4.75 10.82 5.82 11.16 7.25H8C7.31 7.25 6.75 6.69 6.75 6C6.75 5.31 7.31 4.75 8 4.75ZM4.75 9.25H11.25V11.25H4.75V9.25ZM5.75 12.75H11.25V19.25H5.75V12.75ZM18.25 19.25H12.75V12.75H18.25V19.25Z" fill="#000000"/>
				</svg>
			`;
				shadow.appendChild(badgeEl);

				const popupContentEl = popupContainer.firstChild;

				const minimize = () => {
					popupContentEl.style.opacity = "0";
					popupContentEl.style.transform = "scale(0.95)";
					popupContainer.style.backgroundColor = "transparent";
					popupContainer.style.pointerEvents = "none";

					// Restore body styles when minimizing
					document.body.style.overflow = config.initialOverflow;
					document.body.style.margin = config.initialMargin;

					badgeEl.style.display = "block";
					setTimeout(() => badgeEl.classList.add("visible"), 10);

					sessionStorage.setItem("cb_minimized_" + cpm.objectId, "true");
				};

				const maximize = () => {
					popupContentEl.style.opacity = "1";
					popupContentEl.style.transform = "scale(1)";
					popupContainer.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
					popupContainer.style.pointerEvents = "auto";

					// Hide overflow when maximizing popup
					document.body.style.overflow = "hidden";
					document.body.style.margin = "0px";

					badgeEl.classList.remove("visible");
					setTimeout(() => {
						if (!badgeEl.classList.contains("visible")) {
							badgeEl.style.display = "none";
						}
					}, 300);

					sessionStorage.removeItem("cb_minimized_" + cpm.objectId);
				};

				// Make close button behave like minimize when badge is enabled
				popup.close.addEventListener("click", function () {
					minimize();
				});

				badgeEl.addEventListener("click", maximize);

				if (options.minimized) {
					// Directly set minimized state without transition
					popupContentEl.style.transition = "none";
					popupContainer.style.transition = "none";
					minimize();
					// Restore transitions for user actions
					setTimeout(() => {
						popupContentEl.style.transition =
							"opacity 0.3s ease, transform 0.3s ease";
						popupContainer.style.transition = "background-color 0.3s ease";
					}, 10);
				}
			} else {
				// Normal close behavior when badge is not enabled
				popup.close.addEventListener("click", function () {
					utils.ping(cpm, "dismissals");
					popup.hide(cpm);
				});
			}

			// trigger impression only if not minimized
			if (!options.minimized) {
				utils.ping(cpm, "impressions");
			}

			// track engagement (once per popup show)
			if (!options.minimized) {
				var engagementTracked = false;

				if (config.device >= 2) {
					// Desktop: pointer enters popup and stays ≥300ms
					var engagementTimer = null;
					popupContainer.addEventListener("pointerenter", function () {
						if (engagementTracked) return;
						engagementTimer = setTimeout(function () {
							if (!engagementTracked) {
								engagementTracked = true;
								utils.ping(cpm, "engagements");
							}
						}, 300);
					});
					popupContainer.addEventListener("pointerleave", function () {
						if (engagementTimer) {
							clearTimeout(engagementTimer);
							engagementTimer = null;
						}
					});
				} else {
					// Mobile: touchstart inside popup
					popupContainer.addEventListener("touchstart", function () {
						if (engagementTracked) return;
						engagementTracked = true;
						utils.ping(cpm, "engagements");
					});
				}
			}

			// find submit element (if any)
			popup.submit = shadow.querySelector("[cv-submit]");

			// find input element (if any)
			popup.input = shadow.querySelector("[cv-input]");

			// handle AMBASSADOR campaign type: wrap inputs in form and set validation
			if (cpm.type === "AMBASSADOR") {
				var nameInput = shadow.querySelector("[cv-name-input]");
				var emailInput = shadow.querySelector("[cv-email-input]");

				if (nameInput && emailInput) {
					// find the container that holds both inputs
					var inputContainer = nameInput.parentElement;

					// preserve container styles
					var containerStyles = window.getComputedStyle(inputContainer);

					// create form element
					var form = document.createElement("form");
					form.setAttribute("novalidate", ""); // Prevent default browser validation popup

					// copy container styles to form to preserve layout
					if (containerStyles.display)
						form.style.display = containerStyles.display;
					if (containerStyles.flexDirection)
						form.style.flexDirection = containerStyles.flexDirection;

					// set email input type and required attributes
					emailInput.setAttribute("type", "email");
					emailInput.setAttribute("required", "");
					nameInput.setAttribute("required", "");

					// move inputs into form
					while (inputContainer.firstChild) {
						form.appendChild(inputContainer.firstChild);
					}

					// append form to container
					inputContainer.appendChild(form);
				}
			}

			// find discount code element (if any)
			popup.discount = shadow.querySelector('[data-type="Discount code"]');

			// remove hidden discount element before reveal-mode checks —
			// templates keep the element with display:none when no code is set
			if (popup.discount && popup.discount.style.display == "none") {
				popup.discount.remove();
				popup.discount = null;
			}

			// find reveal text element to check for reveal button functionality
			var revealTextElement = shadow.querySelector('[data-type="Reveal text"]');
			var revealButtonText = revealTextElement
				? revealTextElement.getAttribute("data-reveal-button-text")
				: null;

			// store original submit button text; first click reveals when a
			// discount overlay is present (split-tab or inline), not redirect
			var originalSubmitText = popup.submit ? popup.submit.textContent : null;
			var isRevealMode = false;

			if (
				popup.submit &&
				popup.discount &&
				revealTextElement &&
				!options.forceReveal
			) {
				isRevealMode = true;
				if (revealButtonText) {
					popup.submit.textContent = revealButtonText;
				}
			}

			// apply metallic effect if cv-metallic-effect attribute is present
			if (popup.discount && popup.discount.hasAttribute("cv-metallic-effect")) {
				popup.discount.classList.add("metallic-effect");
			}

			// find referral link element (if any) and set the referral link
			var referralLink = shadow.querySelector("[cv-share-link]");
			if (cpm.referralLink && referralLink) {
				referralLink.innerHTML = cpm.referralLink;
			}

			// function to show success message
			var showSuccessMessage = function (message = "Code copied!") {
				// check if message already exists
				var existingMessage = shadow.querySelector(".cv-success-message");
				if (existingMessage) {
					existingMessage.remove();
				}

				// create success message element
				var successMessage = document.createElement("div");
				successMessage.className = "cv-success-message";
				successMessage.textContent = message;

				// add to popup container (first child of shadow)
				var popupContent = popupContainer.firstChild;
				if (popupContent) {
					popupContent.appendChild(successMessage);

					// show message with animation
					setTimeout(() => {
						successMessage.classList.add("show");
					}, 10);

					// hide message after 3 seconds
					setTimeout(() => {
						successMessage.classList.add("fade-out");
						// remove from DOM after animation completes
						setTimeout(() => {
							if (successMessage.parentNode) {
								successMessage.remove();
							}
						}, 300);
					}, 3000);
				}
			};

			// create a shared function to handle discount code reveal
			var handleDiscountReveal = async function (revealOptions) {
				revealOptions = revealOptions || {};

				// trigger click
				if (!revealOptions.skipPing) utils.ping(cpm, "clicks");

				// check if campaign has a unique discount code
				if (
					cpm.customDiscountCodesAvailable ||
					(config.customDiscountCodesAvailable &&
						cpm.codesRunOut !== "template")
				) {
					var initialText = popup.discount.innerHTML;
					var loadingText = "Loading...";

					var discountCodeDiv = shadow.querySelector(
						'[data-type="Discount code"]',
					);

					// set loading text
					discountCodeDiv.innerHTML = discountCodeDiv.innerHTML.replace(
						initialText,
						loadingText,
					);

					// get discount code
					var discountCode = await utils.getDiscountCode(cpm.objectId);
					var code = discountCode.code ? discountCode.code : discountCode.error;

					// set discount code
					discountCodeDiv.innerHTML = discountCodeDiv.innerHTML.replace(
						loadingText,
						code,
					);
				}

				// add copy icon to discount code element (if not already added)
				if (popup.discount && !popup.discount.querySelector("img")) {
					var copyIcon = document.createElement("img");
					copyIcon.src =
						"https://firebasestorage.googleapis.com/v0/b/conversingjs.appspot.com/o/popup-assets%2Fcopy.svg?alt=media";
					copyIcon.style.width = "16px";
					copyIcon.style.position = "absolute";
					copyIcon.style.top = "50%";
					copyIcon.style.transform = "translateY(-50%)";
					copyIcon.style.right = "8px";

					// set styling
					popup.discount.style.paddingRight = "32px";
					popup.discount.style.cursor = "pointer";
					popup.discount.style.position = "relative";

					popup.discount.appendChild(copyIcon);

					// add copy event to discount code element with success message
					utils.copyElementText(
						popup.discount,
						popup.discount.textContent,
						function () {
							showSuccessMessage("Code copied!");
						},
					);
				}

				// automatically copy the discount code and show success message
				// (skipped on forced reveal: clipboard needs a user gesture)
				if (popup.discount && !revealOptions.skipAutoCopy) {
					var discountCodeText = popup.discount.textContent.trim();
					utils.copyToClipboard(discountCodeText);
					showSuccessMessage("Code copied!");
				}

				// set session storage item
				sessionStorage.setItem(
					`cb_discount_code_${cpm.objectId}`,
					popup.discount.textContent,
				);

				// also save as cookie for auto-apply on Shopify sites
				var discountCodeValue = popup.discount.textContent.trim();
				if (discountCodeValue) {
					document.cookie =
						"discount_code=" +
						encodeURIComponent(discountCodeValue) +
						"; path=/";
				}

				// if in reveal mode, restore original submit button text and functionality
				if (isRevealMode) {
					popup.submit.textContent = originalSubmitText;
					isRevealMode = false;
				}
			};

			// helper function to re-initialize step 2 template for AMBASSADOR campaigns
			var reinitializeStep2 = function () {
				// make first child relative
				if (popupContainer.firstChild) {
					popupContainer.firstChild.style.position = "relative";
				}

				// find close button and set up handler
				var closeBtn = shadow.querySelector("[cv-close]");
				if (closeBtn) {
					closeBtn.removeAttribute("cv-close");
					closeBtn.style.color = config.defaults?.body?.color || "#000000";
					if (cpm.badge) {
						closeBtn.addEventListener("click", function () {
							// minimize logic would go here if badge is enabled
						});
					} else {
						closeBtn.addEventListener("click", function () {
							utils.ping(cpm, "dismissals");
							popup.hide(cpm);
						});
					}
				}

				// find referral link element (if any) and set the referral link
				var referralLinkEl = shadow.querySelector("[cv-share-link]");
				if (cpm.referralLink && referralLinkEl) {
					referralLinkEl.innerHTML = cpm.referralLink;
				} else if (!cpm.referralLink) {
					log("[AMBASSADOR] No referralLink found for campaign:", cpm.objectId);
				}

				// find image and handle blurhash if present
				var step2Image = shadow.querySelector('[data-type="Image"]');
				if (step2Image && step2Image.style.display !== "none") {
					var bgImage = new Image();
					bgImage.onload = function () {
						step2Image.style.backgroundImage = `url(${bgImage.src})`;
					};
					bgImage.src = step2Image.style.backgroundImage
						.replace(/url\((['"])?(.*?)\1\)/gi, "$2")
						.split(",")[0];

					if (cpm.blurhash) {
						var width = step2Image.clientWidth || 100;
						var height = step2Image.clientHeight || 100;
						var pixels = BlurHash.decode(cpm.blurhash, width, height, 1);
						var canvas = document.createElement("canvas");
						canvas.width = width;
						canvas.height = height;
						var ctx = canvas.getContext("2d");
						var imageData = ctx.createImageData(width, height);
						imageData.data.set(pixels);
						ctx.putImageData(imageData, 0, 0);
						step2Image.style.backgroundImage = `url(${canvas.toDataURL()})`;
					}
				}

				// handle cv-share button for native share API
				var shareButton = shadow.querySelector("[cv-share]");
				if (shareButton) {
					if (!cpm.referralLink) {
						log(
							"[AMBASSADOR] Share button found but no referralLink for campaign:",
							cpm.objectId,
						);
					}
					if (cpm.referralLink) {
						shareButton.addEventListener("click", function () {
							utils.ping(cpm, "clicks");

							// Try native Web Share API if available
							if (navigator.share) {
								var headingEl = shadow.querySelector('[data-type="Heading"]');
								var title = headingEl
									? headingEl.textContent
									: "Check this out!";

								var shareLink = `https://${cpm.referralLink}`;
								navigator
									.share({
										title: title,
										url: shareLink,
									})
									.catch(function (error) {
										log("[SHARE] Native share error:", error);
										// Fallback to copying link
										if (referralLinkEl) {
											utils.copyToClipboard(cpm.referralLink);
											if (typeof showSuccessMessage === "function") {
												showSuccessMessage("Link copied!");
											}
										}
									});
							} else {
								// Fallback: copy link to clipboard
								if (referralLinkEl) {
									utils.copyToClipboard(cpm.referralLink);
									if (typeof showSuccessMessage === "function") {
										showSuccessMessage("Link copied!");
									}
								}
							}
						});
					}
				}

				// handle social share buttons if present
				utils.handleSocialShares(shadow, cpm);

				// handle copy link button if present
				var copyLink = shadow.querySelector("[cv-copy-link]");
				if (copyLink && cpm.referralLink) {
					utils.copyElementText(copyLink, cpm.referralLink, function () {
						utils.ping(cpm, "clicks");
					});
				}

				// process countdown timers if any
				popup.countdownIntervals = utils.processCountdowns(shadow);
			};

			// attach click event
			if (popup.submit)
				popup.submit.addEventListener("click", async function (e) {
					var submissionErrorEl = shadow.querySelector(".cv-ambassador-error");

					var ensureSubmissionErrorElement = function () {
						if (submissionErrorEl) {
							return submissionErrorEl;
						}

						submissionErrorEl = document.createElement("div");
						submissionErrorEl.className = "cv-ambassador-error";
						submissionErrorEl.style.cssText = `
							margin-top: 8px;
							color: #d32f2f;
							font-size: 13px;
							font-weight: 500;
						`;

						var parent = popup.submit.parentNode || popupContainer;
						parent.appendChild(submissionErrorEl);

						return submissionErrorEl;
					};

					var showSubmissionError = function (message) {
						var el = ensureSubmissionErrorElement();
						el.textContent = message;
						el.style.display = "block";
					};

					var clearSubmissionError = function () {
						if (submissionErrorEl) {
							submissionErrorEl.textContent = "";
							submissionErrorEl.style.display = "none";
						}
					};

					var resetSubmitButton = function () {
						if (!popup.submit) return;
						popup.submit.textContent = originalButtonText;
						popup.submit.disabled = false;
					};

					// handle AMBASSADOR campaign type
					if (cpm.type === "AMBASSADOR") {
						e.preventDefault();
						e.stopPropagation();

						var nameInput = shadow.querySelector("[cv-name-input]");
						var emailInput = shadow.querySelector("[cv-email-input]");
						var form = nameInput ? nameInput.closest("form") : null;

						if (!nameInput || !emailInput || !form) {
							log("[AMBASSADOR] Missing required inputs or form");
							return;
						}

						// validate form using HTML5 validation
						if (!form.checkValidity()) {
							form.reportValidity();
							return;
						}

						var name = nameInput.value.trim();
						var email = emailInput.value.trim();

						if (!name || !email) {
							return;
						}

						// show loading state
						var originalButtonText = popup.submit.textContent;
						popup.submit.textContent = "Loading...";
						popup.submit.disabled = true;
						clearSubmissionError();

						try {
							// submit to backend
							const submissionResponse = await utils.ambassadorSubmit(
								cpm.objectId,
								name,
								email,
							);

							if (
								!submissionResponse ||
								!submissionResponse.success ||
								!submissionResponse.referralLink
							) {
								showSubmissionError(
									submissionResponse?.message ||
										"Unable to generate referral link. Please try again.",
								);
								resetSubmitButton();
								return;
							}

							cpm.referralLink = submissionResponse.referralLink;
						} catch (error) {
							log("[AMBASSADOR] Submission error:", error);
							showSubmissionError(
								"Something went wrong while submitting. Please try again.",
							);
							resetSubmitButton();
							return;
						}

						// trigger click tracking
						utils.ping(cpm, "clicks");

						// replace template with step 2 (proceed even if API failed)
						if (cpm.templateStep2) {
							popupContainer.innerHTML = cpm.templateStep2;
							popupContainer.id = cpm.objectId;
							popupContainer.setAttribute("cv-popup", "");

							// re-initialize step 2 features
							reinitializeStep2();
						}

						return;
					}

					// if in reveal mode, behave like reveal overlay
					if (isRevealMode) {
						// stop event propagation
						e.stopPropagation();

						// split-tab method: reveal happens in a new tab while this
						// tab redirects to the click callback URL
						if (utils.shouldSplitTab(cpm)) {
							utils.splitTabReveal(cpm);
							return;
						}

						// remove reveal overlay if it exists
						if (revealTextElement) {
							revealTextElement.remove();
						}

						// handle the discount reveal using shared function
						await handleDiscountReveal();

						return; // exit early, don't execute normal submit behavior
					}

					// normal submit button behavior
					// check if there is an input present
					if (popup.input && !popup.input.value.length)
						return popup.input.focus();

					// split-tab method for campaigns without a discount code:
					// the redirect URL (or the current page) opens in a new tab
					// while this tab redirects to the click callback URL
					if (!popup.discount && utils.shouldSplitTab(cpm)) {
						var newTabURL;
						if (cpm.submitURL) {
							newTabURL = cpm.submitURL;
							if (newTabURL.includes("{{current_page}}")) {
								var currentPageUrl = utils.buildSplitTabURL(
									window.location.href,
								);
								newTabURL = newTabURL.replace(
									"{{current_page}}",
									encodeURIComponent(currentPageUrl),
								);
							}
						} else {
							newTabURL = utils.buildSplitTabURL(window.location.href, {
								suppressCampaignId: cpm.objectId,
							});
						}

						// trigger close
						popup.hide(cpm);

						// trigger lead
						if (popup.input) utils.lead(cpm.objectId, popup.input.value);

						utils.splitTabRedirect(cpm, newTabURL);
						return;
					}

					// trigger click
					if (!popup.discount) utils.ping(cpm, "clicks");

					// trigger close
					popup.hide(cpm);

					// trigger lead
					if (popup.input) utils.lead(cpm.objectId, popup.input.value);

					// trigger redirect
					if (!!cpm.submitURL) {
						let timeout = 500;

						if (!cpm.clickURLs[0] && !cpm.impressionURLs[0]) {
							timeout = 0;
						}

						setTimeout(function () {
							let finalURL = cpm.submitURL;
							if (finalURL.includes("{{current_page}}")) {
								finalURL = finalURL.replace(
									"{{current_page}}",
									encodeURIComponent(window.location.href),
								);
							}
							window.location.href = finalURL;
						}, timeout);
					}
				});

			// check if campaign has a discount code
			if (popup.discount) {
				// set up traditional reveal overlay (always available)
				var overlay = shadow.querySelector('[data-type="Reveal text"]');
				if (overlay) {
					overlay.addEventListener("click", async function (e) {
						// stop event propagation
						e.stopPropagation();

						// split-tab method: reveal happens in a new tab while this
						// tab redirects to the click callback URL
						if (utils.shouldSplitTab(cpm)) {
							utils.splitTabReveal(cpm);
							return;
						}

						// remove overlay
						overlay.remove();

						// handle the discount reveal
						await handleDiscountReveal();
					});
				}

				// forced reveal (split-tab new tab): show the code immediately
				if (options.forceReveal) {
					if (overlay) overlay.remove();
					handleDiscountReveal({ skipPing: true, skipAutoCopy: true }).then(
						function () {
							// clipboard writes need a user gesture, so auto-copy on
							// load is blocked; copy on the first interaction instead
							if (!popup.discount) return;

							showSuccessMessage("Tap to copy");

							var copyOnFirstInteraction = function () {
								popupContainer.removeEventListener(
									"pointerdown",
									copyOnFirstInteraction,
									true,
								);

								var codeText = popup.discount.textContent.trim();
								if (!codeText || codeText === "Loading...") return;

								utils.copyToClipboard(codeText);
								showSuccessMessage("Code copied!");
							};

							// listen inside the shadow tree so the first tap/click
							// anywhere in the popup counts as a user gesture
							popupContainer.addEventListener(
								"pointerdown",
								copyOnFirstInteraction,
								true,
							);
						},
					);
				}
			}

			// find image element (if any)
			popup.image = shadow.querySelector('[data-type="Image"]');

			// Append the shadow DOM to the body
			document.body.appendChild(popup.content);

			// if the campaign has an image and blurhash, draw blurhash and replace with image on load
			if (popup.image && popup.image.style.display !== "none") {
				// set image
				var bgImage = new Image();

				// replace image on load
				bgImage.onload = function () {
					popup.image.style.backgroundImage = `url(${bgImage.src})`;
				};

				// set image source
				bgImage.src = popup.image.style.backgroundImage
					.replace(/url\((['"])?(.*?)\1\)/gi, "$2")
					.split(",")[0];

				// get image blurhash
				var blurhash = cpm.blurhash;

				// check if image has blurhash
				if (blurhash) {
					// get image width
					var width = popup.image.clientWidth || 100;
					var height = popup.image.clientHeight || 100;

					// get image pixels
					var pixels = BlurHash.decode(blurhash, width, height, 1);

					// create canvas
					var canvas = document.createElement("canvas");
					canvas.width = width;
					canvas.height = height;

					// get context
					var ctx = canvas.getContext("2d");

					var imageData = ctx.createImageData(width, height);
					imageData.data.set(pixels);
					ctx.putImageData(imageData, 0, 0);

					// set image source
					popup.image.style.backgroundImage = `url(${canvas.toDataURL()})`;
				}
			}

			// handle social shares
			utils.handleSocialShares(shadow, cpm);

			// process countdown timers
			popup.countdownIntervals = utils.processCountdowns(shadow);

			var copyLink = shadow.querySelector("[cv-copy-link]");

			// check if copy link element exists
			if (copyLink && cpm.referralLink) {
				utils.copyElementText(copyLink, cpm.referralLink, function () {
					utils.ping(cpm, "clicks");
				});
			}
		},
		hide: function (cpm) {
			log("[POPUP] Hide");

			// get popup element by its unique id
			var element = document.getElementById(
				"cv-popup-container-" + cpm.objectId,
			);

			// check if it exists
			if (!element) return false;

			// clean up countdown intervals
			if (popup.countdownIntervals) {
				popup.countdownIntervals.forEach(function (intervalId) {
					clearInterval(intervalId);
				});
				popup.countdownIntervals = [];
			}

			// remove popup
			element.remove();

			// restore initial values (if no other popups are shown)
			if (!document.querySelector('[id^="cv-popup-container-"]')) {
				document.body.style.overflow = config.initialOverflow;
				document.body.style.margin = config.initialMargin;
			}

			utils.prefillDiscountCode(cpm);
		},
	};

	/**
	 * Triggers
	 */
	var triggers = {
		exit: function (cpm) {
			log(
				`[EXIT - ${config.device < 2 ? "TOUCH" : "CURSOR"}] %cListener set`,
				"color: cyan",
				cpm.objectId,
			);

			if (config.device < 2) {
				// touch device logic
				document.addEventListener(
					"scroll",
					function () {
						if (utils.scrollCheck() > -60) return false;

						if (conditions.check(cpm.conditions)) popup.show(cpm);
					},
					false,
				);
			} else {
				let exitTimer = null;

				// create trigger for mouse exit
				document.addEventListener(
					"mouseleave",
					function (e) {
						if (e.clientY >= 0) return false;

						// Clear any existing timer
						if (exitTimer) clearTimeout(exitTimer);

						// get delay default to 1 seconds
						const delay =
							(typeof cpm.trigger.value !== "undefined" &&
							cpm.trigger.value != null
								? cpm.trigger.value
								: 1) * 1000;

						// Set new timer
						exitTimer = setTimeout(function () {
							if (conditions.check(cpm.conditions)) popup.show(cpm);
						}, delay);
					},
					false,
				);

				// Cancel trigger if mouse returns to viewport
				document.addEventListener(
					"mouseenter",
					function () {
						if (exitTimer) {
							clearTimeout(exitTimer);
							exitTimer = null;
						}
					},
					false,
				);
			}
		},
		timer: function (cpm) {
			log("[TIMER] %cListener set", "color: cyan", cpm.objectId);

			// create trigger
			var id = setTimeout(function () {
				if (conditions.check(cpm.conditions)) popup.show(cpm);
			}, cpm.trigger.value * 1000);

			// push listener
			listeners.push({
				cpm: cpm.objectId,
				type: cpm.trigger.type,
				unbind: function () {
					clearTimeout(id);
				},
			});
		},
		scroll: function (cpm) {
			log("[SCROLL] %cListener set", "color: cyan", cpm.objectId);

			// create trigger
			document.addEventListener(
				"scroll",
				function () {
					if (utils.getScrollPercent() < cpm.trigger.value) return false;

					if (conditions.check(cpm.conditions)) popup.show(cpm);
				},
				false,
			);
		},
		tab: function (cpm, options) {
			options = options || {};
			const recoveryOnly = options.recoveryOnly === true;
			const trackingConfig = options.config;
			log("[TAB] %cListener set", "color: cyan", cpm.objectId);

			// check if conditions are met
			if (!conditions.check(cpm.conditions)) return false;

			// check site-level traffic sources
			if (
				sessionStorage.getItem("cb_traffic_sources_include_site") === "false"
			) {
				log("[POPUP] Site-level traffic sources include condition not met");
				return false;
			}
			if (
				sessionStorage.getItem("cb_traffic_sources_exclude_site") === "true"
			) {
				log("[POPUP] Site-level traffic sources exclude condition met");
				return false;
			}

			// check campaign-level traffic sources
			if (
				sessionStorage.getItem(`cb_traffic_sources_include_${cpm.objectId}`) ===
				"false"
			) {
				log("[POPUP] Traffic sources include condition not met");
				return false;
			}
			if (
				sessionStorage.getItem(`cb_traffic_sources_exclude_${cpm.objectId}`) ===
				"true"
			) {
				log("[POPUP] Traffic sources exclude condition met");
				return false;
			}

			const initialTitle = document.title;
			let canTrigger = false;
			let inactiveTracked = false;

			// Create a static property to track the current active campaign
			if (typeof triggers.tab.activeWorker === "undefined") {
				triggers.tab.activeWorker = null;
				triggers.tab.activeCampaignId = null;
				triggers.tab.lastCheckTime = {}; // Add this to track last check time per campaign
				triggers.tab.activeWorkerCleanup = null;
			}

			const workerCode = `
				let initialDelayTimeout = null;
				let toggleInterval = null;

				function clearTimers() {
					if (initialDelayTimeout) {
						clearTimeout(initialDelayTimeout);
						initialDelayTimeout = null;
					}
					if (toggleInterval) {
						clearInterval(toggleInterval);
						toggleInterval = null;
					}
				}

				onmessage = function(e) {
					if (!e.data || !e.data.action) return;

					if (e.data.action === 'start') {
						clearTimers();
						const delayMs = Math.max(0, Number(e.data.delay) || 0) * 1000;

						initialDelayTimeout = setTimeout(function() {
							postMessage('toggle');
							toggleInterval = setInterval(function() {
								postMessage('toggle');
							}, 1000);
						}, delayMs);
						return;
					}

					if (e.data.action === 'stop') {
						clearTimers();
					}
				}
			`;
			let worker = null;
			let workerUrl = null;

			const toggleTitle = (newTitle) => {
				// Only change title if this is the active campaign
				if (triggers.tab.activeCampaignId === cpm.objectId) {
					if (!recoveryOnly) {
						document.title = newTitle;
					}
					canTrigger = true;
				}
			};

			const bindWorkerEvents = () => {
				worker.onmessage = function (e) {
					if (e.data === "toggle" && document.hidden) {
						// We need to check if this tab is hidden (user is in another tab)
						// Using our simplified tab tracking system to check if user is in another tab of our site

						if (utils.tabTracking.isUserActiveInOtherTab()) {
							log(
								"[TAB] User active in another tab with our script, not toggling title",
							);
							return;
						}

						// Otherwise, proceed since user is likely on an external site
						log(
							"[TAB] User not active in another tab with our script, triggering tab recovery lifecycle",
						);
						const newTitle =
							document.title === initialTitle ? cpm.trigger.title : initialTitle;
						toggleTitle(newTitle);
					}
				};
			};

			const ensureWorker = () => {
				if (worker) return true;

				try {
					const blob = new Blob([workerCode], { type: "application/javascript" });
					workerUrl = URL.createObjectURL(blob);
					worker = new Worker(workerUrl);
					bindWorkerEvents();
					return true;
				} catch (error) {
					log("[TAB] Failed to create worker for tab trigger", error);

					if (workerUrl) {
						URL.revokeObjectURL(workerUrl);
						workerUrl = null;
					}

					worker = null;
					return false;
				}
			};

			const cleanupWorker = () => {
				inactiveTracked = false;

				if (worker) {
					try {
						worker.postMessage({ action: "stop" });
					} catch (error) {
						log("[TAB] Worker stop message failed, terminating directly", error);
					}

					worker.terminate();
					worker = null;
				}

				if (workerUrl) {
					URL.revokeObjectURL(workerUrl);
					workerUrl = null;
				}

				if (triggers.tab.activeCampaignId === cpm.objectId) {
					triggers.tab.activeWorker = null;
					triggers.tab.activeCampaignId = null;
					triggers.tab.activeWorkerCleanup = null;
				}
			};

			const startWorker = () => {
				if (!ensureWorker()) {
					log("[TAB] Worker unavailable, skipping tab trigger start");
					return false;
				}

				try {
					worker.postMessage({
						action: "start",
						delay: cpm.trigger.value,
					});
					return true;
				} catch (error) {
					log("[TAB] Failed to start worker for tab trigger", error);
					cleanupWorker();
					return false;
				}
			};

			const visibilityChangeHandler = function () {
				if (!document.hidden) {
					if (triggers.tab.activeCampaignId === cpm.objectId) {
						// Always restore the original title when tab becomes visible again
						document.title = initialTitle;
						log(
							"[TAB] Tab became visible, restored original title:",
							initialTitle,
						);

						// Only trigger popup if title was toggled
						if (canTrigger) {
							maybeTrackTabRecoveryEvent(
								trackingConfig,
								cpm,
								TAB_RECOVERY_EVENTS.reactivated,
							);

							if (recoveryOnly) {
								log("[TAB] Recovery-only trigger completed, suppressing popup");
							} else {
								log("[TAB] Title was changed, showing popup");
								popup.show(cpm);
							}

							canTrigger = false;
						}

						if (triggers.tab.activeWorkerCleanup) {
							triggers.tab.activeWorkerCleanup();
						}
						inactiveTracked = false;
					}
					return;
				}

				// Check conditions after a small delay to ensure proper order of campaign triggers
				setTimeout(() => {
					const now = Date.now();
					// Skip if this campaign was checked recently (within last second)
					if (
						triggers.tab.lastCheckTime[cpm.objectId] &&
						now - triggers.tab.lastCheckTime[cpm.objectId] < 1000
					) {
						return;
					}

					triggers.tab.lastCheckTime[cpm.objectId] = now;

					if (!conditions.check(cpm.conditions) || utils.checkIfShown(cpm)) {
						return;
					}

					// Only skip starting worker if we are certain user is in another tab with our script
					if (utils.tabTracking.isUserActiveInOtherTab()) {
						log(
							"[TAB] User active in another tab with our script, not starting worker",
						);
						return;
					}

					// Compare trigger values to determine priority
					if (triggers.tab.activeWorker) {
						const activeCampaign = config.campaigns.find(
							(c) => c.objectId === triggers.tab.activeCampaignId,
						);

						// If current active campaign has lower trigger value (higher priority)
						// than this campaign, don't override it
						if (
							activeCampaign &&
							Number(activeCampaign.trigger.value) < Number(cpm.trigger.value)
						) {
							return;
						}
					}

					// If there's an active worker, terminate it
					if (triggers.tab.activeWorkerCleanup) {
						triggers.tab.activeWorkerCleanup();
					}

					// Set this as the active campaign
					ensureWorker();
					triggers.tab.activeWorker = worker;
					triggers.tab.activeCampaignId = cpm.objectId;
					triggers.tab.activeWorkerCleanup = cleanupWorker;
					document.title = initialTitle; // Reset title before starting new campaign

					if (startWorker()) {
						if (!inactiveTracked) {
							maybeTrackTabRecoveryEvent(
								trackingConfig,
								cpm,
								TAB_RECOVERY_EVENTS.inactive,
							);
							inactiveTracked = true;
						}
					} else {
						cleanupWorker();
					}
				}, 0);
			};

			const unloadHandler = () => {
				cleanupWorker();
			};

			document.addEventListener("visibilitychange", visibilityChangeHandler);
			window.addEventListener("unload", unloadHandler);

			listeners.push({
				cpm: cpm.objectId,
				type: cpm.trigger.type,
				unbind: function () {
					document.removeEventListener(
						"visibilitychange",
						visibilityChangeHandler,
					);
					window.removeEventListener("unload", unloadHandler);
					cleanupWorker();
				},
			});
		},
		invalid_code: function (cpm) {
			if (!conditions.check(cpm.conditions)) return false;

			const interval = setInterval(function () {
				const element = utils.queryDeepSelector(cpm.trigger.value);

				if (!element) return false;

				// check if element contains the search text
				if (
					cpm.trigger.searchText &&
					!element.textContent.includes(cpm.trigger.searchText)
				) {
					log(
						"[INVALID CODE] Search text not found. Element text content:",
						element.textContent,
					);
					return false;
				}

				if (element && element?.checkVisibility()) {
					clearInterval(interval);

					const delay = cpm.trigger.delay || 2;

					setTimeout(function () {
						popup.show(cpm);
					}, delay * 1000);
				}
			}, 1000);

			listeners.push({
				cpm: cpm.objectId,
				type: cpm.trigger.type,
				unbind: function () {
					clearInterval(interval);
				},
			});
		},
		badge: function (cpm) {
			log("[BADGE] %cListener set", "color: cyan", cpm.objectId);

			// check if conditions are met
			if (!conditions.check(cpm.conditions)) return false;

			// respect the campaign's frequency cap (same rules as popup.show)
			if (utils.checkIfShown(cpm)) {
				log("[BADGE] Skipped due to frequency cap:", cpm.objectId);
				return false;
			}

			// check if badge already exists for this campaign
			var existingBadge = document.getElementById(
				"cv-badge-trigger-" + cpm.objectId,
			);
			if (existingBadge) {
				log("[BADGE] Badge already exists for campaign:", cpm.objectId);
				return false;
			}

			var badgeType = cpm.trigger.badgeType || "floating";
			var badgeDismissedStorageKey = getBadgeDismissedStorageKey(cpm);

			if (
				badgeType === "sticky" &&
				sessionStorage.getItem(badgeDismissedStorageKey) === "true"
			) {
				log("[BADGE] Skipped due to session dismissal:", cpm.objectId);
				return false;
			}

			var position = cpm.trigger.value || "left";
			var bgColor =
				config.defaults?.badge?.backgroundColor || "#4539F6";
			var iconColor = config.defaults?.badge?.iconColor || "#ffffff";
			var badgeEl;

			if (badgeType === "sticky") {
				// ── Sticky sidebar badge ──
				badgeEl = document.createElement("div");
				badgeEl.id = "cv-badge-trigger-" + cpm.objectId;
				badgeEl.setAttribute("cv-badge-trigger", "");
				badgeEl.style.position = "fixed";
				badgeEl.style.top = "50%";
				badgeEl.style.transform = "translateY(-50%)";
				badgeEl.style.zIndex = "99999990";
				badgeEl.style.display = "flex";
				badgeEl.style.flexDirection = "column";
				badgeEl.style.alignItems = "stretch";
				badgeEl.style.fontFamily = "Inter, sans-serif";
				badgeEl.style.cursor = "pointer";
				badgeEl.style.overflow = "hidden";
				badgeEl.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
				badgeEl.style.backgroundColor = bgColor;
				badgeEl.style.color = iconColor;

				if (position === "right") {
					badgeEl.style.right = "0";
					badgeEl.style.borderRadius = "8px 0 0 8px";
				} else {
					badgeEl.style.left = "0";
					badgeEl.style.borderRadius = "0 8px 8px 0";
				}

				// close button
				var closeBtn = document.createElement("button");
				closeBtn.style.display = "flex";
				closeBtn.style.alignItems = "center";
				closeBtn.style.justifyContent = "center";
				closeBtn.style.padding = "10px";
				closeBtn.style.cursor = "pointer";
				closeBtn.style.border = "none";
				closeBtn.style.background = "none";
				closeBtn.style.color = "inherit";
				closeBtn.style.lineHeight = "1";
				closeBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';

				closeBtn.addEventListener("click", function (e) {
					e.stopPropagation();
					sessionStorage.setItem(badgeDismissedStorageKey, "true");

					// treat dismissal as an impression so frequency cap applies
					if (sessionStorage.getItem(STORAGE_KEYS.demoMode) !== "true") {
						window.localStorage.setItem(cpm.objectId, Date.now());
						window.localStorage.setItem(cpm.type, Date.now());
					}

					badgeEl.style.transition =
						"transform 0.3s ease, opacity 0.3s ease";
					badgeEl.style.opacity = "0";
					badgeEl.style.pointerEvents = "none";
					if (position === "right") {
						badgeEl.style.transform =
							"translateX(100%) translateY(-50%)";
					} else {
						badgeEl.style.transform =
							"translateX(-100%) translateY(-50%)";
					}
					setTimeout(function () {
						if (badgeEl.parentNode) badgeEl.parentNode.removeChild(badgeEl);
					}, 300);
				});

				// body with icon + text
				var bodyEl = document.createElement("div");
				bodyEl.style.display = "flex";
				bodyEl.style.flexDirection = "column";
				bodyEl.style.alignItems = "center";
				bodyEl.style.justifyContent = "center";
				bodyEl.style.gap = "10px";
				bodyEl.style.padding = "0 10px 14px 10px";

				var textEl = document.createElement("span");
				textEl.style.writingMode =
					position === "left" ? "sideways-rl" : "sideways-lr";
				textEl.style.textOrientation = "mixed";
				textEl.style.fontSize = "14px";
				textEl.style.fontWeight = "bold";
				textEl.style.letterSpacing = "0.5px";
				textEl.style.whiteSpace = "nowrap";
				textEl.textContent =
					config.defaults?.badge?.text || "Refer & Earn";

				bodyEl.appendChild(textEl);
				badgeEl.appendChild(closeBtn);
				badgeEl.appendChild(bodyEl);
			} else {
				// ── Floating badge (default) ──
				badgeEl = document.createElement("div");
				badgeEl.id = "cv-badge-trigger-" + cpm.objectId;
				badgeEl.setAttribute("cv-badge-trigger", "");
				badgeEl.style.position = "fixed";
				badgeEl.style.backgroundColor = bgColor;
				badgeEl.style.color = iconColor;
				badgeEl.style.padding = "10px 15px";
				badgeEl.style.borderRadius = "25px";
				badgeEl.style.cursor = "pointer";
				badgeEl.style.zIndex = "99999990";
				badgeEl.style.fontFamily = "Inter, sans-serif";
				badgeEl.style.fontSize = "14px";
				badgeEl.style.fontWeight = "bold";
				badgeEl.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
				badgeEl.style.transition =
					"transform 0.3s ease, opacity 0.3s ease";
				badgeEl.style.display = "flex";
				badgeEl.style.alignItems = "center";
				badgeEl.style.justifyContent = "center";
				badgeEl.style.width = "56px";
				badgeEl.style.height = "56px";
				badgeEl.style.padding = "0";

				var customPosition = cpm.trigger.customPosition || {};

				if (position === "custom") {
					if (customPosition.top)
						badgeEl.style.top = customPosition.top;
					if (customPosition.right)
						badgeEl.style.right = customPosition.right;
					if (customPosition.bottom)
						badgeEl.style.bottom = customPosition.bottom;
					if (customPosition.left)
						badgeEl.style.left = customPosition.left;
				} else if (position === "left") {
					badgeEl.style.left = "24px";
					badgeEl.style.bottom = "24px";
				} else if (position === "right") {
					badgeEl.style.right = "24px";
					badgeEl.style.bottom = "24px";
				}

				badgeEl.innerHTML = `
				<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24" fill="currentColor">
					<path d="M19.5 7.75H18.1C18.5 7.27 18.75 6.67 18.75 6C18.75 4.48 17.52 3.25 16 3.25C14.32 3.25 12.84 4.14 12 5.46C11.16 4.14 9.68 3.25 8 3.25C6.48 3.25 5.25 4.48 5.25 6C5.25 6.67 5.5 7.27 5.9 7.75H4.5C3.81 7.75 3.25 8.31 3.25 9V11.5C3.25 12.1 3.68 12.58 4.25 12.7V19.5C4.25 20.19 4.81 20.75 5.5 20.75H18.5C19.19 20.75 19.75 20.19 19.75 19.5V12.7C20.32 12.58 20.75 12.1 20.75 11.5V9C20.75 8.31 20.19 7.75 19.5 7.75ZM19.25 11.25H12.75V9.25H19.25V11.25ZM16 4.75C16.69 4.75 17.25 5.31 17.25 6C17.25 6.69 16.69 7.25 16 7.25H12.84C13.18 5.82 14.47 4.75 16 4.75ZM8 4.75C9.53 4.75 10.82 5.82 11.16 7.25H8C7.31 7.25 6.75 6.69 6.75 6C6.75 5.31 7.31 4.75 8 4.75ZM4.75 9.25H11.25V11.25H4.75V9.25ZM5.75 12.75H11.25V19.25H5.75V12.75ZM18.25 19.25H12.75V12.75H18.25V19.25Z"/>
				</svg>
			`;
			}

			// add click handler to show popup
			badgeEl.addEventListener("click", function () {
				if (conditions.check(cpm.conditions)) {
					if (badgeType === "sticky") {
						popup.show(cpm).then(function (result) {
							if (result === false) return;

							// hide sticky badge (keep in DOM for re-show)
							badgeEl.style.transition =
								"transform 0.3s ease, opacity 0.3s ease";
							badgeEl.style.opacity = "0";
							badgeEl.style.pointerEvents = "none";
							if (position === "right") {
								badgeEl.style.transform =
									"translateX(100%) translateY(-50%)";
							} else {
								badgeEl.style.transform =
									"translateX(-100%) translateY(-50%)";
							}

							// hook into popup close to re-show sticky badge
							var closeEl = popup.close;
							if (closeEl) {
								closeEl.addEventListener(
									"click",
									function () {
										badgeEl.style.opacity = "1";
										badgeEl.style.pointerEvents = "auto";
										badgeEl.style.transform =
											"translateY(-50%)";
									},
									{ once: true },
								);
							}
						});
					} else {
						popup.show(cpm).then(function (result) {
							if (result !== false && badgeEl && badgeEl.parentNode) {
								badgeEl.parentNode.removeChild(badgeEl);
							}
						});
					}
				}
			});

			// append badge to body
			document.body.appendChild(badgeEl);

			// push listener for cleanup
			listeners.push({
				cpm: cpm.objectId,
				type: cpm.trigger.type,
				unbind: function () {
					if (badgeEl && badgeEl.parentNode) {
						badgeEl.parentNode.removeChild(badgeEl);
					}
				},
			});
		},
		custom_html: function (cpm) {
			log("[CUSTOM_HTML] %cListener set", "color: cyan", cpm.objectId);
			log("[CUSTOM_HTML] Trigger config:", cpm.trigger);

			// check if conditions are met
			if (!conditions.check(cpm.conditions)) {
				log("[CUSTOM_HTML] %cConditions not met", "color: red", cpm.objectId);
				return false;
			}
			log("[CUSTOM_HTML] %cConditions passed", "color: green", cpm.objectId);

			// find target element using selector
			log(
				"[CUSTOM_HTML] Looking for target selector:",
				cpm.trigger.targetSelector,
			);
			const targetElement = utils.queryDeepSelector(cpm.trigger.targetSelector);
			if (!targetElement) {
				log(
					"[CUSTOM_HTML] %cTarget element not found:",
					"color: red",
					cpm.trigger.targetSelector,
				);
				return false;
			}
			log(
				"[CUSTOM_HTML] %cTarget element found:",
				"color: green",
				targetElement,
			);

			// create a temporary container to parse the HTML
			var temp = document.createElement("div");
			temp.innerHTML = cpm.trigger.customHtml;
			log("[CUSTOM_HTML] Custom HTML to inject:", cpm.trigger.customHtml);

			// get all child nodes (elements and text nodes)
			var insertedNodes = Array.from(temp.childNodes);
			log(
				"[CUSTOM_HTML] Parsed nodes count:",
				insertedNodes.length,
				insertedNodes,
			);

			// insert nodes based on value (prepend or append)
			log(
				"[CUSTOM_HTML] Insertion mode:",
				cpm.trigger.value || "append (default)",
			);
			if (cpm.trigger.value === "prepend") {
				// insert in reverse order to maintain order when prepending
				insertedNodes.reverse().forEach(function (node) {
					targetElement.insertBefore(node, targetElement.firstChild);
				});
				// restore original order for reference
				insertedNodes.reverse();
				log("[CUSTOM_HTML] %cNodes prepended to target", "color: green");
			} else {
				insertedNodes.forEach(function (node) {
					targetElement.appendChild(node);
				});
				log("[CUSTOM_HTML] %cNodes appended to target", "color: green");
			}

			// add click handler to all inserted element nodes
			var elementCount = 0;
			insertedNodes.forEach(function (node) {
				if (node.nodeType === Node.ELEMENT_NODE) {
					elementCount++;
					node.style.cursor = "pointer";
					node.setAttribute("cv-custom-html-trigger", cpm.objectId);
					node.addEventListener("click", function (e) {
						log("[CUSTOM_HTML] %cElement clicked", "color: yellow", node);
						e.preventDefault();
						if (conditions.check(cpm.conditions)) {
							log(
								"[CUSTOM_HTML] %cShowing popup",
								"color: green",
								cpm.objectId,
							);
							popup.show(cpm);
						} else {
							log(
								"[CUSTOM_HTML] %cConditions not met on click",
								"color: red",
								cpm.objectId,
							);
						}
					});
				}
			});
			log(
				"[CUSTOM_HTML] Click handlers attached to",
				elementCount,
				"element(s)",
			);

			// push listener for cleanup
			listeners.push({
				cpm: cpm.objectId,
				type: cpm.trigger.type,
				unbind: function () {
					log("[CUSTOM_HTML] Cleaning up inserted nodes for:", cpm.objectId);
					insertedNodes.forEach(function (node) {
						if (node && node.parentNode) {
							node.parentNode.removeChild(node);
						}
					});
				},
			});

			log("[CUSTOM_HTML] %cSetup complete", "color: cyan", cpm.objectId);
		},
	};

	/**
	 * Conditions
	 */
	var conditions = {
		check: function (list) {
			if (!list.length) return true;

			// check for RT bypass
			// for (var i = 0; i < sessionStorage.length; i++) {
			// 	// get key
			// 	var key = sessionStorage.key(i);

			// 	// check key
			// 	if (key.substr(-7) !== "_bypass") continue;

			// 	// check value
			// 	if (sessionStorage.getItem(key) !== "expired") continue;

			// 	log("%cFAILED", "color: red", "Detected RT bypass expired");

			// 	// exit if site contains any bypass expired value
			// 	return false;
			// }

			// iterate through main conditions (AND)
			for (var i = 0; i < list.length; i++) {
				config.debug && console.group(`GROUP ${i + 1}`);

				var groupStatus = false;

				log(list[i]);

				// iterate through sub conditions (OR)
				for (var j = 0; j < list[i].length; j++) {
					// get condition data
					var condition = list[i][j];
					// check if condition logic exists
					if (!conditions[condition.prop]) {
						log(`Missing condition logic: ${condition.prop}`);
						break;
					}

					// stop at first truthy subgroup
					if (conditions[condition.prop](condition)) {
						log(
							"%cPASSED",
							"color: green",
							`${condition.prop}.${condition.type} ${condition.value}`,
						);
						groupStatus = true;
						break;
					} else {
						log(
							"%cFAILED",
							"color: red",
							`${condition.prop}.${condition.type} ${condition.value}`,
						);
					}
				}

				if (groupStatus)
					log("%cOVERALL PASSED", "font-weight: bold; color: green");
				else log("%cOVERALL FAILED", "font-weight: bold; color: red");

				config.debug && console.groupEnd();

				// stop at first falsy group
				if (!groupStatus) return false;
			}

			return true;
		},
		url: function (condition) {
			var types = {
				equals: function (value) {
					return value.toLowerCase() == window.location.href.toLowerCase();
				},
				notEquals: function (value) {
					return value.toLowerCase() != window.location.href.toLowerCase();
				},
				contains: function (value) {
					return (
						window.location.href.toLowerCase().indexOf(value.toLowerCase()) >= 0
					);
				},
				notContains: function (value) {
					return (
						window.location.href.toLowerCase().indexOf(value.toLowerCase()) < 0
					);
				},
				startsWith: function (value) {
					return window.location.href.substring(0, value.length) == value;
				},
				endsWith: function (value) {
					return (
						window.location.href.substring(
							window.location.href.length - value.length,
							window.location.href.length,
						) == value
					);
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		scroll: function (condition) {
			return utils.getScrollPercent() >= condition.value * 1;
		},
		device: function (condition) {
			switch (condition.value) {
				case "mobile":
					return config.device == 0;
				case "tablet":
					return config.device == 1;
				case "desktop":
					return config.device == 2;
				default:
					return false;
			}
		},
		timer: function (condition) {
			var types = {
				lessThan: function (value) {
					return Date.now() - config.init <= value;
				},
				greaterThan: function (value) {
					return Date.now() - config.init >= value;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value * 1000);
		},
		browser: function (condition) {
			var types = {
				language: function (value) {
					var lang = navigator.language;

					return lang === value;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		visitor: function (condition) {
			var types = {
				type: function (value) {
					if (value == "new")
						return window.sessionStorage.hasOwnProperty(TAG_VISITOR);

					if (value == "recurring")
						return (
							!window.sessionStorage.hasOwnProperty(TAG_VISITOR) &&
							window.localStorage.hasOwnProperty(TAG_VISITOR)
						);

					return false;
				},
				country: function () {
					return config.country == condition.value.toUpperCase();
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		referrer: function (condition) {
			return (
				document.referrer
					.toLowerCase()
					.indexOf(condition.value.toLowerCase()) >= 0
			);
		},
		cartItems: function (condition) {
			var types = {
				cssSelector: function (value) {
					//get text content of the element
					var element = utils.queryDeepSelector(value);
					if (!element) {
						log("[CONDITIONS] Element not found, selector: ", value);
						return false;
					}
					// extract only numbers from the text content
					var number = element.textContent.replace(/[^0-9]/g, "");
					return Number(number) > 0;
				},
				localStorage: function (value) {
					return !!localStorage.getItem(value);
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			// Try immediate check first
			var immediateResult = types[condition.type](condition.value);
			if (immediateResult) {
				log("[CONDITIONS] Cart items condition satisfied immediately");
				return true;
			}

			// If immediate check fails, start polling
			log(
				"[CONDITIONS] Starting cart items polling for:",
				condition.type,
				condition.value,
			);

			// Avoid starting multiple intervals for the same condition
			var intervalKey = `cb_cartItems_interval_${condition.type}_${btoa(condition.value)}`;
			if (window[intervalKey]) {
				return false; // Polling already in progress
			}

			var pollCount = 0;
			var maxPolls = 30; // Poll for max 30 seconds

			window[intervalKey] = setInterval(function () {
				pollCount++;

				var result = types[condition.type](condition.value);
				if (result) {
					log(
						"[CONDITIONS] Cart items condition satisfied after",
						pollCount,
						"polls",
					);
					clearInterval(window[intervalKey]);
					delete window[intervalKey];
					return;
				}

				// Stop polling after max attempts
				if (pollCount >= maxPolls) {
					log(
						"[CONDITIONS] Cart items polling timeout after",
						maxPolls,
						"seconds",
					);
					clearInterval(window[intervalKey]);
					delete window[intervalKey];
				}
			}, 1000); // Poll every 1 second

			return false; // Return false initially, will be re-evaluated when polling succeeds
		},
		elementExists: function (condition) {
			var element = utils.queryDeepSelector(condition.value);
			if (!element) {
				log("[CONDITIONS] Element not found, selector: ", condition.value);
				return false;
			}

			return true;
		},
		cssSelector: function (condition) {
			var types = {
				exists: function (value) {
					var element = utils.queryDeepSelector(value);
					if (!element) {
						log("[CONDITIONS] Element not found, selector: ", value);
						return false;
					}
					return true;
				},
				notExists: function (value) {
					var element = utils.queryDeepSelector(value);
					if (element) {
						log("[CONDITIONS] Element found, selector: ", value);
						return false;
					}
					return true;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		queryParamName: function (condition) {
			var searchParams = new URLSearchParams(window.location.search);
			var types = {
				equals: function (value) {
					return searchParams.has(value);
				},
				contains: function (value) {
					var contains = false;
					searchParams.forEach(function (_, key) {
						if (key.indexOf(value) >= 0) contains = true;
					});

					return contains;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		queryParamValue: function (condition) {
			var searchParams = new URLSearchParams(window.location.search);

			var types = {
				equals: function (value) {
					var equals = false;
					searchParams.forEach(function (val) {
						if (val === value) equals = true;
					});

					return equals;
				},
				contains: function (value) {
					var contains = false;
					searchParams.forEach(function (val) {
						if (val.indexOf(value) >= 0) contains = true;
					});

					return contains;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		queryParam: function (condition) {
			var searchParams = new URLSearchParams(window.location.search);
			var [key, value] = condition.value.split("=");

			if (!key || !value) {
				log("[CONDITIONS] Invalid key-value format for queryParam condition");
				return false;
			}

			return searchParams.get(key) === value;
		},
		localStorage: function (condition) {
			var [key, value] = condition.value.split("=");

			if (!key || !value) {
				log("[CONDITIONS] Invalid key-value format for localStorage condition");
				return false;
			}

			return localStorage.getItem(key) === value;
		},
		sessionStorage: function (condition) {
			var [key, value] = condition.value.split("=");

			if (!key || !value) {
				log(
					"[CONDITIONS] Invalid key-value format for sessionStorage condition",
				);
				return false;
			}

			return sessionStorage.getItem(key) === value;
		},
		cookie: function (condition) {
			var [key, value] = condition.value.split("=");

			if (!key || !value) {
				log("[CONDITIONS] Invalid key-value format for cookie condition");
				return false;
			}

			return document.cookie.split(";").some(function (cookie) {
				var [cookieKey, cookieValue] = cookie.trim().split("=");
				return cookieKey === key && cookieValue === value;
			});
		},
		cookieName: function (condition) {
			var types = {
				equals: function (value) {
					return document.cookie
						.split(";")
						.some((cookie) => cookie.trim().split("=")[0] === value);
				},
				contains: function (value) {
					return document.cookie
						.split(";")
						.some((cookie) => cookie.trim().split("=")[0].indexOf(value) >= 0);
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		cookieValue: function (condition) {
			var types = {
				equals: function (value) {
					return document.cookie
						.split(";")
						.some((cookie) => cookie.trim().split("=")[1] === value);
				},
				contains: function (value) {
					return document.cookie
						.split(";")
						.some((cookie) => cookie.trim().split("=")[1]?.indexOf(value) >= 0);
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		localStorageName: function (condition) {
			var types = {
				equals: function (value) {
					for (let i = 0; i < localStorage.length; i++) {
						if (localStorage.key(i) === value) return true;
					}
					return false;
				},
				contains: function (value) {
					for (let i = 0; i < localStorage.length; i++) {
						if (localStorage.key(i).indexOf(value) >= 0) return true;
					}
					return false;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		localStorageValue: function (condition) {
			var types = {
				equals: function (value) {
					for (let i = 0; i < localStorage.length; i++) {
						const key = localStorage.key(i);
						if (localStorage.getItem(key) === value) return true;
					}
					return false;
				},
				contains: function (value) {
					for (let i = 0; i < localStorage.length; i++) {
						const key = localStorage.key(i);
						const itemValue = localStorage.getItem(key);
						if (itemValue && itemValue.indexOf(value) >= 0) return true;
					}
					return false;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		sessionStorageName: function (condition) {
			var types = {
				equals: function (value) {
					for (let i = 0; i < sessionStorage.length; i++) {
						if (sessionStorage.key(i) === value) return true;
					}
					return false;
				},
				contains: function (value) {
					for (let i = 0; i < sessionStorage.length; i++) {
						if (sessionStorage.key(i)?.indexOf(value) >= 0) return true;
					}
					return false;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		sessionStorageValue: function (condition) {
			var types = {
				equals: function (value) {
					for (let i = 0; i < sessionStorage.length; i++) {
						const key = sessionStorage.key(i);
						if (sessionStorage.getItem(key) === value) return true;
					}
					return false;
				},
				contains: function (value) {
					for (let i = 0; i < sessionStorage.length; i++) {
						const key = sessionStorage.key(i);
						if (sessionStorage.getItem(key)?.indexOf(value) >= 0) return true;
					}
					return false;
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
		campaign: function (condition) {
			var types = {
				isSeen: function (value) {
					// Check if campaign has been seen (localStorage item exists for campaignId)
					return !!window.localStorage.getItem(value);
				},
				isClicked: function (value) {
					// Check if campaign has been clicked (localStorage item exists for campaignId_click)
					return !!window.localStorage.getItem(`${value}_click`);
				},
			};

			// check if condition logic exists
			if (!types[condition.type]) {
				log(
					`[CONDITIONS] Missing condition logic: ${condition.prop}.${condition.type}`,
				);
				return false;
			}

			return types[condition.type](condition.value);
		},
	};

	// Helper function to check if a condition is query parameter based
	function isQueryParamCondition(condition) {
		return (
			condition.prop === "queryParam" ||
			condition.prop === "queryParamName" ||
			condition.prop === "queryParamValue"
		);
	}

	// Helper function to store query parameters
	function storeQueryParams() {
		const searchParams = new URLSearchParams(window.location.search);
		const params = {};
		searchParams.forEach((value, key) => {
			params[key] = value;
		});
		sessionStorage.setItem("cb_query_params", JSON.stringify(params));
	}

	// Helper function to get storage key for traffic source conditions
	function getTrafficSourceKey(type, objectId) {
		return objectId
			? `cb_traffic_sources_${type}_${objectId}`
			: `cb_traffic_sources_${type}_site`;
	}

	// Helper function to evaluate and store traffic source conditions
	function evaluateTrafficSourceConditions(condList, type, objectId) {
		const key = getTrafficSourceKey(type, objectId);

		// Split conditions into query param based and dynamic conditions
		const queryParamConditions = condList
			.flat()
			.filter((condition) => isQueryParamCondition(condition));
		const dynamicConditions = condList
			.flat()
			.filter((condition) => !isQueryParamCondition(condition));

		// For query param conditions, use stored params
		let queryParamResult = true;
		if (queryParamConditions.length > 0) {
			const storedParams = JSON.parse(
				sessionStorage.getItem("cb_query_params") || "{}",
			);
			queryParamResult = conditions.check([
				queryParamConditions.map((condition) => ({
					...condition,
					// Override the check to use stored params instead of window.location
					check: () => {
						const [key, value] = condition.value.split("=");
						return storedParams[key] === value;
					},
				})),
			]);
			log("[TRAFFIC SOURCES] Query param result:", queryParamResult);
		}

		// For dynamic conditions, always re-evaluate
		let dynamicResult = true;
		if (dynamicConditions.length > 0) {
			// Use the original conditions without modification
			dynamicResult = conditions.check([dynamicConditions]);
			log("[TRAFFIC SOURCES] Dynamic result:", dynamicResult);
		}

		// Combine results
		const result = queryParamResult && dynamicResult;
		log("[TRAFFIC SOURCES] Final result:", result);

		// Store the result if there are query param conditions
		if (queryParamConditions.length > 0) {
			sessionStorage.setItem(key, result.toString());
		} else {
			// For dynamic conditions, always store the result
			sessionStorage.setItem(key, result.toString());
		}

		return result;
	}

	function unbindCampaignListeners() {
		for (var i = 0; i < listeners.length; i++) {
			if (listeners[i].unbind) {
				log(
					"[LISTENERS] Unbinding",
					listeners[i].type,
					"for",
					listeners[i].cpm,
				);
				listeners[i].unbind();
			}

			// empty listeners if processing the last one
			if (i == listeners.length - 1) listeners = [];
		}
	}

	function evaluateSiteTrafficSources(config) {
		if (!config.trafficSources) {
			return;
		}

		if (
			config.trafficSources.include &&
			config.trafficSources.include?.length > 0
		) {
			const result = evaluateTrafficSourceConditions(
				config.trafficSources.include,
				"include",
			);
			if (!sessionStorage.getItem("cb_traffic_sources_include_site")) {
				sessionStorage.setItem(
					"cb_traffic_sources_include_site",
					result.toString(),
				);
			}
		}

		if (
			config.trafficSources.exclude &&
			config.trafficSources.exclude.length > 0
		) {
			const result = evaluateTrafficSourceConditions(
				config.trafficSources.exclude,
				"exclude",
			);
			if (!sessionStorage.getItem("cb_traffic_sources_exclude_site")) {
				sessionStorage.setItem(
					"cb_traffic_sources_exclude_site",
					result.toString(),
				);
			}
		}
	}

	function evaluateCampaignTrafficSources(cpm) {
		if (!cpm.trafficSources) {
			return;
		}

		if (
			!sessionStorage.getItem("cb_traffic_sources_include_site") &&
			cpm.trafficSources.include &&
			cpm.trafficSources.include.length > 0 &&
			!sessionStorage.getItem(`cb_traffic_sources_include_${cpm.objectId}`)
		) {
			const result = evaluateTrafficSourceConditions(
				cpm.trafficSources.include,
				"include",
				cpm.objectId,
			);
			sessionStorage.setItem(
				`cb_traffic_sources_include_${cpm.objectId}`,
				result.toString(),
			);
		}

		if (
			!sessionStorage.getItem("cb_traffic_sources_exclude_site") &&
			cpm.trafficSources.exclude &&
			cpm.trafficSources.exclude.length > 0 &&
			!sessionStorage.getItem(`cb_traffic_sources_exclude_${cpm.objectId}`)
		) {
			const result = evaluateTrafficSourceConditions(
				cpm.trafficSources.exclude,
				"exclude",
				cpm.objectId,
			);
			sessionStorage.setItem(
				`cb_traffic_sources_exclude_${cpm.objectId}`,
				result.toString(),
			);
		}
	}

	function registerControlTabRecovery(config) {
		config.init = Date.now();
		utils.tabTracking.init();
		unbindCampaignListeners();
		evaluateSiteTrafficSources(config);

		for (var i = 0; i < config.campaigns.length; i++) {
			var cpm = config.campaigns[i];
			evaluateCampaignTrafficSources(cpm);

			if (cpm.trigger && cpm.trigger.type === "tab" && triggers.tab) {
				triggers.tab(cpm, { recoveryOnly: true, config: config });
			}
		}
	}

	// tracking method
	var track = function (config) {
		// Store query params at the start
		storeQueryParams();

		// config api
		if (!config.api) {
			config.api = API_BASE_FALLBACK;
		}

		assignAbGroup(config);
		maybeTrackConversion(config);
		maybeTrackSession(config);
		maybePingOrderConfirmationVisit(config);

		// Keep the existing site health ping independent from campaign delivery.
		if (config.shouldPing) {
			utils.pingSite();
		}

		if (isControlGroup(config)) {
			log("[AB] Control group assigned, registering recovery-only tab tracking");
			registerControlTabRecovery(config);
			return;
		}

		// check for minimized campaigns on load
		config.campaigns.forEach(function (cpm) {
			if (
				cpm.badge &&
				sessionStorage.getItem("cb_minimized_" + cpm.objectId) === "true"
			) {
				popup.show(cpm, { minimized: true });
			}
		});

		maybeReportAmbassadorOrder(config);

		// set init timestamp
		config.init = Date.now();

		// Initialize tab tracking for cross-tab awareness
		utils.tabTracking.init();

		// save initial values
		config.initialOverflow = document.body.style.overflow;
		config.initialMargin = document.body.style.margin;

		// set user as new (if not previously visited)
		if (
			!window.localStorage.hasOwnProperty(TAG_VISITOR) &&
			sessionStorage.getItem(STORAGE_KEYS.demoMode) !== "true"
		) {
			// set user as new (in this session)
			window.sessionStorage.setItem(TAG_VISITOR, Date.now());

			// set user recurring (for future checks)
			window.localStorage.setItem(TAG_VISITOR, Date.now());
		}

		// unbind previously defined listeners
		unbindCampaignListeners();

		// check site-level traffic sources first if they exist and have conditions
		evaluateSiteTrafficSources(config);

		// split-tab flow: force-show the popup with the code revealed
		var forceRevealId = null;
		try {
			var revealParams = new URLSearchParams(window.location.search);
			var revealFromUrl = revealParams.get("cb_reveal");
			if (revealFromUrl && revealFromUrl.trim()) {
				forceRevealId = revealFromUrl.trim();
			}
		} catch (error) {
			log("[SPLIT-TAB] Failed to read cb_reveal from URL", error);
		}

		if (!forceRevealId) {
			forceRevealId = sessionStorage.getItem(STORAGE_KEYS.forceReveal);
		}

		if (forceRevealId) {
			sessionStorage.removeItem(STORAGE_KEYS.forceReveal);

			var revealCpm = config.campaigns.find(function (c) {
				return c.objectId === forceRevealId;
			});

			if (
				revealCpm &&
				utils.campaignHasDiscountCode(revealCpm) &&
				sessionStorage.getItem(
					STORAGE_KEYS.popupSuppressedPrefix + forceRevealId,
				) !== "true"
			) {
				log("[SPLIT-TAB] Forced reveal for campaign", forceRevealId);

				// the originating tab already counted the click and fired the
				// callbacks; this tab must not count anything again
				revealCpm.suppressPings = true;

				popup.show(revealCpm, { forceReveal: true });
			} else {
				log(
					"[SPLIT-TAB] Forced reveal skipped",
					forceRevealId,
					revealCpm ? "no discount or suppressed" : "campaign not found",
				);
			}
		}

		// loop campaigns
		for (var i = 0; i < config.campaigns.length; i++) {
			// get campaign info
			var cpm = config.campaigns[i];

			// Track visit if conditions are met
			if (conditions.check(cpm.conditions)) {
				utils.ping(cpm, "visits");
			}

			// check if traffic sources conditions are met
			evaluateCampaignTrafficSources(cpm);

			// check if campaign has a nested campaign
			if (cpm.nested) {
				//set skip frequency cap to true
				cpm.nested.skipFrequencyCap = true;

				// initialize nested trigger
				triggers[cpm.nested.trigger.type](cpm.nested, { config: config });
			}

			// initialize trigger
			if (triggers[cpm.trigger.type])
				triggers[cpm.trigger.type](cpm, { config: config });

			// prefill discount code
			utils.prefillDiscountCode(cpm);
		}
	};

	// listen to url changes
	if (config.spa && typeof window.history.pushState != "undefined") {
		history.pushState = function () {
			History.prototype.pushState.apply(history, arguments);

			if (arguments[2]) {
				log("[URL] Push", arguments[2]);
				track(config);
			}
		};

		history.replaceState = function () {
			History.prototype.pushState.apply(history, arguments);

			if (arguments[2]) {
				log("[URL] Replace", arguments[2]);
				track(config);
			}
		};

		window.addEventListener("popstate", function () {
			log("[URL] Pop", window.location.pathname);
			track(config);
		});
	}

	// Initialize tracking system
	console.log(
		"%cCartBooster Script Initialized",
		"color: #00A36C; font-weight: bold; font-size: 16px;",
	);
	track(config);
})();
