Contact Us

We are here to help with course selection, account support, and collaboration.

0/1000
m' ? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : v); }); qs('#openLogin')?.addEventListener('click', () => qs('#loginModal')?.showModal()); qs('#closeLogin')?.addEventListener('click', () => qs('#loginModal')?.close()); qs('#openRegister')?.addEventListener('click', () => qs('#registerModal')?.showModal()); qs('#closeRegister')?.addEventListener('click', () => qs('#registerModal')?.close()); qs('#openCmd')?.addEventListener('click', () => { qs('#cmdModal')?.showModal(); qs('#cmdInput')?.focus(); }); window.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); qs('#cmdModal')?.showModal(); qs('#cmdInput')?.focus(); } }); // Dialog backdrop close document.querySelectorAll('dialog').forEach(dlg => { dlg.addEventListener('click', (e) => { const rect = dlg.getBoundingClientRect(); const inDialog = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom; if (!inDialog) dlg.close(); }); }); }; // ETA timer: countdown to next 6pm on a business day const etaEl = document.getElementById('etaTimer'); const isBusinessDay = (d) => { const day = d.getDay(); // 0 Sun ... 6 Sat return day !== 0 && day !== 6; }; const nextBusinessCutoff = () => { const now = new Date(); const target = new Date(now); target.setHours(18, 0, 0, 0); if (!isBusinessDay(now) || now > target) { // move to next business day 18:00 do { target.setDate(target.getDate() + 1); } while (!isBusinessDay(target)); target.setHours(18, 0, 0, 0); } return target; }; const formatDur = (ms) => { const s = Math.max(0, Math.floor(ms / 1000)); const d = Math.floor(s / 86400); const h = Math.floor((s % 86400) / 3600); const m = Math.floor((s % 3600) / 60); const sec = s % 60; const parts = []; if (d) parts.push(d + 'd'); if (h || d) parts.push(h + 'h'); if (m || h || d) parts.push(m + 'm'); parts.push(sec + 's'); return parts.join(' '); }; const runEta = () => { const update = () => { const target = nextBusinessCutoff(); const diff = target - new Date(); if (etaEl) etaEl.textContent = formatDur(diff); }; update(); setInterval(update, 1000); }; // Cookie banner const cookieBar = document.getElementById('cookieBar'); const cookieModal = document.getElementById('cookieModal'); const cookiePrefsBtn = document.getElementById('cookiePrefsBtn'); const cookieAcceptAll = document.getElementById('cookieAcceptAll'); const cookieSave = document.getElementById('cookieSave'); const cookieDecline = document.getElementById('cookieDecline'); const consentAnalytics = document.getElementById('consentAnalytics'); const consentMarketing = document.getElementById('consentMarketing'); const getConsent = () => { try { return JSON.parse(localStorage.getItem('cookieConsent') || '{}'); } catch { return {}; } }; const setConsent = (obj) => { localStorage.setItem('cookieConsent', JSON.stringify({ ...obj, updatedAt: Date.now() })); cookieBar?.classList.add('hidden'); cookieModal?.close(); }; const initCookies = () => { const consent = getConsent(); if (!consent.set) { setTimeout(() => cookieBar?.classList.remove('hidden'), 600); } else { cookieBar?.classList.add('hidden'); } if (typeof consent.analytics === 'boolean') consentAnalytics.checked = consent.analytics; if (typeof consent.marketing === 'boolean') consentMarketing.checked = consent.marketing; cookiePrefsBtn?.addEventListener('click', () => cookieModal.showModal()); cookieAcceptAll?.addEventListener('click', () => setConsent({ set: true, necessary: true, analytics: true, marketing: true })); cookieSave?.addEventListener('click', (e) => { e.preventDefault(); setConsent({ set: true, necessary: true, analytics: consentAnalytics.checked, marketing: consentMarketing.checked }); }); cookieDecline?.addEventListener('click', (e) => { e.preventDefault(); setConsent({ set: true, necessary: true, analytics: false, marketing: false }); }); }; // Form validation and interactions const form = document.getElementById('contactForm'); const errorModal = document.getElementById('errorModal'); const errorList = document.getElementById('errorList'); const okModal = document.getElementById('okModal'); const clarityBtn = document.getElementById('clarityCheck'); const clarityModal = document.getElementById('clarityModal'); const clarityList = document.getElementById('clarityList'); const emailValid = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim()); const phoneNormalize = (v) => v.replace(/[^\d+]/g, '').replace(/(?!^)\+/g, ''); const phoneValid = (v) => { if (!v) return true; const s = phoneNormalize(v); // E.164-like: starts with + and 8-15 digits total including country code return /^\+?[1-9]\d{7,14}$/.test(s); }; const formatPhoneLive = (value) => { // Keep user-friendly: group digits but keep plus if present const raw = phoneNormalize(value); const plus = raw.startsWith('+') ? '+' : ''; const digits = raw.replace(/^\+/, ''); // Simple grouping: +X XXX XXX XXXX... let out = ''; for (let i = 0; i < digits.length; i++) { out += digits[i]; if ([1, 4, 7, 11].includes(i + 1) && i + 1 < digits.length) out += ' '; } return plus + out.trim(); }; const buildErrors = () => { const errs = []; const name = document.getElementById('name').value.trim(); const email = document.getElementById('email').value.trim(); const phone = document.getElementById('phone').value.trim(); const message = document.getElementById('message').value.trim(); const consent = document.getElementById('consent').checked; const honeypot = document.querySelector('input[name="company"]').value.trim(); if (honeypot) errs.push('Unexpected field filled. Please clear the "company" field.'); if (name.length < 2) errs.push('Please enter your full name (at least 2 characters).'); if (!emailValid(email)) errs.push('Please enter a valid email address.'); if (phone && !phoneValid(phone)) errs.push('Please enter a valid phone number (international format, e.g., +1 202 555 0147).'); if (message.length < 20) errs.push('Message is too short. Please provide at least 20 characters.'); if (!consent) errs.push('You must agree to the processing of personal data.'); // Content checks if ((message.match(/https?:\/\//g) || []).length > 3) errs.push('Too many links. Please limit the number of URLs in the message.'); if (/(viagra|casino|loan|crypto)\b/i.test(message)) errs.push('Prohibited content detected. Please remove sensitive keywords.'); return errs; }; const showErrors = (errs) => { errorList.innerHTML = ''; errs.forEach(e => { const li = document.createElement('li'); li.textContent = e; errorList.appendChild(li); }); errorModal.showModal(); }; const simulateSubmit = async (payload) => { // Simulate a network queue with a short delay and localStorage stash localStorage.setItem('lastMessage', JSON.stringify({ payload, queuedAt: Date.now() })); await new Promise(r => setTimeout(r, 400)); okModal.showModal(); }; clarityBtn.addEventListener('click', () => { const message = document.getElementById('message').value.trim(); const suggestions = []; if (!message) { suggestions.push('Message is empty. Share your goals or questions to get tailored advice.'); } else { const words = message.split(/\s+/).filter(Boolean); const sentences = message.split(/[.!?]+/).filter(s => s.trim().length > 0); const avgLen = sentences.length ? Math.round(words.length / sentences.length) : words.length; if (avgLen > 28) suggestions.push('Your sentences are quite long on average. Consider splitting them for readability.'); if (words.length < 40) suggestions.push('Consider adding a bit more detail about your goals, timeline, or constraints.'); const fillers = ['just','really','very','actually','basically','maybe','kinda','sort of','perhaps','stuff','things','like']; const foundFillers = fillers.filter(f => new RegExp(`\\b${f.replace(' ', '\\s+')}\\b`, 'i').test(message)); if (foundFillers.length) suggestions.push('Remove filler words: ' + foundFillers.join(', ') + '.'); const passive = /\b(?:am|is|are|was|were|be|been|being)\s+\w+ed\b/i.test(message); if (passive) suggestions.push('Use active voice where possible for clarity.'); const excls = (message.match(/!/g) || []).length; if (excls > 2) suggestions.push('Reduce exclamation marks for a professional tone.'); if (!/\b(budget|timeline|deadline|scope|audience)\b/i.test(message)) suggestions.push('Mention key details such as target audience, budget, scope, or timeline.'); } clarityList.innerHTML = ''; if (suggestions.length === 0) { const li = document.createElement('li'); li.textContent = 'Looks great. Clear, concise, and actionable!'; clarityList.appendChild(li); } else { suggestions.forEach(s => { const li = document.createElement('li'); li.textContent = s; clarityList.appendChild(li); }); } clarityModal.showModal(); }); form.addEventListener('input', (e) => { if (e.target.id === 'phone') { const caret = e.target.selectionStart; const formatted = formatPhoneLive(e.target.value); e.target.value = formatted; // caret management is best-effort; leave default behavior } }); form.addEventListener('submit', async (e) => { e.preventDefault(); const errs = buildErrors(); if (errs.length) { showErrors(errs); return; } const payload = { name: document.getElementById('name').value.trim(), email: document.getElementById('email').value.trim(), phone: document.getElementById('phone').value.trim(), message: document.getElementById('message').value.trim(), }; await simulateSubmit(payload); form.reset(); }); // Initialize on DOMContentLoaded document.addEventListener('DOMContentLoaded', () => { injectPartials(); runEta(); initCookies(); });