/* ════════════════════════════════════════════════════════════════════ NexFlow · Affiliate referral capture (engine/ref-capture.js) ──────────────────────────────────────────────────────────────────── First-party ONLY. No third-party pixels, no analytics, no network calls. The hypecryptonow.com affiliate program hands out referral links shaped https://www.nex-flow.io/?ref=NF-XXXXXX. This script captures that code on landing and persists it so the referral is attributed when the visitor later converts (books a map / sends a message), even across pages and return visits within the window. How it persists & attributes: 1. cookie nf_ref — 90 days, SameSite=Lax, Secure, domain=.nex-flow.io. The contact/book forms POST same-origin via fetch, so the browser sends this cookie automatically; api/contact-handler.php and api/book-request.php read $_COOKIE['nf_ref'] and attach it to the lead (owner email + n8n fan-out). 2. localStorage nf_ref — backup store + survives cookie clears within the same browser; mirrors the cookie value. 3. window.NF_REF — exposed for any in-page script that wants it. 4. hidden injected into every form — belt-and- suspenders for the no-JS native form fallback (those posts carry it as $_POST['ref']). The code is validated to the affiliate code charset before storage, so a crafted ?ref= value can never inject into a cookie, a log line, an email, or a form field. ════════════════════════════════════════════════════════════════════ */ (function () { 'use strict'; var COOKIE = 'nf_ref'; var MAX_AGE = 60 * 60 * 24 * 90; // 90 days, in seconds var VALID = /^NF-[A-Z0-9]{4,16}$/; // affiliate code shape (NF-XXXXXX) function clean(raw) { if (!raw) return ''; var v = String(raw).trim().toUpperCase(); return VALID.test(v) ? v : ''; } function readCookie(name) { try { var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); return m ? clean(decodeURIComponent(m[1])) : ''; } catch (e) { return ''; } } function writeCookie(name, value) { try { // domain=.nex-flow.io covers both apex and www; falls back gracefully // (browser ignores the attribute) on any other host. var secure = (location.protocol === 'https:') ? '; Secure' : ''; document.cookie = name + '=' + encodeURIComponent(value) + '; Max-Age=' + MAX_AGE + '; Path=/; SameSite=Lax' + secure + '; domain=.nex-flow.io'; } catch (e) { /* cookies disabled — fall through to localStorage */ } } function readStore() { try { return clean(window.localStorage.getItem(COOKIE) || ''); } catch (e) { return ''; } } function writeStore(value) { try { window.localStorage.setItem(COOKIE, value); } catch (e) {} } // 1) Prefer a fresh code from the URL; otherwise reuse a stored one. var fromUrl = ''; try { fromUrl = clean(new URLSearchParams(location.search).get('ref') || ''); } catch (e) { fromUrl = ''; } var ref = fromUrl || readCookie(COOKIE) || readStore(); // 2) Persist (only (re)write when we actually have a code). if (ref) { if (fromUrl) { writeCookie(COOKIE, ref); } // refresh the 90-day window on a new landing else if (!readCookie(COOKIE)) { writeCookie(COOKIE, ref); } // restore cookie from store writeStore(ref); } window.NF_REF = ref || ''; // 3) Inject/maintain a hidden ref field on every form (no-JS fallback path). function decorate(form) { if (!window.NF_REF || !form || form.__nfRef) return; try { var input = form.querySelector('input[name="ref"]'); if (!input) { input = document.createElement('input'); input.type = 'hidden'; input.name = 'ref'; form.appendChild(input); } input.value = window.NF_REF; form.__nfRef = true; } catch (e) {} } function scan() { if (!window.NF_REF) return; var forms = document.getElementsByTagName('form'); for (var i = 0; i < forms.length; i++) { decorate(forms[i]); } } if (window.NF_REF) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', scan); } else { scan(); } // React mounts its forms after hydration — observe so late forms get the field too. try { var mo = new MutationObserver(scan); mo.observe(document.documentElement, { childList: true, subtree: true }); // Stop watching after 20s; by then the SPA has mounted. setTimeout(function () { try { mo.disconnect(); } catch (e) {} }, 20000); } catch (e) {} } })();