// ── Speedrun Slider (inline script from how-it-works section) ── (function () { const slider = document.getElementById("speedrunSlider"); const dotsContainer = document.getElementById("speedrunDots"); if (!slider || !dotsContainer) return; const slides = slider.querySelectorAll(".video-slide"); const total = slides.length; if (total === 0) return; for (let i = 0; i < total; i++) { const dot = document.createElement("button"); dot.className = "slider-dot" + (i === 0 ? " active" : ""); dot.setAttribute("aria-label", "Go to slide " + (i + 1)); dot.onclick = function () { slides[i].scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start", }); }; dotsContainer.appendChild(dot); } function updateDots() { const threshold = 120; let activeIdx = 0; for (let i = 0; i < total; i++) { const rect = slides[i].getBoundingClientRect(); const center = rect.left + rect.width / 2; const sl = slider.getBoundingClientRect(); const slCenter = sl.left + sl.width / 2; if (Math.abs(center - slCenter) < threshold) { activeIdx = i; } } dotsContainer.querySelectorAll(".slider-dot").forEach((d, i) => { d.classList.toggle("active", i === activeIdx); }); } slider.addEventListener("scroll", updateDots); updateDots(); })(); function slideSpeedrun(dir) { plausible("Speedrun Nav", { props: { direction: dir > 0 ? "next" : "prev" }, }); const slider = document.getElementById("speedrunSlider"); if (!slider) return; const slideWidth = slider.querySelector(".video-slide").offsetWidth + 20; slider.scrollBy({ left: slideWidth * dir, behavior: "smooth", }); } // ── Mobile menu ────────────────────────────────────────── function toggleMobile() { plausible("Mobile Menu Toggle"); const nav = document.getElementById("nav-links"); nav.classList.toggle("mobile-open"); } // ── FAQ accordion ──────────────────────────────────────── function toggleFaq(btn) { const q = btn.innerText.replace(/[?✦]/g, "").trim(); plausible("FAQ Toggle", { props: { question: q } }); const answer = btn.nextElementSibling; const isOpen = answer.classList.contains("open"); // Close all document .querySelectorAll(".faq-answer.open") .forEach((el) => el.classList.remove("open")); document .querySelectorAll(".faq-question.open") .forEach((el) => el.classList.remove("open")); if (!isOpen) { answer.classList.add("open"); btn.classList.add("open"); } } // ── Signup Webhook URLs (matching app.derez.ai) ────────── const SIGNUP_URL = "https://n8n.derez.ai/webhook/4de196b7-2ad2-4f9a-9b4d-dc8d3b30865b"; const AUTH_URL = "https://n8n.derez.ai/webhook/aca1b27f-c18e-4424-b7a0-de01007a3fe6"; const COUPON_URL = "https://n8n.derez.ai/webhook/fd6375cb-6d73-4482-bfa3-ef8365f43a67"; let inlineCouponApplied = false; // ── Helper: show/hide auth messages ────────────────────── function showAuthError(id, msg) { const el = document.getElementById(id); el.textContent = msg; el.style.display = "flex"; } function hideAuthError(id) { document.getElementById(id).style.display = "none"; } // ── Helper: btn loading state ──────────────────────────── function btnLoad(btn, text) { btn.disabled = true; btn.textContent = text; } function btnReset(btn) { btn.disabled = false; btn.textContent = "Buy Agent — $9.50/mo"; } function isValidEmail(e) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e); } // ── Coupon ────────────────────────────────────────────── function handleCouponKey(e) { if (e.key === "Enter") { e.preventDefault(); applyInlineCoupon(); } } async function applyInlineCoupon() { const input = document.getElementById("signup-coupon-input"); const spinner = document.getElementById("signup-coupon-spinner"); const errorEl = document.getElementById("signup-coupon-error"); const successEl = document.getElementById("signup-coupon-success"); const code = input.value.trim(); if (!code) return; spinner.style.display = "flex"; errorEl.style.display = "none"; successEl.style.display = "none"; input.classList.remove("signup-coupon-input-error"); try { const res = await fetch(COUPON_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code }), }); if (!res.ok) throw new Error("Invalid"); const data = await res.json(); const raw = Array.isArray(data) ? data[0] : data; const item = raw && typeof raw === "object" && "json" in raw ? raw.json : raw; if (item?.valid === true || item?.status === "ok") { inlineCouponApplied = true; successEl.style.display = "flex"; input.style.display = "none"; } else { throw new Error("Invalid"); } } catch { input.classList.add("signup-coupon-input-error"); errorEl.style.display = "block"; } finally { spinner.style.display = "none"; } } // ── Sign Up (inline on landing page) ──────────────────── async function doInlineSignUp() { const email = document.getElementById("inline-signup-email").value.trim(); const password = document.getElementById("inline-signup-password").value; const agent = document.getElementById("inline-signup-agent").value; const location = document.getElementById("inline-signup-location").value; hideAuthError("inline-signup-error"); document.getElementById("inline-signup-success").style.display = "none"; if (!isValidEmail(email)) { showAuthError("inline-signup-error", "Please enter a valid email address."); return; } if (password.length < 8) { showAuthError( "inline-signup-error", "Password must be at least 8 characters.", ); return; } const btn = document.getElementById("inline-signup-btn"); btnLoad(btn, "Hiring agent\u2026"); // Open blank tab synchronously for Stripe redirect const stripeTab = window.open("", "_blank"); try { // Step 1: authenticate const authRes = await fetch(AUTH_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); if (!authRes.ok) throw new Error("Account creation failed. Please try again later."); // Step 2: create agent const createRes = await fetch(SIGNUP_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password, agent, location, }), }); if (!createRes.ok) { throw new Error("Agent provisioning failed. Please contact support."); } const createData = await createRes.json(); const createRaw = Array.isArray(createData) ? createData[0] : createData; const createItem = createRaw && typeof createRaw === "object" && "json" in createRaw ? createRaw.json : createRaw; // Navigate the pre-opened tab to Stripe checkout const checkoutUrl = createItem?.checkout_url || createItem?.checkoutUrl || createItem?.url; if (checkoutUrl && checkoutUrl.startsWith("http")) { stripeTab.location.href = checkoutUrl; } else { stripeTab.close(); } // Show success message document.getElementById("inline-signup-success").textContent = "Agent created! \u2713 Redirecting to checkout\u2026"; document.getElementById("inline-signup-success").style.display = "flex"; } catch (err) { stripeTab?.close(); showAuthError("inline-signup-error", err.message); } finally { btnReset(btn); } } // ── Close mobile menu on link click ────────────────────── document.querySelectorAll(".nav-links a").forEach((link) => { link.addEventListener("click", () => { document.getElementById("nav-links").classList.remove("mobile-open"); }); }); // ── Smooth scroll offset for fixed header ──────────────── document.querySelectorAll('a[href^="#"]').forEach((anchor) => { anchor.addEventListener("click", function (e) { const target = document.querySelector(this.getAttribute("href")); if (target) { e.preventDefault(); const headerHeight = 60; const top = target.getBoundingClientRect().top + window.pageYOffset - headerHeight; window.scrollTo({ top, behavior: "smooth" }); } }); }); /* ── Period / billing update ──────────────────────────── */ function updatePeriodDisplay() { const sel = document.getElementById("pricing-period"); const yearly = sel.value === "yearly"; document.getElementById("bonus-trainee").classList.toggle("show", yearly); document.getElementById("bonus-junior").classList.toggle("show", yearly); document.getElementById("bonus-senior").classList.toggle("show", yearly); document.getElementById("period-trainee").textContent = yearly ? "/ month ✦ yearly" : "/ month"; document.getElementById("period-junior").textContent = yearly ? "/ month ✦ yearly" : "/ month"; document.getElementById("period-senior").textContent = yearly ? "/ month ✦ yearly" : "/ month"; } const pricingPeriod = document.getElementById("pricing-period"); if (pricingPeriod) { pricingPeriod.addEventListener("change", updatePeriodDisplay); updatePeriodDisplay(); // fire on load so yearly bonuses show by default } const SERVER_WEBHOOK = "https://n8n.derez.ai/webhook/server"; initServerLocations(); // fetch live server availability /* ── Capture UTM params from URL ──────────────────────────── */ (function () { const params = new URLSearchParams(window.location.search); ["source", "medium", "campaign", "term", "content"].forEach(function (key) { const val = params.get("utm_" + key); const el = document.getElementById("utm_" + key); if (val && el) el.value = val; }); })(); /* ── Buy Agent Webhook ───────────────────────────── */ const ORDER_WEBHOOK = "https://n8n.derez.ai/webhook/payment"; function showPricingToast(msg) { const el = document.getElementById("pricing-toast"); el.textContent = msg; el.classList.add("show"); if (window._pricingToastTimer) clearTimeout(window._pricingToastTimer); window._pricingToastTimer = setTimeout(function () { el.classList.remove("show"); }, 20000); } async function hireAgent(product) { const agent = document.getElementById("pricing-agent").value; const period = document.getElementById("pricing-period").value; const location = document.getElementById("pricing-location").value; const email = document.getElementById("pricing-email").value.trim(); const coupon = document.getElementById("pricing-coupon").value.trim(); // Validate email const emailInput = document.getElementById("pricing-email"); emailInput.classList.remove("invalid"); if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { emailInput.classList.add("invalid"); showPricingToast( "Please configure your agent and provide a valid email address.", ); emailInput.focus(); return; } plausible("Pricing Click", { props: { product: product, agent: agent, period: period }, }); try { // Use a hidden form POST so the 302 redirect opens naturally in the new tab const form = document.createElement("form"); form.method = "POST"; form.action = ORDER_WEBHOOK; form.target = "_self"; form.style.display = "none"; const fields = { product: product, agent: agent, period: period, location: location, email: email || "", coupon: coupon || "", utm_source: document.getElementById("utm_source").value || "", utm_medium: document.getElementById("utm_medium").value || "", utm_campaign: document.getElementById("utm_campaign").value || "", utm_term: document.getElementById("utm_term").value || "", utm_content: document.getElementById("utm_content").value || "", }; Object.keys(fields).forEach(function (key) { const inp = document.createElement("input"); inp.type = "hidden"; inp.name = key; inp.value = fields[key]; form.appendChild(inp); }); document.body.appendChild(form); form.submit(); document.body.removeChild(form); } catch (err) { console.error("Order webhook failed:", err); } } /* ── Chat Widget ──────────────────────────────────────────── */ const CHAT_URL = "https://n8n.derez.ai/webhook/a58d00c4-f0c9-40cd-bb50-4f45f0442ef0"; let chatId = null; let chatOpen = false; let chatBusy = false; let greeted = false; let probeTimer = null; function chatToggle() { plausible("Chat Toggle", { props: { action: chatOpen ? "close" : "open" }, }); chatOpen = !chatOpen; document.getElementById("chat-widget").classList.toggle("open", chatOpen); document.getElementById("chat-fab").classList.toggle("open", chatOpen); document.getElementById("chat-widget").setAttribute("aria-hidden", !chatOpen); if (chatOpen) { // User opened chat manually — cancel the auto-open probe if (probeTimer) { clearTimeout(probeTimer); probeTimer = null; } if (!chatId) { chatId = String(Math.floor(100000 + Math.random() * 900000)); chatAppendMsg( "bot", "Hi! I'm your Sales Agent. Ask me about plans, setup, pricing, integrations, or anything about your AI agent.", ); } setTimeout(() => document.getElementById("chat-input").focus(), 300); } } function chatClose() { if (chatOpen) chatToggle(); } function chatOpenSilent() { if (!chatOpen) { chatOpen = true; document.getElementById("chat-widget").classList.add("open"); document.getElementById("chat-widget").setAttribute("aria-hidden", "false"); } } function chatAppendMsg(role, text) { const msgs = document.getElementById("chat-msgs"); const div = document.createElement("div"); div.className = "chat-msg chat-msg-" + role; // strip