Public Access
- Hero is the homepage's (same markup/assets): slide-in city image + the server location selector below it that slides a chosen city in; headline adapted to the offer. - Order now: hero > location selector > deal (HOST27, $295 first year / $395 after, one-click product-4 buy with redirect=/coupon/HOST27?r=/shop/checkout) > trust bar > Black Friday FAQ > features + trial card > FAQ > pricing (buy section) > Bootcamp > footer. - Bootcamp box restored from webmaster/out/2026-11-02-bootcamp-box-removed-archive.md: heading, USD 595.00, mechanism line, deadline, CTA, its meta/og contribution, its JSON-LD Product entry and the FAQ answer are all back and consistent. - Excluded (no blog, no OCA modules, no Project Plan); no dangling refs. - New /static/black-friday.js = the homepage behaviours this page needs (root-relative image paths), with the blog/OCA/project-plan blocks removed. - Canonical URL unchanged; competitor QC 0 on the page.
427 lines
17 KiB
JavaScript
427 lines
17 KiB
JavaScript
/* Page-local script for /odoo-black-friday/.
|
|
The homepage behaviours this landing page needs, copied from the site's /script.js:
|
|
the hero slide-in image driven by the server-location selector, the Buy Now buttons,
|
|
the free-trial card, the Custom Quote modal and the footer contact form.
|
|
The blog, OCA-modules and project-plan blocks of /script.js are removed here because this
|
|
page does not carry those sections (and those blocks throw on a page without their markup).
|
|
Image paths are root-relative: this page lives in a subdirectory (/odoo-black-friday/).
|
|
ES5, no build step, no new dependency beyond the gsap the homepage already loads. */
|
|
(function () {
|
|
"use strict";
|
|
|
|
/* ---- UTM tracking helpers (verbatim from /script.js) ---- */
|
|
(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();
|
|
})();
|
|
|
|
/* ---- Hero slide-in image + server-location selector (homepage behaviour) ---- */
|
|
(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' }
|
|
];
|
|
/* Root-relative on purpose: this page is served from /odoo-black-friday/. */
|
|
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);
|
|
});
|
|
}
|
|
|
|
function animateHero(city, image, showLabel) {
|
|
var hero = document.querySelector('.hero');
|
|
if (!hero || !window.gsap) 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) {
|
|
var oldLabel = hero.querySelector('.hero-city-label');
|
|
if (oldLabel) oldLabel.remove();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Location selector: click a server card to slide its city into the hero. */
|
|
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;
|
|
serverCards.forEach(function (c) { c.classList.remove('server-best'); });
|
|
card.classList.add('server-best');
|
|
animateHero(null, heroImages[s.host], false);
|
|
});
|
|
});
|
|
|
|
/* Populate the trial + pricing location selects. */
|
|
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');
|
|
var cityVal = s.host.replace('api.', '').replace('.odoo4projects.com', '');
|
|
opt.value = cityVal;
|
|
opt.textContent = s.city;
|
|
el.appendChild(opt);
|
|
});
|
|
}
|
|
});
|
|
|
|
/* Ping all servers, highlight the fastest, slide its image in. */
|
|
var results = [];
|
|
var pending = SERVERS.length;
|
|
SERVERS.forEach(function (s) {
|
|
pingServer(s.host).then(function (r) {
|
|
results.push(r);
|
|
pending--;
|
|
if (pending !== 0) return;
|
|
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 (x) { return x.host === best.host; })[0];
|
|
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]) {
|
|
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; }
|
|
}
|
|
var img = new Image();
|
|
img.onload = function () { animateHero(bestServer.city, heroImages[best.host]); };
|
|
img.src = heroImages[best.host];
|
|
}
|
|
});
|
|
});
|
|
|
|
/* Free-trial card. */
|
|
var trialForm = document.getElementById('trialForm');
|
|
if (trialForm) {
|
|
trialForm.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var email = document.getElementById('trialEmail').value.trim();
|
|
var location = trialLoc ? 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);
|
|
});
|
|
});
|
|
}
|
|
})();
|
|
|
|
/* ---- Buy Now — POST the basket straight to the shop, open checkout in a 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]);
|
|
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);
|
|
};
|
|
|
|
/* ---- Buy buttons + Custom Quote modal ---- */
|
|
(function () {
|
|
document.querySelectorAll('.btn-buy').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var id = this.getAttribute('data-id');
|
|
if (id) buyNow(parseInt(id, 10));
|
|
});
|
|
});
|
|
|
|
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';
|
|
}
|
|
window.openCustomQuoteModal = openCustomQuoteModal;
|
|
window.closeCustomQuoteModal = closeCustomQuoteModal;
|
|
|
|
document.querySelectorAll('.btn-empire').forEach(function (btn) {
|
|
btn.addEventListener('click', function () { openCustomQuoteModal(); });
|
|
});
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape') {
|
|
var modal = document.getElementById('customQuoteModal');
|
|
if (modal && modal.style.display === 'flex') { closeCustomQuoteModal(); }
|
|
}
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
}
|
|
})();
|
|
|
|
/* ---- 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);
|
|
});
|
|
});
|
|
})();
|
|
|
|
/* ---- Scroll-triggered reveal (same grids as the homepage, minus blog / project-plan) ---- */
|
|
(function () {
|
|
if (!window.gsap || !('IntersectionObserver' in window)) return;
|
|
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');
|
|
grids.forEach(function (g) {
|
|
if (g.children.length > 0) { gsap.set(g.children, { y: 30, opacity: 0 }); }
|
|
observer.observe(g);
|
|
});
|
|
})();
|
|
})();
|