const questions = [
// SECTION 1: Customer Base Complexity (Index: 0)
{
id: "complexity_volume",
section: 0,
type: "slider",
text: "How many active customer accounts does your business manage?",
desc: "",
min: 10,
max: 5000,
step: 10,
default: 150,
labels: ["10", "1,000", "2,500", "5,000+"]
},
{
id: "complexity_gtm",
section: 0,
type: "radio",
text: "What is your average Annual Contract Value (ACV) per account?",
desc: "Your average deal size dictates your risk exposure and capabilities needed to protect accounts.",
options: [
{ label: "Under £5,000 / year", value: 60, acv: 2400 },
{ label: "£5,000 - £25,000 / year", value: 80, acv: 18000 },
{ label: "£25,000 - £50,000 / year", value: 40, acv: 35000 },
{ label: "Over £50,000 / year", value: 50, acv: 75000 }
]
},
// SECTION 2: Data Fragmentation (Index: 1)
{
id: "frag_tools",
section: 1,
type: "checkbox",
text: "Which systems store your customer health and product usage data?",
desc: "Select all that apply.",
items: [
{ id: "crm", label: "CRM (e.g. Salesforce / HubSpot)", icon: "crm" },
{ id: "cs", label: "CS Platform (e.g. Gainsight / ChurnZero)", icon: "cs" },
{ id: "db", label: "Warehouse / DB (e.g. Snowflake / PostgreSQL)", icon: "db" },
{ id: "analytics", label: "Product Analytics (e.g. Mixpanel / Amplitude)", icon: "analytics" },
{ id: "helpdesk", label: "Helpdesk (e.g. Zendesk / Intercom)", icon: "helpdesk" },
{ id: "spreadsheets", label: "Spreadsheets / Manual Trackers", icon: "spreadsheets" }
]
},
// SECTION 3: Risk Prediction & Action (Index: 2)
{
id: "risk_ident",
section: 2,
type: "radio",
text: "How do you predict risk and what do you do about it?",
desc: "Your methods for identifying churn threat and standardising response determine recovery success.",
options: [
{ label: "Reactive & Ad-hoc", value: 15 },
{ label: "Manual Checks & Checklists", value: 45 },
{ label: "Rule-Based Triggers & Manual Outreach", value: 75 },
{ label: "Predictive Automation & Auto-Playbooks", value: 100 },
{ label: "Other", value: 30, isOther: true }
]
},
// SECTION 4: Revenue Operations Efficiency (Index: 3)
{
id: "revops_time",
section: 3,
type: "radio",
text: "How much time do you spend manually compiling health reports weekly?",
desc: "",
options: [
{ label: "Less than 1 hour per week", value: 100, hours: 0.5 },
{ label: "1 - 3 hours per week", value: 75, hours: 2.0 },
{ label: "3 - 5 hours per week", value: 40, hours: 4.0 },
{ label: "5+ hours per week", value: 15, hours: 7.0 }
]
}
];
const sections = [
{ name: "Customer Base Complexity", weight: 0.10 },
{ name: "Data Fragmentation", weight: 0.30 },
{ name: "Risk Prediction & Action", weight: 0.35 },
{ name: "Revenue Operations Efficiency", weight: 0.25 }
];
let currentStep = 0;
let userAnswers = {};
let leadDetails = {};
document.addEventListener("DOMContentLoaded", () => {
// Bind Landing CTA -> Opens Lead Capture Screen first
const startBtn = document.getElementById("start-audit-btn");
if (startBtn) {
startBtn.addEventListener("click", () => {
showLeadCaptureScreen();
});
}
// Bind Wizard Navigation Buttons
const prevBtn = document.getElementById("prev-btn");
if (prevBtn) {
prevBtn.addEventListener("click", handlePrev);
}
const nextBtn = document.getElementById("next-btn");
if (nextBtn) {
nextBtn.addEventListener("click", handleNext);
}
// Bind Lead Form Submission
const leadForm = document.getElementById("lead-capture-form");
if (leadForm) {
leadForm.addEventListener("submit", handleLeadSubmit);
}
// Bind Restart Button
const restartBtn = document.getElementById("restart-audit-btn");
if (restartBtn) {
restartBtn.addEventListener("click", () => {
currentStep = 0;
userAnswers = {};
leadDetails = {};
const nameInput = document.getElementById("lead-name");
if (nameInput) nameInput.value = "";
const emailInput = document.getElementById("lead-email");
if (emailInput) emailInput.value = "";
const roleInput = document.getElementById("lead-role");
if (roleInput) roleInput.value = "";
const phoneInput = document.getElementById("lead-phone");
if (phoneInput) phoneInput.value = "";
const optinInput = document.getElementById("lead-optin");
if (optinInput) optinInput.checked = false;
const resultsView = document.getElementById("results-view");
if (resultsView) resultsView.classList.remove("active");
const landingView = document.getElementById("landing-view");
if (landingView) {
landingView.classList.add("active");
} else {
const leadView = document.getElementById("lead-view");
if (leadView) leadView.classList.add("active");
}
});
}
// Fallback: If no landing-view exists on page load, make lead-view active immediately
if (!document.getElementById("landing-view")) {
const leadView = document.getElementById("lead-view");
if (leadView) leadView.classList.add("active");
}
});
function transitionToStep(stepIndex) {
const landingView = document.getElementById("landing-view");
if (landingView) {
landingView.classList.remove("active");
}
const previewContainer = document.getElementById("risqly-landing-preview-container");
if (previewContainer) {
previewContainer.style.display = "none";
}
const assessmentView = document.getElementById("assessment-view");
if (assessmentView) assessmentView.classList.add("active");
const leadView = document.getElementById("lead-view");
if (leadView) leadView.classList.remove("active");
const resultsView = document.getElementById("results-view");
if (resultsView) resultsView.classList.remove("active");
currentStep = stepIndex;
renderQuestion(currentStep);
updateProgressBar();
}
function renderQuestion(index) {
const q = questions[index];
const container = document.getElementById("question-container");
container.innerHTML = "";
const title = document.createElement("h2");
title.className = "risqly-question-title";
title.textContent = q.text;
if (q.id === "complexity_volume") {
title.style.setProperty("margin-bottom", "18px", "important");
} else if (q.id === "revops_time") {
title.style.setProperty("margin-bottom", "18px", "important");
}
container.appendChild(title);
if (q.desc) {
const desc = document.createElement("p");
desc.className = "risqly-question-desc";
desc.textContent = q.desc;
container.appendChild(desc);
}
if (q.type === "radio" || q.type === "binary") {
const optionsWrapper = document.createElement("div");
optionsWrapper.className = "risqly-question-options";
q.options.forEach((opt, oIndex) => {
const card = document.createElement("div");
card.className = "risqly-option-card";
const isSelected = userAnswers[q.id] === oIndex;
if (isSelected) card.classList.add("risqly-selected");
card.innerHTML = `
${opt.label}
${opt.hint ? `
${opt.hint}
` : ''}
`;
card.addEventListener("click", (e) => {
if (e.target.tagName === "INPUT") return;
userAnswers[q.id] = oIndex;
renderQuestion(index);
updateNavigationButtons();
});
if (opt.isOther && isSelected) {
const inputWrapper = document.createElement("div");
inputWrapper.style.marginTop = "10px";
inputWrapper.style.width = "100%";
const otherInput = document.createElement("input");
otherInput.type = "text";
otherInput.className = "form-input";
otherInput.placeholder = "Please specify...";
otherInput.style.width = "100%";
otherInput.style.boxSizing = "border-box";
otherInput.value = userAnswers[q.id + "_other_text"] || "";
otherInput.addEventListener("input", (ev) => {
userAnswers[q.id + "_other_text"] = ev.target.value;
updateNavigationButtons();
});
inputWrapper.appendChild(otherInput);
const infoDiv = card.querySelector(".risqly-option-info");
infoDiv.appendChild(inputWrapper);
}
optionsWrapper.appendChild(card);
});
container.appendChild(optionsWrapper);
} else if (q.type === "slider") {
const sliderWrapper = document.createElement("div");
sliderWrapper.className = "risqly-slider-container";
sliderWrapper.style.setProperty("margin-top", "60px", "important");
const currentValue = userAnswers[q.id] !== undefined ? userAnswers[q.id] : q.default;
userAnswers[q.id] = currentValue;
const bubble = document.createElement("div");
bubble.className = "risqly-slider-val-bubble";
bubble.id = "slider-bubble";
bubble.textContent = formatSliderValue(q.id, currentValue);
const input = document.createElement("input");
input.type = "range";
input.className = "risqly-range-slider";
input.style.width = "100%";
input.style.boxSizing = "border-box";
if (q.id === "complexity_volume") {
input.min = 0;
input.max = 300;
input.step = 1;
input.value = mapValueToSliderPos(currentValue);
} else {
input.min = q.min;
input.max = q.max;
input.step = q.step;
input.value = currentValue;
}
if (q.id === "complexity_volume") {
updateSliderBackground(input, 0, 300, mapValueToSliderPos(currentValue));
} else {
updateSliderBackground(input, q.min, q.max, currentValue);
}
input.addEventListener("input", (e) => {
const rawVal = parseFloat(e.target.value);
let val = rawVal;
if (q.id === "complexity_volume") {
val = mapSliderPosToValue(rawVal);
}
userAnswers[q.id] = val;
bubble.textContent = formatSliderValue(q.id, val);
const pct = (rawVal - input.min) / (input.max - input.min);
bubble.style.left = `calc(${pct * 100}% - ${(pct - 0.5) * 20}px)`;
updateSliderBackground(e.target, input.min, input.max, rawVal);
});
sliderWrapper.appendChild(bubble);
sliderWrapper.appendChild(input);
const labelsRow = document.createElement("div");
labelsRow.className = "risqly-slider-labels";
q.labels.forEach((label, idx) => {
const span = document.createElement("span");
span.innerHTML = label;
const pct = (idx / (q.labels.length - 1)) * 100;
span.style.left = `${pct}%`;
labelsRow.appendChild(span);
});
sliderWrapper.appendChild(labelsRow);
container.appendChild(sliderWrapper);
setTimeout(() => {
const rawVal = q.id === "complexity_volume" ? mapValueToSliderPos(currentValue) : currentValue;
const pct = (rawVal - input.min) / (input.max - input.min);
bubble.style.left = `calc(${pct * 100}% - ${(pct - 0.5) * 20}px)`;
}, 0);
} else if (q.type === "checkbox") {
const grid = document.createElement("div");
grid.className = "risqly-tool-grid";
if (!userAnswers[q.id]) userAnswers[q.id] = [];
q.items.forEach(item => {
const card = document.createElement("div");
card.className = "risqly-tool-card";
const isChecked = userAnswers[q.id].includes(item.id);
if (isChecked) card.classList.add("risqly-selected");
card.innerHTML = `
${getToolIconSvg(item.icon)}
${item.label}
`;
card.addEventListener("click", () => {
let list = [...userAnswers[q.id]];
if (list.includes(item.id)) {
list = list.filter(id => id !== item.id);
} else {
list.push(item.id);
}
userAnswers[q.id] = list;
renderQuestion(index);
updateNavigationButtons();
});
grid.appendChild(card);
});
container.appendChild(grid);
}
updateNavigationButtons();
}
function updateSliderBackground(slider, min, max, value) {
const percentage = ((value - min) / (max - min)) * 100;
slider.style.background = `linear-gradient(to right, var(--primary) 0%, var(--primary) ${percentage}%, rgba(0, 0, 0, 0.06) ${percentage}%, rgba(0, 0, 0, 0.06) 100%)`;
}
function formatSliderValue(id, val) {
if (id === "complexity_volume") {
return `${val.toLocaleString()} accounts`;
}
if (id === "frag_unity") {
const texts = ["Siloed", "Fragmented", "Partially Unified", "Unified", "Real-Time"];
return texts[val - 1];
}
return val;
}
function mapSliderPosToValue(pos) {
const p = pos / 300;
if (p <= 1/3) {
const pPrime = p * 3;
return Math.round(10 + pPrime * (1000 - 10));
} else if (p <= 2/3) {
const pPrime = (p - 1/3) * 3;
return Math.round(1000 + pPrime * (2500 - 1000));
} else {
const pPrime = (p - 2/3) * 3;
return Math.round(2500 + pPrime * (5000 - 2500));
}
}
function mapValueToSliderPos(val) {
if (val <= 1000) {
const pPrime = (val - 10) / (1000 - 10);
const p = pPrime / 3;
return p * 300;
} else if (val <= 2500) {
const pPrime = (val - 1000) / (2500 - 1000);
const p = (pPrime / 3) + (1/3);
return p * 300;
} else {
const pPrime = (val - 2500) / (5000 - 2500);
const p = (pPrime / 3) + (2/3);
return p * 300;
}
}
function getToolIconSvg(key) {
const svgs = {
crm: ``,
cs: ``,
db: ``,
analytics: ``,
helpdesk: ``,
spreadsheets: ``
};
return svgs[key] || svgs.crm;
}
function updateProgressBar() {
const total = questions.length;
const pct = Math.round((currentStep / total) * 100);
document.getElementById("progress-fill").style.width = `${pct}%`;
document.getElementById("risqly-progress-percent").textContent = `${pct}% Complete`;
const currentSectionIdx = questions[currentStep].section;
const indicators = document.querySelectorAll(".risqly-step-indicator");
indicators.forEach((ind, iIndex) => {
ind.className = "risqly-step-indicator";
if (iIndex === currentSectionIdx) {
ind.classList.add("active");
} else if (iIndex < currentSectionIdx) {
ind.classList.add("completed");
}
});
}
function updateNavigationButtons() {
const q = questions[currentStep];
const nextBtn = document.getElementById("next-btn");
const prevBtn = document.getElementById("prev-btn");
if (currentStep === 0) {
prevBtn.style.visibility = "hidden";
} else {
prevBtn.style.visibility = "visible";
}
let isAnswered = false;
if (q.type === "radio" || q.type === "binary") {
isAnswered = userAnswers[q.id] !== undefined;
if (isAnswered && q.options && q.options[userAnswers[q.id]] && q.options[userAnswers[q.id]].isOther) {
const otherText = userAnswers[q.id + "_other_text"] || "";
isAnswered = otherText.trim().length > 0;
}
} else if (q.type === "slider") {
isAnswered = true;
} else if (q.type === "checkbox") {
isAnswered = userAnswers[q.id] && userAnswers[q.id].length > 0;
}
if (isAnswered) {
nextBtn.className = "btn btn-primary";
nextBtn.removeAttribute("disabled");
} else {
nextBtn.className = "btn risqly-btn-disabled";
nextBtn.setAttribute("disabled", "true");
}
if (currentStep === questions.length - 1) {
nextBtn.innerHTML = `Calculate `;
} else {
nextBtn.innerHTML = `Continue `;
}
}
function handlePrev() {
if (currentStep > 0) {
currentStep--;
renderQuestion(currentStep);
updateProgressBar();
}
}
function handleNext() {
const q = questions[currentStep];
if (q.type === "radio" || q.type === "binary") {
if (userAnswers[q.id] === undefined) return;
} else if (q.type === "checkbox") {
if (!userAnswers[q.id] || userAnswers[q.id].length === 0) return;
}
if (currentStep < questions.length - 1) {
currentStep++;
renderQuestion(currentStep);
updateProgressBar();
} else {
// Last step complete, transition directly to Results Dashboard
showResultsDashboardScreen();
}
}
function showLeadCaptureScreen() {
const landingView = document.getElementById("landing-view");
if (landingView) landingView.classList.remove("active");
const assessmentView = document.getElementById("assessment-view");
if (assessmentView) assessmentView.classList.remove("active");
const resultsView = document.getElementById("results-view");
if (resultsView) resultsView.classList.remove("active");
const leadView = document.getElementById("lead-view");
if (leadView) leadView.classList.add("active");
}
function getReadableAnswer(question) {
const value = userAnswers[question.id];
if (value === undefined || value === null) {
return "Not answered";
}
if (question.type === "slider") {
if (question.labels && question.labels.length > 0) {
if (question.min === 1 && question.max === question.labels.length) {
const label = question.labels[value - 1] || "";
return `${value} (${label.replace(/
/g, " ")})`;
}
}
return value.toString();
}
if (question.type === "checkbox") {
if (Array.isArray(value)) {
return value.map(id => {
const item = question.items.find(i => i.id === id);
return item ? item.label : id;
}).join(", ");
}
return "";
}
if (question.type === "radio" || question.type === "binary") {
const option = question.options[value];
if (option && option.isOther) {
return "Other: " + (userAnswers[question.id + "_other_text"] || "Not specified");
}
return option ? option.label : `Option ${value}`;
}
return value.toString();
}
function submitFormToWebflow(formName, fields) {
const siteId = document.documentElement.getAttribute("data-wf-site");
if (!siteId) {
console.log(`[Local Staging] Simulated Webflow form submission: "${formName}"`, fields);
return;
}
let formId = "";
let idPrefix = "";
if (formName === "Risqly Audit Lead") {
formId = "wf-audit-form";
idPrefix = "wf-audit-";
} else if (formName === "Risqly Beta Request") {
formId = "wf-beta-form";
idPrefix = "wf-beta-";
}
const form = document.getElementById(formId);
if (!form) {
console.warn(`[Webflow Submit] Hidden form not found for: ${formName}`);
return;
}
// Populate hidden input elements
for (const [key, value] of Object.entries(fields)) {
let inputIdKey = key.toLowerCase();
if (inputIdKey === "cs-tool") inputIdKey = "cs";
if (inputIdKey === "overall-score") inputIdKey = "score";
if (inputIdKey === "maturity-tier") inputIdKey = "tier";
const inputId = idPrefix + inputIdKey;
let input = document.getElementById(inputId);
if (!input) {
input = document.createElement("input");
input.type = "hidden";
input.id = inputId;
form.appendChild(input);
}
input.name = key;
input.setAttribute("data-name", key);
input.value = value !== undefined && value !== null ? value : "";
}
// Find or dynamically create a hidden submit button
const submitBtnId = idPrefix + "submit-btn";
let submitBtn = document.getElementById(submitBtnId);
if (!submitBtn) {
submitBtn = document.createElement("input");
submitBtn.type = "submit";
submitBtn.id = submitBtnId;
submitBtn.style.display = "none";
form.appendChild(submitBtn);
}
// Directly trigger Webflow's form handler via jQuery submit, or fall back to native dispatchEvent.
// This triggers Webflow's native form listener to capture and send it via AJAX.
try {
if (window.jQuery) {
window.jQuery(form).submit();
console.log(`Webflow native form submission triggered via jQuery submit(): "${formName}"`);
} else {
const event = new Event("submit", { bubbles: true, cancelable: true });
form.dispatchEvent(event);
console.log(`Webflow native form submission triggered via dispatchEvent: "${formName}"`);
}
} catch (err) {
console.warn(`Webflow submit event triggering failed, falling back to click:`, err);
try {
submitBtn.click();
} catch (clickErr) {
console.error("Webflow native form submission click fallback failed:", clickErr);
}
}
}
const ZAPIER_WEBHOOK_URL = "https://hook.eu1.make.com/8ldca5j95llbhon64xbpnqhrdp4pgm62";
function buildPDFElement(scores) {
const dateStr = new Date().toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" });
const company = leadDetails.company || "Your Company";
const name = leadDetails.name || "";
const overallScore = scores.overallScore || 0;
const tier = scores.tier || "Reactive";
const tierDesc = scores.tierDesc || "";
const cats = scores.categories || {};
const logoWhiteUrl = "https://cdn.prod.website-files.com/691eff3fa46175f70dca8dd8/6a1aee0ed891929be360995f_Untitled%20(450%20x%20250%20px)%20(1).png";
const logoDarkUrl = "https://cdn.prod.website-files.com/691eff3fa46175f70dca8dd8/691effd3a43669d5b9e18633_Risqly-Logo.png";
function scoreColor(s) {
return s >= 70 ? "#C6ECD3" : s >= 30 ? "#fbefaf" : "#D80027";
}
function fmt(v) {
if (v >= 1000000) return "£" + (v / 1000000).toFixed(1) + "M";
if (v >= 1000) return "£" + Math.round(v / 1000) + "K";
return "£" + (v || 0);
}
const catRows = [
["Customer Base Complexity", cats.complexity || 0],
["Data Fragmentation", cats.fragmentation || 0],
["Risk Prediction & Action", cats.riskPrediction || 0],
["Revenue Operations Efficiency", cats.revops || 0]
].map(([label, score]) => `
| ${label} |
|
${score}/100 |
`).join("");
const answerRows = questions.map(q => `
| ${q.text} |
${getReadableAnswer(q)} |
`).join("");
const main = `
RETENTION HEALTH AUDIT REPORT
${company}
${dateStr} · Prepared for ${name}
${overallScore}
Overall Retention Health Score
${tier} Maturity
${tierDesc}
${fmt(scores.arrExposure)}
ARR at Risk
${scores.blindSpotPct || 0}%
Retention Blind Spot
${scores.collectiveCSMHours || 0}h
Weekly CSM Overhead
Section Score Breakdown
How your organisation scored across the 4 retention capability dimensions.
Your Assessment Answers
A record of your responses to the Retention Health Audit questionnaire.
Want to improve your score?
Start 7-Day Free Trial
CONFIDENTIAL · Generated by Risqly Retention Health Audit · ${dateStr}
`;
return main;
}
function generatePDFReport(sendViaEmail = false) {
if (typeof html2pdf === "undefined") {
alert("PDF generation library is still loading. Please try again in a moment.");
return Promise.reject("html2pdf undefined");
}
const scores = window.lastComputedScores;
if (!scores) { console.warn("No scores for PDF"); return Promise.reject("no scores"); }
const sanitizedCompany = (leadDetails.company || "Company").replace(/[^a-z0-9]/gi, "-");
const pdfHtml = buildPDFElement(scores);
const opt = {
margin: [0, 0, 0, 0],
filename: `Risqly-Retention-Audit-${sanitizedCompany}.pdf`,
image: { type: "jpeg", quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, logging: false, scrollX: 0, scrollY: 0 },
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
pagebreak: { mode: ["css", "legacy"] }
};
if (sendViaEmail) {
return html2pdf().set(opt).from(pdfHtml, 'string').outputPdf("blob").then(blob => {
console.log("PDF Blob size:", blob.size);
return blobToBase64(blob);
}).then(base64 => {
console.log("Base64 length:", base64.length);
const payload = {
recipient_name: leadDetails.name || "",
recipient_email: leadDetails.email || "",
company: leadDetails.company || "",
role: leadDetails.role || "",
crm: leadDetails.crm || "",
cs_tool: leadDetails.cs || "",
overall_score: scores.overallScore || "",
maturity_tier: scores.tier || "",
pdf_filename: `Risqly-Retention-Audit-${sanitizedCompany}.pdf`,
pdf_base64: base64
};
// Append all 12 questionnaire answers to the payload
if (typeof questions !== "undefined" && typeof userAnswers !== "undefined") {
questions.forEach(q => {
const payloadKey = "q_" + q.id;
payload[payloadKey] = getReadableAnswer(q);
});
}
return fetch(ZAPIER_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
}).then(res => {
if (res && res.ok) { console.log("Webhook success, PDF sent via email."); }
else { console.warn("Webhook status:", res ? res.status : "unknown"); }
return res;
}).catch(err => {
console.error("PDF email dispatch failed:", err);
throw err;
});
} else {
return html2pdf().set(opt).from(pdfHtml, 'string').save()
.catch(err => { console.error("PDF generation failed:", err); throw err; });
}
}
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64 = reader.result.split(",")[1];
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
function showEmailSentBanner() {
let banner = document.getElementById("risqly-email-banner");
if (!banner) {
banner = document.createElement("div");
banner.id = "risqly-email-banner";
banner.style.cssText = [
"position:fixed",
"bottom:32px",
"left:50%",
"transform:translateX(-50%)",
"background:linear-gradient(135deg,#7c3aed,#4f46e5)",
"color:#fff",
"padding:14px 28px",
"border-radius:12px",
"font-size:15px",
"font-weight:600",
"box-shadow:0 8px 32px rgba(124,58,237,0.35)",
"z-index:99999",
"display:flex",
"align-items:center",
"gap:10px",
"transition:opacity 0.4s"
].join(";");
banner.innerHTML = `
Your report has been sent to ${leadDetails.email || "your email"}
`;
document.body.appendChild(banner);
}
banner.style.opacity = "1";
banner.style.display = "flex";
setTimeout(() => {
banner.style.opacity = "0";
setTimeout(() => { banner.style.display = "none"; }, 400);
}, 5000);
}
// Handle Form Submission at the start of audit
function handleLeadSubmit(e) {
e.preventDefault();
leadDetails.name = document.getElementById("lead-name").value.trim();
leadDetails.email = document.getElementById("lead-email").value.trim();
leadDetails.role = document.getElementById("lead-role").value.trim();
const phoneInput = document.getElementById("lead-phone");
leadDetails.phone = phoneInput ? phoneInput.value.trim() : "";
// Set default fallbacks for Webflow / webhook fields we no longer ask
let extractedCompany = "Your Company";
if (leadDetails.email && leadDetails.email.includes("@")) {
const domain = leadDetails.email.split("@")[1].trim().toLowerCase();
const domainName = domain.split(".")[0];
const commonDomains = ["gmail", "outlook", "hotmail", "yahoo", "icloud", "mail", "aol", "zoho", "proton", "protonmail"];
if (!commonDomains.includes(domainName)) {
extractedCompany = domainName.charAt(0).toUpperCase() + domainName.slice(1);
}
}
leadDetails.company = extractedCompany;
leadDetails.crm = "None";
leadDetails.cs = "None";
if (!leadDetails.name || !leadDetails.email || !leadDetails.role) {
alert("Please complete all required fields.");
return;
}
// transition to step 0 of the questionnaire
transitionToStep(0);
}
// Show Results Dashboard Screen and send webhook/PDF
function showResultsDashboardScreen() {
const assessmentView = document.getElementById("assessment-view");
if (assessmentView) assessmentView.classList.remove("active");
const resultsView = document.getElementById("results-view");
if (resultsView) resultsView.classList.add("active");
const scores = calculateScores();
window.lastComputedScores = scores;
renderResultsDashboard(scores);
// Submit to Webflow's native form processor
const formFields = {
"Name": leadDetails.name,
"Email": leadDetails.email,
"Company": leadDetails.company,
"Role": leadDetails.role,
"Phone": leadDetails.phone,
"CRM": leadDetails.crm,
"CS-Tool": leadDetails.cs,
"Maturity-Tier": scores.tier,
"Overall-Score": scores.overallScore
};
// Add all questionnaire answers
if (typeof questions !== "undefined" && typeof userAnswers !== "undefined") {
questions.forEach(q => {
const fieldKey = "Q-" + q.id.split("_").map(word => {
if (word.toUpperCase() === "GTM") return "GTM";
if (word.toUpperCase() === "CRM") return "CRM";
if (word.toUpperCase() === "CS") return "CS";
return word.charAt(0).toUpperCase() + word.slice(1);
}).join("-");
formFields[fieldKey] = getReadableAnswer(q);
});
}
// Generate and email the PDF report. Once complete, submit to Webflow.
generatePDFReport(true)
.then(() => {
submitFormToWebflow("Risqly Audit Lead", formFields);
})
.catch(err => {
console.error("Error during PDF flow, proceeding with Webflow submit anyway:", err);
submitFormToWebflow("Risqly Audit Lead", formFields);
});
}
function calculateScores() {
// 1. Customer Complexity Subscore
const gtmIdx = userAnswers["complexity_gtm"];
const gtmScore = questions[1].options[gtmIdx].value;
const averageACV = questions[1].options[gtmIdx].acv || 15000;
const complexityScore = gtmScore;
// 2. Data Fragmentation Subscore
const tools = userAnswers["frag_tools"] || [];
let toolsScore = 100;
if (tools.includes("spreadsheets")) toolsScore -= 30;
if (!tools.includes("cs")) toolsScore -= 20;
if (tools.length >= 4) toolsScore -= 15;
toolsScore = Math.max(10, toolsScore);
const fragmentationScore = toolsScore;
// 3. Risk Prediction & Action Subscore
const riskIdx = userAnswers["risk_ident"];
const riskScore = questions[3].options[riskIdx].value;
// 4. RevOps Efficiency Subscore
const timeIdx = userAnswers["revops_time"];
const timeScore = questions[4].options[timeIdx].value;
const manualHours = questions[4].options[timeIdx].hours || 4;
const revopsScore = timeScore;
// Overall Weighted Score (out of 100)
const overallScore = Math.round(
(complexityScore * sections[0].weight) +
(fragmentationScore * sections[1].weight) +
(riskScore * sections[2].weight) +
(revopsScore * sections[3].weight)
);
// Compute Maturity Tiers based on red (0-44), yellow (45-69), green (70+) boundaries
let tier = "Reactive";
let tierDesc = "Risqly can help you combine fragmented data sources into a single retention view, giving you visibility of how much revenue is at risk and what to do next.";
let tierClass = "reactive";
if (overallScore >= 70) {
tier = "Predictive";
tierDesc = "Risqly can unlock greater visibility of expansion opportunities across your customer base, helping you move from retention to revenue growth.";
tierClass = "predictive";
} else if (overallScore >= 45) {
tier = "Emerging";
tierDesc = "Risqly can help mature your retention systems so you can consolidate fragmented data, quantify revenue at risk, and build a clear action plan.";
tierClass = "emerging";
}
const contracts = userAnswers["complexity_volume"] || 150;
const estimatedArr = contracts * averageACV;
const riskPct = 100 - overallScore;
const rawRiskExposureVal = estimatedArr * (riskPct / 100) * 0.18;
const arrExposure = Math.round(rawRiskExposureVal);
const blindSpotPct = Math.round(100 - overallScore);
const numCSMs = Math.max(1, Math.round(contracts / 65));
const collectiveCSMHours = numCSMs * manualHours;
return {
overallScore,
categories: {
complexity: Math.round(complexityScore),
fragmentation: Math.round(fragmentationScore),
riskPrediction: Math.round(riskScore),
revops: Math.round(revopsScore)
},
tier,
tierDesc,
tierClass,
estimatedArr,
arrExposure,
blindSpotPct,
collectiveCSMHours,
numCSMs,
manualHoursPerCsm: manualHours
};
}
function renderResultsDashboard(scores) {
// Update Header details
const leadMetaEl = document.getElementById("results-lead-meta");
if (leadMetaEl) {
let metaText = `${leadDetails.name} • ${leadDetails.role}`;
if (leadDetails.email) metaText += ` • ${leadDetails.email}`;
if (leadDetails.phone) metaText += ` • ${leadDetails.phone}`;
leadMetaEl.textContent = metaText;
}
const timestampEl = document.getElementById("results-timestamp");
if (timestampEl) {
timestampEl.textContent = new Date().toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
}
// 1. Set Gauge Score
const scoreValEl = document.getElementById("dashboard-score-val");
if (scoreValEl) scoreValEl.textContent = scores.overallScore;
const circle = document.getElementById("dashboard-gauge-fill");
if (circle) {
const offset = 377 * (1 - (scores.overallScore / 100));
circle.getBoundingClientRect();
circle.style.strokeDashoffset = offset;
// Set color dynamically: 0-29 Red (#D80027), 30-69 Yellow (#fbefaf), 70+ Green (#C6ECD3)
let gaugeColor = "#C6ECD3"; // Green default
let textColor = "#198754"; // Readable dark green for text
if (scores.overallScore < 30) {
gaugeColor = "#D80027"; // Red
textColor = "#D80027"; // Readable red for text
} else if (scores.overallScore < 70) {
gaugeColor = "#fbefaf"; // Yellow
textColor = "#b58900"; // Readable dark gold/yellow for text
}
circle.style.stroke = gaugeColor;
const resultsMaturityTitle = document.getElementById("results-maturity-title");
if (resultsMaturityTitle) {
resultsMaturityTitle.style.color = textColor;
}
}
// Update dynamic values inside results page text
const resultsMaturityTitle = document.getElementById("results-maturity-title");
if (resultsMaturityTitle) {
resultsMaturityTitle.textContent = `Maturity Tier: ${scores.tier}`;
}
const resultsMaturityDesc = document.getElementById("results-maturity-desc");
if (resultsMaturityDesc) {
resultsMaturityDesc.textContent = scores.tierDesc;
}
}
function renderCategoryProgress(id, score, avg, rec) {
const container = document.getElementById(id);
if (!container) return;
const scoreLabel = container.querySelector(".cat-score");
if (scoreLabel) scoreLabel.textContent = `${score}/100`;
const fill = container.querySelector(".cat-bar-fill");
if (fill) {
fill.getBoundingClientRect();
fill.style.width = `${score}%`;
if (score < 40) {
fill.style.background = "#D80027";
} else if (score < 70) {
fill.style.background = "#fbefaf";
} else {
fill.style.background = "#C6ECD3";
}
}
const mark = container.querySelector(".cat-bar-benchmark");
if (mark) {
mark.style.left = `${avg}%`;
}
const recEl = container.querySelector(".cat-recommendation");
if (recEl) {
recEl.textContent = rec;
}
}