(() => {
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const MOBILE_RE = /^\d{10,15}$/;
function resolveApiBase() {
if (window.EVENTBUZZ_API_BASE) {
return String(window.EVENTBUZZ_API_BASE).replace(/\/$/, "");
}
const host = window.location.hostname;
if (host === "localhost" || host === "127.0.0.1") {
return "http://localhost:3001/api";
}
return "https://api.eventbuzz.live/api";
}
const API_BASE = resolveApiBase();
function getHashId(anchor) {
const raw = anchor.getAttribute("href") || "";
if (!raw || raw === "#") return "";
try {
const url = new URL(anchor.href, window.location.href);
if (!url.hash || url.hash === "#") return "";
return decodeURIComponent(url.hash.slice(1));
} catch {
return raw.charAt(0) === "#" ? raw.slice(1) : "";
}
}
let activeNavId = "";
let navSpyLocked = false;
let navSpyUnlockTimer = 0;
function setActiveNav(id) {
activeNavId = id || "";
document
.querySelectorAll(".navlinks-desktop a, .nav-drawer-links a")
.forEach((a) => {
if (a.target === "_blank") return;
const linkId = getHashId(a);
if (!linkId) return;
const on = Boolean(activeNavId) && linkId === activeNavId;
a.classList.toggle("is-active", on);
if (on) a.setAttribute("aria-current", "location");
else a.removeAttribute("aria-current");
});
}
function lockActiveNav(id) {
navSpyLocked = true;
setActiveNav(id);
window.clearTimeout(navSpyUnlockTimer);
navSpyUnlockTimer = window.setTimeout(() => {
navSpyLocked = false;
}, 900);
}
/* ---------- Mobile nav drawer ---------- */
function initNav() {
const nav = document.querySelector("nav");
const menuBtn = document.querySelector(".nav-menu-btn");
if (!nav || !menuBtn) return;
const mq = window.matchMedia("(max-width: 1024px)");
let drawerOpen = false;
let drawerEls = null;
const links = Array.from(
document.querySelectorAll(".navlinks-desktop a")
).map((a) => ({
href: a.getAttribute("href") || "#",
label: a.textContent.trim(),
target: a.getAttribute("target") || null,
}));
function syncDrawerMode() {
nav.classList.toggle("is-drawer-mode", mq.matches);
if (!mq.matches) closeDrawer(true);
}
function ensureDrawer() {
if (drawerEls) return drawerEls;
const overlay = document.createElement("div");
overlay.className = "nav-drawer-overlay";
overlay.setAttribute("role", "presentation");
const aside = document.createElement("aside");
aside.className = "nav-drawer";
aside.id = "nav-drawer";
aside.setAttribute("role", "dialog");
aside.setAttribute("aria-modal", "true");
aside.setAttribute("aria-label", "Navigation menu");
aside.innerHTML = `
Menu
${links
.map((l) => {
const extra = l.target
? ` target="${l.target}" rel="noopener noreferrer"`
: "";
return `
${l.label}`;
})
.join("")}
Request demo
`;
overlay.appendChild(aside);
document.body.appendChild(overlay);
overlay.addEventListener("click", (e) => {
if (e.target === overlay) closeDrawer();
});
aside
.querySelector(".nav-drawer-close")
?.addEventListener("click", () => closeDrawer());
aside.querySelectorAll("a").forEach((a) => {
a.addEventListener("click", () => closeDrawer());
});
drawerEls = { overlay, aside };
return drawerEls;
}
function openDrawer() {
const { overlay, aside } = ensureDrawer();
setActiveNav(activeNavId);
drawerOpen = true;
menuBtn.setAttribute("aria-expanded", "true");
menuBtn.setAttribute("aria-label", "Close menu");
requestAnimationFrame(() => {
overlay.classList.add("is-open");
aside.classList.add("is-open");
});
document.body.style.overflow = "hidden";
}
function closeDrawer(instant) {
if (!drawerEls) return;
drawerOpen = false;
menuBtn.setAttribute("aria-expanded", "false");
menuBtn.setAttribute("aria-label", "Open menu");
drawerEls.overlay.classList.remove("is-open");
drawerEls.aside.classList.remove("is-open");
document.body.style.overflow = "";
if (instant) {
drawerEls.overlay.style.transition = "none";
drawerEls.aside.style.transition = "none";
requestAnimationFrame(() => {
drawerEls.overlay.style.transition = "";
drawerEls.aside.style.transition = "";
});
}
}
menuBtn.addEventListener("click", () => {
if (drawerOpen) closeDrawer();
else openDrawer();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && drawerOpen) closeDrawer();
});
syncDrawerMode();
mq.addEventListener("change", syncDrawerMode);
}
/* ---------- In-page section links (no #hash in the URL) ---------- */
function initInPageNav() {
const reduceMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
const PENDING_KEY = "eb-scroll-to";
function normalizePath(path) {
return path.replace(/\/index\.html$/i, "/") || "/";
}
function scrollToId(id) {
const el = document.getElementById(id);
if (!el) return false;
lockActiveNav(id);
el.scrollIntoView({
behavior: reduceMotion ? "auto" : "smooth",
block: "start",
});
return true;
}
function consumeHash() {
if (!window.location.hash || window.location.hash === "#") return "";
const id = decodeURIComponent(window.location.hash.slice(1));
history.replaceState(
null,
"",
window.location.pathname + window.location.search
);
return id;
}
let pending = "";
try {
pending = sessionStorage.getItem(PENDING_KEY) || "";
if (pending) sessionStorage.removeItem(PENDING_KEY);
} catch {
pending = "";
}
const fromUrl = consumeHash();
const target = pending || fromUrl;
if (target) {
const go = () => scrollToId(target);
if (document.readyState === "complete") requestAnimationFrame(go);
else window.addEventListener("load", () => requestAnimationFrame(go), { once: true });
}
document.addEventListener("click", (e) => {
if (
e.defaultPrevented ||
e.button !== 0 ||
e.metaKey ||
e.ctrlKey ||
e.shiftKey ||
e.altKey
) {
return;
}
const a = e.target.closest("a[href]");
if (!a || a.target === "_blank" || a.hasAttribute("download")) return;
if (a.hasAttribute("data-demo-open") || a.hasAttribute("data-talk-open")) {
return;
}
const raw = a.getAttribute("href") || "";
if (!raw || raw === "#") return;
let url;
try {
url = new URL(a.href);
} catch {
return;
}
if (!url.hash || url.hash === "#") return;
const id = decodeURIComponent(url.hash.slice(1));
if (!id) return;
if (
url.origin === window.location.origin &&
normalizePath(url.pathname) === normalizePath(window.location.pathname)
) {
if (!document.getElementById(id)) return;
e.preventDefault();
scrollToId(id);
return;
}
if (url.origin !== window.location.origin) return;
try {
sessionStorage.setItem(PENDING_KEY, id);
} catch {
return;
}
e.preventDefault();
window.location.assign(url.pathname + url.search);
});
}
/* ---------- Highlight nav link for the section in view ---------- */
function initNavSpy() {
const links = Array.from(
document.querySelectorAll(".navlinks-desktop a")
).filter((a) => a.target !== "_blank" && getHashId(a));
const items = links
.map((a) => {
const id = getHashId(a);
const el = document.getElementById(id);
return el ? { id, el } : null;
})
.filter(Boolean)
.sort((a, b) => {
const pos = a.el.compareDocumentPosition(b.el);
if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
return 0;
});
if (!items.length) return;
function currentId() {
const nav = document.querySelector("nav");
const marker = (nav ? nav.offsetHeight : 58) + 20;
let id = "";
items.forEach((item) => {
if (item.el.getBoundingClientRect().top <= marker) id = item.id;
});
return id;
}
let ticking = false;
function update() {
ticking = false;
if (navSpyLocked) return;
setActiveNav(currentId());
}
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(update);
}
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll);
update();
}
/* ---------- Demo modal ---------- */
const EVENT_TYPES = [
"Webinar",
"Conference",
"Exhibition",
"Internal Sales Meet",
"Others",
];
function validateDemo(data) {
const errors = {};
const name = (data.name || "").trim();
const mobile = (data.mobile || "").trim();
const email = (data.email || "").trim();
const eventType = (data.eventType || "").trim();
const message = (data.message || "").trim();
const captchaAnswer = (data.captchaAnswer || "").trim();
if (!name) errors.name = "Full name is required.";
if (!mobile) errors.mobile = "Mobile is required.";
else {
const digits = mobile.replace(/[\s\-().]/g, "");
if (!MOBILE_RE.test(digits)) {
errors.mobile = "Enter a valid mobile number (10-15 digits).";
}
}
if (!email) errors.email = "Email is required.";
else if (!EMAIL_RE.test(email)) {
errors.email = "Enter a valid email address.";
}
if (!eventType) errors.eventType = "Select an event type.";
else if (!EVENT_TYPES.includes(eventType)) {
errors.eventType = "Select a valid event type.";
}
if (!message) errors.message = "Message is required.";
if (!captchaAnswer) errors.captcha = "Please solve the captcha.";
return {
ok: Object.keys(errors).length === 0,
errors,
data: {
name,
mobile: mobile.replace(/[\s\-().]/g, ""),
email,
eventType,
message,
captchaAnswer,
},
};
}
function bindDemoForm(root, options = {}) {
const idPrefix = options.idPrefix || "demo";
let captchaToken = "";
let captchaLoading = false;
let submitting = false;
let success = false;
async function loadCaptcha() {
if (!root) return;
const questionEl = root.querySelector("[data-demo-captcha-question]");
const answerEl = root.querySelector('[name="captchaAnswer"]');
const reloadBtn = root.querySelector("[data-demo-captcha-reload]");
if (!questionEl) return;
captchaLoading = true;
questionEl.textContent = "Loading…";
if (answerEl) answerEl.value = "";
if (reloadBtn) reloadBtn.disabled = true;
try {
const res = await fetch(`${API_BASE}/public/captcha/challenge`, {
cache: "no-store",
});
const body = await res.json();
if (!res.ok || !body?.success || !body?.data?.captchaToken) {
throw new Error(body?.error?.message || "Captcha unavailable");
}
captchaToken = body.data.captchaToken;
questionEl.textContent = body.data.question || "Solve the sum";
} catch {
captchaToken = "";
questionEl.textContent = "Unavailable — tap reload";
} finally {
captchaLoading = false;
if (reloadBtn) reloadBtn.disabled = false;
}
}
function setFieldError(form, key, message) {
const inputName = key === "captcha" ? "captchaAnswer" : key;
const errId =
key === "captchaAnswer" || key === "captcha"
? `${idPrefix}-captcha-err`
: `${idPrefix}-${key}-err`;
const field = form
.querySelector(`[name="${inputName}"]`)
?.closest(".demo-field");
const errEl = document.getElementById(errId);
const input = form.querySelector(`[name="${inputName}"]`);
if (message) {
field?.classList.add("has-error");
if (errEl) {
errEl.hidden = false;
errEl.textContent = message;
}
input?.setAttribute("aria-invalid", "true");
} else {
field?.classList.remove("has-error");
if (errEl) {
errEl.hidden = true;
errEl.textContent = "";
}
input?.removeAttribute("aria-invalid");
}
}
function showSuccess() {
success = true;
const formView = root.querySelector(".demo-modal-form-view");
const successView = root.querySelector(".demo-modal-success");
if (formView) formView.hidden = true;
if (successView) successView.hidden = false;
}
function reset() {
success = false;
captchaToken = "";
const form = root.querySelector(".demo-modal-form");
form?.reset();
root.querySelectorAll(".demo-field-error").forEach((el) => {
el.hidden = true;
el.textContent = "";
});
root.querySelectorAll(".demo-field").forEach((el) => {
el.classList.remove("has-error");
});
const formErr = root.querySelector(".demo-form-error");
if (formErr) {
formErr.hidden = true;
formErr.textContent = "";
}
const formView = root.querySelector(".demo-modal-form-view");
const successView = root.querySelector(".demo-modal-success");
if (formView) formView.hidden = false;
if (successView) successView.hidden = true;
void loadCaptcha();
}
const form = root.querySelector(".demo-modal-form");
if (!form) {
return { loadCaptcha, reset, isSuccess: () => success };
}
root
.querySelector("[data-demo-captcha-reload]")
?.addEventListener("click", () => {
void loadCaptcha();
});
root.querySelector("[data-demo-reset]")?.addEventListener("click", reset);
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (submitting || captchaLoading) return;
const fd = new FormData(form);
const result = validateDemo({
name: fd.get("name"),
mobile: fd.get("mobile"),
email: fd.get("email"),
eventType: fd.get("eventType"),
message: fd.get("message"),
captchaAnswer: fd.get("captchaAnswer"),
});
["name", "mobile", "email", "eventType", "message", "captchaAnswer"].forEach(
(key) => {
const errKey = key === "captchaAnswer" ? "captcha" : key;
setFieldError(form, key, result.errors[errKey] || "");
}
);
const formErr = root.querySelector(".demo-form-error");
if (formErr) {
formErr.hidden = true;
formErr.textContent = "";
}
if (!result.ok) {
const first =
["name", "mobile", "email", "eventType", "message", "captcha"].find(
(k) => result.errors[k]
) || "name";
const focusName = first === "captcha" ? "captchaAnswer" : first;
form.querySelector(`[name="${focusName}"]`)?.focus();
return;
}
if (!captchaToken) {
setFieldError(
form,
"captchaAnswer",
"Captcha is not ready. Please reload and try again."
);
void loadCaptcha();
return;
}
const submitBtn = root.querySelector(".demo-submit-btn");
submitting = true;
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = "Sending…";
}
try {
const res = await fetch(`${API_BASE}/public/demo-request`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: result.data.name,
mobile: result.data.mobile,
email: result.data.email,
eventType: result.data.eventType,
message: result.data.message,
captchaToken,
captchaAnswer: result.data.captchaAnswer,
}),
});
let body = null;
try {
body = await res.json();
} catch {
body = null;
}
if (!res.ok || !body?.success) {
const msg =
body?.error?.message ||
"Could not send your request. Please try again.";
if (/captcha/i.test(msg)) {
setFieldError(form, "captchaAnswer", msg);
void loadCaptcha();
} else if (formErr) {
formErr.hidden = false;
formErr.textContent = msg;
}
return;
}
showSuccess();
} catch {
if (formErr) {
formErr.hidden = false;
formErr.textContent =
"Could not reach the server. Please try again in a moment.";
}
} finally {
submitting = false;
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = "Request demo";
}
}
});
return { loadCaptcha, reset, isSuccess: () => success };
}
function initDemoPage() {
const root = document.querySelector("[data-demo-page]");
if (!root) return;
bindDemoForm(root, { idPrefix: "help" }).reset();
}
function initDemoModal() {
let overlay = null;
let open = false;
let formApi = null;
function build() {
if (overlay) return overlay;
overlay = document.createElement("div");
overlay.className = "demo-modal-overlay";
overlay.setAttribute("role", "presentation");
overlay.innerHTML = `
Thanks, We'll be in touch.
We have received your request. Our team will get in touch with you shortly.
`;
document.body.appendChild(overlay);
overlay.addEventListener("click", (e) => {
if (e.target === overlay) close();
});
overlay
.querySelector(".demo-modal-close")
?.addEventListener("click", close);
overlay
.querySelector(".demo-success-close")
?.addEventListener("click", close);
formApi = bindDemoForm(overlay, { idPrefix: "demo" });
return overlay;
}
function openModal() {
const el = build();
if (!open) formApi.reset();
open = true;
document.body.style.overflow = "hidden";
requestAnimationFrame(() => {
el.classList.add("is-open");
el.querySelector(".demo-modal")?.classList.add("is-open");
if (!formApi.isSuccess()) {
el.querySelector('[name="name"]')?.focus();
}
});
}
function close() {
if (!overlay) return;
open = false;
overlay.classList.remove("is-open");
overlay.querySelector(".demo-modal")?.classList.remove("is-open");
document.body.style.overflow = "";
}
document.addEventListener("click", (e) => {
const trigger = e.target.closest("[data-demo-open]");
if (!trigger) return;
e.preventDefault();
openModal();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && open) close();
});
}
const PLANS = ["Start", "Grow", "Scale", "Enterprise"];
function validateTalk(data) {
const errors = {};
const name = (data.name || "").trim();
const mobile = (data.mobile || "").trim();
const email = (data.email || "").trim();
const selectedPlan = (data.selectedPlan || "").trim();
const message = (data.message || "").trim();
const captchaAnswer = (data.captchaAnswer || "").trim();
if (!name) errors.name = "Full name is required.";
if (!mobile) errors.mobile = "Mobile is required.";
else {
const digits = mobile.replace(/[\s\-().]/g, "");
if (!MOBILE_RE.test(digits)) {
errors.mobile = "Enter a valid mobile number (10-15 digits).";
}
}
if (!email) errors.email = "Email is required.";
else if (!EMAIL_RE.test(email)) {
errors.email = "Enter a valid email address.";
}
if (!selectedPlan) errors.selectedPlan = "Selected plan is required.";
else if (!PLANS.includes(selectedPlan)) {
errors.selectedPlan = "Select a valid plan.";
}
if (!message) errors.message = "Message is required.";
if (!captchaAnswer) errors.captcha = "Please solve the captcha.";
return {
ok: Object.keys(errors).length === 0,
errors,
data: {
name,
mobile: mobile.replace(/[\s\-().]/g, ""),
email,
selectedPlan,
message,
captchaAnswer,
},
};
}
function initTalkModal() {
let overlay = null;
let open = false;
let success = false;
let captchaToken = "";
let captchaLoading = false;
let submitting = false;
let selectedPlan = "";
async function loadCaptcha() {
if (!overlay) return;
const questionEl = overlay.querySelector("[data-talk-captcha-question]");
const answerEl = overlay.querySelector('[name="captchaAnswer"]');
const reloadBtn = overlay.querySelector("[data-talk-captcha-reload]");
if (!questionEl) return;
captchaLoading = true;
questionEl.textContent = "Loading…";
if (answerEl) answerEl.value = "";
if (reloadBtn) reloadBtn.disabled = true;
try {
const res = await fetch(`${API_BASE}/public/captcha/challenge`, {
cache: "no-store",
});
const body = await res.json();
if (!res.ok || !body?.success || !body?.data?.captchaToken) {
throw new Error(body?.error?.message || "Captcha unavailable");
}
captchaToken = body.data.captchaToken;
questionEl.textContent = body.data.question || "Solve the sum";
} catch {
captchaToken = "";
questionEl.textContent = "Unavailable — tap reload";
} finally {
captchaLoading = false;
if (reloadBtn) reloadBtn.disabled = false;
}
}
function setFieldError(form, key, message) {
const inputName = key === "captcha" ? "captchaAnswer" : key;
const errId =
key === "captchaAnswer" || key === "captcha"
? "talk-captcha-err"
: `talk-${key}-err`;
const field = form.querySelector(`[name="${inputName}"]`)?.closest(".demo-field");
const errEl = document.getElementById(errId);
const input = form.querySelector(`[name="${inputName}"]`);
if (message) {
field?.classList.add("has-error");
if (errEl) {
errEl.hidden = false;
errEl.textContent = message;
}
input?.setAttribute("aria-invalid", "true");
} else {
field?.classList.remove("has-error");
if (errEl) {
errEl.hidden = true;
errEl.textContent = "";
}
input?.removeAttribute("aria-invalid");
}
}
function build() {
if (overlay) return overlay;
overlay = document.createElement("div");
overlay.className = "demo-modal-overlay";
overlay.setAttribute("role", "presentation");
overlay.innerHTML = `
Thanks, we'll be in touch.
We have received your request. Our team will get in touch with you shortly.
`;
document.body.appendChild(overlay);
overlay.addEventListener("click", (e) => {
if (e.target === overlay) close();
});
overlay.querySelector(".demo-modal-close")?.addEventListener("click", close);
overlay.querySelector(".talk-success-close")?.addEventListener("click", close);
overlay.querySelector("[data-talk-captcha-reload]")?.addEventListener("click", () => {
void loadCaptcha();
});
const form = overlay.querySelector(".demo-modal-form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (submitting || captchaLoading) return;
const fd = new FormData(form);
const result = validateTalk({
name: fd.get("name"),
mobile: fd.get("mobile"),
email: fd.get("email"),
selectedPlan: fd.get("selectedPlan") || selectedPlan,
message: fd.get("message"),
captchaAnswer: fd.get("captchaAnswer"),
});
["name", "mobile", "email", "selectedPlan", "message", "captchaAnswer"].forEach(
(key) => {
const errKey = key === "captchaAnswer" ? "captcha" : key;
setFieldError(form, key, result.errors[errKey] || "");
}
);
const formErr = overlay.querySelector(".demo-form-error");
if (formErr) {
formErr.hidden = true;
formErr.textContent = "";
}
if (!result.ok) {
const first =
["name", "mobile", "email", "selectedPlan", "message", "captcha"].find(
(k) => result.errors[k]
) || "name";
const focusName = first === "captcha" ? "captchaAnswer" : first;
form.querySelector(`[name="${focusName}"]`)?.focus();
return;
}
if (!captchaToken) {
setFieldError(
form,
"captchaAnswer",
"Captcha is not ready. Please reload and try again."
);
void loadCaptcha();
return;
}
const submitBtn = overlay.querySelector(".talk-submit-btn");
submitting = true;
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = "Sending…";
}
try {
const res = await fetch(`${API_BASE}/public/talk-to-team`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: result.data.name,
mobile: result.data.mobile,
email: result.data.email,
selectedPlan: result.data.selectedPlan,
message: result.data.message,
captchaToken,
captchaAnswer: result.data.captchaAnswer,
}),
});
let body = null;
try {
body = await res.json();
} catch {
body = null;
}
if (!res.ok || !body?.success) {
const msg =
body?.error?.message ||
"Could not send your request. Please try again.";
if (/captcha/i.test(msg)) {
setFieldError(form, "captchaAnswer", msg);
void loadCaptcha();
} else if (formErr) {
formErr.hidden = false;
formErr.textContent = msg;
}
return;
}
showSuccess();
} catch {
if (formErr) {
formErr.hidden = false;
formErr.textContent =
"Could not reach the server. Please try again in a moment.";
}
} finally {
submitting = false;
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = "Talk to our team";
}
}
});
return overlay;
}
function showSuccess() {
success = true;
const formView = overlay.querySelector(".demo-modal-form-view");
const successView = overlay.querySelector(".demo-modal-success");
if (formView) formView.hidden = true;
if (successView) successView.hidden = false;
}
function reset() {
success = false;
captchaToken = "";
const form = overlay?.querySelector(".demo-modal-form");
form?.reset();
overlay?.querySelectorAll(".demo-field-error").forEach((el) => {
el.hidden = true;
el.textContent = "";
});
overlay?.querySelectorAll(".demo-field").forEach((el) => {
el.classList.remove("has-error");
});
const formErr = overlay?.querySelector(".demo-form-error");
if (formErr) {
formErr.hidden = true;
formErr.textContent = "";
}
const formView = overlay?.querySelector(".demo-modal-form-view");
const successView = overlay?.querySelector(".demo-modal-success");
if (formView) formView.hidden = false;
if (successView) successView.hidden = true;
const planInput = overlay?.querySelector('[name="selectedPlan"]');
if (planInput) planInput.value = selectedPlan;
void loadCaptcha();
}
function openModal(plan) {
selectedPlan = plan;
const el = build();
if (!open) reset();
else {
const planInput = el.querySelector('[name="selectedPlan"]');
if (planInput) planInput.value = selectedPlan;
if (!captchaToken) void loadCaptcha();
}
open = true;
document.body.style.overflow = "hidden";
requestAnimationFrame(() => {
el.classList.add("is-open");
el.querySelector(".demo-modal")?.classList.add("is-open");
if (!success) {
el.querySelector('[name="name"]')?.focus();
}
});
}
function close() {
if (!overlay) return;
open = false;
overlay.classList.remove("is-open");
overlay.querySelector(".demo-modal")?.classList.remove("is-open");
document.body.style.overflow = "";
}
document.addEventListener("click", (e) => {
const trigger = e.target.closest("[data-talk-open]");
if (!trigger) return;
e.preventDefault();
const plan =
(trigger.getAttribute("data-plan") || "").trim() ||
trigger.closest(".tier")?.querySelector("h3")?.textContent.trim() ||
"";
openModal(plan);
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && open) close();
});
}
/* ---------- Scroll reveals (GSAP ScrollTrigger when available) ---------- */
function initReveals() {
const nodes = document.querySelectorAll(".reveal");
if (!nodes.length) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
return;
}
const revealLine = () => window.innerHeight * 0.88;
const isPastRevealPoint = (el) => el.getBoundingClientRect().top < revealLine();
if (window.gsap && window.ScrollTrigger) {
const gsap = window.gsap;
const ScrollTrigger = window.ScrollTrigger;
gsap.registerPlugin(ScrollTrigger);
nodes.forEach((el) => {
const y = Number(el.dataset.revealY ?? 28);
const duration = Number(el.dataset.revealDuration ?? 0.7);
const delay = Number(el.dataset.revealDelay ?? 0);
gsap.fromTo(
el,
{ autoAlpha: 0, y, willChange: "opacity, transform" },
{
autoAlpha: 1,
y: 0,
duration,
delay,
ease: "power2.out",
onComplete: () => gsap.set(el, { clearProps: "willChange" }),
scrollTrigger: {
trigger: el,
start: "top 88%",
toggleActions: "play none none none",
once: true,
},
}
);
});
const showRestored = () => {
nodes.forEach((el) => {
if (isPastRevealPoint(el)) {
gsap.set(el, { autoAlpha: 1, y: 0, overwrite: true, clearProps: "willChange" });
}
});
ScrollTrigger.refresh();
};
requestAnimationFrame(showRestored);
window.addEventListener("load", showRestored, { once: true });
window.addEventListener("pageshow", showRestored);
return;
}
// Fallback without GSAP
const show = (el) => {
el.style.opacity = "1";
el.style.transform = "none";
};
const io = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
show(entry.target);
io.unobserve(entry.target);
}
});
},
{ threshold: 0.12, rootMargin: "0px 0px -12% 0px" }
);
const syncFallback = () => {
nodes.forEach((el) => {
if (isPastRevealPoint(el)) {
show(el);
io.unobserve(el);
}
});
};
nodes.forEach((el) => {
if (isPastRevealPoint(el)) {
show(el);
return;
}
el.style.opacity = "0";
el.style.transform = "translateY(28px)";
el.style.transition = "opacity .7s ease, transform .7s ease";
io.observe(el);
});
window.addEventListener("load", syncFallback, { once: true });
window.addEventListener("pageshow", syncFallback);
}
/* ---------- Customer stories slider ---------- */
function initStoriesSlider() {
const root = document.querySelector(".stories-slider");
if (!root) return;
const viewport = root.querySelector(".stories-slider-viewport");
const track = root.querySelector(".stories-slider-track");
const slides = Array.from(root.querySelectorAll(".stories-slide"));
const buttons = root.querySelectorAll(".stories-slider-btn");
const prevBtn = buttons[0];
const nextBtn = buttons[1];
if (!viewport || !track || slides.length < 2 || !prevBtn || !nextBtn) return;
const GAP = 20;
const DEFAULT_AUTOPLAY_MS = 5000;
const reduceMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
let index = 0;
let dragStartX = 0;
let dragging = false;
let autoplayTimer = null;
function autoplayDelay() {
const raw = root.getAttribute("data-autoplay");
if (raw == null || raw === "") return DEFAULT_AUTOPLAY_MS;
if (/^(false|off|none)$/i.test(raw.trim())) return 0;
const ms = Number(raw);
return Number.isFinite(ms) && ms > 0 ? ms : 0;
}
function perView() {
const w = viewport.clientWidth;
if (w < 640) return 1;
if (w < 900) return 2;
return 3;
}
function maxIndex() {
return Math.max(0, slides.length - perView());
}
function stepWidth() {
const n = perView();
return (viewport.clientWidth - GAP * (n - 1)) / n + GAP;
}
function goTo(next, animate) {
index = Math.max(0, Math.min(next, maxIndex()));
const offset = index * stepWidth();
track.style.transition =
animate && !reduceMotion ? "transform .4s ease" : "none";
track.style.transform = offset ? `translateX(-${offset}px)` : "none";
prevBtn.disabled = index <= 0;
nextBtn.disabled = index >= maxIndex();
}
function stopAutoplay() {
if (autoplayTimer) {
clearInterval(autoplayTimer);
autoplayTimer = null;
}
}
function startAutoplay() {
stopAutoplay();
const delay = autoplayDelay();
if (!delay || reduceMotion || maxIndex() <= 0) return;
autoplayTimer = setInterval(() => {
const max = maxIndex();
if (max <= 0) return;
goTo(index >= max ? 0 : index + 1, true);
}, delay);
}
function goAndRestart(next) {
goTo(next, true);
startAutoplay();
}
prevBtn.addEventListener("click", () => goAndRestart(index - 1));
nextBtn.addEventListener("click", () => goAndRestart(index + 1));
viewport.addEventListener("pointerdown", (e) => {
if (e.pointerType === "mouse" && e.button !== 0) return;
dragging = true;
dragStartX = e.clientX;
stopAutoplay();
});
const endDrag = (e) => {
if (!dragging) return;
dragging = false;
const dx = e.clientX - dragStartX;
if (Math.abs(dx) >= 40) goTo(index + (dx < 0 ? 1 : -1), true);
startAutoplay();
};
viewport.addEventListener("pointerup", endDrag);
viewport.addEventListener("pointercancel", () => {
dragging = false;
startAutoplay();
});
root.addEventListener("mouseenter", stopAutoplay);
root.addEventListener("mouseleave", startAutoplay);
root.addEventListener("focusin", stopAutoplay);
root.addEventListener("focusout", (e) => {
if (!root.contains(e.relatedTarget)) startAutoplay();
});
document.addEventListener("visibilitychange", () => {
if (document.hidden) stopAutoplay();
else startAutoplay();
});
const onResize = () => goTo(index, false);
window.addEventListener("resize", onResize);
if (typeof ResizeObserver !== "undefined") {
new ResizeObserver(onResize).observe(viewport);
}
goTo(0, false);
startAutoplay();
}
/* ---------- Back to top ---------- */
function initBackToTop() {
if (document.querySelector(".back-to-top")) return;
const btn = document.createElement("button");
btn.type = "button";
btn.className = "back-to-top";
btn.setAttribute("aria-label", "Back to top");
btn.innerHTML =
'';
document.body.appendChild(btn);
const SHOW_AFTER = 400;
const reduceMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
const sync = () => {
btn.classList.toggle("is-visible", window.scrollY > SHOW_AFTER);
};
window.addEventListener("scroll", sync, { passive: true });
sync();
btn.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" });
});
}
/* ---------- Platform feature showcase ---------- */
function initPlatShowcase() {
const root = document.querySelector(".plat-split");
if (!root) return;
const cards = Array.from(root.querySelectorAll(".plat-card"));
const images = Array.from(root.querySelectorAll(".plat-preview img"));
const descEl = root.querySelector("[data-plat-desc]");
if (!cards.length || !images.length) return;
const DEFAULT_AUTOPLAY_MS = 5000;
const reduceMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
const gsap = window.gsap;
const canAnimate = Boolean(gsap) && !reduceMotion;
if (canAnimate) root.classList.add("is-gsap-preview");
let index = Math.max(
0,
cards.findIndex((card) => card.classList.contains("is-active"))
);
let autoplayTimer = null;
let inView = typeof IntersectionObserver === "undefined";
function autoplayDelay() {
const raw = root.getAttribute("data-autoplay");
if (raw == null || raw === "") return DEFAULT_AUTOPLAY_MS;
if (/^(false|off|none)$/i.test(raw.trim())) return 0;
const ms = Number(raw);
return Number.isFinite(ms) && ms > 0 ? ms : 0;
}
function imageFor(i) {
const id = cards[i]?.dataset.plat;
return images.find((img) => img.dataset.plat === id) || null;
}
function setCards(i) {
cards.forEach((card, idx) => {
const on = idx === i;
card.classList.toggle("is-active", on);
card.setAttribute("aria-selected", on ? "true" : "false");
});
if (descEl) {
descEl.textContent =
cards[i]?.querySelector("p")?.textContent.trim() || "";
}
}
function direction(from, to) {
const n = cards.length;
if (from === n - 1 && to === 0) return 1;
if (from === 0 && to === n - 1) return -1;
return to > from ? 1 : -1;
}
function showInstant(nextIndex) {
index = nextIndex;
setCards(index);
images.forEach((img) => {
const on = img === imageFor(index);
img.classList.toggle("is-active", on);
if (gsap) {
gsap.killTweensOf(img);
gsap.set(img, { autoAlpha: on ? 1 : 0, x: 0, zIndex: on ? 1 : 0 });
}
});
}
function show(nextIndex, animate) {
if (nextIndex === index) return;
const prevImg = imageFor(index);
const nextImg = imageFor(nextIndex);
if (!nextImg) return;
if (!canAnimate || !animate || !prevImg) {
showInstant(nextIndex);
return;
}
const dir = direction(index, nextIndex);
index = nextIndex;
setCards(index);
gsap.killTweensOf(images);
images.forEach((img) => {
if (img !== prevImg && img !== nextImg) {
img.classList.remove("is-active");
gsap.set(img, { autoAlpha: 0, x: 0, zIndex: 0 });
}
});
nextImg.classList.add("is-active");
prevImg.classList.remove("is-active");
gsap.set(prevImg, { zIndex: 1, autoAlpha: 1, x: 0 });
gsap.set(nextImg, { zIndex: 2, autoAlpha: 0, x: 40 * dir });
gsap.to(prevImg, {
autoAlpha: 0,
x: -32 * dir,
duration: 0.45,
ease: "power2.inOut",
onComplete: () => {
gsap.set(prevImg, { x: 0, zIndex: 0 });
},
});
gsap.to(nextImg, {
autoAlpha: 1,
x: 0,
duration: 0.5,
ease: "power2.out",
});
}
function stopAutoplay() {
if (autoplayTimer) {
clearInterval(autoplayTimer);
autoplayTimer = null;
}
}
function startAutoplay() {
stopAutoplay();
const delay = autoplayDelay();
if (!delay || !inView || document.hidden || cards.length < 2) return;
autoplayTimer = setInterval(() => {
show((index + 1) % cards.length, true);
}, delay);
}
function goAndRestart(nextIndex) {
show(nextIndex, true);
startAutoplay();
}
cards.forEach((card, i) => {
card.addEventListener("click", () => goAndRestart(i));
});
root.addEventListener("mouseenter", stopAutoplay);
root.addEventListener("mouseleave", startAutoplay);
root.addEventListener("focusin", stopAutoplay);
root.addEventListener("focusout", (e) => {
if (!root.contains(e.relatedTarget)) startAutoplay();
});
document.addEventListener("visibilitychange", () => {
if (document.hidden) stopAutoplay();
else startAutoplay();
});
if (typeof IntersectionObserver !== "undefined") {
const io = new IntersectionObserver(
(entries) => {
inView = entries.some((entry) => entry.isIntersecting);
if (inView) startAutoplay();
else stopAutoplay();
},
{ threshold: 0.35 }
);
io.observe(root);
}
showInstant(index);
startAutoplay();
}
document.addEventListener("DOMContentLoaded", () => {
initNav();
initInPageNav();
initNavSpy();
initDemoPage();
initDemoModal();
initTalkModal();
initReveals();
initStoriesSlider();
initPlatShowcase();
initBackToTop();
});
})();