1(() => { 2 const button = document.querySelector('[data-site-menu-button]'); 3 const menu = document.querySelector('[data-site-menu]'); 4 5 if (button && menu) { 6 const closeMenu = () => { 7 button.setAttribute('aria-expanded', 'false'); 8 menu.dataset.open = 'false'; 9 document.body.classList.remove('site-menu-open'); 10 }; 11 12 button.addEventListener('click', () => { 13 const open = button.getAttribute('aria-expanded') !== 'true'; 14 button.setAttribute('aria-expanded', String(open)); 15 menu.dataset.open = String(open); 16 document.body.classList.toggle('site-menu-open', open); 17 }); 18 19 menu.addEventListener('click', (event) => { 20 if (event.target.closest('a')) closeMenu(); 21 }); 22 23 document.addEventListener('keydown', (event) => { 24 if (event.key === 'Escape' && button.getAttribute('aria-expanded') === 'true') { 25 closeMenu(); 26 button.focus(); 27 } 28 }); 29 } 30 31 document.querySelectorAll('details[data-faq]').forEach((details) => { 32 const summary = details.querySelector('summary'); 33 if (summary) summary.setAttribute('aria-expanded', String(details.open)); 34 details.addEventListener('toggle', () => { 35 const summary = details.querySelector('summary'); 36 if (summary) summary.setAttribute('aria-expanded', String(details.open)); 37 }); 38 }); 39 40 const prefillKey = 'ghc:offer-prefill:v1'; 41 const homepageForm = document.querySelector('.home-form'); 42 const homepageProperty = document.querySelector('#home-address'); 43 const homepageError = document.querySelector('#home-address-error'); 44 45 const showHomepageError = () => { 46 if (!homepageProperty || !homepageError) return; 47 homepageProperty.setAttribute('aria-invalid', 'true'); 48 homepageError.hidden = false; 49 }; 50 51 if (homepageForm && homepageProperty && homepageError) { 52 homepageProperty.addEventListener('invalid', showHomepageError); 53 homepageProperty.addEventListener('input', () => { 54 if (!homepageProperty.value.trim()) return; 55 homepageProperty.removeAttribute('aria-invalid'); 56 homepageError.hidden = true; 57 }); 58 homepageForm.addEventListener('submit', (event) => { 59 const value = homepageProperty.value.trim().slice(0, 200); 60 if (!value) { 61 event.preventDefault(); 62 showHomepageError(); 63 homepageProperty.focus(); 64 return; 65 } 66 try { 67 sessionStorage.setItem(prefillKey, value); 68 } catch { 69 return; 70 } 71 event.preventDefault(); 72 location.assign('/offer'); 73 }); 74 } 75 76 const offerPrefill = window.__ghcOfferPrefill; 77 delete window.__ghcOfferPrefill; 78 if (typeof offerPrefill === 'string') { 79 const target = /^\d{5}(?:-\d{4})?$/.test(offerPrefill) 80 ? document.querySelector('#zip') 81 : document.querySelector('#address_1'); 82 if (target) { 83 target.value = offerPrefill; 84 target.dispatchEvent(new Event('input', { bubbles: true })); 85 target.dispatchEvent(new Event('change', { bubbles: true })); 86 } 87 } 88 89 const analytics = window.dataLayer = window.dataLayer || []; 90 const canonicalUrl = document.querySelector('link[rel="canonical"]')?.href || `${location.origin}${location.pathname}`; 91 const pageType = document.querySelector('.about-page-main') ? 'about' : document.querySelector('.blog-index-main') ? 'blog_index' : document.querySelector('.guide-article-main, .blog-post') ? 'article' : 'marketing'; 92 const track = (event, fields = {}) => analytics.push({ event, ...fields }); 93 94 track('ghc_page_view', { 95 page_type: pageType, 96 page_template: document.body.className || 'default', 97 canonical_url: canonicalUrl, 98 }); 99 100 const progressSent = new Set(); 101 const trackProgress = () => { 102 const pageHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); 103 const available = pageHeight - innerHeight; 104 const position = Math.max(scrollY, document.documentElement.scrollTop, document.body.scrollTop); 105 const progress = available > 0 ? Math.round((position / available) * 100) : 0; 106 [25, 50, 75, 100].forEach((milestone) => { 107 if (progress >= milestone && !progressSent.has(milestone)) { 108 progressSent.add(milestone); 109 track('ghc_content_progress', { percent: milestone, page_type: pageType }); 110 } 111 }); 112 }; 113 addEventListener('scroll', trackProgress, { passive: true }); 114 document.body.addEventListener('scroll', trackProgress, { passive: true }); 115 trackProgress(); 116 117 document.addEventListener('click', (event) => { 118 const link = event.target.closest('a[href]'); 119 if (!link) return; 120 const destination = new URL(link.href, location.href); 121 const fields = { 122 link_text: link.textContent.trim().slice(0, 100), 123 destination: `${destination.origin}${destination.pathname}`, 124 module: link.closest('[data-analytics-module]')?.dataset.analyticsModule || link.closest('header,main,footer')?.tagName.toLowerCase() || 'unknown', 125 }; 126 if (destination.origin === location.origin) track('ghc_internal_link_click', fields); 127 if (destination.protocol === 'tel:') track('phone_click', { module: fields.module }); 128 if (destination.protocol === 'mailto:') track('email_click', { module: fields.module }); 129 if (link.matches('[data-cta-position]')) { 130 track('ghc_cta_click', { 131 cta_text: fields.link_text, 132 destination: fields.destination, 133 position: link.dataset.ctaPosition, 134 }); 135 } 136 }); 137 138 document.querySelectorAll('form').forEach((form) => { 139 let started = false;
140 form.addEventListener('focusin', () => { 141 if (started) return; 142 started = true; 143 track('ghc_form_start', { form_id: form.id || form.getAttribute('name') || 'unnamed' }); 144 }); 145 form.addEventListener('invalid', (event) => { 146 track('ghc_form_error', { 147 form_id: form.id || form.getAttribute('name') || 'unnamed', 148 field_name: event.target.name || event.target.id || 'unnamed', 149 error_type: event.target.validity.valueMissing ? 'required' : event.target.validity.typeMismatch ? 'type' : 'invalid', 150 }); 151 }, true); 152 }); 153})();
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.