split + record

This commit is contained in:
oliver
2026-06-12 14:23:16 -03:00
parent 101100bfc0
commit afbc9462f0
3 changed files with 2743 additions and 2816 deletions
+1834
View File
File diff suppressed because it is too large Load Diff
+837
View File
@@ -0,0 +1,837 @@
// ── 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";
}
document
.getElementById("pricing-period")
.addEventListener("change", updatePeriodDisplay);
updatePeriodDisplay(); // fire on load so yearly bonuses show by default
/* ── 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);
if (val) document.getElementById("utm_" + key).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 <script> and on* handlers to prevent XSS, but render safe HTML
div.innerHTML = text
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/\son\w+\s*=\s*"[^"]*"/gi, "")
.replace(/\son\w+\s*=\s*'[^']*'/gi, "");
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
return div;
}
function chatKey(e) {
if (e.key === "Enter") chatSend();
}
async function chatSend() {
const input = document.getElementById("chat-input");
const btn = document.getElementById("chat-send-btn");
const msg = input.value.trim();
if (!msg || chatBusy) return;
plausible("Chat Message Sent");
chatBusy = true;
chatOpenSilent();
chatAppendMsg("user", msg);
input.value = "";
btn.disabled = true;
btn.innerHTML = '<span style="opacity:0.6">…</span>';
const think = chatAppendMsg("bot", "…");
think.className = "chat-msg chat-msg-thinking";
try {
const res = await fetch(CHAT_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: msg,
sessionId: chatId,
}),
});
const json = await res.json();
const first = Array.isArray(json) ? json[0] : json;
const reply =
typeof json === "string"
? json
: (first &&
(first.output ||
first.message ||
first.reply ||
first.response ||
first.text)) ||
JSON.stringify(json);
think.remove();
chatAppendMsg("bot", reply);
} catch (err) {
think.remove();
chatAppendMsg(
"bot",
"Sorry, I couldn't connect right now. Please try again later.",
);
} finally {
chatBusy = false;
btn.disabled = false;
btn.innerHTML = "➤";
document.getElementById("chat-input").focus();
}
}
/* ── Visitor Probe — auto-open after 15s ────────────────── */
(function () {
let fired = false;
probeTimer = setTimeout(function () {
fetch("https://ipinfo.io/json")
.then(function (r) {
return r.json();
})
.then(function (geo) {
fireProbe(geo);
})
.catch(function () {
fireProbe(null);
});
}, 15000);
function fireProbe(geo) {
if (fired) return;
fired = true;
const cid = chatId || String(Math.floor(100000 + Math.random() * 900000));
const ua = navigator.userAgent;
let lines = [
"NEW VISITOR — 15s Probe",
"========================================",
"",
"- Page section: hero",
"- Scroll depth: " +
Math.min(
100,
Math.round(
(window.pageYOffset /
Math.max(
1,
document.documentElement.scrollHeight - window.innerHeight,
)) *
100,
),
) +
"%",
"- Time on page: 15 seconds",
"- Timestamp: " + new Date().toUTCString(),
"",
"--- BROWSER & DEVICE ---",
"- Browser: " +
(/Edg\/([\d.]+)/.test(ua)
? "Edge " + RegExp.$1
: /Chrome\/([\d.]+)/.test(ua)
? "Chrome " + RegExp.$1
: /Firefox\/([\d.]+)/.test(ua)
? "Firefox " + RegExp.$1
: /Version\/([\d.]+).*Safari/.test(ua)
? "Safari " + RegExp.$1
: "Unknown"),
"- OS: " +
(/Windows NT 10/.test(ua)
? "Windows 10/11"
: /Mac OS X/.test(ua)
? "macOS"
: /Android/.test(ua)
? "Android"
: /iPhone/.test(ua)
? "iOS"
: /Linux/.test(ua)
? "Linux"
: "Unknown"),
"- Device: " + (/Mobi|Android|iPhone/.test(ua) ? "Mobile" : "Desktop"),
"- Screen: " + screen.width + "x" + screen.height,
"- Language: " + navigator.language,
"- User Agent: " + ua,
"",
"--- PAGE ---",
"- URL: " + window.location.href,
"- Referrer: " + (document.referrer || "direct / none"),
"- Title: " + document.title,
];
if (geo) {
lines.push("");
lines.push("--- LOCATION ---");
if (geo.country) lines.push("- Country: " + geo.country);
if (geo.city) lines.push("- City: " + geo.city);
if (geo.org) lines.push("- ISP: " + geo.org);
}
lines.push("");
lines.push("- Chat ID: " + cid);
const payload = {
message: lines.join("\n"),
sessionId: cid,
};
fetch(CHAT_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
})
.then(function (r) {
const ct = r.headers.get("content-type") || "";
if (ct.indexOf("json") !== -1) return r.json();
return r.text().then(function (t) {
return { output: t };
});
})
.then(function (d) {
const item = Array.isArray(d) ? d[0] : d;
const reply =
(item &&
(item.output ||
item.message ||
item.reply ||
item.response ||
item.text)) ||
null;
if (reply) {
if (!chatId) chatId = cid;
chatAppendMsg("bot", reply);
greeted = true;
if (!chatOpen) chatOpenSilent();
}
})
.catch(function () {});
}
})();
/* ── Scroll Depth Tracking ────────────────────────── */
(function () {
const thresholds = [25, 50, 75, 100];
let sent = {};
let ticking = false;
window.addEventListener("scroll", function () {
if (!ticking) {
window.requestAnimationFrame(function () {
const scrolled = window.pageYOffset + window.innerHeight;
const total = document.documentElement.scrollHeight;
const pct = Math.min(100, Math.round((scrolled / total) * 100));
thresholds.forEach(function (t) {
if (pct >= t && !sent[t]) {
sent[t] = true;
plausible("Scroll Depth", {
props: { percent: t + "%" },
});
}
});
ticking = false;
});
ticking = true;
}
});
})();
/* ── Coupon Badges ──────────────────────────────────── */
const COUPONS = {
// 499
blog499: 499,
creator499: 499,
friends499: 499,
// 950
blog950: 950,
creator950: 950,
friends950: 950,
record950: 950,
// 1750
blog1750: 1750,
friends1750: 1750,
creator1750: 1750,
};
const PLAN_PRICES = { trainee: 499, junior: 950, senior: 1750 };
function centsToDollar(c) {
return "$" + (c / 100).toFixed(2);
}
function updateCouponBadges() {
var code = document
.getElementById("pricing-coupon")
.value.trim()
.toLowerCase();
var discount = COUPONS[code];
["trainee", "junior", "senior"].forEach(function (plan) {
var badge = document.getElementById("badge-" + plan);
if (discount !== undefined) {
var firstMonth = Math.max(0, PLAN_PRICES[plan] - discount);
badge.querySelector(".badge-price").textContent =
centsToDollar(firstMonth);
badge.classList.add("visible");
if (firstMonth === 0) badge.classList.add("free");
else badge.classList.remove("free");
} else {
badge.classList.remove("visible", "free");
badge.querySelector(".badge-price").textContent = "—";
}
});
}
document
.getElementById("pricing-coupon")
.addEventListener("input", updateCouponBadges);
/* ── Blog ──────────────────────────────────────────────── */
const BLOG_POSTS_PER_PAGE = 6;
let blogAllPosts = [];
let blogFilteredPosts = [];
let blogCurrentPage = 0;
function blogFormatDate(dateStr) {
const d = new Date(dateStr);
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
}
function blogAreaClass(area) {
var map = {
howto: "area-howto",
news: "area-news",
"video-scripts": "area-video-scripts",
"speed-run": "area-speed-run",
};
return map[area] || "area-default";
}
function blogRender() {
const start = blogCurrentPage * BLOG_POSTS_PER_PAGE;
const pagePosts = blogFilteredPosts.slice(start, start + BLOG_POSTS_PER_PAGE);
const grid = document.getElementById("blog-grid");
const totalPages = Math.max(
1,
Math.ceil(blogFilteredPosts.length / BLOG_POSTS_PER_PAGE),
);
if (pagePosts.length === 0) {
grid.innerHTML =
'<div class="blog-empty">No posts match your search.</div>';
document.getElementById("blog-page-info").textContent = "";
document.getElementById("blog-prev").disabled = true;
document.getElementById("blog-next").disabled = true;
return;
}
grid.innerHTML = pagePosts
.map(function (p) {
var chip = p.agent
? '<span class="blog-card-chip">' + p.agent + "</span>"
: "";
return (
'<a href="' +
p.link +
'" class="blog-card" style="text-decoration:none" onclick="plausible(\'Blog Card Click\',{props:{post:\'' +
p.id.replace(/'/g, "\\'") +
"',area:'" +
p.area.replace(/'/g, "\\'") +
"'}})'>" +
'<span style="display:flex;gap:6px;flex-wrap:wrap;align-self:flex-start">' +
'<span class="blog-card-area ' +
blogAreaClass(p.area) +
'">' +
p.area +
"</span>" +
chip +
"</span>" +
'<span class="blog-card-date">' +
blogFormatDate(p.date) +
"</span>" +
"<h3>" +
p.headline +
"</h3>" +
"<p>" +
p.teaser +
"</p>" +
'<span class="blog-card-link">Read more &rarr;</span>' +
"</a>"
);
})
.join("");
document.getElementById("blog-page-info").textContent =
"Page " + (blogCurrentPage + 1) + " of " + totalPages;
document.getElementById("blog-prev").disabled = blogCurrentPage === 0;
document.getElementById("blog-next").disabled =
blogCurrentPage >= totalPages - 1;
}
function blogPrev() {
if (blogCurrentPage > 0) {
blogCurrentPage--;
blogRender();
}
}
function blogNext() {
const totalPages = Math.ceil(blogFilteredPosts.length / BLOG_POSTS_PER_PAGE);
if (blogCurrentPage < totalPages - 1) {
blogCurrentPage++;
blogRender();
}
}
function blogFilter() {
const q = document
.getElementById("blog-search-input")
.value.trim()
.toLowerCase();
if (!q) {
blogFilteredPosts = blogAllPosts.slice();
} else {
blogFilteredPosts = blogAllPosts.filter(function (p) {
return (
p.headline.toLowerCase().indexOf(q) !== -1 ||
p.teaser.toLowerCase().indexOf(q) !== -1 ||
p.area.toLowerCase().indexOf(q) !== -1
);
});
}
blogCurrentPage = 0;
blogRender();
}
// Load blog index
fetch("blog/index.json?_=" + Date.now())
.then(function (r) {
return r.json();
})
.then(function (data) {
blogAllPosts = data.posts || [];
blogFilteredPosts = blogAllPosts.slice();
blogRender();
})
.catch(function () {
document.getElementById("blog-grid").innerHTML =
'<div class="blog-empty">Blog coming soon.</div>';
});
// Search on input
document
.getElementById("blog-search-input")
.addEventListener("input", blogFilter);
+62 -2806
View File
File diff suppressed because it is too large Load Diff