Public Access
script.js: delete applyCoupon(). Its only caller was the coupon button deleted above, so the function was unreachable dead code that still carried the removed 'Coupon FREE TRIAL applied - 1 month free' promise in a publicly served file. It would also have thrown on the two DOM ids that no longer exist. PRODUCT.md: this file is served publicly and still advertised $19.40/mo, $36.25/mo, $49.60/mo, the FREE TRIAL coupon, 'yearly billing saves roughly 8-10%', a 'Satisfied or reimbursed' guarantee and 'redundant slots'. Corrected to the yearly list prices the site publishes and to the decisions above.
960 lines
42 KiB
JavaScript
960 lines
42 KiB
JavaScript
var si = 0,
|
|
slides = document.querySelectorAll(".hero-slide"),
|
|
dots = document.querySelectorAll(".carousel-dot"),
|
|
t,
|
|
dir = 1,
|
|
autoStop = false,
|
|
autoInterval = 7000;
|
|
|
|
/* ---- UTM tracking helpers ---- */
|
|
(function () {
|
|
function getCookie(name) {
|
|
var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
|
return match ? decodeURIComponent(match[2]) : null;
|
|
}
|
|
|
|
function setCookie(name, value, days) {
|
|
var expires = '';
|
|
if (days) {
|
|
var date = new Date();
|
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
expires = '; expires=' + date.toUTCString();
|
|
}
|
|
document.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/; SameSite=Lax';
|
|
}
|
|
|
|
function parseUTM() {
|
|
var urlParams = new URLSearchParams(window.location.search);
|
|
var utm = {};
|
|
var keys = ['source', 'medium', 'campaign'];
|
|
for (var i = 0; i < keys.length; i++) {
|
|
var val = urlParams.get('utm_' + keys[i]);
|
|
if (val) {
|
|
utm[keys[i]] = val;
|
|
setCookie('utm_' + keys[i], val, 90);
|
|
}
|
|
}
|
|
return utm;
|
|
}
|
|
|
|
function getStoredUTM() {
|
|
var utm = {};
|
|
var keys = ['source', 'medium', 'campaign'];
|
|
for (var i = 0; i < keys.length; i++) {
|
|
var val = getCookie('utm_' + keys[i]);
|
|
if (val) utm[keys[i]] = val;
|
|
}
|
|
return utm;
|
|
}
|
|
|
|
function injectUTM(data) {
|
|
var stored = getStoredUTM();
|
|
if (Object.keys(stored).length > 0) {
|
|
data.utm_source = stored.source || null;
|
|
data.utm_medium = stored.medium || null;
|
|
data.utm_campaign = stored.campaign || null;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function appendToForm(form) {
|
|
var stored = getStoredUTM();
|
|
var keys = ['source', 'medium', 'campaign'];
|
|
for (var i = 0; i < keys.length; i++) {
|
|
if (stored[keys[i]]) {
|
|
var input = document.createElement('input');
|
|
input.type = 'hidden';
|
|
input.name = 'utm_' + keys[i];
|
|
input.value = stored[keys[i]];
|
|
form.appendChild(input);
|
|
}
|
|
}
|
|
}
|
|
|
|
function toQuery() {
|
|
var stored = getStoredUTM();
|
|
var parts = [];
|
|
var keys = ['source', 'medium', 'campaign'];
|
|
for (var i = 0; i < keys.length; i++) {
|
|
if (stored[keys[i]]) {
|
|
parts.push('utm_' + keys[i] + '=' + encodeURIComponent(stored[keys[i]]));
|
|
}
|
|
}
|
|
return parts.join('&');
|
|
}
|
|
|
|
window.UTM = { parse: parseUTM, getStored: getStoredUTM, inject: injectUTM, appendToForm: appendToForm, toQuery: toQuery };
|
|
window.UTM.parse(); // Parse on page load
|
|
})();
|
|
|
|
/* ---- Spinning Up Widget (shown when ?message=spinning_up) ---- */
|
|
(function () {
|
|
var urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.get('message') === 'spinning_up') {
|
|
var el = document.getElementById('spinningUp');
|
|
if (el) el.style.display = 'block';
|
|
}
|
|
})();
|
|
|
|
/* ---- Hero confirmation notice (shown when ?confirm=...) ---- */
|
|
(function () {
|
|
var message = new URLSearchParams(window.location.search).get('confirm');
|
|
if (!message || !message.trim()) return;
|
|
|
|
var notice = document.getElementById('heroConfirmNotice');
|
|
if (!notice) return;
|
|
|
|
var text = notice.querySelector('.hero-notice-text');
|
|
var close = notice.querySelector('.hero-notice-close');
|
|
var hero = notice.closest('.hero');
|
|
|
|
text.textContent = message.trim();
|
|
notice.hidden = false;
|
|
if (hero) hero.classList.add('has-hero-notice');
|
|
|
|
if (close) {
|
|
close.addEventListener('click', function () {
|
|
notice.hidden = true;
|
|
if (hero) hero.classList.remove('has-hero-notice');
|
|
});
|
|
}
|
|
})();
|
|
|
|
if (slides.length > 0 && dots.length > 0) {
|
|
function show(i, direction) {
|
|
if (direction === undefined) return;
|
|
var current = slides[si];
|
|
var next = slides[i];
|
|
if (direction === 1) {
|
|
current.style.transform = "translateY(100%)";
|
|
current.style.opacity = "0";
|
|
current.style.zIndex = "1";
|
|
next.style.transform = "translateY(-100%)";
|
|
next.style.opacity = "0";
|
|
next.style.zIndex = "1";
|
|
requestAnimationFrame(function () {
|
|
next.style.transition =
|
|
"transform 0.5s ease, opacity 0.5s ease";
|
|
next.style.transform = "translateY(0)";
|
|
next.style.opacity = "1";
|
|
next.style.zIndex = "2";
|
|
});
|
|
} else {
|
|
current.style.transform = "translateY(-100%)";
|
|
current.style.opacity = "0";
|
|
current.style.zIndex = "1";
|
|
next.style.transform = "translateY(100%)";
|
|
next.style.opacity = "0";
|
|
next.style.zIndex = "1";
|
|
requestAnimationFrame(function () {
|
|
next.style.transition =
|
|
"transform 0.5s ease, opacity 0.5s ease";
|
|
next.style.transform = "translateY(0)";
|
|
next.style.opacity = "1";
|
|
next.style.zIndex = "2";
|
|
});
|
|
}
|
|
for (var j = 0; j < dots.length; j++) {
|
|
if (j === i) {
|
|
dots[j].classList.add("active");
|
|
} else {
|
|
dots[j].classList.remove("active");
|
|
}
|
|
}
|
|
si = i;
|
|
}
|
|
function nextSlide() {
|
|
show((si + 1) % slides.length, 1);
|
|
resetTimer();
|
|
}
|
|
function prevSlide() {
|
|
show((si - 1 + slides.length) % slides.length, -1);
|
|
resetTimer();
|
|
}
|
|
function goSlide(i) {
|
|
autoStop = true;
|
|
show(i, i > si ? 1 : -1);
|
|
clearInterval(t);
|
|
t = null;
|
|
}
|
|
function resetTimer() {
|
|
clearInterval(t);
|
|
t = setInterval(nextSlide, autoInterval);
|
|
}
|
|
|
|
t = setInterval(nextSlide, autoInterval);
|
|
|
|
for (var s = 0; s < slides.length; s++) {
|
|
slides[s].addEventListener("click", function () {
|
|
autoStop = true;
|
|
clearInterval(t);
|
|
t = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
/* spinner guard */
|
|
(function() {
|
|
var sp = document.getElementById("spinner");
|
|
if (!sp) return;
|
|
sp.classList.add("hidden");
|
|
setTimeout(function() { sp.style.display = "none"; }, 500);
|
|
setTimeout(function() {
|
|
if (document.getElementById("spinner")) {
|
|
document.getElementById("spinner").classList.add("hidden");
|
|
document.getElementById("spinner").style.display = "none";
|
|
}
|
|
}, 3000);
|
|
})();
|
|
|
|
document.querySelectorAll(".tab-btn").forEach(function (b) {
|
|
b.addEventListener("click", function () {
|
|
document.querySelectorAll(".tab-btn").forEach(function (x) {
|
|
x.classList.remove("active");
|
|
});
|
|
document
|
|
.querySelectorAll(".tab-pane")
|
|
.forEach(function (p) {
|
|
p.classList.remove("active");
|
|
});
|
|
this.classList.add("active");
|
|
document
|
|
.getElementById(this.getAttribute("data-t"))
|
|
.classList.add("active");
|
|
});
|
|
});
|
|
|
|
/* ---- Order Widget ---- */
|
|
var orderPopupShown = false;
|
|
function openOrderWidget(
|
|
plan,
|
|
yearlyPrice,
|
|
isEmpire,
|
|
) {
|
|
document.getElementById("orderPanelBuy").style.display =
|
|
"block";
|
|
var widget = document.getElementById("orderWidgetOverlay");
|
|
document.getElementById("orderPlanTitle").textContent = plan;
|
|
var price = yearlyPrice;
|
|
var amountEl = document.getElementById("orderPlanPrice");
|
|
var prefix = isEmpire ? ">" : "";
|
|
amountEl.textContent = prefix + "$ " + price.toFixed(2);
|
|
document.getElementById("orderPlanPeriod").textContent = "/ year";
|
|
var couponMsg = document.getElementById("orderCouponMsg");
|
|
var yearlySave = document.getElementById("orderYearlySave");
|
|
couponMsg.style.display = "none";
|
|
yearlySave.style.display = "block";
|
|
var buyBtn = document.getElementById("orderBuyBtn");
|
|
var loc = document.getElementById("orderServerLocation").value;
|
|
var utmQuery = UTM.toQuery();
|
|
buyBtn.href =
|
|
"/3/" +
|
|
price.toFixed(2) +
|
|
"/4/" +
|
|
encodeURIComponent(plan) +
|
|
"?location=" +
|
|
encodeURIComponent(loc) +
|
|
(utmQuery ? "&" + utmQuery : "");
|
|
widget.style.display = "flex";
|
|
orderPopupShown = true;
|
|
}
|
|
function closeOrderWidget() {
|
|
document.getElementById("orderWidgetOverlay").style.display =
|
|
"none";
|
|
}
|
|
// Escape key closes the order widget
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.key === "Escape") {
|
|
var widget = document.getElementById(
|
|
"orderWidgetOverlay",
|
|
);
|
|
if (widget && widget.style.display === "flex") {
|
|
closeOrderWidget();
|
|
}
|
|
}
|
|
});
|
|
document
|
|
.getElementById("orderServerLocation")
|
|
.addEventListener("change", function () {
|
|
var plan =
|
|
document.getElementById("orderPlanTitle").textContent;
|
|
var priceText = document
|
|
.getElementById("orderPlanPrice")
|
|
.textContent.replace(">$", "")
|
|
.replace("$", "")
|
|
.trim();
|
|
var price = parseFloat(priceText);
|
|
var loc = this.value;
|
|
var utmQuery = UTM.toQuery();
|
|
document.getElementById("orderBuyBtn").href =
|
|
"/3/" +
|
|
price.toFixed(2) +
|
|
"/4/" +
|
|
encodeURIComponent(plan) +
|
|
"?location=" +
|
|
encodeURIComponent(loc) +
|
|
(utmQuery ? "&" + utmQuery : "");
|
|
});
|
|
/* ---- Blog Module ---- */
|
|
var blogState = {
|
|
allPosts: [],
|
|
filteredPosts: [],
|
|
currentPage: 1,
|
|
perPage: 6,
|
|
searchTerm: "",
|
|
};
|
|
|
|
function formatDate(dateStr) {
|
|
var d = new Date(dateStr + "T00:00:00Z");
|
|
var months = [
|
|
"Jan",
|
|
"Feb",
|
|
"Mar",
|
|
"Apr",
|
|
"May",
|
|
"Jun",
|
|
"Jul",
|
|
"Aug",
|
|
"Sep",
|
|
"Oct",
|
|
"Nov",
|
|
"Dec",
|
|
];
|
|
return (
|
|
months[d.getUTCMonth()] +
|
|
" " +
|
|
d.getUTCDate() +
|
|
", " +
|
|
d.getUTCFullYear()
|
|
);
|
|
}
|
|
|
|
function badgeClass(area) {
|
|
var m = {
|
|
"speed-run": "speed-run",
|
|
guide: "guide",
|
|
howto: "howto",
|
|
news: "news",
|
|
};
|
|
return m[area] || "default";
|
|
}
|
|
|
|
function renderBlog() {
|
|
var grid = document.getElementById("blogGrid");
|
|
var search = (blogState.searchTerm || "").toLowerCase();
|
|
blogState.filteredPosts = blogState.allPosts.filter(
|
|
function (p) {
|
|
if (!search) return true;
|
|
return (
|
|
(p.headline + " " + p.teaser)
|
|
.toLowerCase()
|
|
.indexOf(search) !== -1
|
|
);
|
|
},
|
|
);
|
|
var total = blogState.filteredPosts.length;
|
|
var totalPages = Math.max(
|
|
1,
|
|
Math.ceil(total / blogState.perPage),
|
|
);
|
|
if (blogState.currentPage > totalPages)
|
|
blogState.currentPage = totalPages;
|
|
var start = (blogState.currentPage - 1) * blogState.perPage;
|
|
var pagePosts = blogState.filteredPosts.slice(
|
|
start,
|
|
start + blogState.perPage,
|
|
);
|
|
|
|
var html = "";
|
|
if (pagePosts.length === 0) {
|
|
html =
|
|
'<div class="blog-empty">No posts match your search.</div>';
|
|
} else {
|
|
for (var i = 0; i < pagePosts.length; i++) {
|
|
var p = pagePosts[i];
|
|
var bc = badgeClass(p.area);
|
|
var agentHtml = "";
|
|
if (p.agent)
|
|
agentHtml =
|
|
'<span class="blog-agent">' +
|
|
p.agent +
|
|
"</span>";
|
|
html +=
|
|
'<div class="blog-card">' +
|
|
'<div class="blog-card-top">' +
|
|
'<span class="blog-badge blog-badge-' +
|
|
bc +
|
|
'">' +
|
|
p.area +
|
|
"</span>" +
|
|
agentHtml +
|
|
"</div>" +
|
|
'<div class="blog-date">' +
|
|
formatDate(p.date) +
|
|
"</div>" +
|
|
'<h3><a href="' +
|
|
p.link +
|
|
'">' +
|
|
p.headline +
|
|
"</a></h3>" +
|
|
'<p class="blog-teaser">' +
|
|
p.teaser +
|
|
"</p>" +
|
|
'<a href="' +
|
|
p.link +
|
|
'" class="blog-readmore">Read more →</a>' +
|
|
"</div>";
|
|
}
|
|
}
|
|
grid.innerHTML = html;
|
|
|
|
var pag = document.getElementById("blogPagination");
|
|
var pagHtml = "";
|
|
if (totalPages > 1) {
|
|
var prevPage = blogState.currentPage > 1 ? blogState.currentPage - 1 : null;
|
|
var nextPage = blogState.currentPage < totalPages ? blogState.currentPage + 1 : null;
|
|
var isFirst = blogState.currentPage <= 1;
|
|
var isLast = blogState.currentPage >= totalPages;
|
|
|
|
pagHtml += '<a class="blog-page-btn' + (isFirst ? ' blog-page-btn-disabled' : '') + '" href="?page=1"' + (isFirst ? ' aria-disabled="true"' : ' aria-label="Go to first page"') + '>«</a>';
|
|
if (prevPage) {
|
|
pagHtml += '<a class="blog-page-btn" href="?page=' + prevPage + '" rel="prev" aria-label="Go to previous page">‹ Prev</a>';
|
|
} else {
|
|
pagHtml += '<span class="blog-page-btn-disabled">‹ Prev</span>';
|
|
}
|
|
pagHtml += '<span class="blog-page-info" aria-live="polite">Page ' + blogState.currentPage + " of " + totalPages + "</span>";
|
|
if (nextPage) {
|
|
pagHtml += '<a class="blog-page-btn" href="?page=' + nextPage + '" rel="next" aria-label="Go to next page">Next ›</a>';
|
|
} else {
|
|
pagHtml += '<span class="blog-page-btn-disabled">Next ›</span>';
|
|
}
|
|
pagHtml += '<a class="blog-page-btn' + (isLast ? ' blog-page-btn-disabled' : '') + '" href="?page=' + totalPages + '"' + (isLast ? ' aria-disabled="true"' : ' aria-label="Go to last page"') + '>»</a>';
|
|
}
|
|
pag.innerHTML = pagHtml;
|
|
}
|
|
|
|
function blogGo(page) {
|
|
blogState.currentPage = page;
|
|
renderBlog();
|
|
}
|
|
|
|
/* ---- Silent ping for hero carousel ---- */
|
|
(function() {
|
|
var SERVERS = [
|
|
{ host: 'api.meppel.odoo4projects.com', city: 'Amsterdam' },
|
|
{ host: 'api.boston.odoo4projects.com', city: 'Boston' },
|
|
{ host: 'api.manchester.odoo4projects.com', city: 'Manchester' },
|
|
{ host: 'api.mumbai.odoo4projects.com', city: 'Mumbai' },
|
|
{ host: 'api.saopaulo.odoo4projects.com', city: 'S\u00e3o Paulo' },
|
|
{ host: 'api.sydney.odoo4projects.com', city: 'Sydney' }
|
|
];
|
|
var heroImages = {
|
|
'api.meppel.odoo4projects.com': 'images/hero/amsterdam.webp',
|
|
'api.boston.odoo4projects.com': 'images/hero/boston.webp',
|
|
'api.manchester.odoo4projects.com': 'images/hero/london.webp',
|
|
'api.mumbai.odoo4projects.com': 'images/hero/mumbai.webp',
|
|
'api.saopaulo.odoo4projects.com': 'images/hero/brazil.webp',
|
|
'api.sydney.odoo4projects.com': 'images/hero/sydney.webp'
|
|
};
|
|
|
|
function pingServer(host) {
|
|
return new Promise(function(resolve) {
|
|
var start = performance.now();
|
|
var img = new Image();
|
|
var timedOut = false;
|
|
var timer = setTimeout(function() {
|
|
timedOut = true; img.src = '';
|
|
resolve({ host: host, time: -1 });
|
|
}, 2000);
|
|
img.onload = function() {
|
|
var elapsed = performance.now() - start;
|
|
if (!timedOut) { clearTimeout(timer); resolve({ host: host, time: elapsed }); }
|
|
};
|
|
img.onerror = function() {
|
|
var elapsed = performance.now() - start;
|
|
if (!timedOut) { clearTimeout(timer); resolve({ host: host, time: elapsed }); }
|
|
};
|
|
img.src = 'https://' + host + '/favicon.ico?_=' + Date.now() + '_' + Math.random().toString(36).slice(2,6);
|
|
});
|
|
}
|
|
|
|
/* Click on a server card — slide its city into hero, no label */
|
|
var serverCards = document.querySelectorAll('.server-card');
|
|
serverCards.forEach(function(card, idx) {
|
|
card.addEventListener('click', function() {
|
|
var s = SERVERS[idx];
|
|
if (!s || !heroImages[s.host]) return;
|
|
/* Move golden frame */
|
|
serverCards.forEach(function(c) { c.classList.remove('server-best'); });
|
|
card.classList.add('server-best');
|
|
/* Slide hero bg without label */
|
|
animateHero(null, heroImages[s.host], false);
|
|
});
|
|
});
|
|
|
|
function animateHero(city, image, showLabel) {
|
|
var hero = document.querySelector('.hero');
|
|
if (!hero) return;
|
|
var bg = hero.querySelector('.hero-bg');
|
|
if (!bg) {
|
|
bg = document.createElement('div');
|
|
bg.className = 'hero-bg';
|
|
hero.insertBefore(bg, hero.firstChild);
|
|
}
|
|
var hasExisting = bg.children.length > 0;
|
|
var newEl = document.createElement('div');
|
|
newEl.className = 'hero-bg-slide';
|
|
newEl.style.backgroundImage = "url('" + image + "')";
|
|
bg.appendChild(newEl);
|
|
|
|
var tl = gsap.timeline();
|
|
gsap.set(newEl, { x: '100%' });
|
|
|
|
var label = null;
|
|
if (showLabel !== false && city) {
|
|
label = document.createElement('div');
|
|
label.className = 'hero-city-label';
|
|
label.innerHTML = 'Our <strong>' + city + '</strong> server fits your location';
|
|
hero.appendChild(label);
|
|
gsap.set(label, { x: '150%' });
|
|
}
|
|
|
|
if (hasExisting) {
|
|
var oldEl = bg.children[0];
|
|
gsap.set(oldEl, { x: '0%' });
|
|
tl.to(oldEl, { x: '100%', duration: 3, ease: 'power2.out' }, 0)
|
|
.to(newEl, { x: '0%', duration: 3, ease: 'power2.out' }, 0);
|
|
if (label) {
|
|
tl.to(label, { x: '0%', duration: 2.5, ease: 'power2.out' }, 0.3);
|
|
}
|
|
tl.call(function() { oldEl.remove(); });
|
|
} else {
|
|
tl.to(newEl, { x: '0%', duration: 3, ease: 'power2.out' }, 0);
|
|
if (label) {
|
|
tl.to(label, { x: '0%', duration: 2.5, ease: 'power2.out' }, 0.3);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Ping all servers, pick best, animate hero */
|
|
var results = [];
|
|
var pending = SERVERS.length;
|
|
|
|
/* Populate trial form AND pricing server location dropdowns */
|
|
var trialLoc = document.getElementById('trialLocation');
|
|
var pricingLoc = document.getElementById('pricingServerLocation');
|
|
[trialLoc, pricingLoc].forEach(function(el) {
|
|
if (el) {
|
|
SERVERS.forEach(function(s) {
|
|
var opt = document.createElement('option');
|
|
// Extract city from host: api.saopaulo.odoo4projects.com -> saopaulo
|
|
var cityVal = s.host.replace('api.', '').replace('.odoo4projects.com', '');
|
|
opt.value = cityVal;
|
|
opt.textContent = s.city;
|
|
el.appendChild(opt);
|
|
});
|
|
}
|
|
});
|
|
|
|
SERVERS.forEach(function(s) {
|
|
pingServer(s.host).then(function(r) {
|
|
results.push(r);
|
|
pending--;
|
|
if (pending === 0) {
|
|
var valid = results.filter(function(x) { return x.time >= 0; });
|
|
if (valid.length === 0) return;
|
|
valid.sort(function(a, b) { return a.time - b.time; });
|
|
var best = valid[0];
|
|
var bestServer = SERVERS.filter(function(s) { return s.host === best.host; })[0];
|
|
/* Preselect best server in trial and pricing forms */
|
|
if (bestServer) {
|
|
[trialLoc, pricingLoc].forEach(function(el) {
|
|
if (el) {
|
|
var cityVal = bestServer.host.replace('api.', '').replace('.odoo4projects.com', '');
|
|
for (var i = 0; i < el.options.length; i++) {
|
|
if (el.options[i].value === cityVal) {
|
|
el.selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (bestServer && heroImages[best.host]) {
|
|
/* Highlight the matching server card */
|
|
var cards = document.querySelectorAll('.server-card');
|
|
for (var i = 0; i < SERVERS.length && i < cards.length; i++) {
|
|
if (SERVERS[i].host === best.host) {
|
|
cards[i].classList.add('server-best');
|
|
break;
|
|
}
|
|
}
|
|
/* Preload and animate hero */
|
|
var img = new Image();
|
|
img.onload = function() { animateHero(bestServer.city, heroImages[best.host]); };
|
|
img.src = heroImages[best.host];
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
/* Trial form submission */
|
|
var trialForm = document.getElementById('trialForm');
|
|
if (trialForm) {
|
|
trialForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
var email = document.getElementById('trialEmail').value.trim();
|
|
var location = trialLoc.value;
|
|
if (!email) return;
|
|
var btn = trialForm.querySelector('.btn-brass');
|
|
btn.textContent = 'Sending...';
|
|
btn.disabled = true;
|
|
fetch('https://n8n.odoo4projects.com/webhook/ODOO4projects/trial', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(UTM.inject({ email: email, location: location }))
|
|
}).then(function() {
|
|
trialForm.style.display = 'none';
|
|
document.getElementById('trialSuccess').classList.add('visible');
|
|
}).catch(function() {
|
|
btn.textContent = 'Send';
|
|
btn.disabled = false;
|
|
var errorMsg = document.createElement('div');
|
|
errorMsg.className = 'trial-error visible';
|
|
errorMsg.textContent = 'Something went wrong. Please try again.';
|
|
trialForm.appendChild(errorMsg);
|
|
setTimeout(function() { errorMsg.classList.remove('visible'); }, 3000);
|
|
});
|
|
});
|
|
}
|
|
})();
|
|
|
|
fetch("blog/index.json?_=" + Date.now())
|
|
.then(function (r) {
|
|
return r.json();
|
|
})
|
|
.then(function (data) {
|
|
blogState.allPosts = data.posts;
|
|
renderBlog();
|
|
})
|
|
.catch(function () {
|
|
document.getElementById("blogGrid").innerHTML =
|
|
'<div class="blog-empty">Could not load blog posts.</div>';
|
|
});
|
|
|
|
document
|
|
.getElementById("blogSearch")
|
|
.addEventListener("input", function () {
|
|
blogState.searchTerm = this.value;
|
|
blogState.currentPage = 1;
|
|
renderBlog();
|
|
});
|
|
|
|
/* ---- Scroll-triggered section animations ---- */
|
|
(function () {
|
|
var animated = {};
|
|
var observer = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (!entry.isIntersecting) return;
|
|
var grid = entry.target;
|
|
if (!grid || !grid.children || grid.children.length === 0) return;
|
|
var key = grid.className || 'anon';
|
|
if (animated[key]) return;
|
|
animated[key] = true;
|
|
gsap.fromTo(
|
|
grid.children,
|
|
{ y: 30, opacity: 0 },
|
|
{
|
|
y: 0,
|
|
opacity: 1,
|
|
duration: 0.7,
|
|
ease: 'power2.out',
|
|
stagger: 0.08,
|
|
overwrite: 'auto'
|
|
}
|
|
);
|
|
});
|
|
}, { threshold: 0.1 });
|
|
|
|
var grids = document.querySelectorAll(
|
|
'.features-grid, .faq-grid, .pricing-grid, .blog-grid, .biz-wizard .container'
|
|
);
|
|
grids.forEach(function (g) {
|
|
/* Pre-set children invisible so no flash even before observer fires */
|
|
if (g.children.length > 0) {
|
|
gsap.set(g.children, { y: 30, opacity: 0 });
|
|
}
|
|
observer.observe(g);
|
|
});
|
|
})();
|
|
|
|
/* ---- Business Wizard Form ---- */
|
|
(function () {
|
|
var form = document.getElementById('bizForm');
|
|
if (!form) return;
|
|
var success = document.getElementById('bizSuccess');
|
|
var submitBtn = document.getElementById('bizSubmitBtn');
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var description = document.getElementById('bizDescription').value.trim();
|
|
var goals = document.getElementById('bizGoals').value.trim();
|
|
var challenges = document.getElementById('bizChallenges').value.trim();
|
|
if (!description || !goals || !challenges) return;
|
|
|
|
var email = document.getElementById('bizEmail').value.trim();
|
|
if (!email) return;
|
|
|
|
var hasHomepage = document.getElementById('bizHasHomepage').checked;
|
|
var usesOdoo = document.getElementById('bizUsesOdoo').checked;
|
|
var hasDeveloper = document.getElementById('bizHasDeveloper').checked;
|
|
|
|
submitBtn.textContent = 'Generating...';
|
|
submitBtn.disabled = true;
|
|
|
|
/* POST silently to webhook — the report will be emailed */
|
|
fetch('https://n8n.odoo4projects.com/webhook/bring-your-business', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(UTM.inject({
|
|
email: email,
|
|
description: description,
|
|
goals: goals,
|
|
challenges: challenges,
|
|
has_homepage: hasHomepage,
|
|
uses_odoo: usesOdoo,
|
|
has_developer: hasDeveloper
|
|
}))
|
|
}).catch(function () { /* ignore */ });
|
|
|
|
/* Show success message — we'll email the report */
|
|
form.style.display = 'none';
|
|
success.style.display = 'block';
|
|
});
|
|
})();
|
|
|
|
/* Buy Now — POST basket via form with server location, opens checkout in new tab */
|
|
window.buyNow = function (productId) {
|
|
var locSelect = document.getElementById('pricingServerLocation');
|
|
var location = locSelect ? locSelect.value : '';
|
|
var verSelect = document.getElementById('pricingOdooVersion');
|
|
var version = verSelect ? verSelect.value : '';
|
|
var form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = 'https://20.odoo4projects.com/shop/post_basket';
|
|
form.target = '_blank';
|
|
form.style.display = 'none';
|
|
var basket = document.createElement('input');
|
|
basket.type = 'hidden';
|
|
basket.name = 'basket';
|
|
var item = { product_id: productId, qty: 1 };
|
|
var info = location ? location.replace('.odoo4projects.com', '') : '';
|
|
if (version) { info += (info ? '|' : '') + version; }
|
|
if (info) { item.info = info; }
|
|
basket.value = JSON.stringify([item]);
|
|
console.log('Buy basket:', basket.value);
|
|
form.appendChild(basket);
|
|
var clear = document.createElement('input');
|
|
clear.type = 'hidden';
|
|
clear.name = 'clear';
|
|
clear.value = '1';
|
|
form.appendChild(clear);
|
|
UTM.appendToForm(form);
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
document.body.removeChild(form);
|
|
};
|
|
|
|
/* ---- OCA Modules Status ---- */
|
|
(function () {
|
|
var tbody = document.getElementById('ocaTbody');
|
|
var searchInput = document.getElementById('ocaSearch');
|
|
var empty = document.getElementById('ocaEmpty');
|
|
var loading = document.getElementById('ocaLoading');
|
|
var allModules = [];
|
|
|
|
function render(filter) {
|
|
var q = (filter || '').toLowerCase();
|
|
var matched = q ? allModules.filter(function (m) { return (m.name + ' ' + m.area).toLowerCase().indexOf(q) !== -1; }) : allModules;
|
|
if (matched.length === 0) {
|
|
tbody.innerHTML = '';
|
|
empty.style.display = 'block';
|
|
return;
|
|
}
|
|
empty.style.display = 'none';
|
|
var html = '';
|
|
for (var i = 0; i < matched.length; i++) {
|
|
var mod = matched[i];
|
|
var v19 = mod.v19 ? '<span class="oca-check">✓</span>' : '<span class="oca-cross">–</span>';
|
|
var v20 = mod.v20 ? '<span class="oca-check">✓</span>' : '<span class="oca-cross">–</span>';
|
|
html += '<tr>' +
|
|
'<td>' + esc(mod.name) + '</td>' +
|
|
'<td>' + esc(mod.area) + '</td>' +
|
|
'<td>' + v19 + '</td>' +
|
|
'<td>' + v20 + '</td>' +
|
|
'<td><a href="' + esc(mod.URL) + '" target="_blank" rel="noopener" class="oca-icon-link" title="View on GitHub"><i class="fas fa-external-link-alt"></i></a></td>' +
|
|
'</tr>';
|
|
}
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
function esc(s) {
|
|
var d = document.createElement('div');
|
|
d.appendChild(document.createTextNode(s || ''));
|
|
return d.innerHTML;
|
|
}
|
|
|
|
fetch('https://n8n.odoo4projects.com/webhook/oc-guy/modules')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) {
|
|
if (data && Array.isArray(data.data)) {
|
|
allModules = data.data;
|
|
} else if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0].data)) {
|
|
allModules = data[0].data;
|
|
} else if (Array.isArray(data)) {
|
|
allModules = data;
|
|
}
|
|
loading.style.display = 'none';
|
|
render(searchInput ? searchInput.value : '');
|
|
})
|
|
.catch(function () {
|
|
loading.textContent = 'Could not load module status.';
|
|
});
|
|
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', function () {
|
|
render(this.value);
|
|
});
|
|
}
|
|
|
|
/* Buy Now buttons (no inline onclick) */
|
|
document.querySelectorAll('.btn-buy').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
var id = this.getAttribute('data-id');
|
|
if (id) buyNow(parseInt(id));
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.btn-empire').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
openCustomQuoteModal();
|
|
});
|
|
});
|
|
|
|
/* ---- Custom Quote Modal ---- */
|
|
function openCustomQuoteModal() {
|
|
document.getElementById('customQuotePanel').style.display = 'block';
|
|
document.getElementById('customQuoteModal').style.display = 'flex';
|
|
}
|
|
function closeCustomQuoteModal() {
|
|
document.getElementById('customQuoteModal').style.display = 'none';
|
|
document.getElementById('customQuoteForm').reset();
|
|
document.getElementById('customQuoteSuccess').style.display = 'none';
|
|
}
|
|
/* Expose to global scope so the inline onclick handlers in the modal markup can reach them */
|
|
window.openCustomQuoteModal = openCustomQuoteModal;
|
|
window.closeCustomQuoteModal = closeCustomQuoteModal;
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
var modal = document.getElementById('customQuoteModal');
|
|
if (modal && modal.style.display === 'flex') {
|
|
closeCustomQuoteModal();
|
|
}
|
|
}
|
|
});
|
|
|
|
/* Submit custom quote form */
|
|
var customQuoteForm = document.getElementById('customQuoteForm');
|
|
if (customQuoteForm) {
|
|
customQuoteForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
var name = document.getElementById('cqName').value.trim();
|
|
var email = document.getElementById('cqEmail').value.trim();
|
|
var requirements = document.getElementById('cqRequirements').value.trim();
|
|
|
|
if (!name || !email || !requirements) return;
|
|
|
|
var submitBtn = customQuoteForm.querySelector('.btn-lg');
|
|
submitBtn.textContent = 'Sending...';
|
|
submitBtn.disabled = true;
|
|
|
|
fetch('https://n8n.odoo4projects.com/webhook/ODOO4projects/crm', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(UTM.inject({
|
|
name: name,
|
|
email: email,
|
|
requirements: requirements,
|
|
area: 'quote'
|
|
}))
|
|
}).then(function(r) {
|
|
if (!r.ok) throw new Error('bad status ' + r.status);
|
|
customQuoteForm.style.display = 'none';
|
|
document.getElementById('customQuoteSuccess').style.display = 'block';
|
|
submitBtn.textContent = 'Submit Request';
|
|
submitBtn.disabled = false;
|
|
}).catch(function() {
|
|
submitBtn.textContent = 'Submit Request';
|
|
submitBtn.disabled = false;
|
|
var errorMsg = document.createElement('div');
|
|
errorMsg.style.marginTop = '1rem';
|
|
errorMsg.style.color = '#dc3545';
|
|
errorMsg.textContent = 'Something went wrong. Please try again.';
|
|
customQuoteForm.appendChild(errorMsg);
|
|
setTimeout(function() { if(errorMsg.parentNode) errorMsg.parentNode.removeChild(errorMsg); }, 3000);
|
|
});
|
|
});
|
|
}
|
|
|
|
/* Attach "Get Custom Quote" click handlers to Empire buttons */
|
|
document.querySelectorAll('.btn-empire').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
openCustomQuoteModal();
|
|
});
|
|
});
|
|
})();
|
|
|
|
/* ---- Footer contact form ---- */
|
|
(function () {
|
|
var form = document.getElementById('footerContactForm');
|
|
if (!form) return;
|
|
var emailInput = document.getElementById('footerContactEmail');
|
|
var textInput = document.getElementById('footerContactText');
|
|
var submitBtn = form.querySelector('.footer-send-btn');
|
|
var status = document.getElementById('footerFormStatus');
|
|
|
|
function showStatus(message, isError) {
|
|
status.textContent = message;
|
|
status.classList.toggle('error', !!isError);
|
|
status.classList.add('visible');
|
|
}
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var email = emailInput.value.trim();
|
|
var text = textInput.value.trim();
|
|
if (!email || !text) return;
|
|
|
|
submitBtn.disabled = true;
|
|
status.classList.remove('visible');
|
|
|
|
fetch('https://n8n.odoo4projects.com/webhook/ODOO4projects/crm', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(UTM.inject({
|
|
email: email,
|
|
requirements: text,
|
|
area: 'message'
|
|
}))
|
|
}).then(function (r) {
|
|
if (!r.ok) throw new Error('bad status ' + r.status);
|
|
form.reset();
|
|
submitBtn.disabled = false;
|
|
showStatus("Thanks! We'll get back to you shortly.", false);
|
|
}).catch(function () {
|
|
submitBtn.disabled = false;
|
|
showStatus('Something went wrong. Please try again.', true);
|
|
});
|
|
});
|
|
})();
|