1/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 SECTION INTRO HEADER (W-090) â progressive-enhancement behavior. 3 4 The eyebrow / heading / text are static server-rendered structural content and 5 need NO JS. This file adds only the three interactive faces, each guarded so a 6 load failure degrades gracefully and a page with no intro is a pure no-op: 7 8 1. FILTERS â a toggle-button GROUP (role=group, aria-pressed) that dispatches 9 the lightweight `hzo:filter { value, target }` CustomEvent (bubbling from 10 the group root) to a REMOTE grid named by data-hzo-intro-filter. Radio-like 11 single-select. If the target selector matches NOTHING on the page the group 12 is disabled (a false affordance suppressed, not a dead control â G11). The 13 intro NEVER touches the grid's DOM (event-based decoupling); the grid owns 14 results + its own live-region announcement (LIVE-REGION-OUTSIDE-FRAGMENT). 15 16 2. TABS â the APG tablist a11y (roving tabindex, Arrow/Home/End, activation, 17 `hzo:tabchange`) is DELEGATED to the always-loaded tabs.js (the tablist is 18 wrapped in .hzo-tabs). tabs.js only toggles panels INSIDE its own root; a 19 section intro's tabpanels live REMOTELY (elsewhere on the page), so this 20 file syncs those panels' [hidden] on init + on every hzo:tabchange. 21 22 3. MEASUREMENT â a delegated tel: mirror (a `tel:` CTA tap â dataLayer 23 phone_call; desktop tel: no-ops silently, so the number is ALSO the 24 anchor's selectable link text), plus select_content on filter/tab change. 25 Bound once; a strict no-op when window.dataLayer is never created. 26 27 This file is idempotent (roots guarded by data-hzo-section-intro-ready), needs 28 no dependencies, and scripts NO motion (state changes are instant; the CSS 29 transitions die under prefers-reduced-motion). Exposed on the shared namespace 30 for AJAX-injected instances. 31 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 32(function () { 33 'use strict'; 34 35 window.hzo = window.hzo || {}; 36 37 function pushDataLayer(payload) { 38 window.dataLayer = window.dataLayer || []; 39 window.dataLayer.push(payload); 40 } 41 42 // ââ Filters ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 43 // Select a chip (radio-like), then dispatch hzo:filter to the remote grid. 44 function selectChip(group, chip) { 45 var chips = group.querySelectorAll('.hzo-section-intro__chip'); 46 for (var i = 0; i < chips.length; i++) { 47 var on = chips[i] === chip; 48 chips[i].setAttribute('aria-pressed', on ? 'true' : 'false'); 49 chips[i].classList.toggle('is-active', on); 50 } 51 var value = chip.getAttribute('data-hzo-intro-value') || ''; 52 var target = group.getAttribute('data-hzo-intro-filter') || ''; 53 54 // Emit the lightweight W-090 signal â the grid self-scopes by detail.target. 55 group.dispatchEvent(new CustomEvent('hzo:filter', { 56 bubbles: true, 57 detail: { value: value, target: target } 58 })); 59 60 // Measurement (§13): filter selection mirrors select_content. 61 pushDataLayer({ event: 'select_content', content_type: 'section_filter', item_id: value }); 62 } 63 64 function setupFilterGroup(group) { 65 if (group.getAttribute('data-hzo-intro-filter-ready') === 'true') { return; } 66 group.setAttribute('data-hzo-intro-filter-ready', 'true'); 67 68 // Verify the target exists â a selector that matches nothing is a false 69 // affordance: disable the group rather than ship a dead control (G11). 70 var target = group.getAttribute('data-hzo-intro-filter') || ''; 71 var exists = false;
72 try { exists = !!(target && document.querySelector(target)); } catch (e) { exists = false; } 73 if (!exists) { 74 group.setAttribute('aria-disabled', 'true'); 75 if (window.console && console.warn) { 76 console.warn('[hzo_section_intro] filter-target "' + target + '" matches no element on the page â chips disabled.'); 77 } 78 } 79 } 80 81 // ââ Tabs (remote-panel visibility; tabs.js owns the tablist a11y) âââââââââââ 82 function panelById(id) { 83 if (!id) { return null; } 84 return document.getElementById(id); 85 } 86 87 // Show the panel for the selected tab; hide the panels of its siblings. 88 function syncTabPanels(tablist, selectedControls) { 89 var tabs = tablist.querySelectorAll('[role="tab"]'); 90 for (var i = 0; i < tabs.length; i++) { 91 var controls = tabs[i].getAttribute('aria-controls'); 92 var panel = panelById(controls); 93 if (panel) { panel.hidden = (controls !== selectedControls); } 94 } 95 } 96 97 function currentSelected(tablist) { 98 var tabs = tablist.querySelectorAll('[role="tab"]'); 99 for (var i = 0; i < tabs.length; i++) { 100 if (tabs[i].getAttribute('aria-selected') === 'true') { 101 return tabs[i].getAttribute('aria-controls'); 102 } 103 } 104 return tabs.length ? tabs[0].getAttribute('aria-controls') : ''; 105 } 106 107 function setupTablist(tablist) { 108 if (tablist.getAttribute('data-hzo-intro-tabs-ready') === 'true') { return; } 109 tablist.setAttribute('data-hzo-intro-tabs-ready', 'true'); 110 // Initial remote-panel sync from the server-selected tab (tabs.js fires no 111 // event on its init pass, so we do the first sync ourselves). 112 syncTabPanels(tablist, currentSelected(tablist)); 113 } 114 115 // ââ Delegated document listeners (bound ONCE; dynamically-injected intros work 116 // with no re-init and double-binding is impossible) ââââââââââââââââââââââ 117 function bindDelegates() { 118 if (window.hzo.__introBound) { return; } 119 window.hzo.__introBound = true; 120 121 // Filter chip tap. 122 document.addEventListener('click', function (e) { 123 var chip = e.target && e.target.closest 124 ? e.target.closest('.hzo-section-intro__actions--filters .hzo-section-intro__chip') 125 : null; 126 if (!chip) { return; } 127 var group = chip.closest('[data-hzo-intro-filter]'); 128 if (!group || group.getAttribute('aria-disabled') === 'true') { return; } 129 selectChip(group, chip); 130 }); 131 132 // tel: CTA tap â dataLayer phone_call (measurement only; the number is also 133 // the anchor's selectable text, so this is not the desktop fallback). 134 document.addEventListener('click', function (e) { 135 var a = e.target && e.target.closest 136 ? e.target.closest('[data-hzo-section-intro] a[href^="tel:"]') 137 : null; 138 if (!a) { return; } 139 pushDataLayer({ 140 event: 'phone_call', 141 phone_number: (a.getAttribute('href') || '').replace(/^tel:/, ''), 142 source: 'section_intro' 143 }); 144 }); 145 146 // hzo:tabchange (delegated from tabs.js's .hzo-tabs root) â sync remote 147 // panels + mirror select_content. Only handle intros (our tablist inside). 148 document.addEventListener('hzo:tabchange', function (e) { 149 var root = e.target; 150 if (!root || !root.querySelector) { return; } 151 var tablist = root.querySelector('.hzo-section-intro__actions--tabs'); 152 if (!tablist) { return; } 153 var id = e.detail && e.detail.id ? e.detail.id : ''; 154 syncTabPanels(tablist, id); 155 pushDataLayer({ event: 'select_content', content_type: 'section_tab', item_id: id }); 156 }); 157 } 158 159 // ââ Init (idempotent) âââââââââââââââââââââââââââââââââââââââââââââââââââââââ 160 function setup(root) { 161 if (!root || root.getAttribute('data-hzo-section-intro-ready') === 'true') { return; } 162 root.setAttribute('data-hzo-section-intro-ready', 'true'); 163 164 var groups = root.querySelectorAll('[data-hzo-intro-filter]'); 165 for (var g = 0; g < groups.length; g++) { setupFilterGroup(groups[g]); } 166 167 var tablists = root.querySelectorAll('.hzo-section-intro__actions--tabs'); 168 for (var t = 0; t < tablists.length; t++) { setupTablist(tablists[t]); } 169 } 170 171 function init(scope) { 172 bindDelegates(); 173 var roots = (scope || document).querySelectorAll('[data-hzo-section-intro]:not
173([data-hzo-section-intro-ready])'); 174 for (var i = 0; i < roots.length; i++) { setup(roots[i]); } 175 } 176 177 // Shared engine namespace (never a new top-level global). 178 window.hzo.sectionIntro = { init: init, setup: setup }; 179 180 if (document.readyState === 'loading') { 181 document.addEventListener('DOMContentLoaded', function () { init(); }); 182 } else { 183 init(); 184 } 185})();
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.