1// Lakestone AI - Contact analytics 2// Contact-page tracking built on the shared analytics runtime 3 4(function() { 5 const ContactAnalytics = { 6 viewedSections: new Set(), 7 8 init() { 9 if (document.body?.dataset.pageType !== 'contact' || typeof Analytics === 'undefined') { 10 return; 11 } 12 13 Analytics.setPageContext({ 14 page_type: 'contact', 15 page_section: 'contact-main', 16 journey_stage: 'action' 17 }); 18 Analytics.trackPageView(); 19 20 this.setupSectionTracking(); 21 this.setupClickTracking(); 22 this.setupFormSubmission(); 23 }, 24 25 setupSectionTracking() { 26 const sections = document.querySelectorAll('[data-analytics-section]'); 27 if (!sections.length) return; 28 29 const observer = new IntersectionObserver((entries) => { 30 entries.forEach((entry) => { 31 if (!entry.isIntersecting) return; 32 33 const sectionName = entry.target.dataset.analyticsSection; 34 if (!sectionName || this.viewedSections.has(sectionName)) { 35 observer.unobserve(entry.target); 36 return; 37 } 38 39 this.viewedSections.add(sectionName); 40 Analytics.trackSectionView(sectionName, { page_type: 'contact' }); 41 observer.unobserve(entry.target); 42 }); 43 }, { 44 threshold: 0.45, 45 rootMargin: '0px 0px -10% 0px' 46 }); 47 48 sections.forEach((section) => observer.observe(section)); 49 }, 50 51 setupClickTracking() { 52 document.addEventListener('click', (event) => { 53 const nav = event.target.closest('[data-analytics-nav]'); 54 if (nav) { 55 const destination = nav.getAttribute('href') || nav.dataset.navTarget || 'unknown'; 56 const navLocation = nav.dataset.analyticsNav || 'navigation'; 57 Analytics.trackNavigation(destination, { 58 link_text: nav.dataset.analyticsLabel || nav.textContent.trim(), 59 nav_location: navLocation, 60 page_section: navLocation, 61 page_type: 'contact' 62 }); 63 } 64 65 const cta = event.target.closest('[data-analytics-cta]'); 66 if (cta) { 67 const ctaText = cta.dataset.analyticsCta || cta.textContent.trim(); 68 const ctaLocation = cta.dataset.ctaLocation || Analytics.getSectionNameForElement(cta) || 'contact-main'; 69 70 Analytics.trackCTAClick(ctaText, ctaLocation, { 71 page_section: ctaLocation, 72 page_type: 'contact' 73 }); 74 } 75 76 const contactTarget = event.target.closest('[data-contact-method]'); 77 if (!contactTarget) return; 78 79 const method = contactTarget.dataset.contactMethod; 80 const sectionName = Analytics.getSectionNameForElement(contactTarget) || 'contact-main'; 81 const contactAction = contactTarget.dataset.contactAction || 'click'; 82 83 if (contactAction === 'copy') { 84 event.preventDefault(); 85 this.copyEmail(contactTarget, sectionName); 86 return; 87 } 88 89 Analytics.trackContactAction(method, { 90 page_section: sectionName, 91 page_type: 'contact' 92 }); 93 }); 94 }, 95 96 async copyEmail(element, sectionName) { 97 const emailAddress = '[email protected]'; 98 99 try { 100 if (navigator.clipboard?.writeText) { 101 await navigator.clipboard.writeText(emailAddress); 102 } else { 103 this.copyWithFallback(emailAddress); 104 } 105 106 Analytics.trackContactIntent('email', { 107 page_section: sectionName, 108 page_type: 'contact', 109 contact_action: 'copy' 110 }); 111 this.showCopyFeedback(element, 'Copied!'); 112 } catch (error) { 113 console.error('Email copy failed:', error); 114 this.showCopyFeedback(element, 'Copy failed'); 115 } 116 }, 117 118 copyWithFallback(value) { 119 const textArea = document.createElement('textarea'); 120 textArea.value = value; 121 textArea.setAttribute('readonly', ''); 122 textArea.style.position = 'absolute'; 123 textArea.style.left = '-9999px'; 124 document.body.appendChild(textArea); 125 textArea.select(); 126 document.execCommand('copy'); 127 document.body.removeChild(textArea); 128 }, 129
130 showCopyFeedback(element, message) { 131 const originalText = element.dataset.originalText || element.textContent; 132 element.dataset.originalText = originalText; 133 element.textContent = message; 134 element.style.color = message === 'Copied!' ? '#28a745' : '#dc3545'; 135 136 window.setTimeout(() => { 137 element.textContent = originalText; 138 element.style.color = ''; 139 }, 2000); 140 }, 141 142 setupFormSubmission() { 143 const form = document.getElementById('contactLeadForm'); 144 const submitBtn = document.getElementById('contactSubmit'); 145 if (!form || !submitBtn) return; 146 147 form.addEventListener('submit', async (event) => { 148 event.preventDefault(); 149 150 const originalText = submitBtn.textContent; 151 submitBtn.textContent = 'Sending...'; 152 submitBtn.disabled = true; 153 154 const formData = new FormData(); 155 formData.append('name', document.getElementById('contactName').value); 156 formData.append('email', document.getElementById('contactEmail').value); 157 formData.append('phone', document.getElementById('contactPhone').value); 158 formData.append('subject', 'Contact Page Enquiry'); 159 formData.append('message', document.getElementById('contactMessage').value); 160 161 try { 162 const response = await fetch(window.CONTACT_ENDPOINT || '/api/contact', { 163 method: 'POST', 164 body: formData, 165 headers: { 'X-Requested-With': 'XMLHttpRequest' } 166 }); 167 168 const data = JSON.parse(await response.text()); 169 170 if (!response.ok || !data.success) { 171 throw new Error(data.error || 'Failed to send message'); 172 } 173 174 submitBtn.textContent = 'Message Sent!'; 175 submitBtn.style.backgroundColor = '#28a745'; 176 form.reset(); 177 178 Analytics.trackFormSubmission('Contact Page Form', { 179 page_section: 'contact-main', 180 page_type: 'contact', 181 form_context: 'contact-page', 182 conversion_type: 'contact_form', 183 conversion_value: 25 184 }); 185 } catch (error) { 186 submitBtn.textContent = 'Error - Try Again'; 187 submitBtn.style.backgroundColor = '#dc3545'; 188 console.error('Contact form error:', error); 189 } finally { 190 window.setTimeout(() => { 191 submitBtn.textContent = originalText; 192 submitBtn.style.backgroundColor = ''; 193 submitBtn.disabled = false; 194 }, 3000); 195 } 196 }); 197 } 198 }; 199 200 window.ContactPageAnalytics = ContactAnalytics; 201 document.addEventListener('DOMContentLoaded', () => ContactAnalytics.init()); 202})();
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.