1(() => { 2 const track = (event, parameters) => { 3 // Tracking must never turn an accepted enquiry into a visible send failure. 4 try { 5 if (typeof window.gtag === 'function') window.gtag('event', event, parameters); 6 } catch (_) {} 7 }; 8 document.addEventListener('click', event => { 9 const link = event.target.closest('a[href]'); 10 if (!link) return; 11 if (link.href.startsWith('mailto:')) track('email_click', {contact_method: 'email'}); 12 else if (new URL(link.href).hostname === 'www.linkedin.com') track('linkedin_click', {contact_method: 'linkedin'}); 13 }); 14 const form = document.getElementById('enquiry-form'); 15 if (!form) return; 16 const status = document.getElementById('enquiry-status'); 17 const button = form.querySelector('[type="submit"]'); 18 const endpoint = new URL(form.getAttribute('action'), location.href); 19 let token = '', submissionId = '', busy = false, complete = false; 20 async function prepare() { 21 const response = await fetch(endpoint, {cache: 'no-store', credentials: 'same-origin', headers: {Accept: 'application/json'}}); 22 const data = await response.json(); 23 if (!response.ok || !data.token) throw new Error('The form is unavailable. Please email [email protected].'); 24 token = data.token; 25 submissionId = data.submissionId; 26 } 27 const ready = prepare().catch(() => { status.textContent = 'The form is unavailable right now. You can email me directly above.'; }); 28 form.addEventListener('submit', async event => { 29 event.preventDefault(); 30 if (busy || complete || !form.reportValidity()) return; 31 busy = true; 32 button.disabled = true; 33 button.textContent = 'Sendingâ¦'; 34 status.textContent = ''; 35 form.setAttribute('aria-busy', 'true'); 36 try { 37 await ready; 38 if (!token) await prepare(); 39 const payload = new FormData(form); 40 payload.set('token', token); 41 payload.set('submission_id', submissionId); 42 const response = await fetch(endpoint, {method: 'POST', body: payload, credentials: 'same-origin', headers: {Accept: 'application/json'}}); 43 const data = await response.json(); 44 if (!response.ok || data.success !== true) { 45 if (response.status === 403) {token = ''; await prepare();} 46 throw new Error(data.message || 'Your enquiry could not be sent. Please try again or email me directly.'); 47 } 48 complete = true; 49 status.textContent = 'Thanksâyour enquiry is on its way. Iâll be in touch by email.'; 50 status.classList.add('enquiry-success'); 51 button.textContent = 'Enquiry sent â'; 52 form.querySelectorAll('input, select, textarea').forEach(field => {field.disabled = true;}); 53 // Both events belong only to a confirmed success, never load or click. 54 // No names, emails, company names or project text go to tracking. 55 track('generate_lead', {form_id: 'project_enquiry', lead_source: 'website_form'}); 56 track('ads_conversion_Submit_lead_form_1', {}); 57 } catch (error) { 58 status.textContent = error.message.includes('Failed to fetch') ? 'Your connection was interrupted. Please try again or email me directly.' : error.message; 59 button.disabled = false; 60 button.textContent = 'Send enquiry â'; 61 } finally { 62 busy = false; 63 form.removeAttribute('aria-busy'); 64 } 65 }); 66})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.