/**
* Client-side Guide Router
* ========================
* Fetches /inc/config/guide-routing.json once per session and resolves
* the best-matched guide for the current URL.
*
* Deploy to: /home/petronellatech/public_html/inc/js/guide-routing.js
*
* Exposes: window.PTGGuide = {
* resolve: function(url) -> Promise,
* current: function() -> Promise
* };
*
* Consumed by: default-bottom.tpl slide-in + /inc/exit-popup.js
*/
(function () {
'use strict';
var CACHE_KEY = 'ptg-guide-routing-v1';
var CACHE_TTL = 6 * 60 * 60 * 1000; // 6 hours
var CONFIG_URL = '/inc/config/guide-routing.json';
var inflight = null;
function loadRouting() {
if (inflight) return inflight;
try {
var cached = sessionStorage.getItem(CACHE_KEY);
if (cached) {
var parsed = JSON.parse(cached);
if (parsed && parsed._ts && (Date.now() - parsed._ts) < CACHE_TTL && parsed.data) {
inflight = Promise.resolve(parsed.data);
return inflight;
}
}
} catch (e) { /* ignore */ }
inflight = fetch(CONFIG_URL, { credentials: 'omit' })
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (data) {
if (!data || !data.guides || !data.routing) return null;
try {
sessionStorage.setItem(CACHE_KEY, JSON.stringify({ _ts: Date.now(), data: data }));
} catch (e) { /* ignore quota */ }
return data;
})
.catch(function () { return null; });
return inflight;
}
function matchGuideId(url, routing) {
var u = (url || '/').toLowerCase();
var best = null;
var fallback = null;
for (var i = 0; i < routing.length; i++) {
var r = routing[i];
var pat = (r.match_pattern || '').toLowerCase();
var t = r.match_type || '';
var pri = +r.priority || 0;
if (t === 'default') { fallback = r; continue; }
if (!pat) continue;
var hit = false;
if (t === 'url_prefix') hit = u.indexOf(pat) === 0;
else if (t === 'url_contains') hit = u.indexOf(pat) !== -1;
else if (t === 'url_regex') {
try { hit = new RegExp(pat, 'i').test(u); } catch (e) {}
}
if (hit && (!best || pri > (+best.priority || 0))) best = r;
}
return (best && best.guide_id) || (fallback && fallback.guide_id) || 'business-tech-2026';
}
function resolve(url) {
return loadRouting().then(function (data) {
if (!data) return defaultGuide();
var gid = matchGuideId(url, data.routing);
var g = data.guides[gid] || data.guides['business-tech-2026'] || defaultGuide();
g = Object.assign({}, g, { guide_id: gid });
return g;
});
}
function defaultGuide() {
return {
guide_id: 'business-tech-2026',
title: '2026 SMB Cybersecurity Survival Guide',
landing_page_url: 'https://petronellatech.com/2026-guide/',
download_url: 'https://petronellatech.com/2026-guide/download.php',
popup_heading: 'Before you go…',
popup_body: 'Get the 2026 SMB Cybersecurity Survival Guide — free, 28 pages of actionable strategies.',
popup_cta_text: 'Download Free Guide',
pages: 28
};
}
// ── A/B Variant Assignment (Workstream A5) ────────────────────────
// Stable per (session, guide_id). Cookie: ptg_pop_var_.
// 33/33/33 split across control / treatment-a / treatment-b.
var VARIANTS = ['control', 'treatment-a', 'treatment-b'];
function readCookie(name) {
var m = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/[.$?*|{}()[\]\\/+^]/g, '\\$&') + '=([^;]*)'));
return m ? decodeURIComponent(m[1]) : '';
}
function writeCookie(name, value, days) {
var exp = new Date(Date.now() + days * 86400000).toUTCString();
document.cookie = name + '=' + encodeURIComponent(value) +
'; expires=' + exp + '; path=/; SameSite=Lax';
}
function pickVariant() {
// Prefer crypto for a fair split; fall back to Math.random.
if (window.crypto && window.crypto.getRandomValues) {
var buf = new Uint32Array(1);
window.crypto.getRandomValues(buf);
return VARIANTS[buf[0] % VARIANTS.length];
}
return VARIANTS[Math.floor(Math.random() * VARIANTS.length)];
}
function variantFor(guideId) {
var gid = guideId || 'unknown';
var cookieName = 'ptg_pop_var_' + gid.replace(/[^A-Za-z0-9_-]/g, '_');
var existing = readCookie(cookieName);
if (VARIANTS.indexOf(existing) !== -1) return existing;
var v = pickVariant();
writeCookie(cookieName, v, 30);
return v;
}
// ── Session ID cookie (for popup event dedup / funnel joins) ─────
function sessionId() {
var existing = readCookie('ptg_pop_sid');
if (/^[a-f0-9]{32}$/.test(existing)) return existing;
var sid;
if (window.crypto && window.crypto.getRandomValues) {
var arr = new Uint8Array(16);
window.crypto.getRandomValues(arr);
sid = Array.prototype.map.call(arr, function (b) {
return ('0' + b.toString(16)).slice(-2);
}).join('');
} else {
sid = '';
while (sid.length < 32) sid += Math.floor(Math.random() * 16).toString(16);
}
writeCookie('ptg_pop_sid', sid, 30);
return sid;
}
window.PTGGuide = {
resolve: resolve,
current: function () { return resolve(window.location.pathname + window.location.search); },
variant: variantFor,
sessionId: sessionId,
_matchGuideId: matchGuideId
};
})();