/**
* PTG Exit-Intent Popup — Contextual Lead Magnet
* ================================================
* Agent F6 — Conversion Optimization
* Date: 2026-03-02 (v1), 2026-04-14 (v2 contextual routing)
*
* Features:
* - Desktop: fires on mouse leaving viewport (toward browser chrome)
* - Mobile: fires after 60% scroll + 30 seconds on page
* - Lead magnet: resolved contextually via window.PTGGuide (guide-routing.js)
* - Falls back to static 2026-guide copy if routing unavailable
* - Email capture form POSTs to /inc/lead-capture.php with guide_id
* - Respects sessionStorage (show once per session)
* - CSP-compatible (no external scripts, all inline)
* - Dark Authority theme styling (matches site design)
* - Accessible: focus trap, Escape key dismissal, ARIA labels
* - Respects prefers-reduced-motion
*
* Prerequisite: /inc/js/guide-routing.js must be loaded on the page.
* default-bottom.tpl includes it before this script. If it's not present,
* the popup still works with the static 2026-guide fallback.
*
* NOTE: Do NOT load on conversion pages (/contact-us/, /free-it-consultation/,
* /schedule-appointment/, /emergency-room/, /landing/*)
*/
(function () {
'use strict';
// ── Configuration ──────────────────────────────────────────────
var CONFIG = {
formAction: '/inc/lead-capture.php',
logAction: '/inc/popup-log.php',
storageKey: 'ptg-exit-popup-dismissed',
excludePages: [
'/contact-us/',
'/contact-form/',
'/solutions/free-it-consultation/',
'/schedule-appointment/',
'/contact-us/emergency-room/',
'/thank-you/',
'/pay/'
],
mouseYThreshold: 10,
mobileScrollThreshold: 0.6,
mobileTimeThreshold: 30000,
globalDelay: 5000,
suppressOnCtaClick: true
};
// Static fallback guide — used if PTGGuide unavailable or resolution fails.
var FALLBACK_GUIDE = {
guide_id: 'business-tech-2026',
title: '2026 SMB Cybersecurity Survival Guide',
landing_page_url: '/2026-guide/',
popup_heading: 'Before you go\u2026',
popup_body: 'Get the 2026 SMB Cybersecurity Survival Guide, free, 28 pages of actionable strategies to protect your business from ransomware, phishing, AI-era threats, and compliance gaps.',
popup_cta_text: 'Download Free Guide'
};
// ── Turnstile Script Loader ──────────────────────────────────
// Idempotent - other forms (guide-submit, [[Forms]] module) may already
// have loaded this. Only injects if absent.
if (!document.querySelector('script[src*="challenges.cloudflare.com/turnstile"]')) {
var ts = document.createElement('script');
ts.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
ts.async = true;
ts.defer = true;
document.head.appendChild(ts);
}
// ── Early Exit Checks ──────────────────────────────────────────
if (sessionStorage.getItem(CONFIG.storageKey)) return;
var path = window.location.pathname;
for (var i = 0; i < CONFIG.excludePages.length; i++) {
if (path.indexOf(CONFIG.excludePages[i]) === 0) return;
}
if (path.indexOf('/landing/') === 0) return;
// ── State ──────────────────────────────────────────────────────
var popupShown = false;
var ctaClicked = false;
var pageLoadTime = Date.now();
var currentGuide = FALLBACK_GUIDE;
// Kick off contextual guide resolution immediately. If PTGGuide isn't
// available yet (script still parsing), poll briefly, then fall back.
(function resolveGuide() {
function apply(g) { if (g) currentGuide = g; }
if (window.PTGGuide && typeof window.PTGGuide.current === 'function') {
window.PTGGuide.current().then(apply).catch(function(){});
return;
}
var tries = 0;
var iv = setInterval(function () {
tries++;
if (window.PTGGuide && typeof window.PTGGuide.current === 'function') {
clearInterval(iv);
window.PTGGuide.current().then(apply).catch(function(){});
} else if (tries >= 10) {
clearInterval(iv); // give up, FALLBACK_GUIDE stays in effect
}
}, 200);
})();
// ── Server-side event logger (Workstream A5) ─────────────────────
// Fire-and-forget. Uses sendBeacon when available so the event survives
// page navigation (matters for dismiss + unload paths).
function logEvent(eventName) {
try {
var gid = (currentGuide && currentGuide.guide_id) || 'unknown';
var sid = (window.PTGGuide && typeof window.PTGGuide.sessionId === 'function')
? window.PTGGuide.sessionId() : '';
var variant = (window.PTGGuide && typeof window.PTGGuide.variant === 'function')
? window.PTGGuide.variant(gid) : '';
var payload = JSON.stringify({
event: eventName,
page: window.location.pathname,
guide_id: gid,
variant: variant,
session_id: sid
});
if (navigator.sendBeacon) {
var blob = new Blob([payload], { type: 'application/json' });
navigator.sendBeacon(CONFIG.logAction, blob);
return;
}
fetch(CONFIG.logAction, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true,
credentials: 'same-origin'
}).catch(function () {});
} catch (e) { /* never throw from instrumentation */ }
}
// HTML escaping for safe attribute injection (body HTML is trusted static JSON).
function esc(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
// Explicit Turnstile render for the popup. api.js auto-scans on
// DOMContentLoaded; the popup is injected later on exit-intent, so we
// must render it manually once the widget container is in the DOM.
function renderTurnstileInPopup() {
var container = document.querySelector('#ptg-exit-popup .cf-turnstile');
if (!container || container.dataset.rendered === 'true') return;
function doRender() {
if (!(window.turnstile && typeof window.turnstile.render === 'function')) return;
try {
window.turnstile.render(container, {
sitekey: '0x4AAAAAAAIyUAfUG6rovSlf',
theme: 'dark',
size: 'normal'
});
container.dataset.rendered = 'true';
} catch (err) { /* already rendered or transient error */ }
}
if (window.turnstile) { doRender(); return; }
var tries = 0;
var iv = setInterval(function () {
tries++;
if (window.turnstile) { clearInterval(iv); doRender(); }
else if (tries >= 50) { clearInterval(iv); } // 5s cap
}, 100);
}
if (CONFIG.suppressOnCtaClick) {
document.addEventListener('click', function (e) {
var link = e.target.closest('a');
if (!link) return;
var href = link.getAttribute('href') || '';
if (
href.indexOf('/contact') > -1 ||
href.indexOf('schedule') > -1 ||
href.indexOf('assessment') > -1 ||
href.indexOf('tel:') === 0 ||
href.indexOf('book.petronella.ai') > -1 ||
href.indexOf('calendly') > -1
) {
ctaClicked = true;
}
});
}
// ── Create Popup HTML + CSS ────────────────────────────────────
function createPopup() {
var overlay = document.createElement('div');
overlay.id = 'ptg-exit-overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-labelledby', 'ptg-exit-heading');
overlay.setAttribute('aria-describedby', 'ptg-exit-desc');
var g = currentGuide || FALLBACK_GUIDE;
var heading = g.popup_heading || FALLBACK_GUIDE.popup_heading;
var bodyHtml = g.popup_body || FALLBACK_GUIDE.popup_body;
var ctaLabel = g.popup_cta_text || FALLBACK_GUIDE.popup_cta_text;
var guideId = g.guide_id || FALLBACK_GUIDE.guide_id;
var guideUrl = g.landing_page_url || FALLBACK_GUIDE.landing_page_url;
overlay.innerHTML =
'
';
// ── Inject Styles ─────────────────────────────────────────
var style = document.createElement('style');
style.textContent =
'/* PTG Exit-Intent Popup — Dark Authority Theme */' +
'#ptg-exit-overlay{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.7);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);opacity:0;transition:opacity 0.3s ease;padding:16px}' +
'#ptg-exit-overlay.is-visible{opacity:1}' +
'.ptg-exit-popup{position:relative;width:100%;max-width:480px;background:linear-gradient(135deg,#0d1117 0%,#161b2e 50%,#1a1f2e 100%);border:1px solid rgba(255,255,255,0.1);border-radius:16px;box-shadow:0 24px 80px rgba(0,0,0,0.6),0 0 0 1px rgba(255,255,255,0.05) inset;padding:40px 32px 32px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#f1f5f9;transform:translateY(20px) scale(0.97);transition:transform 0.3s cubic-bezier(0.16,1,0.3,1)}' +
'#ptg-exit-overlay.is-visible .ptg-exit-popup{transform:translateY(0) scale(1)}' +
'.ptg-exit-close{position:absolute;top:12px;right:12px;width:40px;height:40px;border:none;background:rgba(255,255,255,0.06);border-radius:8px;color:#94a3b8;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.2s,color 0.2s;-webkit-tap-highlight-color:transparent}' +
'.ptg-exit-close:hover,.ptg-exit-close:focus-visible{background:rgba(255,255,255,0.12);color:#f1f5f9;outline:2px solid #00A9E0;outline-offset:2px}' +
'.ptg-exit-badge{display:inline-block;padding:4px 14px;background:linear-gradient(135deg,#00A9E0,#7C3AED);color:#fff;font-size:0.75rem;font-weight:800;letter-spacing:0.08em;text-transform:uppercase;border-radius:6px;margin-bottom:16px}' +
'.ptg-exit-heading{font-size:1.5rem;font-weight:800;color:#f1f5f9;margin:0 0 12px 0;line-height:1.3}' +
'.ptg-exit-desc{font-size:0.95rem;color:#94a3b8;line-height:1.7;margin:0 0 16px 0}' +
'.ptg-exit-desc strong{color:#00e5ff}' +
'.ptg-exit-trust{display:flex;gap:16px;flex-wrap:wrap;margin-bottom:20px;padding:12px 16px;background:rgba(255,255,255,0.04);border-radius:10px;border:1px solid rgba(255,255,255,0.06)}' +
'.ptg-exit-trust span{font-size:0.8rem;font-weight:600;color:rgba(240,240,240,0.7);white-space:nowrap}' +
'.ptg-exit-form{display:flex;flex-direction:column;gap:10px;margin-bottom:12px}' +
'.ptg-exit-turnstile{margin:6px 0;display:flex;justify-content:center}' +
'.ptg-exit-field input{width:100%;padding:13px 16px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.12);border-radius:10px;color:#f1f5f9;font-size:0.9375rem;font-family:inherit;transition:border-color 0.2s,box-shadow 0.2s;box-sizing:border-box}' +
'.ptg-exit-field input::placeholder{color:#64748b}' +
'.ptg-exit-field input:focus{outline:none;border-color:#00A9E0;box-shadow:0 0 0 3px rgba(0,169,224,0.15)}' +
'.ptg-exit-submit{width:100%;padding:14px 32px;background:linear-gradient(135deg,#00A9E0,#7C3AED);color:#fff;font-size:1rem;font-weight:700;font-family:inherit;border:none;border-radius:10px;cursor:pointer;transition:transform 0.2s,box-shadow 0.2s,opacity 0.2s;box-shadow:0 4px 24px rgba(0,169,224,0.3)}' +
'.ptg-exit-submit:hover{transform:translateY(-1px);box-shadow:0 8px 32px rgba(0,169,224,0.4)}' +
'.ptg-exit-submit:active{transform:translateY(0)}' +
'.ptg-exit-submit:disabled{opacity:0.6;cursor:not-allowed;transform:none}' +
'.ptg-exit-privacy{font-size:0.75rem;color:#64748b;text-align:center;margin:0 0 16px 0}' +
'.ptg-exit-privacy a{color:#94a3b8;text-decoration:underline;text-underline-offset:2px}' +
'.ptg-exit-alt{text-align:center;padding-top:16px;border-top:1px solid rgba(255,255,255,0.06)}' +
'.ptg-exit-alt span{font-size:0.85rem;color:#64748b;margin-right:8px}' +
'.ptg-exit-phone{display:inline-flex;align-items:center;gap:6px;font-size:0.95rem;font-weight:700;color:#00A9E0;text-decoration:none;transition:color 0.2s}' +
'.ptg-exit-phone:hover{color:#00e5ff}' +
'#ptg-exit-overlay .visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}' +
'@media(prefers-reduced-motion:reduce){#ptg-exit-overlay,.ptg-exit-popup,.ptg-exit-submit{transition:none!important;transform:none!important}}' +
'@media(max-width:480px){.ptg-exit-popup{padding:32px 20px 24px;max-height:90vh;overflow-y:auto;border-radius:12px}.ptg-exit-heading{font-size:1.25rem}.ptg-exit-trust{flex-direction:column;gap:4px}.ptg-exit-trust span{font-size:0.75rem}}';
document.head.appendChild(style);
document.body.appendChild(overlay);
return overlay;
}
// ── Show Popup ──────────────────────────────────────────────
function showPopup() {
if (popupShown || ctaClicked) return;
if (Date.now() - pageLoadTime < CONFIG.globalDelay) return;
// Suppress when a lead gate is active (calculator pages)
if (document.body.hasAttribute('data-gate-pending')) return;
popupShown = true;
// If PTGGuide is available but we haven't captured a guide yet, give it
// one last chance with a hard 500ms timeout. Never render empty.
if (currentGuide === FALLBACK_GUIDE && window.PTGGuide && typeof window.PTGGuide.current === 'function') {
var done = false;
var to = setTimeout(function () { if (!done) { done = true; _drawPopup(); } }, 500);
window.PTGGuide.current().then(function (g) {
if (done) return;
done = true; clearTimeout(to);
if (g) currentGuide = g;
_drawPopup();
}).catch(function () {
if (done) return;
done = true; clearTimeout(to);
_drawPopup();
});
return;
}
_drawPopup();
}
function _drawPopup() {
var overlay = createPopup();
var closeBtn = document.getElementById('ptg-exit-close');
var form = document.getElementById('ptg-exit-form');
var nameInput = document.getElementById('ptg-exit-name');
// Animate in
requestAnimationFrame(function () {
requestAnimationFrame(function () {
overlay.classList.add('is-visible');
if (nameInput) nameInput.focus();
renderTurnstileInPopup();
});
});
// ── Dismiss Logic ─────────────────────────────────────────
var dismissLogged = false;
function dismiss() {
if (!dismissLogged) { dismissLogged = true; logEvent('dismiss'); }
overlay.classList.remove('is-visible');
sessionStorage.setItem(CONFIG.storageKey, '1');
setTimeout(function () {
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
}, 350);
}
if (closeBtn) closeBtn.addEventListener('click', dismiss);
overlay.addEventListener('click', function (e) {
if (e.target === overlay) dismiss();
});
document.addEventListener('keydown', function handler(e) {
if (e.key === 'Escape') {
dismiss();
document.removeEventListener('keydown', handler);
}
});
// ── Form Submission ───────────────────────────────────────
if (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
e.stopPropagation();
var submitBtn = form.querySelector('.ptg-exit-submit');
// Client-side Turnstile presence check. The widget injects a hidden
// input named "cf-turnstile-response" only after the challenge passes.
// Checking here gives users a clearer message than a 400 from the
// server. FormData still auto-includes the token on submit.
var tsInput = form.querySelector('input[name="cf-turnstile-response"]');
if (!tsInput || !tsInput.value) {
var descElPre = document.querySelector('#ptg-exit-popup .ptg-exit-desc');
if (descElPre) {
descElPre.textContent = 'Please complete the verification check below before submitting.';
}
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = 'Try Again';
}
return;
}
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';
}
var formData = new FormData(form);
fetch(CONFIG.formAction, {
method: 'POST',
body: formData
})
.then(function (response) {
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json();
})
.then(function (data) {
if (!data || data.success !== true) {
throw new Error((data && data.error) || 'Submission failed');
}
var popup = document.getElementById('ptg-exit-popup');
if (popup) {
var formEl = popup.querySelector('.ptg-exit-form');
var privacyEl = popup.querySelector('.ptg-exit-privacy');
var trustEl = popup.querySelector('.ptg-exit-trust');
var descEl = popup.querySelector('.ptg-exit-desc');
var headingEl = popup.querySelector('.ptg-exit-heading');
var badgeEl = popup.querySelector('.ptg-exit-badge');
if (formEl) formEl.remove();
if (privacyEl) privacyEl.remove();
if (trustEl) trustEl.remove();
if (badgeEl) badgeEl.textContent = 'SUCCESS';
if (headingEl) headingEl.textContent = 'Thank You!';
if (descEl) {
descEl.innerHTML =
'Check your inbox for the download link. A Petronella specialist ' +
'will follow up within 1 business hour if you\u2019d ' +
'like to talk through the material.';
}
}
// Track conversion
var gid = (currentGuide && currentGuide.guide_id) || 'unknown';
if (typeof gtag === 'function') {
gtag('event', 'exit_popup_conversion', {
event_category: 'lead_generation',
event_label: path,
guide_id: gid
});
} else {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'exit_popup_conversion',
event_category: 'lead_generation',
event_label: path,
guide_id: gid
});
}
// Fire Lead conversion to ad pixels directly (conversion-tracking.tpl
// only fires on /thank-you/ URLs and this popup never navigates).
// Guard prevents double-fire if user later hits a thank-you page this session.
if (!window._ptgLeadTracked) {
window._ptgLeadTracked = true;
try {
if (typeof twq === 'function') {
twq('event', 'tw-q5skl-q5sl3', { value: '100.00', currency: 'USD' });
}
if (typeof fbq === 'function') {
fbq('track', 'Lead', { value: 100.00, currency: 'USD' });
}
if (typeof uetq !== 'undefined') {
uetq.push('event', 'submit_lead_form', {
event_category: 'conversion',
event_label: path,
event_value: 100
});
}
if (typeof gtag === 'function') {
gtag('event', 'generate_lead', {
event_category: 'form_submission',
event_label: path,
value: 100,
currency: 'USD'
});
}
} catch (e) { /* never throw from instrumentation */ }
}
logEvent('conversion');
dismissLogged = true; // prevent double-logging on auto-dismiss
sessionStorage.setItem(CONFIG.storageKey, '1');
setTimeout(dismiss, 4000);
})
.catch(function (err) {
if (window.console && console.error) {
console.error('[ptg-exit-popup] submission failed:', err);
}
var popup = document.getElementById('ptg-exit-popup');
if (popup) {
var descEl = popup.querySelector('.ptg-exit-desc');
var headingEl = popup.querySelector('.ptg-exit-heading');
if (headingEl) headingEl.textContent = 'Something Went Wrong';
if (descEl) {
descEl.innerHTML =
'We couldn\u2019t submit your request right now. Please call ' +
'919-348-4912 ' +
'and we\u2019ll get you taken care of immediately.';
}
}
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = 'Try Again';
}
});
});
}
// Track impression (server-side A/B log + client analytics)
logEvent('impression');
var gidImp = (currentGuide && currentGuide.guide_id) || 'unknown';
if (typeof gtag === 'function') {
gtag('event', 'exit_popup_impression', {
event_category: 'engagement',
event_label: path,
guide_id: gidImp
});
} else {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'exit_popup_impression',
event_category: 'engagement',
event_label: path,
guide_id: gidImp
});
}
}
// ── Desktop: Mouse Exit Detection ──────────────────────────
function isMobile() {
return window.innerWidth <= 768 || 'ontouchstart' in window;
}
if (!isMobile()) {
document.addEventListener('mouseout', function (e) {
if (
e.clientY <= CONFIG.mouseYThreshold &&
e.relatedTarget === null &&
!popupShown
) {
showPopup();
}
});
}
// ── Mobile: Scroll + Time Detection ────────────────────────
if (isMobile()) {
var mobileScrollReached = false;
window.addEventListener(
'scroll',
function () {
if (popupShown || ctaClicked) return;
var scrollPct =
(window.pageYOffset || document.documentElement.scrollTop) /
(document.documentElement.scrollHeight - window.innerHeight);
if (scrollPct >= CONFIG.mobileScrollThreshold) {
mobileScrollReached = true;
}
if (
mobileScrollReached &&
Date.now() - pageLoadTime >= CONFIG.mobileTimeThreshold
) {
showPopup();
}
},
{ passive: true }
);
}
})();