/** * 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 = '
' + ' ' + '
FREE
' + '

' + esc(heading) + '

' + '

' + bodyHtml + '

' + '
' + ' CMMC-AB Registered Provider Organization #1449' + ' BBB A+ Accredited since 2003' + ' Founded 2002 - Raleigh NC' + '
' + '
' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '
' + ' ' + ' ' + '
' + '
' + ' ' + ' ' + '
' + '
' + ' ' + ' ' + '
' + '
' + '
' + '
' + ' ' + '
' + '

' + ' We respect your privacy. No spam, ever. Privacy Policy' + '

' + '
' + ' Prefer to talk?' + ' ' + ' ' + ' 919-348-4912' + ' ' + '
' + '
'; // ── 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 } ); } })();