Files
ODOO4projects.com/static/black-friday.js
T
webmaster 0a493d4e10 feat(bf): lead /odoo-black-friday/ with the homepage hero, move the Bootcamp box to the foot
- 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.
2026-11-02 08:25:20 +00:00

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);
});
})();
})();