/* Demo player. Renders image or dom steps with hotspot overlay; emits privacy-first beacons. */ (function () { "use strict"; var token = location.pathname.split("/").pop(); var params = new URLSearchParams(location.search); var viewerToken = params.get("v") || null; // R7: identity only via explicit link token var state = { demo: null, i: 0 }; var snap = document.getElementById("snap"); var dots = document.getElementById("dots"); var titleEl = document.getElementById("title"); function beacon(event, stepId) { if (!state.demo) return; var payload = JSON.stringify({ demo_id: state.demo.demo_id, step_id: stepId || null, viewer_token: viewerToken, event: event, }); if (navigator.sendBeacon) { navigator.sendBeacon("/api/public/analytics", new Blob([payload], { type: "application/json" })); } else { fetch("/api/public/analytics", { method: "POST", headers: { "Content-Type": "application/json" }, body: payload }); } } function render() { var step = state.demo.steps[state.i]; snap.innerHTML = ""; var container; if (step.snapshot_type === "image") { container = document.createElement("img"); container.src = "/assets/" + step.asset_id; container.draggable = false; } else { container = document.createElement("iframe"); container.setAttribute("sandbox", ""); // R1/R2: fully inert document, no scripts container.srcdoc = step.dom_html || ""; } snap.appendChild(container); if (step.hotspot) { var h = document.createElement("div"); h.className = "hotspot"; h.style.left = step.hotspot.x + "%"; h.style.top = step.hotspot.y + "%"; h.style.width = step.hotspot.w + "%"; h.style.height = step.hotspot.h + "%"; h.addEventListener("click", next); snap.appendChild(h); if (step.annotation) { var t = document.createElement("div"); t.className = "tooltip"; var below = step.hotspot.y < 70; t.style.left = Math.min(step.hotspot.x, 70) + "%"; t.style.top = below ? (step.hotspot.y + step.hotspot.h + 2) + "%" : "auto"; if (!below) t.style.bottom = (100 - step.hotspot.y + 2) + "%"; t.innerHTML = (step.annotation.title ? "
" : "") + (step.annotation.body ? "" : ""); if (step.annotation.title) t.querySelector("h3").textContent = step.annotation.title; if (step.annotation.body) t.querySelector("p").textContent = step.annotation.body; snap.appendChild(t); } } dots.innerHTML = ""; state.demo.steps.forEach(function (_, j) { var d = document.createElement("div"); d.className = "dot" + (j === state.i ? " active" : ""); dots.appendChild(d); }); } var gatePassed = {}; function showGate(step, after) { var overlay = document.createElement("div"); overlay.style.cssText = "position:absolute;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:10"; var card = document.createElement("form"); card.style.cssText = "background:#fff;border-radius:10px;padding:22px;max-width:320px;width:90%;display:flex;flex-direction:column;gap:10px;box-shadow:0 12px 40px rgba(15,23,42,.25)"; var h = document.createElement("h3"); h.textContent = "Continue watching"; card.appendChild(h); (step.lead_gate.fields || []).forEach(function (fdef) { var inp = document.createElement("input"); inp.name = fdef.name; inp.placeholder = fdef.label + (fdef.required ? " *" : ""); inp.required = !!fdef.required; inp.style.cssText = "border:1px solid #e2e8f0;border-radius:6px;padding:8px 10px;font:inherit"; card.appendChild(inp); }); var btn = document.createElement("button"); btn.textContent = "Continue"; btn.type = "submit"; btn.style.cssText = "background:#2563eb;color:#fff;border:none;border-radius:6px;padding:9px;font:inherit;cursor:pointer"; card.appendChild(btn); card.addEventListener("submit", function (e) { e.preventDefault(); var fields = {}; (step.lead_gate.fields || []).forEach(function (fdef) { fields[fdef.name] = card.elements[fdef.name].value; }); fetch("/api/public/leads", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ demo_id: state.demo.demo_id, viewer_token: viewerToken, fields: fields }), }).finally(function () { gatePassed[step.id] = true; overlay.remove(); after(); }); }); overlay.appendChild(card); snap.appendChild(overlay); } function next() { var cur = state.demo.steps[state.i]; if (cur.lead_gate && cur.lead_gate.enabled && !gatePassed[cur.id]) { showGate(cur, next); return; } if (state.i >= state.demo.steps.length - 1) { beacon("complete", cur.id); return; } state.i++; beacon("advance", state.demo.steps[state.i].id); render(); } function prev() { if (state.i === 0) return; state.i--; beacon("back", state.demo.steps[state.i].id); render(); } document.getElementById("next").addEventListener("click", next); document.getElementById("prev").addEventListener("click", prev); document.addEventListener("keydown", function (e) { if (e.key === "ArrowRight") next(); if (e.key === "ArrowLeft") prev(); }); fetch("/api/public/demo/" + token) .then(function (r) { if (!r.ok) throw new Error("not found"); return r.json(); }) .then(function (demo) { state.demo = demo; titleEl.textContent = demo.name; document.title = demo.name; beacon("view", demo.steps.length ? demo.steps[0].id : null); render(); }) .catch(function () { snap.innerHTML = "