class SiteFooterLists { constructor(root = document) { this.root = root; this.selector = '.SiteFooterList__trigger'; this.mq = window.matchMedia('(min-width: 768px)'); this.handleClick = this.handleClick.bind(this); this.handleKeydown = this.handleKeydown.bind(this); this.handleBreakpointChange = this.handleBreakpointChange.bind(this); this.init(); } init() { this.root.addEventListener('click', this.handleClick); this.root.addEventListener('keydown', this.handleKeydown); this.mq.addEventListener('change', this.handleBreakpointChange); this.applyState(); } isDesktop() { return this.mq.matches; } handleBreakpointChange() { this.applyState(); } applyState() { const triggers = this.root.querySelectorAll(this.selector); triggers.forEach(trigger => { const panelId = trigger.getAttribute('aria-controls'); if (!panelId) return; const panel = document.getElementById(panelId); if (!panel) return; const icon = trigger.querySelector('.SiteFooterList__Icon i'); if (this.isDesktop()) { // ALWAYS override DOM defaults trigger.setAttribute('aria-expanded', 'true'); trigger.setAttribute('aria-disabled', 'true'); panel.removeAttribute('hidden'); if (icon) { icon.classList.remove('icon-circle-chevron-down'); icon.classList.add('icon-circle-chevron-up'); const use = icon.querySelector('use'); if (use) { use.setAttribute('xlink:href', '#icon-circle-chevron-up'); } } } else { trigger.setAttribute('aria-expanded', 'false'); trigger.removeAttribute('aria-disabled'); panel.setAttribute('hidden', ''); if (icon) { icon.classList.remove('icon-circle-chevron-up'); icon.classList.add('icon-circle-chevron-down'); const use = icon.querySelector('use'); if (use) { use.setAttribute('xlink:href', '#icon-circle-chevron-down'); } } } }); } handleClick(e) { if (this.isDesktop()) return; const trigger = e.target.closest(this.selector); if (!trigger) return; this.toggle(trigger); } handleKeydown(e) { if (this.isDesktop()) return; if (e.key !== 'Enter' && e.key !== ' ') return; const trigger = e.target.closest(this.selector); if (!trigger) return; e.preventDefault(); this.toggle(trigger); } toggle(trigger) { const panelId = trigger.getAttribute('aria-controls'); if (!panelId) return; const panel = document.getElementById(panelId); if (!panel) return; const icon = trigger.querySelector('.SiteFooterList__Icon i'); const isExpanded = trigger.getAttribute('aria-expanded') === 'true'; const nextState = !isExpanded; trigger.setAttribute('aria-expanded', String(nextState)); if (nextState) { panel.hidden = false; if (icon) { icon.classList.remove('icon-circle-chevron-down'); icon.classList.add('icon-circle-chevron-up'); const use = icon.querySelector('use'); if (use) { use.setAttribute('xlink:href', '#icon-circle-chevron-up'); } } } else { panel.hidden = true; if (icon) { icon.classList.remove('icon-circle-chevron-up'); icon.classList.add('icon-circle-chevron-down'); const use = icon.querySelector('use'); if (use) { use.setAttribute('xlink:href', '#icon-circle-chevron-down'); } } } } } new SiteFooterLists(); class ResolveFooterOverlaps { constructor(options = {}) { this.footerId = options.footerId || 'SiteFooter'; this.targets = options.targets || []; this.footerEl = null; this.items = []; this.ticking = false; this.onScroll = this.onScroll.bind(this); this.onResize = this.onResize.bind(this); } init() { this.footerEl = document.getElementById(this.footerId); if (!this.footerEl) return; this.collectTargets(); if (this.items.length === 0) return; this.injectStyles(); this.bindEvents(); this.update(); } collectTargets() { this.items = this.targets.reduce((acc, t, i) => { if (!t || !t.selector) return acc; const el = document.querySelector(t.selector); if (!el) return acc; const id = `rfo-${i}`; el.dataset.rfoId = id; acc.push({ el, mode: t.mode === 'offset' ? 'offset' : 'hide', offset: Number.isFinite(t.offset) ? t.offset : 16, className: `rfo-hidden-${id}` }); return acc; }, []); } injectStyles() { const styleId = 'resolve-footer-overlaps'; if (document.getElementById(styleId)) return; const css = this.items .map(item => `[data-rfo-id="${item.el.dataset.rfoId}"].${item.className}{display:none!important;}`) .join(''); if (!css) return; const style = document.createElement('style'); style.id = styleId; style.textContent = css; document.head.appendChild(style); } bindEvents() { window.addEventListener('scroll', this.onScroll, { passive: true }); window.addEventListener('resize', this.onResize); } onScroll() { this.requestTick(); } onResize() { this.requestTick(); } requestTick() { if (this.ticking) return; this.ticking = true; requestAnimationFrame(() => { this.update(); this.ticking = false; }); } update() { if (!this.footerEl || this.items.length === 0) return; const footerRect = this.footerEl.getBoundingClientRect(); if (!footerRect) return; const viewportHeight = window.innerHeight; const isOverlapping = footerRect.top < viewportHeight; this.items.forEach(item => { if (!item || !item.el) return; if (item.mode === 'hide') { item.el.classList.toggle(item.className, isOverlapping); return; } if (item.mode === 'offset') { const bottom = isOverlapping ? (viewportHeight - footerRect.top) + item.offset : item.offset; item.el.style.setProperty('bottom', bottom + 'px', 'important'); } }); } } (function () { const init = () => { const instance = new ResolveFooterOverlaps({ targets: [ { selector: '.jwPlayer--floatingContainer', mode: 'hide' }, { selector: '.social-share-sticky-menu', mode: 'hide' } ] }); if (!instance.targets.length) return; instance.init(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();; /* eslint-disable no-undef */ class GetManager { static PUBLISHER_ALIASES = new Map([ ['arnnet', ['arn']], ['channelasia', ['ca']], ['reseller', ['rsl']], ]); static VALID_PUBLISHERS = new Set([ 'nww', 'iw', 'cw', 'arnnet', 'reseller', 'cso', 'cio', 'channelasia', ]); static EDITION_ALIASES = new Map([ ['uk', ['gb']], ['middle-east', ['me']], ['asean', ['as']], ['africa', ['af']], ]); static VALID_EDITIONS = new Set([ 'africa', 'asean', 'au', 'ca', 'es', 'ie', 'in', 'it', 'jp', 'kr', 'middle-east', 'nl', 'nz', 'uk', 'us', 'de', 'fr', 'pl', 'se', ]); constructor() { window.foundry_is_language = GetManager.foundry_is_language; window.foundry_is_publisher = GetManager.foundry_is_publisher; window.foundry_is_edition = GetManager.foundry_is_edition; window.foundry_get_publisher = GetManager.foundry_get_publisher; window.foundry_get_site = GetManager.foundry_get_site; } static getCanonicalPublisher(name) { if (!name) return null; const lowerName = name.toLowerCase(); const found = Array.from(GetManager.PUBLISHER_ALIASES.entries()).find( ([canonical, aliases]) => canonical === lowerName || aliases.includes(lowerName), ); if (found) return found[0]; return GetManager.VALID_PUBLISHERS.has(lowerName) ? lowerName : null; } static getDocumentLanguage() { const langAttr = document.documentElement.getAttribute('lang') || ''; return langAttr.split('-')[0].toLowerCase(); // Extracts the language without the region (e.g., "en" from "en-US") } static getDocumentEdition() { const editionAttr = document.documentElement.getAttribute('data-edition') || ''; return editionAttr.toLowerCase(); } static getCanonicalEdition(name) { if (!name) return null; const lowerName = name.toLowerCase(); const found = Array.from(GetManager.EDITION_ALIASES.entries()).find( ([canonical, aliases]) => canonical === lowerName || aliases.includes(lowerName), ); if (found) return found[0]; return GetManager.VALID_EDITIONS.has(lowerName) ? lowerName : null; } static foundry_is_publisher(publisherNames) { const brandAttr = document.documentElement.getAttribute('data-brand') || ''; if (!brandAttr) return false; const canonicalBrand = brandAttr.toLowerCase(); if (Array.isArray(publisherNames)) { return publisherNames.some(name => GetManager.getCanonicalPublisher(name) === canonicalBrand); } return GetManager.getCanonicalPublisher(publisherNames) === canonicalBrand; } static foundry_is_language(languages) { const currentLang = GetManager.getDocumentLanguage(); if (!currentLang) return false; const languageList = Array.isArray(languages) ? languages.map(lang => lang.toLowerCase()) : languages.split(',').map(lang => lang.trim().toLowerCase()); return languageList.includes(currentLang); } static foundry_is_edition(editionNames) { const currentEdition = GetManager.getDocumentEdition(); if (!currentEdition) return false; if (Array.isArray(editionNames)) { return editionNames.some(name => GetManager.getCanonicalEdition(name) === currentEdition); } return GetManager.getCanonicalEdition(editionNames) === currentEdition; } static foundry_get_publisher() { const brandAttr = document.documentElement.getAttribute('data-brand') || ''; if (!brandAttr) return null; return brandAttr.trim().toLowerCase(); } static foundry_get_site() { return (window.siteData && window.siteData.site) ? window.siteData.site : ''; } } new GetManager(); // eslint-disable-line no-new; function ToggleContentVisibilityParagraph() { const elements = document.querySelectorAll('[data-readmore-length]'); if (elements.length === 0) return; elements.forEach((element, index) => { const uniqueId = `readmore-${index}`; const modifiedElement = element; modifiedElement.id = uniqueId; const readMoreText = element.getAttribute('data-readmore-txt') || 'Read more'; const hasReadLess = element.hasAttribute('data-readless'); const readLessText = hasReadLess ? element.getAttribute('data-readless-txt') || readMoreText : ''; const readMoreButton = document.createElement('button'); readMoreButton.textContent = readMoreText; readMoreButton.classList.add('read-more-toggle', 'reset-button'); readMoreButton.setAttribute('aria-controls', uniqueId); readMoreButton.setAttribute('aria-expanded', 'false'); element.insertAdjacentElement('afterend', readMoreButton); readMoreButton.addEventListener('click', function () { const isExpanded = readMoreButton.getAttribute('aria-expanded') === 'true'; readMoreButton.setAttribute('aria-expanded', !isExpanded); element.setAttribute('data-expanded', !isExpanded); if (!hasReadLess) { readMoreButton.remove(); } if (isExpanded) { readMoreButton.textContent = readMoreText; } else { readMoreButton.textContent = readLessText; } }); }); } document.addEventListener('DOMContentLoaded', ToggleContentVisibilityParagraph); ; class PrimaryNavDropdown { static init(selector = ".PrimaryNav") { document.querySelectorAll(selector).forEach((root) => { if (root._primaryNavDropdown) return; if (!root.querySelector('[data-nav-item="parent"]')) return; const list = root.querySelector(".PrimaryNav__list"); if (!list || !list.getClientRects().length) return; root._primaryNavDropdown = new PrimaryNavDropdown(root); }); } constructor(root) { this.root = root; this.header = root.closest("#SiteHeader") || root; this.fadeBound = new WeakSet(); this.items = Array.from( root.querySelectorAll('[data-nav-item="parent"]') ) .map((item) => this._hydrateItem(item)) .filter(Boolean); this.activeItem = null; this._buildHorizontalRail(); this._bindItems(); this._bindGlobal(); } _buildHorizontalRail() { const elements = this.header.querySelectorAll( 'a[href], button:not([disabled])' ); this.horizontal = Array.from(elements); this.horizontalIndex = new Map( this.horizontal.map((el, i) => [el, i]) ); this.horizontalSet = new Set(this.horizontal); } _hydrateItem(item) { const toggle = item.querySelector("[data-nav-toggle]"); const link = item.querySelector("[data-nav-link]"); if (!toggle) return null; const key = toggle.dataset.navToggle; const dropdown = item.querySelector( `[data-nav-dropdown="${key}"]` ); if (!dropdown) return null; const icon = toggle.querySelector("i"); const use = icon?.querySelector("use"); const submenuLinks = Array.from( dropdown.querySelectorAll(".PrimaryNav__sublink") ); submenuLinks.forEach(a => a.tabIndex = -1); return { item, toggle, link, dropdown, icon, use, submenuLinks }; } _bindItems() { this.items.forEach((entry) => { const { item, toggle, link, submenuLinks } = entry; const open = () => this._open(entry); const close = () => this._close(entry); toggle.addEventListener("click", (e) => { e.preventDefault(); this.activeItem === entry ? close() : open(); }); [toggle, link].forEach((el) => { if (!el) return; el.addEventListener("keydown", (e) => { switch (e.key) { case " ": e.preventDefault(); this.activeItem === entry ? close() : open(); break; case "ArrowDown": e.preventDefault(); open(); submenuLinks[0]?.focus(); break; } }); el.addEventListener("pointerenter", open); }); item.addEventListener("pointerleave", close); item.addEventListener("focusout", (e) => { if (!item.contains(e.relatedTarget)) close(); }); submenuLinks.forEach((subLink, index) => { subLink.addEventListener("keydown", (e) => { switch (e.key) { case "ArrowDown": e.preventDefault(); submenuLinks[index + 1]?.focus(); break; case "ArrowUp": e.preventDefault(); index === 0 ? toggle.focus() : submenuLinks[index - 1]?.focus(); break; } }); }); }); } _bindGlobal() { this.header.addEventListener("keydown", (e) => { const target = e.target; if (!this.horizontalSet.has(target)) return; if (e.key === "ArrowRight") { e.preventDefault(); this._focusHorizontal(target, 1); } if (e.key === "ArrowLeft") { e.preventDefault(); this._focusHorizontal(target, -1); } if (e.key === "Escape" && this.activeItem) { const { toggle } = this.activeItem; this._close(this.activeItem); toggle.focus(); } }); document.addEventListener("pointerdown", (e) => { if (this.activeItem && !this.header.contains(e.target)) { this._close(this.activeItem); } }); } _bindScrollFade(entry) { const { dropdown, submenuLinks } = entry; if (submenuLinks.length <= 5) { dropdown.classList.remove("has-fade"); return; } const update = () => { const progress = dropdown.scrollTop / (dropdown.scrollHeight - dropdown.clientHeight); const passedThreshold = progress >= 0.6; dropdown.classList.toggle("has-fade", !passedThreshold); }; if (!this.fadeBound.has(dropdown)) { dropdown.addEventListener("scroll", update); this.fadeBound.add(dropdown); } update(); } _focusHorizontal(current, delta) { const i = this.horizontalIndex.get(current); if (i === undefined) return; let nextIndex = i + delta; if (nextIndex < 0) nextIndex = this.horizontal.length - 1; if (nextIndex >= this.horizontal.length) nextIndex = 0; this.horizontal[nextIndex]?.focus(); } _open(entry) { const html = document.documentElement; // if html has data-lightbox-open="topics", return if (html.dataset.lightboxOpen === "topics") { return; } if (html.dataset.sitenavState === "open" && html.dataset.lightboxOpen !== "topics") { return; } if (this.activeItem && this.activeItem !== entry) { this._close(this.activeItem); } const { toggle, dropdown, submenuLinks, icon, use } = entry; toggle.setAttribute("aria-expanded", "true"); dropdown.setAttribute("aria-hidden", "false"); toggle.classList.add("is-open"); requestAnimationFrame(() => { dropdown.scrollTop = 0; }); if (icon) { icon.classList.remove("icon-header-angle-down"); icon.classList.add("icon-header-angle-up"); } if (use) { use.setAttribute("xlink:href", "#icon-header-angle-up"); } submenuLinks.forEach(a => a.tabIndex = 0); this._bindScrollFade(entry); this.activeItem = entry; } _close(entry) { const { toggle, dropdown, submenuLinks, icon, use } = entry; toggle.setAttribute("aria-expanded", "false"); dropdown.setAttribute("aria-hidden", "true"); toggle.classList.remove("is-open"); dropdown.scrollTop = 0; if (icon) { icon.classList.remove("icon-header-angle-up"); icon.classList.add("icon-header-angle-down"); } if (use) { use.setAttribute("xlink:href", "#icon-header-angle-down"); } submenuLinks.forEach(a => a.tabIndex = -1); if (this.activeItem === entry) { this.activeItem = null; } } } class PrimaryNavPersistent { constructor({ primarySelector = '#SiteHeader', persistentSelector = '#SiteHeaderPersistent', offset = 300, hideDelta = 30, recalcLimit = 500 } = {}) { this.primary = document.querySelector(primarySelector); if (!this.primary) return; this.persistent = this.resolvePersistent(persistentSelector); if (!this.persistent) return; this.offset = offset; this.hideDelta = hideDelta; this.recalcLimit = recalcLimit; this.lastScrollY = window.scrollY || 0; this.threshold = 0; this.thresholdLocked = false; this.ticking = false; this.resizeTicking = false; this.visible = false; this.hideStartY = null; this.init(); this.bind(); } resolvePersistent(selector) { let existing = document.querySelector(selector); if (existing) return existing; const clone = this.primary.cloneNode(true); clone.id = selector.replace('#', ''); clone.classList.add('is-persistent'); const list = clone.querySelector('.PrimaryNav__list'); if (list) { // remove all children list.innerHTML = ''; // look for subscribe button inside TrendingBar const trending = document.querySelector('.TrendingBar'); const subscribe = trending ? trending.querySelector('.SubscribeLinkBtn') : null; if (subscribe) { const li = document.createElement('li'); li.className = 'PrimaryNav__item PrimaryNav__item--subscribe'; li.appendChild(subscribe.cloneNode(true)); list.appendChild(li); } } if (!clone.parentNode) { this.primary.insertAdjacentElement('afterend', clone); } return clone; } isStickyAdActive() { const ad = document.querySelector('.advert-sticky'); if (!ad) return false; const rect = ad.getBoundingClientRect(); // ad is occupying top of viewport return rect.top <= 0 && rect.bottom > 0; } calculateThreshold() { this.threshold = this.primary.offsetTop + this.primary.offsetHeight + this.offset; } init() { if (!this.primary || !this.persistent) return; this.calculateThreshold(); this.thresholdLocked = false; this.persistent.classList.remove('is-visible'); this.visible = false; } bind() { window.addEventListener('scroll', () => this.onScroll(), { passive: true }); window.addEventListener('resize', () => this.onResize()); } onResize() { if (this.resizeTicking) return; this.resizeTicking = true; requestAnimationFrame(() => { this.init(); this.resizeTicking = false; }); } onScroll() { if (this.ticking) return; this.ticking = true; requestAnimationFrame(() => { this.update(); this.ticking = false; }); } update() { const currentY = Math.max(0, window.scrollY || window.pageYOffset); if (this.isStickyAdActive()) { if (this.visible) this.hide(); } if (!this.thresholdLocked) { this.calculateThreshold(); if (currentY > this.recalcLimit) { this.thresholdLocked = true; } } const delta = currentY - this.lastScrollY; const isScrollingUp = delta < -1; const isScrollingDown = delta > 1; if (currentY <= this.threshold) { if (this.visible) this.hide(); this.lastScrollY = currentY <= 0 ? 0 : currentY; return; } if (!this.visible) { if (isScrollingUp) { this.show(); } } else { if (isScrollingDown) { if (this.hideStartY === null) { this.hideStartY = currentY; } if (currentY - this.hideStartY >= this.hideDelta) { this.hide(); } } else { this.hideStartY = null; } } this.lastScrollY = currentY <= 0 ? 0 : currentY; } show() { if (!this.persistent) return; // block if sticky ad is active if (this.isStickyAdActive()) return; this.persistent.style.top = '0px'; this.persistent.classList.add('is-visible'); this.visible = true; this.hideStartY = null; } hide() { if (!this.persistent) return; this.persistent.classList.remove('is-visible'); this.visible = false; this.hideStartY = null; this.persistent.style.top = '0px'; } } class MenuAsideController { constructor() { this.html = document.documentElement; this.aside = document.getElementById('MenuAside'); this.overlay = document.getElementById('MenuOverlay'); if (!this.aside || !this.overlay) return; this.closeButtons = document.querySelectorAll('.MenuAside__close'); this.openButtons = document.querySelectorAll('[data-menu-open]'); this.sections = [...this.aside.querySelectorAll('[data-menu-section]')]; this.focusable = []; this.activeTrigger = null; this.init(); this.bind(); } init() { this.html.dataset.sitenavState = 'closed'; this.aside.setAttribute('aria-hidden', 'true'); this.overlay.setAttribute('aria-hidden', 'true'); this.hideSections(); this.sections.forEach(section => { section.setAttribute('inert', ''); this.setTabIndex(section, -1); }); } setTabIndex(section, value) { const elements = section.querySelectorAll( 'a[href], button:not(.MenuGroup__notoggle), textarea, input, select, [tabindex]' ); elements.forEach(el => { el.setAttribute('tabindex', value); }); } bind() { this.openButtons.forEach(btn => { btn.addEventListener('click', () => { this.toggle(btn); }); }); this.closeButtons.forEach(btn => { btn.addEventListener('click', () => this.close()); }); this.overlay.addEventListener('click', () => this.close()); document.addEventListener('keydown', e => { if (e.key === 'Escape' && this.html.dataset.sitenavState === 'open') { this.close(); } if (e.key === 'Tab' && this.html.dataset.sitenavState === 'open') { this.trapFocus(e); } }); /* HARD FOCUS LOCK */ document.addEventListener('focusin', e => { if (this.html.dataset.sitenavState !== 'open') return; if (!this.aside.contains(e.target)) { const first = this.focusable[0] || this.aside; first.focus({ preventScroll: true }); } }); } toggle(trigger) { const name = trigger.dataset.menuOpen; const target = this.sections.find( section => section.dataset.menuSection === name ); if (!target) return; const isOpen = this.html.dataset.sitenavState === 'open'; const current = this.aside.dataset.sectionCurrent; if (!isOpen) { this.open(trigger, target, name); return; } if (current === name) { this.close(); return; } this.open(trigger, target, name); } open(trigger, target, name) { document.dispatchEvent(new CustomEvent('sitenav:aside-open')); this.activeTrigger = trigger; const previous = this.aside.dataset.sectionCurrent || 'default'; if (name === 'default') { this.aside.dataset.backBtn = 'false'; } else if (name === 'topics') { this.aside.dataset.backBtn = 'true'; } else if ( trigger.classList.contains('MenuAside__trigger') && previous === 'default' ) { this.aside.dataset.backBtn = 'true'; } if (trigger.classList.contains('HeaderActions__trigger')) { this.aside.dataset.backBtn = 'false'; } this.hideSections(); this.sections.forEach(section => { section.setAttribute('inert', ''); this.setTabIndex(section, -1); }); target.removeAttribute('inert'); target.setAttribute('aria-hidden', 'false'); this.setTabIndex(target, 0); target.querySelectorAll('.MenuGroup__notoggle') .forEach(el => el.setAttribute('tabindex', '-1')); target.classList.remove('is-visible'); requestAnimationFrame(() => { target.classList.add('is-visible'); }); this.aside.classList.add('is-open'); this.overlay.classList.add('is-open'); this.aside.setAttribute('aria-hidden', 'false'); this.overlay.setAttribute('aria-hidden', 'false'); this.html.dataset.sitenavState = 'open'; this.html.dataset.sitenavSection = name; this.aside.dataset.sectionCurrent = name; document.getElementById('SiteHeader')?.setAttribute('inert', ''); this.syncTriggers(true, name); if (!target.hasAttribute('tabindex')) { target.setAttribute('tabindex', '-1'); } this.buildFocusable(); target.focus({ preventScroll: true }); } close() { this.hideSections(); this.sections.forEach(section => { section.setAttribute('inert', ''); this.setTabIndex(section, -1); }); this.aside.classList.remove('is-open'); this.aside.dataset.backBtn = 'false'; this.overlay.classList.remove('is-open'); this.aside.setAttribute('aria-hidden', 'true'); this.overlay.setAttribute('aria-hidden', 'true'); this.html.dataset.sitenavState = 'closed'; delete this.html.dataset.sitenavSection; delete this.aside.dataset.sectionCurrent; document.getElementById('SiteHeader')?.removeAttribute('inert'); this.syncTriggers(false); this.focusable = []; this.activeTrigger = null; } hideSections() { this.sections.forEach(section => { section.setAttribute('aria-hidden', 'true'); section.classList.remove('is-visible'); }); } buildFocusable() { this.focusable = Array.from( this.aside.querySelectorAll( 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' ) ).filter(el => el.getClientRects().length); } trapFocus(e) { const focusable = this.focusable; if (!focusable.length) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; const active = document.activeElement; if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); } if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); } } syncTriggers(expanded, active = null) { this.openButtons.forEach(btn => { if (!expanded) { btn.setAttribute('aria-expanded', 'false'); return; } btn.setAttribute( 'aria-expanded', btn.dataset.menuOpen === active ? 'true' : 'false' ); }); } } class MenuGroupController { constructor(root) { this.root = root; this.nav = root.querySelector('.MenuAside__nav'); this.groups = [...root.querySelectorAll('.MenuGroup')]; this.toggles = [...root.querySelectorAll('.MenuGroup__toggle')]; this.links = [...root.querySelectorAll('.MenuGroup__link')]; this.topLevelItems = this.nav ? [...this.nav.querySelectorAll(':scope > .MenuGroup > a, :scope > .MenuGroup > button, :scope > button')] : []; this.panels = new Map(); this.icons = new Map(); this.toggles.forEach(toggle => { const panelId = toggle.getAttribute('aria-controls'); const panel = document.getElementById(panelId); if (panel) { this.panels.set(toggle, panel); if (!panel.hasAttribute('tabindex')) { panel.setAttribute('tabindex', '-1'); } } const icon = toggle.querySelector('.MenuGroup__icon i'); const use = icon?.querySelector('use'); this.icons.set(toggle, { icon, use }); }); this.init(); this.bind(); } init() { const isEditions = this.root.dataset.menuSection === 'editions'; this.toggles.forEach(toggle => { const group = toggle.closest('.MenuGroup'); const panel = this.panels.get(toggle); if (!group || !panel) return; const isActive = isEditions || group.classList.contains('is-active'); toggle.setAttribute('aria-expanded', isActive ? 'true' : 'false'); panel.setAttribute('aria-hidden', isActive ? 'false' : 'true'); if (isActive) { this.enableFocus(panel); } else { this.disableFocus(panel); } }); this.root.querySelectorAll('.MenuGroup__notoggle').forEach(el => el.setAttribute('tabindex', '-1')); } updateAsideFocusables() { const aside = this.root.closest('#MenuAside'); const controller = aside?._controller; if (controller) controller.buildFocusable(); } getFocusable() { return [...this.root.querySelectorAll( 'a[href], button:not(.MenuGroup__notoggle):not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])' )] .filter(el => !el.closest('[inert]') && el.getClientRects().length ); } firstLink(panel) { return panel?.querySelector('.MenuGroup__link'); } nextToggle(current) { const i = this.toggles.indexOf(current); if (i === -1) return null; return this.toggles[i + 1] || this.toggles[0]; } focusNextTopLevel(current) { const i = this.topLevelItems.indexOf(current); if (i === -1) return; const next = this.topLevelItems[i + 1] || this.topLevelItems[0]; next.focus(); } focusPrevTopLevel(current) { const i = this.topLevelItems.indexOf(current); if (i === -1) return; const prev = this.topLevelItems[i - 1] || this.topLevelItems[this.topLevelItems.length - 1]; prev.focus(); } bind() { this.root.addEventListener('click', e => { const toggle = e.target.closest('.MenuGroup__toggle'); if (!toggle || !this.root.contains(toggle)) return; const panel = this.panels.get(toggle); if (!panel) return; this.toggle(toggle, panel); }); this.root.addEventListener('keydown', e => { const focusables = this.getFocusable(); const current = document.activeElement; const i = focusables.indexOf(current); const toggle = current?.closest('.MenuGroup__toggle'); const link = current?.closest('.MenuGroup__link'); const panel = current?.closest('.MenuGroup__panel'); if (e.key === 'ArrowDown') { if (i === -1) return; e.preventDefault(); (focusables[i + 1] || focusables[0]).focus(); return; } if (e.key === 'ArrowUp') { if (i === -1) return; e.preventDefault(); (focusables[i - 1] || focusables[focusables.length - 1]).focus(); return; } if (toggle) { const panelEl = this.panels.get(toggle); if (!panelEl) return; switch (e.key) { case 'Enter': case ' ': e.preventDefault(); this.toggle(toggle, panelEl); break; case 'ArrowRight': if (toggle.getAttribute('aria-expanded') !== 'true') { this.toggle(toggle, panelEl); } this.firstLink(panelEl)?.focus(); break; case 'ArrowLeft': if (toggle.getAttribute('aria-expanded') === 'true') { this.close(toggle, panelEl); } toggle.focus(); break; } return; } if (link) { if (e.key === 'ArrowLeft') { const parentToggle = link.closest('.MenuGroup')?.querySelector('.MenuGroup__toggle'); if (parentToggle) { e.preventDefault(); parentToggle.focus(); } } } if (panel && e.key === 'ArrowLeft') { e.preventDefault(); panel .closest('.MenuGroup') ?.querySelector('.MenuGroup__toggle') ?.focus(); } }); } toggle(button, panel) { const expanded = button.getAttribute('aria-expanded') === 'true'; const isEditions = document.documentElement.dataset.sitenavSection === 'editions'; if (!isEditions) { this.closeAll(); } if (expanded) { if (isEditions) { this.close(button, panel); } else { button.focus(); } this.updateAsideFocusables(); return; } button.setAttribute('aria-expanded', 'true'); panel.setAttribute('aria-hidden', 'false'); this.enableFocus(panel); const { icon, use } = this.icons.get(button) || {}; if (icon) { icon.classList.remove('icon-header-angle-down'); icon.classList.add('icon-header-angle-up'); } if (use) { use.setAttribute('xlink:href', '#icon-header-angle-up'); } this.updateAsideFocusables(); panel.focus({ preventScroll: true }); } close(button, panel) { button.setAttribute('aria-expanded', 'false'); panel.setAttribute('aria-hidden', 'true'); this.disableFocus(panel); const { icon, use } = this.icons.get(button) || {}; if (icon) { icon.classList.remove('icon-header-angle-up'); icon.classList.add('icon-header-angle-down'); } if (use) { use.setAttribute('xlink:href', '#icon-header-angle-down'); } this.updateAsideFocusables(); } closeAll() { this.toggles.forEach(toggle => { const panel = this.panels.get(toggle); if (!panel) return; toggle.setAttribute('aria-expanded', 'false'); panel.setAttribute('aria-hidden', 'true'); this.disableFocus(panel); const { icon, use } = this.icons.get(toggle) || {}; if (icon) { icon.classList.remove('icon-header-angle-up'); icon.classList.add('icon-header-angle-down'); } if (use) { use.setAttribute('xlink:href', '#icon-header-angle-down'); } }); } disableFocus(container) { const items = container.querySelectorAll( 'a[href], button:not(.MenuGroup__notoggle):not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])' ); items.forEach(el => { if (!('prevTabindex' in el.dataset)) { el.dataset.prevTabindex = el.getAttribute('tabindex') ?? ''; } el.setAttribute('tabindex', '-1'); }); } enableFocus(container) { const items = container.querySelectorAll( 'a[href], button:not(.MenuGroup__notoggle):not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])' ); items.forEach(el => { if (!('prevTabindex' in el.dataset)) { el.removeAttribute('tabindex'); return; } const prev = el.dataset.prevTabindex; if (prev === '') { el.removeAttribute('tabindex'); } else { el.setAttribute('tabindex', prev); } delete el.dataset.prevTabindex; }); } focusNext(current) { const i = this.toggles.indexOf(current); if (i === -1) return; const next = this.toggles[i + 1] || this.toggles[0]; next.focus(); } focusPrev(current) { const i = this.toggles.indexOf(current); if (i === -1) return; const prev = this.toggles[i - 1] || this.toggles[this.toggles.length - 1]; prev.focus(); } } class LightboxController { constructor() { this.html = document.documentElement; this.overlay = document.getElementById('LightboxOverlay'); this.lightboxes = [...document.querySelectorAll('.Lightbox')]; if (!this.lightboxes.length) return; this.openButtons = document.querySelectorAll('[data-lightbox-open]'); this.closeButtons = document.querySelectorAll('.Lightbox__close'); this.primary = document.getElementById('primary'); this.header = document.getElementById('SiteHeader'); this.panels = new Map( this.lightboxes.map(panel => [panel.dataset.lightbox, panel]) ); this.activePanel = null; this.activeTrigger = null; this.focusable = []; this.resizeRAF = null; this.init(); this.bind(); } init() { this.lightboxes.forEach(panel => { panel.setAttribute('aria-hidden', 'true'); panel.setAttribute('inert', ''); this.setTabIndex(panel, -1); }); if (this.overlay) { this.overlay.setAttribute('aria-hidden', 'true'); } } setTabIndex(panel, value) { const elements = panel.querySelectorAll( 'a[href], button, textarea, input, select, [tabindex]' ); elements.forEach(el => { el.setAttribute('tabindex', value); }); } bind() { this.openButtons.forEach(btn => { btn.addEventListener('click', () => { this.toggle(btn); }); }); this.closeButtons.forEach(btn => { btn.addEventListener('click', () => this.close()); }); if (this.overlay) { this.overlay.addEventListener('click', () => this.close()); } document.addEventListener('keydown', e => { if (e.key === 'Escape' && this.html.dataset.lightboxOpen) { this.close(); } if (e.key === 'Tab' && this.html.dataset.lightboxOpen) { this.trapFocus(e); } }); window.addEventListener('resize', () => { if (this.resizeRAF) return; this.resizeRAF = requestAnimationFrame(() => { this.handleTopicsOverflow(); this.resizeRAF = null; }); }); document.addEventListener('sitenav:aside-open', () => { this.close(); }); } toggle(trigger) { const name = trigger.dataset.lightboxOpen; const panel = this.panels.get(name); if (!panel) return; const expanded = trigger.getAttribute('aria-expanded') === 'true'; if (expanded) { this.close(); return; } if (window.scrollY > 0) { window.scrollTo({ top: 0, behavior: 'smooth' }); const waitForTop = () => { if (window.scrollY <= 0) { window.removeEventListener('scroll', waitForTop); this.open(trigger, panel, name); } }; window.addEventListener('scroll', waitForTop, { passive: true }); return; } this.open(trigger, panel, name); } open(trigger, panel, name) { document.dispatchEvent(new CustomEvent('sitenav:lightbox-open')); this.close(); this.activeTrigger = trigger; this.activePanel = panel; panel.classList.add('is-open'); panel.setAttribute('aria-hidden', 'false'); panel.removeAttribute('inert'); this.setTabIndex(panel, 0); if (!panel.hasAttribute('tabindex')) { panel.setAttribute('tabindex', '-1'); } if (this.overlay) { this.overlay.classList.add('is-open'); this.overlay.setAttribute('aria-hidden', 'false'); } // inert everything except the lightbox + overlay [...document.body.children].forEach(el => { if (!el.contains(panel) && el !== this.overlay) { el.setAttribute('inert', ''); } }); this.html.dataset.lightboxOpen = name; this.syncTriggers(true, name); this.buildFocusable(panel); if (this.focusable.length) { this.focusable[0].focus(); } else { panel.focus({ preventScroll: true }); } } close() { this.lightboxes.forEach(panel => { panel.classList.remove('is-open'); panel.setAttribute('aria-hidden', 'true'); panel.setAttribute('inert', ''); this.setTabIndex(panel, -1); }); if (this.overlay) { this.overlay.classList.remove('is-open'); this.overlay.setAttribute('aria-hidden', 'true'); } [...document.body.children].forEach(el => { el.removeAttribute('inert'); }); delete this.html.dataset.lightboxOpen; this.syncTriggers(false); document.body.style.overflow = ''; this.focusable = []; if (this.activeTrigger) { this.activeTrigger.focus({ preventScroll: true }); this.activeTrigger = null; } this.activePanel = null; } buildFocusable(panel) { this.focusable = Array.from( panel.querySelectorAll( 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' ) ).filter(el => el.getClientRects().length); } trapFocus(e) { const focusable = this.focusable; if (!focusable.length) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; const active = document.activeElement; if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); } if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); } } syncTriggers(expanded, active = null) { this.openButtons.forEach(btn => { if (!expanded) { btn.setAttribute('aria-expanded', 'false'); return; } btn.setAttribute( 'aria-expanded', btn.dataset.lightboxOpen === active ? 'true' : 'false' ); }); } handleTopicsOverflow() { if (this.html.dataset.lightboxOpen !== 'topics') { document.body.style.overflow = ''; return; } const panel = this.activePanel; if (!this.header || !panel) { document.body.style.overflow = ''; return; } const windowHeight = window.innerHeight; const headerHeight = this.header.offsetHeight; const panelHeight = panel.offsetHeight; if (headerHeight + panelHeight < windowHeight) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } } } class PrimaryNavCoordinator { constructor() { this.aside = document.getElementById('MenuAside'); this.lightboxes = [...document.querySelectorAll('.Lightbox')]; document.addEventListener('sitenav:aside-open', () => { this.closeLightboxes(); }); document.addEventListener('sitenav:lightbox-open', () => { this.closeAside(); }); } closeAside() { const aside = this.aside; if (!aside || !aside.classList.contains('is-open')) return; const controller = aside._controller; if (controller) controller.close(); } closeLightboxes() { for (const panel of this.lightboxes) { if (!panel.classList.contains('is-open')) continue; const controller = panel._controller; if (controller) controller.close(); break; } } } function priorityNav() { const runPriority = () => { const primaryList = document.querySelector('.PrimaryNav__list'); if (!primaryList) return; const items = [...primaryList.children]; items.forEach(item => { item.style.position = ''; item.style.top = ''; item.style.visibility = 'visible'; }); if (window.innerWidth <= 768) return; const totalWidth = primaryList.getBoundingClientRect().width; const logoBuffer = 120; // Change this number to adjust the gap const availableWidth = totalWidth - logoBuffer; const gap = parseFloat(window.getComputedStyle(primaryList).gap) || 0; let usedWidth = 0; items.forEach((item, index) => { usedWidth += item.getBoundingClientRect().width; if (index > 0) { usedWidth += gap; } if (usedWidth > availableWidth) { item.style.position = 'absolute'; item.style.top = '-9999px'; item.style.visibility = 'hidden'; } }); }; let resizeFrame; const schedulePriority = () => { cancelAnimationFrame(resizeFrame); resizeFrame = requestAnimationFrame(runPriority); }; runPriority(); window.addEventListener('resize', schedulePriority); } document.addEventListener('DOMContentLoaded', () => { priorityNav(); const audience = ( document.documentElement.getAttribute('data-audience') || document.body.getAttribute('data-audience') || '' ) .trim() .toLowerCase() .replace(/\s+/g, ''); if ( audience !== 'consumer' ) { new PrimaryNavPersistent(); } // PrimaryNavDropdown.init(); new PrimaryNavCoordinator(); let aside; let lightbox; document.addEventListener('click', (e) => { const asideTrigger = e.target.closest('[data-menu-open]'); if (asideTrigger && !aside) { aside = new MenuAsideController(); document.getElementById('MenuAside')._controller = aside; document .querySelectorAll('.MenuAside__section') .forEach(nav => new MenuGroupController(nav)); asideTrigger.click(); return; } const lightboxTrigger = e.target.closest('[data-lightbox-open]'); if (lightboxTrigger && !lightbox) { lightbox = new LightboxController(); document .querySelectorAll('.Lightbox') .forEach(lb => lb._controller = lightbox); lightboxTrigger.click(); } }); }); ; /** * A class to handle the toggling of content visibility with options. */ class ToggleContentVisibility { /** * @param {Element} node - The DOM element to apply the toggle behavior. * @param {Object} options - Configuration options for the class. */ constructor(node, options = {}) { if (!(node instanceof Element)) { throw new Error('Input node is not a valid DOM element.'); } this.node = node; this.options = this.setOptions(options); this.elements = node.querySelectorAll( Array.isArray(this.options.targetElements) ? this.options.targetElements.join(', ') : '', ); this.showingMore = false; this.readMoreButton = this.node.querySelector('.read-more'); this.readLessButton = this.node.querySelector('.read-less'); this.toggleContentBound = this.toggleContent.bind(this); // Bind method once this.initialize(); } setOptions(options) { const defaults = { initialIndex: 2, showReadLess: false, targetElements: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'blockquote', 'pre', 'address', 'figcaption', 'details', 'ul', 'ol', 'dl', 'table', 'img', 'video', 'audio', 'canvas', 'iframe', 'figure', 'script', 'noscript', 'style', 'form', 'hr', 'svg', ], position: 'after', readMoreText: 'Read more', readLessText: 'Read less', transitionEffect: null, }; const readMoreAttribute = this.node.getAttribute('data-readmore'); const readLessAttribute = this.node.getAttribute('data-readless'); const readMoreCountAttribute = this.node.getAttribute('data-readmore-count'); const parsedCount = parseInt(readMoreCountAttribute, 10); const initialIndex = Number.isNaN(parsedCount) ? defaults.initialIndex : parsedCount; return { ...defaults, ...options, initialIndex, showReadLess: readLessAttribute !== 'false', readMoreText: readMoreAttribute || defaults.readMoreText, readLessText: readLessAttribute || defaults.readLessText, }; } initialize() { if (this.elements.length === 0) return; // No elements, nothing to toggle if (this.options.initialIndex >= this.elements.length) { this.showingMore = true; return; } if (!this.node.id) { this.node.id = `readmore-${Math.random().toString(36).substr(2, 9)}`; } Array.from(this.elements).forEach((element, i) => { element.classList.toggle('hidden', i >= this.options.initialIndex); element.setAttribute('aria-hidden', i >= this.options.initialIndex); }); this.showingMore = false; if (!this.readMoreButton) { this.readMoreButton = ToggleContentVisibility.createControlButton( this.options.readMoreText, ['read-more'], this.toggleContentBound, ); this.appendControlButton(this.readMoreButton); } if (this.options.showReadLess && !this.readLessButton) { this.readLessButton = ToggleContentVisibility.createControlButton( this.options.readLessText, ['read-less'], this.toggleContentBound, ); this.appendControlButton(this.readLessButton); } this.node.setAttribute('aria-expanded', this.showingMore); this.manageButtonState(); } static initAll() { const nodes = document.querySelectorAll('[data-readmore]'); nodes.forEach(node => { new ToggleContentVisibility(node); // eslint-disable-line no-new }); } refresh() { this.destroy(); this.initialize(); } update(newOptions) { this.options = this.setOptions({ ...this.options, ...newOptions }); this.refresh(); } destroy() { this.showingMore = false; if (this.readMoreButton) { this.readMoreButton.removeEventListener('click', this.toggleContentBound); this.readMoreButton.remove(); this.readMoreButton = null; } if (this.readLessButton) { this.readLessButton.removeEventListener('click', this.toggleContentBound); this.readLessButton.remove(); this.readLessButton = null; } Array.from(this.elements).forEach(element => { element.classList.remove('hidden'); element.removeAttribute('aria-hidden'); }); this.node.removeAttribute('aria-expanded'); } toggleContent() { if (this.node.getAttribute('data-readless') === null) { this.showContent(); this.readMoreButton.remove(); if (this.readLessButton) { this.readLessButton.remove(); this.readLessButton = null; } } else if (this.showingMore) { this.hideContent(); } else { this.showContent(); } } showContent() { this.showingMore = true; this.toggleElementsVisibility(true); this.manageButtonState(); } hideContent() { this.showingMore = false; this.toggleElementsVisibility(false); this.manageButtonState(); } toggleElementsVisibility(isVisible) { Array.from(this.elements) .slice(this.options.initialIndex) .forEach(element => { element.classList.toggle('hidden', !isVisible); element.setAttribute('aria-hidden', !isVisible); }); this.node.setAttribute('aria-expanded', isVisible); } manageButtonState() { if (this.showingMore) { this.configureReadLessButton(); } else { this.configureReadMoreButton(); } } configureReadMoreButton() { if (this.readMoreButton) { this.readMoreButton.classList.remove('hidden'); this.readMoreButton.setAttribute('aria-hidden', 'false'); } if (this.readLessButton) { this.readLessButton.classList.add('hidden'); this.readLessButton.setAttribute('aria-hidden', 'true'); } } configureReadLessButton() { if (this.readMoreButton) { this.readMoreButton.classList.add('hidden'); this.readMoreButton.setAttribute('aria-hidden', 'true'); } if (this.readLessButton) { this.readLessButton.classList.remove('hidden'); this.readLessButton.setAttribute('aria-hidden', 'false'); } } static createControlButton(text, classNames, handler) { const button = document.createElement('button'); button.textContent = text; button.classList.add(...classNames); button.addEventListener('click', handler); return button; } appendControlButton(button) { if (this.node.parentNode) { this.node.appendChild(button); } } } ToggleContentVisibility.initAll(); ; (() => { class dataLayerTracker { static VERSION = "1.0.0"; constructor({ debug = false, prefix = "[DATA LAYER TRACKER]", analyticsVendor = "Google Analytics", trackers = [], dimensions = {} } = {}) { this.debug = debug === true; this.prefix = prefix; this.analyticsVendor = analyticsVendor; this.trackers = Array.isArray(trackers) ? trackers : []; this.dimensions = ( dimensions && typeof dimensions === "object" && !Array.isArray(dimensions) ) ? { ...dimensions } : {}; this.initialized = false; this.destroyed = false; this.firedEvents = new Set(); this.handleConsentManagerReady = this.handleConsentManagerReady.bind(this); this.handleDocumentClick = this.handleDocumentClick.bind(this); } log(...args) { if (!this.debug) { return; } console.log(this.prefix, ...args); } warn(...args) { if (!this.debug) { return; } console.warn(this.prefix, ...args); } error(...args) { if (!this.debug) { return; } console.error(this.prefix, ...args); } group(label, callback) { if (!this.debug || typeof callback !== "function") { return; } console.groupCollapsed(`${this.prefix} ${label}`); try { callback(); } catch (error) { console.error(this.prefix, "Debug group failed.", error); } finally { console.groupEnd(); } } hasAnalyticsConsent() { if ( typeof window.consentManager?.checkConsentByVendor !== "function" ) { this.log("Consent Manager is unavailable."); return false; } try { return ( window.consentManager.checkConsentByVendor( this.analyticsVendor ) === true ); } catch (error) { this.error("Consent check failed.", error); return false; } } getDataLayer() { if (!Array.isArray(window.dataLayer)) { this.log("window.dataLayer is unavailable."); return null; } return window.dataLayer; } getDimensions() { return { ...this.dimensions }; } setDimensions(dimensions = {}) { if ( !dimensions || typeof dimensions !== "object" || Array.isArray(dimensions) ) { this.warn("Invalid dimensions.", dimensions); return false; } if (!this.hasAnalyticsConsent()) { this.log("Dimensions blocked because analytics consent is unavailable."); return false; } const dataLayer = this.getDataLayer(); if (!dataLayer) { return false; } const normalizedDimensions = Object.fromEntries( Object.entries(dimensions).filter(([key]) => ( typeof key === "string" && key.trim().length > 0 )) ); if (!Object.keys(normalizedDimensions).length) { return false; } this.dimensions = { ...this.dimensions, ...normalizedDimensions }; try { dataLayer.push({ ...normalizedDimensions }); } catch (error) { this.error("Data layer dimension push failed.", { dimensions: normalizedDimensions, error }); return false; } this.log("Data layer dimensions updated.", normalizedDimensions); return true; } isValidEventName(eventName) { return ( typeof eventName === "string" && eventName.trim().length > 0 ); } isValidPayload(payload) { return ( payload === undefined || ( payload !== null && typeof payload === "object" && !Array.isArray(payload) ) ); } hasEventFired(eventKey) { return this.firedEvents.has(eventKey); } markEventAsFired(eventKey) { if (typeof eventKey !== "string" || !eventKey) { return; } this.firedEvents.add(eventKey); } resetEvent(eventKey) { if (typeof eventKey !== "string" || !eventKey) { return; } this.firedEvents.delete(eventKey); } resetEvents() { this.firedEvents.clear(); } buildEventKey(eventName, key = "") { return key ? `${eventName}:${key}` : eventName; } buildPayload(eventName, payload = {}) { return { ...this.dimensions, ...payload, event: eventName }; } pushEvent(eventName, payload = {}, { once = true, key = "" } = {}) { if (!this.isValidEventName(eventName)) { this.warn("Invalid event name.", eventName); return false; } if (!this.isValidPayload(payload)) { this.warn("Invalid event payload.", payload); return false; } const normalizedEventName = eventName.trim(); const eventKey = this.buildEventKey(normalizedEventName, key); if (once && this.hasEventFired(eventKey)) { return false; } if (!this.hasAnalyticsConsent()) { this.log( "Event blocked because analytics consent is unavailable.", normalizedEventName ); return false; } const dataLayer = this.getDataLayer(); if (!dataLayer) { return false; } const eventPayload = this.buildPayload( normalizedEventName, payload ); try { dataLayer.push(eventPayload); } catch (error) { this.error("Data layer push failed.", { eventName: normalizedEventName, payload: eventPayload, error }); return false; } if (once) { this.markEventAsFired(eventKey); } this.group("Data layer event pushed", () => { console.log("Event:", normalizedEventName); console.log("Key:", eventKey); console.log("Payload:", eventPayload); console.log("Data layer length:", dataLayer.length); }); return true; } validateSelector(selector) { if (typeof selector !== "string" || !selector.trim()) { return false; } try { document.querySelector(selector); return true; } catch (error) { this.error("Invalid selector.", { selector, error }); return false; } } getElements(selector) { if (!this.validateSelector(selector)) { return []; } try { return Array.from(document.querySelectorAll(selector)); } catch (error) { this.error("Element lookup failed.", { selector, error }); return []; } } isElementVisible(element) { if (!(element instanceof HTMLElement)) { return false; } if (!element.isConnected) { return false; } if ( element.hidden || element.getAttribute("aria-hidden") === "true" || element.closest('[hidden], [inert], [aria-hidden="true"]') ) { return false; } const style = window.getComputedStyle(element); if ( style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || Number.parseFloat(style.opacity) <= 0 ) { return false; } const rect = element.getBoundingClientRect(); if (rect.width <= 0 || rect.height <= 0) { return false; } return ( rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth ); } handleVisibleElement(element, tracker) { if ( !tracker || !this.isValidEventName(tracker.visibleEvent) || !this.isElementVisible(element) ) { return false; } const eventKey = this.buildEventKey( tracker.visibleEvent, tracker.visibleSelector ); if (this.hasEventFired(eventKey)) { return false; } if (tracker.visibleDimensions) { this.setDimensions(tracker.visibleDimensions); } const pushed = this.pushEvent( tracker.visibleEvent, tracker.visiblePayload || {}, { once: true, key: tracker.visibleSelector } ); if (!pushed) { return false; } this.intersectionObserver?.unobserve(element); this.log("Visible element tracked.", { selector: tracker.visibleSelector, element, event: tracker.visibleEvent }); return true; } handleClickedElement(element, tracker, event) { if ( !tracker || !this.isValidEventName(tracker.clickEvent) || !(element instanceof HTMLElement) || !this.isElementVisible(element) ) { return false; } const pushed = this.pushEvent( tracker.clickEvent, tracker.clickPayload || {}, { once: true, key: tracker.clickSelector } ); if (pushed) { this.log("Clicked element tracked.", { selector: tracker.clickSelector, element, event: tracker.clickEvent, originalEvent: event }); } return pushed; } checkVisibleElements() { for (const tracker of this.trackers) { if (!this.validateSelector(tracker?.visibleSelector)) { continue; } const eventKey = this.buildEventKey( tracker.visibleEvent, tracker.visibleSelector ); if (this.hasEventFired(eventKey)) { continue; } const elements = this.getElements(tracker.visibleSelector); for (const element of elements) { if (this.handleVisibleElement(element, tracker)) { break; } } } } observeElements() { if (typeof MutationObserver !== "function") { this.warn("MutationObserver is unavailable."); return; } this.mutationObserver = new MutationObserver(() => { this.observeCurrentElements(); this.checkVisibleElements(); }); this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: [ "class", "style", "hidden", "aria-hidden" ] }); if (typeof IntersectionObserver !== "function") { this.warn("IntersectionObserver is unavailable."); return; } this.intersectionObserver = new IntersectionObserver(entries => { for (const entry of entries) { if (!entry.isIntersecting) { continue; } for (const tracker of this.trackers) { if ( this.validateSelector(tracker?.visibleSelector) && entry.target.matches(tracker.visibleSelector) ) { this.handleVisibleElement(entry.target, tracker); } } } }, { threshold: 0.01 }); this.observeCurrentElements(); } observeCurrentElements() { if (!this.intersectionObserver) { return; } for (const tracker of this.trackers) { if (!this.validateSelector(tracker?.visibleSelector)) { continue; } const eventKey = this.buildEventKey( tracker.visibleEvent, tracker.visibleSelector ); if (this.hasEventFired(eventKey)) { continue; } const elements = this.getElements(tracker.visibleSelector); for (const element of elements) { this.intersectionObserver.observe(element); } } } disconnectObservers() { this.mutationObserver?.disconnect(); this.intersectionObserver?.disconnect(); this.mutationObserver = null; this.intersectionObserver = null; } handleDocumentClick(event) { if (!(event.target instanceof Element)) { return; } for (const tracker of this.trackers) { if (!this.validateSelector(tracker?.clickSelector)) { continue; } const clickedElement = event.target.closest(tracker.clickSelector); if (!(clickedElement instanceof HTMLElement)) { continue; } this.handleClickedElement(clickedElement, tracker, event); } } attachEventListeners() { document.addEventListener( "click", this.handleDocumentClick, true ); } detachEventListeners() { document.removeEventListener( "click", this.handleDocumentClick, true ); } handleConsentManagerReady() { this.initialize(); } initialize() { if (this.initialized || this.destroyed) { return false; } if (!this.hasAnalyticsConsent()) { this.log("Analytics consent was not granted."); return false; } this.initialized = true; this.setDimensions(this.dimensions); this.attachEventListeners(); this.observeElements(); this.checkVisibleElements(); this.log( `Tracking initialized v${dataLayerTracker.VERSION}.`, { trackers: this.trackers.length, dimensions: this.getDimensions() } ); return true; } registerConsentManager() { document.addEventListener( "consentManagerReady", this.handleConsentManagerReady ); if (window.consentManager?.eventDispatched === true) { this.initialize(); return; } this.log("Waiting for consentManagerReady."); } destroy() { if (this.destroyed) { return; } document.removeEventListener( "consentManagerReady", this.handleConsentManagerReady ); this.detachEventListeners(); this.disconnectObservers(); this.resetEvents(); this.initialized = false; this.destroyed = true; this.log("Tracking destroyed."); } } const welcomeAdTracker = { visibleSelector: '.ad-overlay[data-ad-template="overlay"]', visibleEvent: "welcome_ad_shown", visiblePayload: { welcome_ad_shown: true, welcome_ad_closed: false }, clickSelector: '.ad-overlay[data-ad-template="overlay"] #superstitial-text', clickEvent: "welcome_ad_closed", clickPayload: { welcome_ad_shown: true, welcome_ad_closed: true } }; const topScrollerTracker = { visibleSelector: '[data-adnm-fid="adnami-canvas-desktop-topscroll"]', visibleEvent: "topscroller_ad_shown", visibleDimensions: { topscroller_ad_shown: true } }; const tracker = new dataLayerTracker({ debug: true, prefix: "[DATA LAYER TRACKER]", analyticsVendor: "Google Analytics", dimensions: { topscroller_ad_shown: false }, trackers: [ welcomeAdTracker, topScrollerTracker ] }); tracker.registerConsentManager(); })();; function initTranscript() { try { const transcript = document.querySelector("#transcript"); if (!transcript) return; const jwPlayer = document.querySelector('.video-wrapper[data-type="jw"]'); const videoContainerYT = document.querySelector('.video-wrapper[data-type="youtube"]'); const videoIframe = videoContainerYT?.querySelector("iframe"); transcript.querySelectorAll(".transcript__item").forEach(item => { item.querySelectorAll("p").forEach(p => { if (!p.textContent.trim()) p.remove(); }); item.querySelectorAll("br").forEach((br, index) => { if (index > 0 && br.previousSibling?.nodeName === "BR") br.remove(); }); item.querySelectorAll(".transcript__paragraph").forEach(p => { const clean = p.innerHTML.replace(/ /g, ""); if (clean !== p.innerHTML) { // * Safe cleanup of known markup p.innerHTML = clean; // phpcs:ignore WordPressVIPMinimum.JS.InnerHTML.Found } }); }); const transcriptList = transcript.querySelector(".transcript__list"); if (transcriptList?.children.length === 1) { transcriptList.parentElement.classList.add("is-orphan"); transcriptList.classList.add("is-orphan"); } if (jwPlayer) { document.querySelectorAll(".transcript__jump-button").forEach(btn => btn.remove()); } if (videoIframe) { const videoUrl = new URL(videoIframe.src); const videoId = videoUrl.pathname.match(/embed\/([a-zA-Z0-9_-]+)/)?.[1]; if (videoId) { videoUrl.hostname = "www.youtube-nocookie.com"; videoIframe.src = videoUrl.href; transcript.addEventListener("click", event => { const button = event.target.closest(".transcript__jump-button"); if (!button) return; console.log("[Transcript] - Jump button clicked:", button); const time = Number(button.dataset.time); if (isNaN(time)) return; videoContainerYT.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { videoIframe.contentWindow?.postMessage( JSON.stringify({ event: "command", func: "seekTo", args: [time, true] }), "*" ); }, 300); }); } } } catch (error) { console.error("[initTranscript] - Failed to initialize:", error); } } document.addEventListener("DOMContentLoaded", initTranscript);;