Public Access
Mark the six conversion goals on the self-hosted Plausible instance so the
events match the goals to be registered in Plausible. Event names verbatim:
Trial, FAQ, Buy, Custom Quote, search posts, Project Plan.
CSS-class markings (work without JS logic, on the clickable element):
- FAQ -> index/de/es index .faq-card; odoo-hosting-2027 + odoo-community-upgrade
(de/es twins) and australia <summary>; support.html .faq-question
- Buy -> index/de/es index .btn-buy anchors; australia + bootcamp native
post_basket submit buttons (forms stay native POST)
JS call markings in script.js (loaded by index, de/index, es/index):
- Trial on the successful trial-form POST (.then branch)
- Custom Quote on the successful custom-quote POST (.then branch)
- Project Plan on the successful business-wizard submit
- search posts on the FIRST input of #blogSearch only (guard flag, one per
pageview, not on every keystroke, not on #ocaSearch)
Nothing fires on page load; the 'Start the free trial' anchors that only
scroll to #trialCard are deliberately not tagged. One goal per topic across
all languages, no language suffix. No visual, behavioural or request change.
977 lines
43 KiB
JavaScript
977 lines
43 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 thumbHtml = "";
|
|
if (p.image)
|
|
thumbHtml =
|
|
'<img class="blog-thumb" src="' +
|
|
p.image +
|
|
'" alt="' +
|
|
p.headline +
|
|
'" loading="lazy" />';
|
|
var agentHtml = "";
|
|
if (p.agent)
|
|
agentHtml =
|
|
'<span class="blog-agent">' +
|
|
p.agent +
|
|
"</span>";
|
|
html +=
|
|
'<div class="blog-card">' +
|
|
thumbHtml +
|
|
'<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() {
|
|
if (window.plausible) window.plausible('Trial');
|
|
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>';
|
|
});
|
|
|
|
var blogSearchTracked = false;
|
|
document
|
|
.getElementById("blogSearch")
|
|
.addEventListener("input", function () {
|
|
if (!blogSearchTracked) {
|
|
blogSearchTracked = true;
|
|
if (window.plausible) window.plausible('search posts');
|
|
}
|
|
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 */
|
|
if (window.plausible) window.plausible('Project Plan');
|
|
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);
|
|
if (window.plausible) window.plausible('Custom Quote');
|
|
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);
|
|
});
|
|
});
|
|
})();
|