/**
* The published site's popup runtime: an inline script in `_document`'s
* `
`, right after `window.__tqPopups = ` (see
* project-plugin.ts). Dependency-free ES5, so it runs before any bundle.
*
* Each popup is a component instance at the end of every page (the editor's
* mapper puts it there). Its open workflow listens for the window event
* `workflow:custom:tq-popup-open-`, and its card — drawn only while it is
* open — carries `data-tq-popup-root=""`. This script decides whether and
* when to send that event; everything the visitor sees is the component.
*
* Per page view (on load and on every client-side route change, i.e. a new
* pathname through `history.pushState`/`replaceState` or `popstate`):
* - nothing on an excluded path, nor on a page showing a checkout or
* purchase view (`data-tq-commerce-view`, checked again when it fires);
* - the first eligible popup: an `only` rule naming the path beats an
* `except` rule beats `all`, then the newer (later in the list); eligible =
* not shown within its frequency (`tq_popup:` in localStorage, or
* sessionStorage for once per session) and the right audience (returning =
* `tq_popup_visited` was already set when this document loaded; it is set
* at the end of every evaluation);
* - its trigger armed: exit intent on computers (>= 1024px), the delay, the
* scroll depth, or the next frame — the phone trigger below 1024px;
* - one popup per page view: once it has been shown nothing else opens until
* the next route change.
*
* Opening: the event is sent again every half second (for up to ten seconds)
* until the card is on the page — the component's listener is attached only
* once the page has hydrated, which a head script cannot wait for. Only then
* is the popup counted shown: the frequency is written and `popup_shown`
* tracked. While the cookie banner is on the page nothing is sent: a popup
* would stand between the visitor and that answer, so it waits, and opens the
* moment the banner goes.
*
* While a card is open: a click on a `[data-tq-popup-convert]` control or a
* form submit inside it tracks `popup_converted` (once per opening); a
* converting link also closes the popup right after the click (closing it
* during the click would take the link out of the page before it can
* navigate); `[data-tq-popup-code]` copies its code and says "Copied" for a
* moment; Escape closes a corner or bottom popup (a centred one is a modal
* dialog with its own Escape). Closing = clicking the card's ✕, so the
* component's own close workflow runs.
*
* Analytics go through `window.tpAnalytics.track(name, { recordId })` when the
* tracker is on the page; nothing here throws without it.
*
* ⛔ Shipped as the body of a template literal: no backticks, no dollar-brace,
* and no closing-tag sequence anywhere in it.
*/
export const POPUP_RUNTIME_SOURCE = `(function () {
var config = window.__tqPopups;
if (!config || !config.popups || !config.popups.length) {
return;
}
var POPUPS = config.popups;
var EXCLUDED_PATHS = config.excludedPaths || [];
var OPEN_EVENT_PREFIX = 'workflow:custom:tq-popup-open-';
var ROOT_ATTR = 'data-tq-popup-root';
var CODE_ATTR = 'data-tq-popup-code';
var CONVERT_SELECTOR = '[data-tq-popup-convert]';
var CLOSE_SELECTOR = '[data-thq="thq-modal-close-button"]';
var CHECKOUT_SELECTOR = '[data-tq-commerce-view="begin_checkout"],[data-tq-commerce-view="purchase"]';
var COOKIE_BANNER_SELECTOR = '[data-thq="thq-cookie-consent"]';
var FREQUENCY_PREFIX = 'tq_popup:';
var VISITED_KEY = 'tq_popup_visited';
var DESKTOP_QUERY = '(min-width: 1024px)';
var DESKTOP_MIN_WIDTH = 1024;
var DAY_MS = 86400000;
var OPEN_RETRY_MS = 500;
var OPEN_ATTEMPTS = 20;
var COPIED_LABEL = 'Copied';
var COPIED_MS = 1500;
var SHOW_TEXT = 4;
var run = 0;
var cleanups = [];
var currentPath = null;
var openings = {};
function read(area, key) {
try {
var store = window[area];
return store ? store.getItem(key) : null;
} catch (e) {
return null;
}
}
function write(area, key, value) {
try {
var store = window[area];
if (store) {
store.setItem(key, value);
}
} catch (e) {}
}
var returning = read('localStorage', VISITED_KEY) !== null;
function track(name, id) {
try {
var analytics = window.tpAnalytics;
if (analytics && typeof analytics.track === 'function') {
analytics.track(name, { recordId: id });
}
} catch (e) {}
}
function later(fn, ms) {
var token = run;
var timer = window.setTimeout(function () {
if (token === run) {
fn();
}
}, ms);
cleanups.push(function () {
window.clearTimeout(timer);
});
}
function nextFrame(fn) {
if (typeof window.requestAnimationFrame !== 'function') {
later(fn, 0);
return;
}
var token = run;
var frame = window.requestAnimationFrame(function () {
if (token === run) {
fn();
}
});
cleanups.push(function () {
window.cancelAnimationFrame(frame);
});
}
function listen(target, type, handler, options) {
target.addEventListener(type, handler, options);
cleanups.push(function () {
target.removeEventListener(type, handler, options);
});
}
function cancel() {
run++;
var pending = cleanups;
cleanups = [];
for (var i = 0; i < pending.length; i++) {
try {
pending[i]();
} catch (e) {}
}
}
function normalizePath(path) {
var clean = String(path || '/').split('?')[0].split('#')[0] || '/';
while (clean.length > 1 && clean.charAt(clean.length - 1) === '/') {
clean = clean.slice(0, -1);
}
return clean;
}
function pathMatches(path, pattern) {
if (typeof pattern !== 'string' || !pattern) {
return false;
}
if (pattern.charAt(pattern.length - 1) === '*') {
return path.indexOf(pattern.slice(0, -1)) === 0;
}
return path === normalizePath(pattern);
}
function matchesAny(path, patterns) {
for (var i = 0; i < patterns.length; i++) {
if (pathMatches(path, patterns[i])) {
return true;
}
}
return false;
}
function pageRank(popup, path) {
var rule = popup.pages || {};
var paths = rule.paths || [];
if (rule.mode === 'only') {
return matchesAny(path, paths) ? 3 : 0;
}
if (rule.mode === 'except') {
return matchesAny(path, paths) ? 0 : 2;
}
return 1;
}
function storageAreaFor(popup) {
return popup.frequency && popup.frequency.kind === 'session' ? 'sessionStorage' : 'localStorage';
}
function shownRecently(popup) {
var frequency = popup.frequency || {};
var last = read(storageAreaFor(popup), FREQUENCY_PREFIX + popup.id);
if (last === null) {
return false;
}
if (frequency.kind === 'once' || frequency.kind === 'session') {
return true;
}
var shownAt = Number(last);
if (!shownAt) {
return false;
}
return Date.now() - shownAt < (Number(frequency.days) || 0) * DAY_MS;
}
function audienceAllows(popup) {
if (popup.audience === 'new') {
return !returning;
}
if (popup.audience === 'returning') {
return returning;
}
return true;
}
function pickPopup(path) {
var ranked = [];
for (var i = 0; i < POPUPS.length; i++) {
var rank = pageRank(POPUPS[i], path);
if (rank > 0) {
ranked.push({ popup: POPUPS[i], rank: rank, index: i });
}
}
ranked.sort(function (a, b) {
return b.rank - a.rank || b.index - a.index;
});
for (var j = 0; j < ranked.length; j++) {
if (!shownRecently(ranked[j].popup) && audienceAllows(ranked[j].popup)) {
return ranked[j].popup;
}
}
return null;
}
function popupById(id) {
for (var i = 0; i < POPUPS.length; i++) {
if (POPUPS[i].id === id) {
return POPUPS[i];
}
}
return null;
}
function checkoutOnScreen() {
return !!document.querySelector(CHECKOUT_SELECTOR);
}
function cookieBannerOnScreen() {
return !!document.querySelector(COOKIE_BANNER_SELECTOR);
}
function isComputer() {
try {
if (typeof window.matchMedia === 'function') {
return window.matchMedia(DESKTOP_QUERY).matches;
}
} catch (e) {}
return window.innerWidth >= DESKTOP_MIN_WIDTH;
}
function scrolledPercent() {
var root = document.documentElement;
var height = Math.max(root.scrollHeight || 0, document.body ? document.body.scrollHeight || 0 : 0);
var room = height - window.innerHeight;
if (room <= 0) {
return 100;
}
var top = window.pageYOffset || root.scrollTop || 0;
return (top / room) * 100;
}
function cardOf(id) {
var cards = document.querySelectorAll('[' + ROOT_ATTR + ']');
for (var i = 0; i < cards.length; i++) {
if (cards[i].getAttribute(ROOT_ATTR) === id) {
return cards[i];
}
}
return null;
}
function onPage(element) {
return !!element && document.documentElement.contains(element);
}
function closeCard(card) {
var button = card.querySelector(CLOSE_SELECTOR);
if (onPage(button)) {
button.click();
}
}
function markShown(popup) {
write(storageAreaFor(popup), FREQUENCY_PREFIX + popup.id, String(Date.now()));
openings[popup.id] = { converted: false };
track('popup_shown', popup.id);
}
function openPopup(popup) {
var attempts = 0;
var settled = false;
var waitingForBanner = false;
var observer = null;
function settle() {
settled = true;
if (observer) {
observer.disconnect();
observer = null;
}
}
function checkCard() {
if (settled) {
return;
}
if (cardOf(popup.id)) {
settle();
markShown(popup);
} else if (waitingForBanner && !cookieBannerOnScreen()) {
waitingForBanner = false;
send();
}
}
function send() {
if (settled) {
return;
}
if (checkoutOnScreen()) {
settle();
return;
}
if (observer && cookieBannerOnScreen()) {
waitingForBanner = true;
return;
}
attempts++;
window.dispatchEvent(new window.CustomEvent(OPEN_EVENT_PREFIX + popup.id, { detail: {} }));
checkCard();
if (settled) {
return;
}
if (attempts < OPEN_ATTEMPTS) {
later(send, OPEN_RETRY_MS);
} else {
settle();
}
}
if (typeof window.MutationObserver === 'function') {
observer = new window.MutationObserver(checkCard);
observer.observe(document.documentElement, { childList: true, subtree: true });
cleanups.push(settle);
}
send();
}
function arm(popup) {
var fired = false;
function fire() {
if (fired) {
return;
}
fired = true;
if (!checkoutOnScreen()) {
openPopup(popup);
}
}
var trigger = popup.trigger || {};
var kind = isComputer() ? trigger.desktop : trigger.phone;
if (kind === 'exit-intent') {
listen(document, 'mouseout', function (event) {
if (!event.relatedTarget && event.clientY <= 0) {
fire();
}
});
} else if (kind === 'scroll') {
var depth = Number(trigger.scrollPercent) || 0;
listen(
window,
'scroll',
function () {
if (scrolledPercent() >= depth) {
fire();
}
},
{ passive: true }
);
} else if (kind === 'delay') {
later(fire, Math.max(0, Number(trigger.delaySeconds) || 0) * 1000);
} else {
nextFrame(fire);
}
}
function evaluate() {
cancel();
currentPath = normalizePath(window.location.pathname);
try {
if (!matchesAny(currentPath, EXCLUDED_PATHS) && !checkoutOnScreen()) {
var popup = pickPopup(currentPath);
if (popup) {
arm(popup);
}
}
} catch (e) {}
write('localStorage', VISITED_KEY, '1');
}
function onRouteChange() {
try {
var path = normalizePath(window.location.pathname);
if (path === currentPath) {
return;
}
cancel();
currentPath = path;
later(evaluate, 0);
} catch (e) {}
}
function patchHistory(method) {
var history = window.history;
var original = history && history[method];
if (typeof original !== 'function') {
return;
}
history[method] = function () {
var result = original.apply(this, arguments);
onRouteChange();
return result;
};
}
function elementOf(target) {
if (!target) {
return null;
}
return target.nodeType === 1 ? target : target.parentElement || null;
}
function cardAround(element) {
return element && typeof element.closest === 'function' ? element.closest('[' + ROOT_ATTR + ']') : null;
}
function converted(id) {
var opening = openings[id];
if (opening && opening.converted) {
return;
}
openings[id] = { converted: true };
track('popup_converted', id);
}
function labelNode(element) {
var walker = document.createTreeWalker(element, SHOW_TEXT);
var node = walker.nextNode();
while (node && !String(node.nodeValue).trim()) {
node = walker.nextNode();
}
return node;
}
function sayCopied(button) {
var node = labelNode(button);
if (!node) {
return;
}
if (button.__tqCopiedTimer) {
window.clearTimeout(button.__tqCopiedTimer);
} else {
button.__tqCopiedLabel = node.nodeValue;
}
node.nodeValue = COPIED_LABEL;
button.__tqCopiedTimer = window.setTimeout(function () {
button.__tqCopiedTimer = null;
if (node.nodeValue === COPIED_LABEL) {
node.nodeValue = button.__tqCopiedLabel;
}
}, COPIED_MS);
}
function copyWithTextarea(text, host) {
var parent = onPage(host) ? host : document.body;
var focused = document.activeElement;
var area = document.createElement('textarea');
area.value = text;
area.setAttribute('readonly', '');
area.setAttribute('aria-hidden', 'true');
area.style.position = 'fixed';
area.style.top = '0';
area.style.left = '0';
area.style.opacity = '0';
parent.appendChild(area);
var copied = false;
try {
area.focus();
area.select();
copied = document.execCommand('copy') === true;
} catch (e) {}
parent.removeChild(area);
if (focused && typeof focused.focus === 'function') {
focused.focus();
}
return copied;
}
function copyCode(button, card) {
var code = button.getAttribute(CODE_ATTR);
if (!code) {
return;
}
function fallback() {
if (copyWithTextarea(code, card)) {
sayCopied(button);
}
}
try {
var clipboard = window.navigator.clipboard;
if (clipboard && typeof clipboard.writeText === 'function') {
clipboard.writeText(code).then(function () {
sayCopied(button);
}, fallback);
return;
}
} catch (e) {}
fallback();
}
function onClick(event) {
try {
var target = elementOf(event.target);
var card = cardAround(target);
if (!card) {
return;
}
var codeButton = target.closest('[' + CODE_ATTR + ']');
if (codeButton && card.contains(codeButton)) {
copyCode(codeButton, card);
}
var control = target.closest(CONVERT_SELECTOR);
if (control && card.contains(control)) {
converted(card.getAttribute(ROOT_ATTR));
var link = target.closest('a[href]');
if (link && card.contains(link)) {
window.setTimeout(function () {
closeCard(card);
}, 0);
}
}
} catch (e) {}
}
function onSubmit(event) {
try {
var card = cardAround(elementOf(event.target));
if (card) {
converted(card.getAttribute(ROOT_ATTR));
}
} catch (e) {}
}
function onKeyDown(event) {
if (event.key !== 'Escape' && event.key !== 'Esc') {
return;
}
try {
var cards = document.querySelectorAll('[' + ROOT_ATTR + ']');
for (var i = 0; i < cards.length; i++) {
var popup = popupById(cards[i].getAttribute(ROOT_ATTR));
if (popup && (popup.layout === 'corner' || popup.layout === 'bottom')) {
closeCard(cards[i]);
}
}
} catch (e) {}
}
try {
document.addEventListener('click', onClick, true);
document.addEventListener('submit', onSubmit, true);
document.addEventListener('keydown', onKeyDown);
patchHistory('pushState');
patchHistory('replaceState');
window.addEventListener('popstate', onRouteChange);
currentPath = normalizePath(window.location.pathname);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', evaluate);
} else {
evaluate();
}
} catch (e) {}
})();`