1(function() { 2 // Define a namespace to encapsulate all variables and functions 3 const itv_acc_bb = (() => { 4 /** 5 * Mutation Observer for 3rd Party Sliders 6 */ 7 function observe3rdPartySliders( setupAttr, sliderControls ) { 8 let retries = 0; 9 const maxRetries = 50; // Try up to 50 times (~10 seconds) 10 const retryInterval = 200; // Check every 200ms 11 12 const observer = new MutationObserver(() => { 13 if (sliderControls.length) { 14 setupAttr(sliderControls, 'role', 'button'); 15 setupAttr(sliderControls, 'tabindex', '0'); 16 const allUpdated = Array.from(sliderControls).every(el => el.getAttribute('role') === 'button'); 17 if (allUpdated) { 18 observer.disconnect(); // Stop observing once the attributes are applied 19 } 20 } else if (++retries >= maxRetries) { 21 observer.disconnect(); // Stop observing if the element never appears 22 } 23 }); 24 25 const targetNode = document.body; 26 observer.observe(targetNode, { childList: true, subtree: true }); 27 } 28 29 function syncSwiperSlideA11y(carousel) { 30 if (!carousel) return; 31 32 const slides = carousel.querySelectorAll('.swiper-slide'); 33 if (!slides.length) return; 34
35 slides.forEach(slide => { 36 const isDuplicate = slide.classList.contains('swiper-slide-duplicate'); 37 38 // Keep this conservative: expose active/prev/next only. 39 // If your carousel shows multiple slides at once, this still typically covers what's visible. 40 /*const isVisible = 41 slide.classList.contains('swiper-slide-active') || 42 slide.classList.contains('swiper-slide-prev') || 43 slide.classList.contains('swiper-slide-next');*/ 44 45 const shouldHide = isDuplicate; 46 47 if (shouldHide) { 48 slide.setAttribute('aria-hidden', 'true'); 49 } else { 50 slide.removeAttribute('aria-hidden'); 51 } 52 }); 53 } 54 55 function initSwiperA11y() { 56 const carousels = document.querySelectorAll('.pp-image-carousel.swiper, .pp-image-carousel.swiper-container'); 57 if (!carousels.length) return; 58 59 carousels.forEach(carousel => { 60 // Initial pass 61 syncSwiperSlideA11y(carousel); 62 63 // Avoid stacking observers if initializeAttributes runs more than once 64 if (carousel.dataset.itaccSwiperObserved === '1') return; 65 carousel.dataset.itaccSwiperObserved = '1'; 66 67 const wrapper = carousel.querySelector('.swiper-wrapper'); 68 if (!wrapper) return; 69 70 // Observe class changes because Swiper flips slide classes on every transition 71 const obs = new MutationObserver(() => { 72 syncSwiperSlideA11y(carousel); 73 }); 74 75 obs.observe(wrapper, { 76 attributes: true, 77 subtree: true, 78 attributeFilter: ['class'] 79 }); 80 }); 81 } 82 83 function removeUnwantedSchemaElements() { 84 // Remove author schema elements from containers with grid-remove-author class 85 const removeAuthorContainers = document.querySelectorAll('.grid-remove-author'); 86 removeAuthorContainers.forEach(container => { 87 const authorElements = container.querySelectorAll('div[itemprop="author"][itemtype="https://schema.org/Person"]'); 88 authorElements.forEach(el => el.remove()); 89 }); 90 91 // Remove interaction count schema elements from containers with grid-remove-interaction-count class 92 const removeInteractionContainers = document.querySelectorAll('.grid-remove-interaction-count'); 93 removeInteractionContainers.forEach(container => { 94 const interactionElements = container.querySelectorAll('div[itemprop="interactionStatistic"][itemtype="https://schema.org/InteractionCounter"]'); 95 interactionElements.forEach(el => el.remove()); 96 }); 97 98 // Add aria-hidden to all schema div containers that only contain meta tags 99 // This prevents accessibility scanners from flagging empty schema containers 100 const allSchemaContainers = document.querySelectorAll('div[itemscope], div[itemprop]'); 101 allSchemaContainers.forEach(div => { 102 // Check if this div only contains meta tags (no visible content) 103 const hasOnlyMetaTags = Array.from(div.childNodes).every(node => { 104 // Allow meta tags, text nodes that are only whitespace, and comments 105 if (node.nodeType === Node.ELEMENT_NODE) { 106 return node.tagName.toLowerCase() === 'meta'; 107 } 108 if (node.nodeType === Node.TEXT_NODE) { 109 return node.textContent.trim() === ''; 110 } 111 if (node.nodeType === Node.COMMENT_NODE) { 112 return true; 113 } 114 return false; 115 }); 116 117 if (hasOnlyMetaTags && div.childNodes.length > 0) { 118 div.setAttribute('aria-hidden', 'true'); 119 } 120 }); 121 } 122 123 function observeSchemaElements() { 124 let retries = 0; 125 const maxRetries = 50; 126 127 let scheduled = false; 128 const observer = new MutationObserver(() => { 129 if (scheduled) return; 130 scheduled = true; 131 132 requestAnimationFrame(() => { 133 scheduled = false; 134 removeUnwantedSchemaElements(); 135 136 if (++retries >= maxRetries) { 137 observer.disconnect(); 138 } 139 }); 140 }); 141 142 observer.observe(document.body, { childList: true, subtree: true }); 143 } 144 145 /** 146 * Initialize attributes for elements in the DOM. 147 */ 148 function initializeAttributes() { 149 // Make sure we have access to the Accessibility Core javascript 150 if ( !window.itv_acc ) { 151 console.error('ITACC: itv_acc is not loaded'); 152 return; 153 } 154 const { setupAttr } = window.itv_acc; 155 156 157 const scrollAdjustElements = document.querySelectorAll( 158 'header[data-sticky="1"], ' + 159 '.pp-tabs, ' + 160 '.pp-tabs .pp-tabs-label' 161 ); 162 163 // Landmarks 164 const ppOffCanvasWrap = document.querySelectorAll('div.pp-offcanvas-container'); 165 setupAttr( ppOffCanvasWrap, 'role', 'none' ); 166 167 // Replace fl-main-content div with a proper <main> element 168 /*const flMainDiv = document.getElementById('fl-main-content'); 169 if (flMainDiv && flMainDiv.tagName !== 'MAIN') { 170 const mainEl = document.createElement('main'); 171 // Copy all attributes, skipping role="main" since <main> is semantically equivalent
172 [...flMainDiv.attributes].forEach(attr => { 173 if (!(attr.name === 'role' && attr.value === 'main')) { 174 mainEl.setAttribute(attr.name, attr.value); 175 } 176 }); 177 // Move all children 178 while (flMainDiv.firstChild) mainEl.appendChild(flMainDiv.firstChild); 179 mainEl.removeAttribute('itemprop') 180 flMainDiv.parentNode.replaceChild(mainEl, flMainDiv); 181 }*/ 182 183 // Check if any tab elements exist on the page 184 /*if (scrollAdjustElements.length > 0) { 185 // Check if the style hasn't already been added 186 if (!document.querySelector('style[data-itacc-scroll-padding]')) { 187 // Create and append the style element 188 const styleElement = document.createElement('style'); 189 styleElement.setAttribute('data-itacc-scroll-padding', 'true'); 190 styleElement.textContent = 'html { scroll-padding-top: 25vh; }'; 191 document.body.appendChild(styleElement); 192 } 193 }*/ 194 195 const nestedColGroups = document.querySelectorAll('.fl-col-group-nested'); 196 setupAttr(nestedColGroups, 'role', 'group'); 197 198 // Menu toggles 199 const menuToggles = document.querySelectorAll('.fl-menu-toggle'); 200 setupAttr(menuToggles, 'aria-label', 'Menu Toggle'); 201 setupAttr(menuToggles, 'role', 'button'); 202 setupAttr(menuToggles, 'tabindex', '0'); 203 204 // Menu Fixes & Dropdowns 205 /* const flMenuWrappers = document.querySelectorAll('.fl-module-menu .fl-menu'); 206 if ( flMenuWrappers.length ) { 207 flMenuWrappers.forEach( wrapper => { 208 const nav = wrapper.querySelector('nav'); 209 if ( !nav ) return; 210 211 if ( !wrapper.hasAttribute('role') ) { 212 wrapper.setAttribute('role', 'navigation'); 213 } 214 }); 215 } */ 216 217 const menuDropdowns = document.querySelectorAll('div.fl-has-submenu-container'); 218 setupAttr(menuDropdowns, 'aria-haspopup', 'true'); 219 setupAttr(menuDropdowns, 'role', 'none'); 220 setupAttr(menuDropdowns, 'aria-expanded', 'false'); 221 const ppMenuSubMenu = document.querySelectorAll('ul.menu li.menu-item-has-children div.pp-has-submenu-container'); 222 setupAttr(ppMenuSubMenu, 'aria-haspopup', 'true'); 223 setupAttr(ppMenuSubMenu, 'aria-expanded', 'false'); 224 const uabbMenuWrap = document.querySelectorAll('ul.uabb-creative-menu-horizontal'); 225 setupAttr(uabbMenuWrap, 'role', 'navigation'); 226 const uabbMenuSubMenu = document.querySelectorAll('ul.uabb-creative-menu-horizontal div.uabb-has-submenu-container'); 227 setupAttr(uabbMenuSubMenu, 'aria-haspopup', 'true'); 228 setupAttr(uabbMenuSubMenu, 'aria-expanded', 'false'); 229 230 // Separator elements 231 const bbSeparators = document.querySelectorAll( 232 '.fl-module.fl-module-separator .fl-separator, ' + 233 '.pp-heading-separator.line_only' 234 ); 235 setupAttr(bbSeparators, 'aria-hidden', 'true'); 236 const separatorElements = document.querySelectorAll('.fl-module.fl-module-separator, .pp-separator-line'); 237 setupAttr(separatorElements, 'role', 'none'); 238 239 // Layout elements with background image 240 const layoutElements = document.querySelectorAll( 241 '.fl-row .fl-row-content-wrap, ' + 242 '.fl-col .fl-col-content, ' + 243 '.fl-slide-bg-photo' 244 ); 245 /*setupAttr(layoutElements, 'role', 'img', itv_acc.hasBackgroundImage); // -- changed to help scanners from flagging background CSS images */ 246 setupAttr(layoutElements, 'role', 'none', itv_acc.hasBackgroundImage); 247 248 // Headers 249 const ppHeaderPs = document.querySelectorAll('.pp-heading p.heading-title .title-text'); 250 setupAttr(ppHeaderPs, 'role', 'text'); 251 252 // Video Backgrounds 253 const rowVideoBkgds = document.querySelectorAll('.fl-row .fl-row-content-wrap .fl-bg-video[data-fallback]'); 254 setupAttr(rowVideoBkgds, 'role', 'none'); 255 256 // Parallax rows 257 const parallaxRowElements = document.querySelectorAll('.fl-row.fl-row-bg-parallax .fl-row-content-wrap'); 258 setupAttr(parallaxRowElements, 'role', 'none'); 259 260 // Expandable rows 261 const expandRowElements = document.querySelectorAll('.pp-er-wrap'); 262 setupAttr(expandRowElements, 'role', 'button'); 263 setupAttr(expandRowElements, 'tabindex', '0'); 264 265 // Swiper slides 266 const imgSwiperElements = document.querySelectorAll('figure.swiper-slide-inner.use-as-background'); 267 setupAttr(imgSwiperElements, 'role', 'none'); 268 const figureSwiperElements = document.querySelectorAll('figure.swiper-slide-inner'); 269 setupAttr(figureSwiperElements, 'role', 'none', itv_acc.hasChildOfTag('img')); 270 271 // Slide drawer elements 272 const slidedrawerElements = document.querySelectorAll('.uabb-slide-box-wrap .uabb-slide-dropdown .uabb-icon-wrap'); 273 setupAttr(slidedrawerElements, 'role', 'button'); 274 setupAttr(slidedrawerElements, 'tabindex', '0'); 275 setupAttr(slidedrawerElements, 'aria-label', 'Slides open'); 276 277 const slidedrawerIcons = document.querySelectorAll('.uabb-slide-box-wrap .uabb-slide-dropdown .uabb-icon-wrap .uabb-icon'); 278 setupAttr(slidedrawerIcons, 'role', 'none'); 279 280 // Info box elements 281 const infoBoxImgElements = document.querySelectorAll('.uabb-ib2-outter .uabb-new-ib-img'); 282 setupAttr(infoBoxImgElements, 'role', 'none'); 283 //setupAttr(infoBoxImgElements, 'aria-hidden', 'true'); 284 285 const infoBoxWrapElements = document.querySelectorAll('.uabb-ib2-outter .uabb-new-ib-desc'); 286 //setupAttr(infoBoxWrapElements, 'aria-hidden', 'true'); 287 setupAttr(infoBoxWrapElements, 'role', 'button'); 288 289 const hoverCardElements = document.querySelectorAll('.pp-hover-card');
290 hoverCardElements.forEach(el => { 291 if (!el.getAttribute('onclick')?.trim()) { 292 el.removeAttribute('onclick'); 293 } 294 }); 295 const hoverBoxElements = document.querySelectorAll('.uabb-ih-container .uabb-ih-item .uabb-ih-line'); 296 setupAttr(hoverBoxElements, 'role', 'none'); 297 298 const infoBoxDescriptions = document.querySelectorAll('.pp-infobox-description'); 299 setupAttr(infoBoxDescriptions, 'aria-hidden', 'true', itv_acc.isDeepEmpty); 300 301 // Panels / Tabs 302 const tabElements = document.querySelectorAll( 303 'a.pp-panel-link, ' + 304 'a.uabb-tab-link' 305 ); 306 setupAttr(tabElements, 'role', 'button'); 307 // -- Update PP Tabs / Accordions 308 const ppTabsAccordionLabels = document.querySelectorAll( 309 '.pp-tabs .pp-tabs-panels .pp-tabs-panel .pp-tabs-panel-label[data-index]' 310 ); 311 const isAccordionLabel = el => { 312 if (!el) return false; 313 if (el.closest('[role="tablist"]')) return false; 314 if (!el.hasAttribute('data-index')) return false; 315 return itv_acc.hasChildWithClass('pp-toggle-icon')(el); 316 }; 317 setupAttr(ppTabsAccordionLabels, 'role', 'button', isAccordionLabel); 318 setupAttr(ppTabsAccordionLabels, 'tabindex', '0', isAccordionLabel);
319 ppTabsAccordionLabels.forEach(el => { 320 if (!isAccordionLabel(el)) return; 321 322 const isOpen = el.classList.contains('pp-tab-active'); 323 el.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); 324 }); 325 if (!window.itv_acc_bb_pp_tabs_keydown) { 326 window.itv_acc_bb_pp_tabs_keydown = true; 327 328 document.addEventListener('keydown', e => { 329 const target = e.target && e.target.closest 330 ? e.target.closest('.pp-tabs-panel-label[role="button"][data-index]') 331 : null; 332 333 if (!target) return; 334 335 if (e.key === 'Enter' || e.key === ' ') { 336 e.preventDefault(); 337 target.click(); 338 } 339 }); 340 } 341 // -- END: Update PP Tabs / Accordions 342 // -- UABB Tabs 343 const uabbTabNav = document.querySelectorAll('nav.uabb-tabs-nav > ul'); 344 const uabbTabLink = document.querySelectorAll('nav.uabb-tabs-nav > ul > li[data-index]'); 345 setupAttr(uabbTabNav, 'role', 'tablist'); 346 setupAttr(uabbTabLink, 'role', 'tab'); 347 // -- END UABB Tabs 348 349 // Lists / Icon / Info Lists 350 const uabbInfoListTitles = document.querySelectorAll('.uabb-info-list-title'); 351 setupAttr(uabbInfoListTitles, 'role', 'heading'); 352 const uabbInfoListLinks = document.querySelectorAll('.uabb-info-list-title a[aria-label]'); 353 uabbInfoListLinks.forEach(a => { 354 const label = a.getAttribute('aria-label') || ''; 355 if (/^go to https?:\/\//i.test(label.trim()) && itv_acc.hasVisibleText(a)) { 356 a.removeAttribute('aria-label'); 357 } 358 }); 359 360 // Testimonial elements 361 const testimonialElements = document.querySelectorAll( 362 '.pp-testimonials .pp-testimonial .pp-testimonials-content, ' + 363 '.uabb-testimonials .uabb-testimonial-info .uabb-testimonial-author-description, ' + 364 '.uabb-testimonials .uabb-testimonial-info .uabb-testimonial-author-description *' 365 ); 366 setupAttr(testimonialElements, 'role', 'none'); 367 368 // Modals 369 const modalElements = document.querySelectorAll( 370 '.pp-modal-wrap, ' + 371 '.pp-modal-wrap[role="dialog"], ' + 372 '.uabb-modal-wrap' 373 ); 374 setupAttr(modalElements, 'aria-modal', 'true' ); 375 setupAttr(modalElements, 'role', 'dialog' ); 376 377 // Row Shape Dividers 378 const bbRowShapeDividers = document.querySelectorAll('.fl-builder-shape-layer svg'); 379 bbRowShapeDividers.forEach(svg => { 380 svg.setAttribute('aria-hidden', 'true'); 381 svg.setAttribute('role', 'none'); 382 383 if (svg.hasAttribute('alt') && svg.getAttribute('alt').trim() === '') { 384 svg.removeAttribute('alt'); 385 } 386 387 // BB sometimes injects these; remove to prevent naming/flags 388 // svg.querySelectorAll('title, desc').forEach(el => el.remove()); 389 }); 390 391 // Galleries and Overlays 392 const ppGalleryOverlays = document.querySelectorAll('.pp-gallery-overlay'); 393 setupAttr(ppGalleryOverlays, 'aria-hidden', 'true', itv_acc.isDeepEmpty); 394 395 // Sliders 396 const uabbBXSliderPrev = document.querySelectorAll('.uabb-slider-prev .bx-prev'); 397 const uabbBXSliderNext = document.querySelectorAll('.uabb-slider-next .bx-next'); 398 const sliderControls = [...uabbBXSliderPrev, ...uabbBXSliderNext]; 399 400 // Placeholder iFrames 401 const placeholderIFrames = document.querySelectorAll('iframe.pp-video-iframe:not([src]):not([src=""])'); 402 setupAttr(placeholderIFrames, 'title', 'Placeholder Video Loader'); 403 404 // Run functions once 405 removeUnwantedSchemaElements(); 406 407 // Start Observers
408 observe3rdPartySliders(setupAttr, sliderControls); 409 initSwiperA11y(); 410 observeSchemaElements(); 411 412 // Fix incorrect PowerPack Smart Button Roles last 413 document.querySelectorAll('a.pp-button[href][role="button"]').forEach(a => { 414 if ( !itv_acc.isNavigationHref(a) ) return; 415 416 const before = getComputedStyle(a).cssText; 417 a.removeAttribute('role'); 418 const after = getComputedStyle(a).cssText; 419 420 // If styles changed, something depended on role="button" â put it back 421 if (before !== after) { 422 a.setAttribute('role', 'button'); 423 } 424 }); 425 } 426 427 // Expose functions within the namespace 428 return { 429 initializeAttributes 430 }; 431 })(); 432 433 window.itv_acc_bb = itv_acc_bb; 434 window.addEventListener( 'load', function () { 435 setTimeout( function () { 436 itv_acc_bb.initializeAttributes(); 437 }, 101 ); // Optional slight delay to let 3rd party render 438 }); 439 440 // âââ PowerPack Accordion: Hash / Deep-link Scroll Fix âââââââââââââââââââââââ 441 (function () { 442 if (window.ppAccordionFixLoaded) return; 443 window.ppAccordionFixLoaded = true; 444 const KEY = 'ppAccordionTarget'; 445 if (window.location.hash && window.location.hash.length > 1) { 446 sessionStorage.setItem(KEY, window.location.hash); 447 } 448 function getHeaderOffset() { 449 const header = document.querySelector('header'); 450 if (header) { 451 return header.getBoundingClientRect().height + 40; 452 } 453 return 120; 454 } 455 function scrollToStoredTarget() { 456 const storedHash = sessionStorage.getItem(KEY); 457 if (!storedHash || storedHash.length < 2) return; 458 const id = decodeURIComponent(storedHash.substring(1)); 459 let tries = 0; 460 const maxTries = 30; 461 const attempt = () => { 462 const el = document.getElementById(id); 463 if (!el) { 464 if (++tries < maxTries) setTimeout(attempt, 100); 465 return; 466 } 467 const header = el.querySelector( 468 '.pp-accordion-item-label, .pp-accordion-item-heading, .pp-advanced-accordion-header' 469 ); 470 if (header && header.getAttribute('aria-expanded') !== 'true') { 471 header.click(); 472 } 473 setTimeout(() => { 474 const headerOffset = getHeaderOffset(); 475 const group = el.closest('.pp-accordion'); 476 const items = group ? Array.from(group.querySelectorAll('.pp-accordion-item')) : []; 477 const itemIndex = items.indexOf(el); 478 const twoBack = itemIndex >= 2 ? items[itemIndex - 2] : null; 479 const scrollTarget = twoBack ? twoBack : (group ? group : el); 480 const y = scrollTarget.getBoundingClientRect().top + window.pageYOffset - headerOffset; 481 window.scrollTo({ top: y, behavior: 'smooth' }); 482 sessionStorage.removeItem(KEY); 483 }, 350); 484 }; 485 attempt(); 486 } 487 window.addEventListener('load', scrollToStoredTarget); 488 })(); 489 // âââ END: PowerPack Accordion Hash Fix ââââââââââââââââââââââââââââââââââââââ 490})();
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.