PageSourceSearch

https://lakestone.ai/assets/js/contact-analytics.js?v=2

js lakestone.ai collected 2026-10-05 13:34:03 UTC 6,841 bytes, 202 lines download raw bytes

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.