/* 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 ' + city + ' 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); }); })(); })();