1/** 2 * Accessibility Fixer â Around the Mountain Dental 3 * Fixes all DOM-level WCAG issues detected by accessScan. 4 */ 5(function () { 6 'use strict'; 7 8 var hashJumpGuardUntil = 0; 9 var hashJumpGuardScroll = null; 10 var hadKeyboardFocusEvent = false; 11 12 onPageReady(function () { 13 runWhenIdle(runAccessibilityFixes); 14 }); 15 16 function onPageReady(callback) { 17 if (document.readyState === 'complete') { 18 callback(); 19 return; 20 } 21 22 window.addEventListener('load', function onLoad() { 23 window.removeEventListener('load', onLoad); 24 callback(); 25 }); 26 } 27 28 function runWhenIdle(callback) { 29 if ('requestIdleCallback' in window) { 30 window.requestIdleCallback(callback, { timeout: 1500 }); 31 return; 32 } 33 34 window.setTimeout(callback, 250); 35 } 36 37 function runAccessibilityFixes() { 38 if (isFixEnabled('focus_visibility')) enableFocusVisibility(); 39 if (isFixEnabled('image_alt_text')) fixImageAltText(); 40 if (isFixEnabled('active_nav_aria_current')) fixCurrentPageLinks(); 41 if (isFixEnabled('skip_link')) fixSkipLinks(); 42 if (isFixEnabled('icon_only_links')) fixIconOnlyLinks(); 43 if (isFixEnabled('lightbox_links')) fixEmptyLightboxLinks(); 44 if (isFixEnabled('read_more_links')) { 45 fixReadMoreLinks(); 46 watchReadMoreLinks(); 47 } 48 if (isFixEnabled('book_online_links')) fixBookOnlineLinks(); 49 if (isFixEnabled('positive_tabindex_forms')) { 50 fixPositiveTabindexInForms(); 51 watchPositiveTabindexInForms(); 52 } 53 if (isFixEnabled('button_like_links')) { 54 fixButtonLikeLinks(); 55 watchButtonLikeLinks(); 56 } 57 if (isFixEnabled('x_anchor_button_accessibility')) { 58 fixXAnchorButtons(); 59 watchXAnchorButtons(); 60 enableRoleButtonKeyboardSupport(); 61 watchRoleButtonKeyboardSupport(); 62 } 63 if (isFixEnabled('x_anchor_toggle_remove_aria_label')) { 64 removeXAnchorToggleAriaLabels(); 65 watchXAnchorToggleAriaLabels(); 66 scheduleXAnchorToggleAriaLabelCleanup(); 67 } 68 if (isFixEnabled('empty_hash_links')) { 69 fixEmptyHashLinks(); 70 watchEmptyHashLinks(); 71 } 72 if (isFixEnabled('x_menu_navigation_landmark')) { 73 wrapXMenuNavigation(); 74 watchXMenuNavigation(); 75 } 76 if (isFixEnabled('x_acc_header_remove_role')) { 77 removeXAccHeaderRoles(); 78 watchXAccHeaderRoles(); 79 } 80 if (isFixEnabled('x_acc_item_child_div_remove_role')) { 81 removeXAccItemChildDivRoles(); 82 watchXAccItemChildDivRoles(); 83 } 84 if (isFixEnabled('cornerstone_accordion_headers')) { 85 fixCornerstoneAccordionHeaders(); 86 watchCornerstoneAccordionHeaders(); 87 } 88 if (isFixEnabled('nav_menu_button_roles')) { 89 removeNavMenuButtonRoles(); 90 watchNavMenuButtonRoles(); 91 } 92 if (isFixEnabled('modal_links')) fixModalLinks(); 93 if (isFixEnabled('slick_carousel')) fixSlickCarousel(); 94 if (isFixEnabled('hidden_modals')) fixHiddenModals(); 95 if (isFixEnabled('decorative_icons')) fixIconElements(); 96 if (isFixEnabled('frame_names')) { 97 fixFrameAccessibleNames(); 98 watchFrameAccessibleNames(); 99 } 100 if (isFixEnabled('title_only_info')) fixTitleOnlyInformation(); 101 if (isFixEnabled('aria_label_visible_text')) fixAriaLabelsOverridingVisibleText(); 102 if (isFixEnabled('article_page_tags')) { 103 fixArticlePageTags(); 104 watchArticlePageTags(); 105 } 106 if (isFixEnabled('article_regions')) fixArticleRegions(); 107 if (isFixEnabled('main_content_div_to_main')) { 108 changeMainContentDivToMain(); 109 watchMainContentDivToMain(); 110 } 111 if (isFixEnabled('main_landmark') || isFixEnabled('landmark_nesting') || isFixEnabled('contentinfo_landmark')) fixPageLandmarks(); 112 if (isFixEnabled('secondary_nav_role')) fixSecondaryNavRole(); 113 if (isFixEnabled('minor_navigation_landmarks')) { 114 fixMinorNavigationLandmarks(); 115 watchMinorNavigationLandmarks(); 116 } 117 if (isFixEnabled('links_discernible_text')) { 118 fixLinksWithoutDiscernibleText(); 119 watchLinksWithoutDiscernibleText(); 120 } 121 if (isFixEnabled('pdf_link_warning')) { 122 fixPdfLinkWarnings(); 123 watchPdfLinkWarnings(); 124 } 125 126 // Re-run carousel fix after Slick initialises (it may run after DOMContentLoaded) 127 if (isFixEnabled('slick_carousel')) waitForSlick(); 128 enableEmptyHashJumpGuard(); 129 } 130 131 function isFixEnabled(key) { 132 if (!window.atmA11ySettings || !window.atmA11ySettings.enabledFixes) return true; 133 return window.atmA11ySettings.enabledFixes[key] !== 0 && window.atmA11ySettings.enabledFixes[key] !== '0'; 134 } 135 136 function enableFocusVisibility() { 137 document.documentElement.classList.add('atm-a11y-focus-enabled'); 138 updateStickyHeaderOffset(); 139 protectFocusedElementsFromStickyHeader(); 140 141 window.addEventListener('resize', debounce(updateStickyHeaderOffset, 150)); 142 window.addEventListener('orientationchange', debounce(updateStickyHeaderOffset, 150)); 143 } 144 145 function protectFocusedElementsFromStickyHeader() { 146 if (document.documentElement.getAttribute('data-atm-a11y-focus-protection') === '1') return; 147 document.documentElement.setAttribute('data-atm-a11y-focus-protection', '1'); 148 149 document.addEventListener('keydown', function (event) { 150 if (event.key === 'Tab' || event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight') { 151 hadKeyboardFocusEvent = true; 152 } 153 }, true); 154 155 document.addEventListener('mousedown', function () { 156 hadKeyboardFocusEvent = false;
157 }, true); 158 159 document.addEventListener('touchstart', function () { 160 hadKeyboardFocusEvent = false; 161 }, true); 162 163 document.addEventListener('pointerdown', function () { 164 hadKeyboardFocusEvent = false; 165 }, true); 166 167 document.addEventListener('focusin', function (event) { 168 var target = event.target; 169 if (!target || target === document.body || target === document.documentElement) return; 170 if (!hadKeyboardFocusEvent) return; 171 172 window.setTimeout(function () { 173 scrollFocusedElementBelowStickyHeader(target); 174 }, 0); 175 }); 176 177 document.addEventListener('click', function (event) { 178 var link = event.target && event.target.closest ? event.target.closest('a[href^="#"]') : null; 179 if (!link) return; 180 181 window.setTimeout(function () { 182 var id = decodeURIComponent((link.getAttribute('href') || '').replace(/^#/, '')); 183 var target = id ? document.getElementById(id) : null; 184 if (target) scrollFocusedElementBelowStickyHeader(target); 185 }, 50); 186 }); 187 } 188 189 function scrollFocusedElementBelowStickyHeader(el) { 190 var offset = getStickyHeaderOffset(); 191 if (!offset) return; 192 193 var rect = el.getBoundingClientRect(); 194 var buffer = 12; 195 196 if (rect.top < offset + buffer) { 197 window.scrollBy({ 198 top: rect.top - offset - buffer, 199 left: 0, 200 behavior: 'auto' 201 }); 202 } 203 } 204 205 function updateStickyHeaderOffset() { 206 document.documentElement.style.setProperty('--atm-header-height', getStickyHeaderOffset() + 'px'); 207 } 208 209 function getStickyHeaderOffset() { 210 var maxBottom = 0; 211 var selectors = [ 212 'header', 213 '.site-header', 214 '#site-header', 215 '#masthead', 216 '.sticky-header', 217 '.fixed-header', 218 '.et-l--header', 219 '.et-fixed-header', 220 '.elementor-location-header', 221 '.elementor-sticky--active', 222 '.x-navbar', 223 '.x-bar-fixed', 224 '.x-masthead', 225 '.cs-header' 226 ]; 227 228 document.querySelectorAll(selectors.join(',')).forEach(function (el) { 229 var style = window.getComputedStyle(el); 230 var rect = el.getBoundingClientRect(); 231 var isStickyOrFixed = style.position === 'fixed' || style.position === 'sticky'; 232 233 if (!isStickyOrFixed || style.display === 'none' || style.visibility === 'hidden') return; 234 if (rect.height <= 0 || rect.bottom <= 0 || rect.top > 80) return; 235 236 maxBottom = Math.max(maxBottom, rect.bottom); 237 }); 238 239 return Math.ceil(maxBottom); 240 } 241 242 function debounce(fn, delay) { 243 var timer = null; 244 return function () { 245 window.clearTimeout(timer); 246 timer = window.setTimeout(fn, delay); 247 }; 248 } 249 250 function fixTitleOnlyInformation() { 251 var elements = document.querySelectorAll('[title]'); 252 elements.forEach(function (el) { 253 var title = (el.getAttribute('title') || '').trim(); 254 if (title === '') return; 255 if ((el.getAttribute('aria-label') || '').trim() !== '') return; 256 if ((el.getAttribute('aria-labelledby') || '').trim() !== '') return; 257 if (!isFormOrFrameElement(el) && getVisibleText(el) !== '') return; 258 259 el.setAttribute('aria-label', title); 260 }); 261 } 262 263 function fixAriaLabelsOverridingVisibleText() { 264 var elements = document.querySelectorAll('a[aria-label], button[aria-label], [role="button"][aria-label], [role="link"][aria-label]'); 265 elements.forEach(function (el) { 266 fixAriaLabelForVisibleText(el); 267 }); 268 } 269 270 function fixAriaLabelForVisibleText(el) { 271 if (isLandmark(el)) return; 272 273 var visibleText = getVisibleText(el); 274 if (visibleText === '') return; 275 276 var label = (el.getAttribute('aria-label') || '').trim(); 277 if (label === '' || labelContainsVisibleText(label, visibleText)) return; 278 279 el.setAttribute('aria-label', visibleText); 280 } 281 282 function labelContainsVisibleText(label, visibleText) { 283 return normalizeAccessibleText(label).indexOf(normalizeAccessibleText(visibleText)) !== -1; 284 } 285 286 function normalizeAccessibleText(text) { 287 return (text || '').replace(/\s+/g, ' ').trim().toLowerCase(); 288 } 289 290 function isFormOrFrameElement(el) {
291 return /^(input|textarea|select|iframe|frame)$/i.test(el.tagName); 292 } 293 294 function fixCurrentPageLinks() { 295 var links = document.querySelectorAll('a[href], area[href], [role="link"]'); 296 links.forEach(function (link) { 297 if ((link.getAttribute('aria-current') || '').trim() !== '') return; 298 if (!pointsToCurrentPage(link)) return; 299 300 link.setAttribute('aria-current', 'page'); 301 }); 302 } 303 304 function pointsToCurrentPage(link) { 305 var href = link.getAttribute('href'); 306 if (!href || href.charAt(0) === '#') return false; 307 308 try { 309 var target = new URL(href, window.location.href); 310 var current = new URL(window.location.href); 311 312 return normalizeUrlPath(target) === normalizeUrlPath(current) && 313 target.hostname === current.hostname && 314 target.protocol === current.protocol; 315 } catch (e) { 316 return false; 317 } 318 } 319 320 function normalizeUrlPath(url) { 321 var path = url.pathname.replace(/\/+$/, ''); 322 return path === '' ? '/' : path; 323 } 324 325 function fixSkipLinks() { 326 var skipLink = document.querySelector('.atm-skip-link[href^="#"]'); 327 if (!skipLink) return; 328 329 var targetId = (skipLink.getAttribute('href') || '').replace(/^#/, ''); 330 var target = targetId ? document.getElementById(targetId) : null; 331 332 if (!target) { 333 target = findSkipLinkTarget(); 334 if (!target) return; 335 336 if (!target.id) { 337 target.id = 'main-content'; 338 } 339 340 skipLink.setAttribute('href', '#' + target.id); 341 } 342 343 if (!isFocusable(target)) { 344 target.setAttribute('tabindex', '-1'); 345 } 346 } 347 348 function findSkipLinkTarget() { 349 var selectors = [ 350 'main', 351 '[role="main"]', 352 '#main-content', 353 '#content', 354 '#primary', 355 '.site-content', 356 '.content-area', 357 '#main', 358 '.main-content', 359 '.entry-content', 360 '.x-main' 361 ]; 362 363 for (var i = 0; i < selectors.length; i++) { 364 var el = document.querySelector(selectors[i]); 365 if (el && hasMeaningfulContent(el)) { 366 return el; 367 } 368 } 369 370 return null; 371 } 372 373 function isFocusable(el) { 374 if (!el || !el.matches) return false; 375 if (el.matches('a[href], area[href], button, input, select, textarea, iframe, object, embed')) return true; 376 var tabindex = el.getAttribute('tabindex'); 377 return tabindex !== null && !isNaN(parseInt(tabindex, 10)); 378 } 379 380 function fixArticleRegions() { 381 var articles = document.querySelectorAll('article'); 382 articles.forEach(function (article) { 383 if (!shouldDemoteArticle(article)) return; 384 385 demoteArticle(article); 386 }); 387 } 388 389 function shouldDemoteArticle(article) { 390 if (article.hasAttribute('data-atm-keep-article')) return false; 391 392 var className = getClassString(article).toLowerCase(); 393 if (/\b(page|type-page|et_pb_post|et-l|x-section|cs-content|elementor|fl-builder|vc_row)\b/.test(className)) { 394 return true; 395 } 396 397 if (/\b(post|type-post|single-post|blog|news|article|hentry)\b/.test(className) && !/\bpage\b/.test(className)) { 398 return false; 399 } 400 401 return false; 402 } 403 404 function fixArticlePageTags(root) { 405 var scope = root || document; 406 var articles = Array.prototype.slice.call(scope.querySelectorAll('article[class]')).filter(isArticlePageTag); 407 408 if (isArticlePageTag(scope)) { 409 articles.unshift(scope); 410 } 411 412 articles.forEach(function (article) { 413 demoteArticle(article); 414 }); 415 } 416 417 function isArticlePageTag(el) { 418 return !!el && el.matches && el.matches('article') && el.classList && el.classList.contains('page'); 419 } 420 421 function watchArticlePageTags() { 422 if (!window.MutationObserver) return; 423 424 var observer = new MutationObserver(function (mutations) { 425 mutations.forEach(function (mutation) { 426 if (mutation.type === 'attributes') { 427 fixArticlePageTags(mutation.target); 428 return; 429 } 430 431 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 432 if (!node || node.nodeType !== 1) return; 433 fixArticlePageTags(node); 434 }); 435 }); 436 }); 437 438 observer.observe(document.body, { 439 attributes: true, 440 attributeFilter: ['class'], 441 childList: true, 442 subtree: true 443 }); 444 } 445 446 function demoteArticle(article) { 447 var replacement = document.createElement('div'); 448 copyAttributes(article, replacement, []); 449 replacement.setAttribute('data-atm-a11y-demoted-article', 'true'); 450 451 while (article.firstChild) { 452 replacement.appendChild(article.firstChild); 453 } 454 455 article.parentNode.replaceChild(replacement, article); 456 } 457 458 // ------------------------------------------------------------------------- 459 // 1. Images without alt text â default "Image" 460 // ------------------------------------------------------------------------- 461 function fixImageAltText() { 462 var imgs = document.querySelectorAll('img');
463 imgs.forEach(function (img) { 464 // Skip images already marked decorative (role="presentation" or aria-hidden) 465 if ( 466 img.getAttribute('role') === 'presentation' || 467 img.getAttribute('aria-hidden') === 'true' || 468 img.hasAttribute('data-uw-rm-ignore') 469 ) { 470 return; 471 } 472 if (!img.hasAttribute('alt') || img.getAttribute('alt') === '') { 473 img.setAttribute('alt', 'Image'); 474 } 475 }); 476 } 477 478 // ------------------------------------------------------------------------- 479 // 2. Social media / icon-only links â aria-label 480 // Handles <a><i class="fa fa-google"></i></a> pattern 481 // ------------------------------------------------------------------------- 482 var iconLabelMap = { 483 'fa-google': 'Visit us on Google', 484 'fa-facebook-f': 'Visit us on Facebook', 485 'fa-facebook': 'Visit us on Facebook', 486 'fa-instagram': 'Visit us on Instagram', 487 'fa-twitter': 'Visit us on Twitter', 488 'fa-youtube': 'Visit us on YouTube', 489 'fa-linkedin': 'Visit us on LinkedIn', 490 'fa-yelp': 'Visit us on Yelp', 491 }; 492 493 function fixIconOnlyLinks() { 494 var links = document.querySelectorAll('a'); 495 links.forEach(function (a) { 496 // Only target links whose visible text is empty 497 if (a.textContent.trim() !== '') return; 498 if (a.getAttribute('aria-label')) return; 499 500 var icon = a.querySelector('i[class]'); 501 if (!icon) return; 502 503 var classes = icon.className.split(' '); 504 for (var c = 0; c < classes.length; c++) { 505 if (iconLabelMap[classes[c]]) { 506 a.setAttribute('aria-label', iconLabelMap[classes[c]]); 507 break; 508 } 509 } 510 511 // Fallback for any unmatched icon link â make at least non-empty 512 if (!a.getAttribute('aria-label')) { 513 a.setAttribute('aria-label', 'External link'); 514 } 515 }); 516 } 517 518 // ------------------------------------------------------------------------- 519 // 3. Empty gallery lightbox links â label + role 520 // <a class="dm-lightbox" style="background-image: url(...)"></a> 521 // ------------------------------------------------------------------------- 522 function fixEmptyLightboxLinks() { 523 var lightboxLinks = document.querySelectorAll('a.dm-lightbox'); 524 lightboxLinks.forEach(function (a, index) { 525 if (!a.getAttribute('aria-label')) { 526 // Try to extract filename for a better label 527 var href = a.getAttribute('href') || ''; 528 var filename = href.split('/').pop().replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ') || ''; 529 var label = filename 530 ? 'View gallery image: ' + filename + ' (opens image)' 531 : 'View gallery image ' + (index + 1) + ' (opens image)'; 532 a.setAttribute('aria-label', label); 533 } 534 if (!a.getAttribute('role')) { 535 a.setAttribute('role', 'button'); 536 } 537 }); 538 } 539 540 // ------------------------------------------------------------------------- 541 // 4. Ambiguous "Read More" links â context-aware aria-label 542 // ------------------------------------------------------------------------- 543 function fixReadMoreLinks() { 544 var links = document.querySelectorAll('a'); 545 links.forEach(function (link, index) { 546 fixGenericLinkContext(link, index); 547 }); 548 } 549 550 function watchReadMoreLinks() { 551 if (!window.MutationObserver) return; 552 553 var observer = new MutationObserver(function (mutations) { 554 mutations.forEach(function (mutation) { 555 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 556 if (!node || node.nodeType !== 1) return; 557 558 if (isLinkElement(node)) { 559 fixGenericLinkContext(node, 0); 560 } 561 562 Array.prototype.slice.call(node.querySelectorAll('a')).forEach(function (link, index) { 563 fixGenericLinkContext(link, index); 564 }); 565 }); 566 }); 567 }); 568 569 observer.observe(document.body, { childList: true, subtree: true }); 570 } 571 572 function fixGenericLinkContext(link, index) { 573 var visibleText = getVisibleText(link); 574 var currentLabel = (link.getAttribute('aria-label') || '').trim(); 575 576 if (!isGenericLinkText(visibleText) && !isGenericLinkText(currentLabel)) return; 577 578 var context = getLinkContextText(link); 579 if (context === '') return; 580 581 link.setAttribute('aria-label', getContextualLinkAction(link, index) + ' ' + context); 582 } 583 584 function isGenericLinkText(text) { 585 var normalized = normalizeAccessibleText(text).replace(/^[\s.:\-|»âº>â¦]+|[\s.:\-|»âº>â¦]+$/g, ''); 586 return /^(read more|learn more|more|view more|find out more|discover more|see more|show more|click here|here|details|view details|more details|continue reading|keep reading)$/i.test(normalized); 587 } 588 589 function getLinkContextText(link) { 590 var title = (link.getAttribute('title') || '').trim(); 591 if (title !== '' && !isGenericLinkText(title)) return normalizeContextText(title); 592 593 var imageLabel = getLabelFromLinkedImage(link); 594 if (imageLabel !== '' && !isGenericLinkText(imageLabel)) return normalizeContextText(imageLabel); 595 596 var svgLabel = getLabelFromSvg(link); 597 if (svgLabel !== '' && !isGenericLinkText(svgLabel)) return normalizeContextText(svgLabel); 598 599 var heading = findPrecedingHeading(link); 600 if (heading) return normalizeContextText(heading); 601 602 var hrefLabel = getContextFromHref(link.getAttribute('href') || ''); 603 if (hrefLabel !== '') return normalizeContextText(hrefLabel); 604 605 var classLabel = getContextFromClassName(link); 606 if (classLabel !== '') return normalizeContextText(classLabel); 607 608 return ''; 609 } 610 611 function getContextualLinkAction(link, index) { 612 var visibleText = normalizeAccessibleText(getVisibleText(link)); 613 var actions = [ 614 'Read more about', 615 'Learn more about', 616 'View details about', 617 'Explore', 618 'Discover', 619 'Find out more about', 620 'See more about', 621 'Open details for', 622 'Continue reading about', 623 'Get more information about', 624 'Review details for' 625 ]; 626 627 if (visibleText === 'learn more') return 'Learn more about'; 628 if (visibleText === 'read more') return 'Read more about'; 629 return actions[Math.abs(hashString((link.getAttribute('href') || '') + ' ' + index)) % actions.length]; 630 } 631 632 function hashString(text) { 633 var hash = 0; 634 for (var i = 0; i < text.length; i++) { 635 hash = ((hash << 5) - hash) + text.charCodeAt(i); 636 hash |= 0; 637 } 638 return hash; 639 } 640 641 function getContextFromHref(href) { 642 if (!href || href === '#') return ''; 643 if (href.indexOf('tel:') === 0) return 'phone number ' + href.replace('tel:', '').replace(/[^\d+]/g, ''); 644 if (href.indexOf('mailto:') === 0) return href.replace('mailto:', '').split('?')[0]; 645 646 try { 647 var url = new URL(href, window.location.href);
648 var segments = url.pathname.split('/').filter(Boolean); 649 var segment = segments.pop() || ''; 650 return segment.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' '); 651 } catch (e) { 652 return href.split('#')[0].split('?')[0].replace(/\/$/, '').split('/').pop().replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' '); 653 } 654 } 655 656 function getContextFromClassName(link) { 657 var classText = (link.className && typeof link.className === 'string') ? link.className : ''; 658 classText = classText.replace(/\b(button|btn|link|read|more|learn|cta|primary|secondary)\b/gi, '').replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim(); 659 return isGenericLinkText(classText) ? '' : classText; 660 } 661 662 function normalizeContextText(text) { 663 return (text || '').replace(/\s+/g, ' ').trim(); 664 } 665 666 function findPrecedingHeading(el) { 667 // Walk up the DOM tree looking for a sibling or ancestor heading 668 var node = el; 669 while (node && node !== document.body) { 670 node = node.parentElement; 671 if (!node) break; 672 var headings = node.querySelectorAll('h1, h2, h3, h4'); 673 if (headings.length) { 674 return headings[headings.length - 1].textContent.trim(); 675 } 676 } 677 return null; 678 } 679 680 // ------------------------------------------------------------------------- 681 // 5. "Book Online" anchor acting as button 682 // ------------------------------------------------------------------------- 683 function fixBookOnlineLinks() { 684 var links = document.querySelectorAll('a'); 685 links.forEach(function (a) { 686 var text = a.textContent.trim().toLowerCase(); 687 if (text !== 'book online') return; 688 689 var href = a.getAttribute('href'); 690 if (href === null || href === '' || href === '#') { 691 a.setAttribute('role', 'button'); 692 if (!a.getAttribute('tabindex')) { 693 a.setAttribute('tabindex', '0'); 694 } 695 // Keyboard activation support 696 a.addEventListener('keydown', function (e) { 697 if (e.key === 'Enter' || e.key === ' ') { 698 e.preventDefault(); 699 a.click(); 700 } 701 }); 702 preventHashJumpForButtonLikeLink(a); 703 } 704 }); 705 } 706 707 function fixPositiveTabindexInForms(root) { 708 var scope = root || document; 709 var elements = Array.prototype.slice.call(scope.querySelectorAll('form[tabindex], form [tabindex], input[tabindex], select[tabindex], textarea[tabindex], button[tabindex]')); 710 711 if (scope.matches && scope.matches('form[tabindex], form [tabindex], input[tabindex], select[tabindex], textarea[tabindex], button[tabindex]')) { 712 elements.unshift(scope); 713 } 714 715 elements.forEach(function (el) { 716 normalizePositiveTabindex(el); 717 }); 718 } 719 720 function watchPositiveTabindexInForms() { 721 if (!window.MutationObserver) return; 722 723 var observer = new MutationObserver(function (mutations) { 724 mutations.forEach(function (mutation) { 725 if (mutation.type === 'attributes') { 726 normalizePositiveTabindex(mutation.target); 727 return; 728 } 729 730 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 731 if (!node || node.nodeType !== 1) return; 732 fixPositiveTabindexInForms(node); 733 }); 734 }); 735 }); 736 737 observer.observe(document.body, { 738 attributes: true, 739 attributeFilter: ['tabindex'], 740 childList: true, 741 subtree: true 742 }); 743 } 744 745 function normalizePositiveTabindex(el) { 746 if (!isFormTabindexElement(el)) return; 747 748 var tabindex = parseInt(el.getAttribute('tabindex'), 10); 749 if (!isNaN(tabindex) && tabindex > 0) { 750 el.setAttribute('tabindex', '0'); 751 } 752 } 753 754 function isFormTabindexElement(el) { 755 if (!el || !el.matches || !el.hasAttribute('tabindex')) return false; 756 return el.matches('form, input, select, textarea, button') || !!el.closest('form'); 757 } 758 759 // ------------------------------------------------------------------------- 760 // 6. Button-like anchors used by builders, sliders, popups, and Cornerstone 761 // ------------------------------------------------------------------------- 762 function fixButtonLikeLinks() { 763 var links = document.querySelectorAll('a'); 764 links.forEach(function (link) { 765 fixButtonLikeLink(link); 766 }); 767 } 768 769 function watchButtonLikeLinks() { 770 if (!window.MutationObserver) return; 771 772 var observer = new MutationObserver(function (mutations) { 773 mutations.forEach(function (mutation) {
774 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 775 if (!node || node.nodeType !== 1) return; 776 777 if (node.matches && node.matches('a')) { 778 fixButtonLikeLink(node); 779 } 780 781 Array.prototype.slice.call(node.querySelectorAll('a')).forEach(function (link) { 782 fixButtonLikeLink(link); 783 }); 784 }); 785 }); 786 }); 787 788 observer.observe(document.body, { childList: true, subtree: true }); 789 } 790 791 function fixButtonLikeLink(link) { 792 if (isFixEnabled('nav_menu_button_roles') && isNavMenuLink(link)) { 793 removeRoleButtonFromNavMenuLink(link); 794 return; 795 } 796 797 if (!isButtonLikeLink(link)) return; 798 799 link.setAttribute('role', 'button'); 800 if (!link.getAttribute('tabindex')) { 801 link.setAttribute('tabindex', '0'); 802 } 803 804 var currentLabel = (link.getAttribute('aria-label') || '').trim(); 805 if (currentLabel === '' || currentLabel.toLowerCase() === 'read more') { 806 link.setAttribute('aria-label', getButtonLikeLinkLabel(link)); 807 } 808 809 if (isFixEnabled('aria_label_visible_text')) { 810 fixAriaLabelForVisibleText(link); 811 } 812 813 if (link.getAttribute('data-atm-a11y-button-keydown') === '1') return; 814 link.setAttribute('data-atm-a11y-button-keydown', '1'); 815 816 preventHashJumpForButtonLikeLink(link); 817 818 link.addEventListener('keydown', function (e) { 819 if (e.key !== 'Enter' && e.key !== ' ') return; 820 821 e.preventDefault(); 822 link.click(); 823 }); 824 } 825 826 function fixXAnchorButtons(root) { 827 var scope = root || document; 828 var selector = [ 829 '.x-anchor-button', 830 '[class*="x-anchor-button"]' 831 ].join(','); 832 var buttons = Array.prototype.slice.call(scope.querySelectorAll(selector)); 833 834 if (scope.matches && scope.matches(selector)) { 835 buttons.unshift(scope); 836 } 837 838 buttons.forEach(function (button) { 839 button.setAttribute('role', 'button'); 840 841 if (!button.getAttribute('tabindex')) { 842 button.setAttribute('tabindex', '0'); 843 } 844 845 if ((button.getAttribute('aria-label') || '').trim() === '' && getVisibleText(button) === '') { 846 button.setAttribute('aria-label', getButtonLikeLinkLabel(button)); 847 } 848 849 preventHashJumpForButtonLikeLink(button); 850 addRoleButtonKeyboardSupport(button); 851 }); 852 } 853 854 function watchXAnchorButtons() { 855 if (!window.MutationObserver) return; 856 857 var observer = new MutationObserver(function (mutations) { 858 mutations.forEach(function (mutation) { 859 if (mutation.type === 'attributes') { 860 fixXAnchorButtons(mutation.target); 861 return; 862 } 863 864 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 865 if (!node || node.nodeType !== 1) return; 866 fixXAnchorButtons(node); 867 }); 868 }); 869 }); 870 871 observer.observe(document.body, { 872 attributes: true, 873 attributeFilter: ['class'], 874 childList: true, 875 subtree: true 876 }); 877 } 878 879 function enableRoleButtonKeyboardSupport(root) { 880 var scope = root || document; 881 var buttons = Array.prototype.slice.call(scope.querySelectorAll('[role="button"]')); 882 883 if (scope.matches && scope.matches('[role="button"]')) { 884 buttons.unshift(scope); 885 } 886 887 buttons.forEach(addRoleButtonKeyboardSupport); 888 } 889 890 function watchRoleButtonKeyboardSupport() { 891 if (!window.MutationObserver) return; 892 893 var observer = new MutationObserver(function (mutations) { 894 mutations.forEach(function (mutation) { 895 if (mutation.type === 'attributes') { 896 enableRoleButtonKeyboardSupport(mutation.target); 897 return; 898 } 899 900 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 901 if (!node || node.nodeType !== 1) return; 902 enableRoleButtonKeyboardSupport(node); 903 }); 904 }); 905 }); 906 907 observer.observe(document.body, { 908 attributes: true, 909 attributeFilter: ['role'], 910 childList: true, 911 subtree: true 912 }); 913 } 914 915 function addRoleButtonKeyboardSupport(button) { 916 if (button.getAttribute('data-atm-a11y-role-button-keydown') === '1') return; 917 if (button.getAttribute('data-atm-a11y-button-keydown') === '1') { 918 button.setAttribute('data-atm-a11y-role-button-keydown', '1'); 919 return; 920 } 921 922 button.setAttribute('data-atm-a11y-role-button-keydown', '1'); 923 924 button.addEventListener('keydown', function (e) { 925 if (e.key !== 'Enter' && e.key !== ' ') return; 926 927 e.preventDefault(); 928 button.click(); 929 }); 930 } 931 932 function removeXAnchorToggleAriaLabels(root) { 933 var scope = root || document; 934 var selector = [ 935 '.x-anchor-toggle[aria-label]', 936 '.x-anchor-toggle [aria-label]', 937 '[class*="x-anchor-toggle"][aria-label]', 938 '[class*="x-anchor-toggle"] [aria-label]' 939 ].join(','); 940 var toggles = Array.prototype.slice.call(scope.querySelectorAll(selector)); 941 942 if (scope.matches && scope.matches(selector)) { 943 toggles.unshift(scope); 944 } 945
946 toggles.forEach(function (toggle) { 947 toggle.removeAttribute('aria-label'); 948 }); 949 } 950 951 function scheduleXAnchorToggleAriaLabelCleanup() { 952 [100, 500, 1500, 3000].forEach(function (delay) { 953 window.setTimeout(removeXAnchorToggleAriaLabels, delay); 954 }); 955 } 956 957 function watchXAnchorToggleAriaLabels() { 958 if (!window.MutationObserver) return; 959 960 var observer = new MutationObserver(function (mutations) { 961 mutations.forEach(function (mutation) { 962 if (mutation.type === 'attributes') { 963 removeXAnchorToggleAriaLabels(mutation.target); 964 return; 965 } 966 967 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 968 if (!node || node.nodeType !== 1) return; 969 removeXAnchorToggleAriaLabels(node); 970 }); 971 }); 972 }); 973 974 observer.observe(document.body, { 975 attributes: true, 976 attributeFilter: ['aria-label', 'class'], 977 childList: true, 978 subtree: true 979 }); 980 } 981 982 function fixEmptyHashLinks(root) { 983 var scope = root || document; 984 var links = Array.prototype.slice.call(scope.querySelectorAll('a[href="#"]')); 985 986 if (scope.matches && scope.matches('a[href="#"]')) { 987 links.unshift(scope); 988 } 989 990 links.forEach(function (link) { 991 link.setAttribute('href', 'javascript:void(0)'); 992 }); 993 } 994 995 function watchEmptyHashLinks() { 996 if (!window.MutationObserver) return; 997 998 var observer = new MutationObserver(function (mutations) { 999 mutations.forEach(function (mutation) { 1000 if (mutation.type === 'attributes') { 1001 fixEmptyHashLinks(mutation.target); 1002 return; 1003 } 1004 1005 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1006 if (!node || node.nodeType !== 1) return; 1007 fixEmptyHashLinks(node); 1008 }); 1009 }); 1010 }); 1011 1012 observer.observe(document.body, { 1013 childList: true, 1014 subtree: true, 1015 attributes: true, 1016 attributeFilter: ['href'] 1017 }); 1018 } 1019 1020 function wrapXMenuNavigation(root) { 1021 var scope = root || document; 1022 var menus = Array.prototype.slice.call(scope.querySelectorAll('ul.x-menu')); 1023 1024 if (scope.matches && scope.matches('ul.x-menu')) { 1025 menus.unshift(scope); 1026 } 1027 1028 menus.forEach(function (menu) { 1029 if (menu.closest('nav')) return; 1030 if (!menu.parentNode) return; 1031 1032 var nav = document.createElement('nav'); 1033 nav.setAttribute('aria-label', 'Main Navigation'); 1034 menu.parentNode.insertBefore(nav, menu); 1035 nav.appendChild(menu); 1036 }); 1037 } 1038 1039 function watchXMenuNavigation() { 1040 if (!window.MutationObserver) return; 1041 1042 var observer = new MutationObserver(function (mutations) { 1043 mutations.forEach(function (mutation) { 1044 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1045 if (!node || node.nodeType !== 1) return; 1046 wrapXMenuNavigation(node); 1047 }); 1048 }); 1049 }); 1050 1051 observer.observe(document.body, { childList: true, subtree: true }); 1052 } 1053 1054 function fixCornerstoneAccordionHeaders(root) { 1055 var scope = root || document; 1056 var headers = Array.prototype.slice.call(scope.querySelectorAll('.x-acc-header')); 1057 1058 if (scope.matches && scope.matches('.x-acc-header')) { 1059 headers.unshift(scope); 1060 } 1061 1062 headers.forEach(function (header) { 1063 if ((header.getAttribute('role') || '').trim().toLowerCase() === 'button') { 1064 header.removeAttribute('role'); 1065 } 1066 1067 if ((header.getAttribute('type') || '').trim().toLowerCase() === 'button') { 1068 header.removeAttribute('type'); 1069 } 1070 1071 header.removeAttribute('aria-expanded'); 1072 header.removeAttribute('aria-controls'); 1073 }); 1074 } 1075 1076 function removeXAccHeaderRoles(root) { 1077 var scope = root || document; 1078 var headers = Array.prototype.slice.call(scope.querySelectorAll('.x-acc-header[role]')); 1079 1080 if (scope.matches && scope.matches('.x-acc-header[role]')) { 1081 headers.unshift(scope); 1082 } 1083 1084 headers.forEach(function (header) { 1085 header.removeAttribute('role'); 1086 }); 1087 } 1088 1089 function watchXAccHeaderRoles() { 1090 if (!window.MutationObserver) return; 1091 1092 var observer = new MutationObserver(function (mutations) { 1093 mutations.forEach(function (mutation) { 1094 if (mutation.type === 'attributes') { 1095 removeXAccHeaderRoles(mutation.target); 1096 return; 1097 } 1098
1099 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1100 if (!node || node.nodeType !== 1) return; 1101 removeXAccHeaderRoles(node); 1102 }); 1103 }); 1104 }); 1105 1106 observer.observe(document.body, { 1107 attributes: true, 1108 attributeFilter: ['role', 'class'], 1109 childList: true, 1110 subtree: true 1111 }); 1112 } 1113 1114 function removeXAccItemChildDivRoles(root) { 1115 var scope = root || document; 1116 var childDivs = Array.prototype.slice.call(scope.querySelectorAll('.x-acc-item > div[role]')); 1117 1118 if (scope.matches && scope.matches('.x-acc-item > div[role]')) { 1119 childDivs.unshift(scope); 1120 } 1121 1122 childDivs.forEach(function (childDiv) { 1123 childDiv.removeAttribute('role'); 1124 }); 1125 } 1126 1127 function watchXAccItemChildDivRoles() { 1128 if (!window.MutationObserver) return; 1129 1130 var observer = new MutationObserver(function (mutations) { 1131 mutations.forEach(function (mutation) { 1132 if (mutation.type === 'attributes') { 1133 removeXAccItemChildDivRoles(mutation.target); 1134 if (mutation.target.parentElement) { 1135 removeXAccItemChildDivRoles(mutation.target.parentElement); 1136 } 1137 return; 1138 } 1139 1140 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1141 if (!node || node.nodeType !== 1) return; 1142 removeXAccItemChildDivRoles(node); 1143 if (node.parentElement) { 1144 removeXAccItemChildDivRoles(node.parentElement); 1145 } 1146 }); 1147 }); 1148 }); 1149 1150 observer.observe(document.body, { 1151 attributes: true, 1152 attributeFilter: ['role', 'class'], 1153 childList: true, 1154 subtree: true 1155 }); 1156 } 1157 1158 function watchCornerstoneAccordionHeaders() { 1159 if (!window.MutationObserver) return; 1160 1161 var observer = new MutationObserver(function (mutations) { 1162 mutations.forEach(function (mutation) { 1163 if (mutation.type === 'attributes') { 1164 fixCornerstoneAccordionHeaders(mutation.target); 1165 return; 1166 } 1167 1168 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1169 if (!node || node.nodeType !== 1) return; 1170 fixCornerstoneAccordionHeaders(node); 1171 }); 1172 }); 1173 }); 1174 1175 observer.observe(document.body, { 1176 childList: true, 1177 subtree: true, 1178 attributes: true, 1179 attributeFilter: ['role', 'type', 'aria-expanded', 'aria-controls', 'class'] 1180 }); 1181 } 1182 1183 function isButtonLikeLink(link) { 1184 var href = ((link.getAttribute('href') || '') + '').trim().toLowerCase(); 1185 var className = getClassString(link).toLowerCase(); 1186 1187 if (href === '' || href === '#' || href.indexOf('javascript:') === 0) return true; 1188 1189 return /(?:popup|modal|lightbox|trigger|toggle|accordion|tab|carousel|slider|slide|swiper|slick|splide|owl|et-pb-arrow|elementor-swiper-button|elementor-menu-toggle|x-anchor|cs-|cornerstone)/i.test(className); 1190 } 1191 1192 function removeNavMenuButtonRoles() { 1193 var links = document.querySelectorAll('.site-menu a[role="button"], .nav-menu a[role="button"], .x-menu a[role="button"]'); 1194 links.forEach(function (link) { 1195 removeRoleButtonFromNavMenuLink(link); 1196 }); 1197 } 1198 1199 function watchNavMenuButtonRoles() { 1200 if (!window.MutationObserver) return; 1201 1202 var observer = new MutationObserver(function (mutations) { 1203 mutations.forEach(function (mutation) { 1204 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1205 if (!node || node.nodeType !== 1) return; 1206 1207 if (node.matches && node.matches('.site-menu a[role="button"], .nav-menu a[role="button"], .x-menu a[role="button"]')) { 1208 removeRoleButtonFromNavMenuLink(node); 1209 } 1210 1211 Array.prototype.slice.call(node.querySelectorAll('.site-menu a[role="button"], .nav-menu a[role="button"], .x-menu a[role="button"]')).forEach(function (link) { 1212 removeRoleButtonFromNavMenuLink(link); 1213 }); 1214 }); 1215 }); 1216 }); 1217 1218 observer.observe(document.body, { childList: true, subtree: true }); 1219 } 1220 1221 function isNavMenuLink(link) { 1222 return !!(link.closest && link.closest('.site-menu, .nav-menu, .x-menu')); 1223 } 1224 1225 function removeRoleButtonFromNavMenuLink(link) { 1226 if (!isNavMenuLink(link)) return; 1227 if ((link.getAttribute('role') || '').trim().toLowerCase() === 'button') { 1228 link.removeAttribute('role'); 1229 } 1230 } 1231 1232 function preventHashJumpForButtonLikeLink(link) { 1233 if (link.getAttribute('data-atm-a11y-prevent-hash-jump') === '1') return; 1234 1235 var href = ((link.getAttribute('href') || '') + '').trim().toLowerCase(); 1236 if (href !== '#') return; 1237 1238 link.setAttribute('data-atm-a11y-prevent-hash-jump', '1'); 1239 link.addEventListener('click', function () { 1240 var scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0; 1241 var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0; 1242 1243 armEmptyHashJumpGuard(scrollX, scrollY); 1244 restoreHashLinkScroll(scrollX, scrollY); 1245 }); 1246 } 1247 1248 function enableEmptyHashJumpGuard() { 1249 if (document.documentElement.getAttribute('data-atm-a11y-empty-hash-guard') === '1') return; 1250 document.documentElement.setAttribute('data-atm-a11y-empty-hash-guard', '1'); 1251 1252 document.addEventListener('click', function () { 1253 if (!hashJumpGuardScroll || Date.now() > hashJumpGuardUntil) return; 1254 1255 var scrollX = hashJumpGuardScroll.x; 1256 var scrollY = hashJumpGuardScroll.y; 1257 window.setTimeout(function () { 1258 restoreUnexpectedTopJump(scrollX, scrollY); 1259 }, 0); 1260 window.setTimeout(function () { 1261 restoreUnexpectedTopJump(scrollX, scrollY); 1262 }, 80); 1263 window.setTimeout(function () { 1264 restoreUnexpectedTopJump(scrollX, scrollY); 1265 }, 300); 1266 }, true); 1267 } 1268 1269 function armEmptyHashJumpGuard(scrollX, scrollY) { 1270 hashJumpGuardScroll = { x: scrollX, y: scrollY }; 1271 hashJumpGuardUntil = Date.now() + 5000; 1272 } 1273 1274 function restoreUnexpectedTopJump(scrollX, scrollY) { 1275 var currentY = window.pageYOffset || document.documentElement.scrollTop || 0; 1276 if (scrollY < 80 || currentY > 5) return; 1277 if (window.location.hash !== '' && window.location.hash !== '#') return; 1278 1279 window.scrollTo(scrollX, scrollY); 1280 removeEmptyHashFromUrl(); 1281 } 1282 1283 function restoreHashLinkScroll(scrollX, scrollY) { 1284 var restore = function () { 1285 if (window.location.hash === '' || window.location.hash === '#') { 1286 window.scrollTo(scrollX, scrollY); 1287 removeEmptyHashFromUrl(); 1288 } 1289 }; 1290 1291 window.setTimeout(restore, 0); 1292 window.setTimeout(restore, 50); 1293 window.setTimeout(restore, 250); 1294 1295 if (window.requestAnimationFrame) { 1296 window.requestAnimationFrame(restore); 1297 } 1298 } 1299 1300 function removeEmptyHashFromUrl() { 1301 if (!window.history || !window.history.replaceState) return; 1302 if (window.location.hash !== '' && window.location.hash !== '#') return; 1303 1304 var cleanUrl = window.location.href.replace(/#$/, ''); 1305 if (cleanUrl !== window.location.href) { 1306 window.history.replaceState(null, document.title, cleanUrl); 1307 } 1308 } 1309 1310 function getButtonLikeLinkLabel(link) { 1311 var className = getClassString(link).toLowerCase(); 1312 var text = link.textContent.replace(/\s+/g, ' ').trim(); 1313 1314 if (/(?:next|arrow-next|slick-next|swiper-button-next|splide__arrow--next|owl-next)/i.test(className)) { 1315 return 'Next Slide'; 1316 } 1317 if (/(?:prev|previous|arrow-prev|slick-prev|swiper-button-prev|splide__arrow--prev|owl-prev)/i.test(className)) { 1318 return 'Previous Slide'; 1319 } 1320 if (/(?:close|dismiss)/i.test(className + ' ' + text)) { 1321 return 'Close'; 1322 } 1323 if (/(?:menu|nav-toggle|hamburger|elementor-menu-toggle)/i.test(className)) { 1324 return 'Toggle menu'; 1325 } 1326 if (text) { 1327 return text; 1328 } 1329 if (/(?:popup|modal|book|appointment|newpopup)/i.test(className + ' ' + text)) { 1330 return 'Book Appointment'; 1331 } 1332 1333 return 'Button'; 1334 } 1335 1336 function getClassString(el) { 1337 return (el.getAttribute('class') || '').toString(); 1338 } 1339 1340 // ------------------------------------------------------------------------- 1341 // 7. Modal trigger links with tabindex="-1" and href="#" 1342 // <a href="#" class="dm_classic_modal" tabindex="-1">read more</a> 1343 // -------------------------------------------------------------------------
1344 function fixModalLinks() { 1345 var modals = document.querySelectorAll('a.dm_classic_modal'); 1346 modals.forEach(function (a) { 1347 a.setAttribute('role', 'button'); 1348 if (a.getAttribute('tabindex') === '-1') { 1349 a.setAttribute('tabindex', '0'); 1350 } 1351 a.addEventListener('keydown', function (e) { 1352 if (e.key === 'Enter' || e.key === ' ') { 1353 e.preventDefault(); 1354 a.click(); 1355 } 1356 }); 1357 preventHashJumpForButtonLikeLink(a); 1358 }); 1359 } 1360 1361 // ------------------------------------------------------------------------- 1362 // 7. Slick carousel â fix broken ARIA tab roles 1363 // The Slick slider incorrectly uses role="tablist/tab/tabpanel". 1364 // Replace with the ARIA carousel pattern (group + roledescription). 1365 // ------------------------------------------------------------------------- 1366 function fixSlickCarousel() { 1367 // Fix dot navigation (was role="tablist") 1368 var dotLists = document.querySelectorAll('ul.slick-dots[role="tablist"]'); 1369 dotLists.forEach(function (ul) { 1370 ul.removeAttribute('role'); 1371 ul.setAttribute('aria-label', 'Slide navigation'); 1372 }); 1373 1374 // Fix dot buttons (was role="tab") 1375 var dotButtons = document.querySelectorAll('ul.slick-dots button[role="tab"]'); 1376 dotButtons.forEach(function (btn) { 1377 btn.removeAttribute('role'); 1378 btn.removeAttribute('aria-selected'); 1379 }); 1380 1381 // Fix slides (was role="tabpanel") 1382 var slides = document.querySelectorAll('.slick-slide[role="tabpanel"]'); 1383 slides.forEach(function (slide, index) { 1384 slide.removeAttribute('role'); 1385 slide.setAttribute('aria-roledescription', 'slide'); 1386 if (!slide.getAttribute('aria-label')) { 1387 slide.setAttribute('aria-label', 'Slide ' + (index + 1)); 1388 } 1389 }); 1390 1391 // Wrap the slider track in a group if not already 1392 var sliders = document.querySelectorAll('.slick-slider'); 1393 sliders.forEach(function (slider) { 1394 if (!slider.getAttribute('role')) { 1395 slider.setAttribute('role', 'region'); 1396 slider.setAttribute('aria-roledescription', 'carousel'); 1397 slider.setAttribute('aria-label', 'Image slideshow'); 1398 } 1399 }); 1400 } 1401 1402 function waitForSlick() { 1403 if (typeof jQuery === 'undefined') return; 1404 jQuery(document).on('init.slick', function () { 1405 fixSlickCarousel(); 1406 }); 1407 } 1408 1409 // ------------------------------------------------------------------------- 1410 // 8. Hidden modals â add aria-hidden when visually hidden 1411 // ------------------------------------------------------------------------- 1412 function fixHiddenModals() { 1413 var hiddenModals = document.querySelectorAll('.hidde-modals'); 1414 hiddenModals.forEach(function (modal) { 1415 var style = window.getComputedStyle(modal); 1416 if (style.display === 'none' || style.visibility === 'hidden') { 1417 modal.setAttribute('aria-hidden', 'true'); 1418 } 1419 1420 // Watch for visibility changes (e.g. when modal opens via JS) 1421 var observer = new MutationObserver(function () { 1422 var s = window.getComputedStyle(modal); 1423 if (s.display === 'none' || s.visibility === 'hidden') { 1424 modal.setAttribute('aria-hidden', 'true'); 1425 } else { 1426 modal.removeAttribute('aria-hidden'); 1427 // Move focus into the modal 1428 var focusable = modal.querySelector('button, [href], input, [tabindex]:not([tabindex="-1"])'); 1429 if (focusable) focusable.focus(); 1430 } 1431 }); 1432 1433 observer.observe(modal, { attributes: true, attributeFilter: ['style', 'class'] }); 1434 }); 1435 } 1436 1437 // ------------------------------------------------------------------------- 1438 // 9. Decorative icon <i> elements inside links â aria-hidden 1439 // ------------------------------------------------------------------------- 1440 function fixIconElements() { 1441 var icons = document.querySelectorAll('a i[class*="fa "], a i[class*=" fa-"]'); 1442 icons.forEach(function (icon) { 1443 if (!icon.getAttribute('aria-hidden')) { 1444 icon.setAttribute('aria-hidden', 'true'); 1445 } 1446 }); 1447 } 1448 1449 // ------------------------------------------------------------------------- 1450 // 10. Secondary navigation div â remove incorrect role="navigation" 1451 // (PHP handles this on first render;
1451 JS handles cache/CDN edge cases) 1452 // ------------------------------------------------------------------------- 1453 function fixPageLandmarks() { 1454 if (isFixEnabled('main_landmark')) { 1455 ensureLandmarkRole('header, .site-header, #masthead', 'banner'); 1456 } 1457 if (isFixEnabled('contentinfo_landmark')) { 1458 fixContentinfoLandmarks(); 1459 } 1460 1461 var main = findMainLandmark(); 1462 if (!main && isFixEnabled('main_landmark')) { 1463 main = findMainContentContainer(); 1464 if (main) { 1465 main.setAttribute('role', 'main'); 1466 } 1467 } 1468 1469 if (main) { 1470 if (isFixEnabled('main_landmark')) { 1471 main = repairMainLandmarkContent(main); 1472 } 1473 if (isFixEnabled('landmark_nesting')) { 1474 removeParentLandmarks(main); 1475 } 1476 if (isFixEnabled('main_landmark') && !main.id) { 1477 main.id = 'main-content'; 1478 } 1479 updateSkipLinkTarget(main.id); 1480 } 1481 1482 if (isFixEnabled('main_landmark')) { 1483 wrapUnlandmarkedBodyContent(); 1484 } 1485 } 1486 1487 function changeMainContentDivToMain(root) { 1488 var scope = root || document; 1489 var mainContent = null; 1490 1491 if (scope.matches && scope.matches('div#main-content')) { 1492 mainContent = scope; 1493 } else if (scope.querySelector) { 1494 mainContent = scope.querySelector('div#main-content'); 1495 } 1496 1497 if (!mainContent) return; 1498 1499 var replacement = replaceElementTag(mainContent, 'main', []); 1500 updateSkipLinkTarget(replacement.id); 1501 } 1502 1503 function watchMainContentDivToMain() { 1504 if (!window.MutationObserver) return; 1505 1506 var observer = new MutationObserver(function (mutations) { 1507 mutations.forEach(function (mutation) { 1508 if (mutation.type === 'attributes') { 1509 changeMainContentDivToMain(mutation.target); 1510 return; 1511 } 1512 1513 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1514 if (!node || node.nodeType !== 1) return; 1515 changeMainContentDivToMain(node); 1516 }); 1517 }); 1518 }); 1519 1520 observer.observe(document.body, { 1521 attributes: true, 1522 attributeFilter: ['id'], 1523 childList: true, 1524 subtree: true 1525 }); 1526 } 1527 1528 function updateSkipLinkTarget(mainId) { 1529 var skipLink = document.querySelector('.atm-skip-link'); 1530 if (skipLink && mainId) { 1531 skipLink.setAttribute('href', '#' + mainId); 1532 } 1533 } 1534 1535 function repairMainLandmarkContent(main) { 1536 if (!mainContainsRepeatedLandmarks(main)) { 1537 return main; 1538 } 1539 1540 var betterMain = findUniqueContentInsideMain(main); 1541 if (betterMain && betterMain !== main) { 1542 moveMainLandmark(main, betterMain); 1543 return betterMain; 1544 } 1545 1546 demoteRepeatedLandmarksInsideMain(main); 1547 return main; 1548 } 1549 1550 function mainContainsRepeatedLandmarks(main) { 1551 return !!main.querySelector('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"]'); 1552 } 1553 1554 function findUniqueContentInsideMain(main) { 1555 var selectors = [ 1556 '.entry-content', 1557 '.page-content', 1558 '.post-content', 1559 '.site-main', 1560 '#primary', 1561 '.content-area', 1562 '.et_builder_inner_content', 1563 '.elementor', 1564 '.elementor-section-wrap', 1565 '.wp-block-post-content' 1566 ]; 1567 1568 for (var i = 0; i < selectors.length; i++) { 1569 var candidate = main.querySelector(selectors[i]); 1570 if (!candidate || candidate === main) continue; 1571 if (isInsideRepeatedLandmark(candidate)) continue; 1572 if (mainContainsRepeatedLandmarks(candidate)) continue; 1573 if (!hasMeaningfulContent(candidate)) continue; 1574 1575 return candidate; 1576 } 1577 1578 return null; 1579 } 1580 1581 function moveMainLandmark(oldMain, newMain) { 1582 var oldId = oldMain.id; 1583 oldMain.removeAttribute('role'); 1584 1585 if (oldMain.tagName.toLowerCase() === 'main') { 1586 replaceElementTag(oldMain, 'div', ['role', 'id']); 1587 } 1588 1589 if (newMain.tagName.toLowerCase() !== 'main') { 1590 newMain.setAttribute('role', 'main'); 1591 } 1592 1593 if (!newMain.id) { 1594 newMain.id = oldId || 'main-content'; 1595 } 1596 } 1597 1598 function demoteRepeatedLandmarksInsideMain(main) { 1599 var repeated = main.querySelectorAll('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"]'); 1600 repeated.forEach(function (el) { 1601 if (el.closest('[data-atm-a11y-preserve-landmark]')) return; 1602 if (!isBuilderOrThemeRepeatedElement(el)) return; 1603 1604 el.removeAttribute('role'); 1605 if (/^(header|footer|nav)$/i.test(el.tagName)) { 1606 replaceElementTag(el, 'div', ['role']); 1607 } 1608 }); 1609 } 1610 1611 function isBuilderOrThemeRepeatedElement(el) { 1612 var className = getClassString(el).toLowerCase(); 1613 var id = (el.id || '').toLowerCase(); 1614 var text = (id + ' ' + className); 1615 1616 return /(?:header|footer|nav|menu|masthead|site-header|site-footer|et-l--header|et-l--footer|elementor-location-header|elementor-location-footer|x-masthead|x-navbar|cs-header)/i.test(text); 1617 } 1618 1619 function isInsideRepeatedLandmark(el) { 1620 return !!el.closest('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"]'); 1621 } 1622 1623 function replaceElementTag(el, tagName, excludedAttrs) { 1624 var replacement = document.createElement(tagName); 1625 copyAttributes(el, replacement, excludedAttrs || []); 1626 1627 while (el.firstChild) { 1628 replacement.appendChild(el.firstChild); 1629 } 1630 1631 el.parentNode.replaceChild(replacement, el); 1632 return replacement; 1633 } 1634 1635 function removeParentLandmarks(main) { 1636 var parentLandmark = getParentLandmark(main); 1637 1638 while (parentLandmark) { 1639 if (!canDemoteLandmark(parentLandmark)) { 1640 break; 1641 } 1642 1643 demoteLandmark(parentLandmark); 1644 parentLandmark = getParentLandmark(main); 1645 } 1646 } 1647 1648 function getParentLandmark(el) { 1649 if (!el.parentElement) return null; 1650 return el.parentElement.closest('main, aside, section[aria-label], section[aria-labelledby], [role="main"], [role="complementary"], [role="region"]'); 1651 } 1652 1653 function canDemoteLandmark(el) { 1654 if (!el || el === document.body) return false;
1655 if (el.matches('header, footer, nav, [role="banner"], [role="contentinfo"], [role="navigation"], [role="search"]')) return false; 1656 return true; 1657 } 1658 1659 function demoteLandmark(el) { 1660 if (el.tagName.toLowerCase() === 'main') { 1661 var replacement = document.createElement('div'); 1662 copyAttributes(el, replacement, ['role']); 1663 1664 while (el.firstChild) { 1665 replacement.appendChild(el.firstChild); 1666 } 1667 1668 el.parentNode.replaceChild(replacement, el); 1669 return; 1670 } 1671 1672 el.removeAttribute('role'); 1673 if (el.tagName.toLowerCase() === 'section') { 1674 el.removeAttribute('aria-label'); 1675 el.removeAttribute('aria-labelledby'); 1676 } 1677 } 1678 1679 function copyAttributes(from, to, excludedAttrs) { 1680 Array.prototype.slice.call(from.attributes).forEach(function (attr) { 1681 if (excludedAttrs.indexOf(attr.name) === -1) { 1682 to.setAttribute(attr.name, attr.value); 1683 } 1684 }); 1685 } 1686 1687 function ensureLandmarkRole(selector, role) { 1688 var el = document.querySelector(selector); 1689 if (!el || isInsideLandmark(el)) return; 1690 1691 if (!isNativeLandmark(el, role) && !el.getAttribute('role')) { 1692 el.setAttribute('role', role); 1693 } 1694 } 1695 1696 function fixContentinfoLandmarks() { 1697 var globalFooter = findGlobalFooterElement(); 1698 var contentinfos = document.querySelectorAll('footer, [role="contentinfo"]'); 1699 1700 contentinfos.forEach(function (el) { 1701 if (el === globalFooter) return; 1702 1703 if (el.getAttribute('role') === 'contentinfo') { 1704 el.removeAttribute('role'); 1705 } 1706 1707 if (el.tagName.toLowerCase() === 'footer') { 1708 var replacement = replaceElementTag(el, 'div', ['role']); 1709 replacement.setAttribute('data-atm-a11y-demoted-footer', 'true'); 1710 } 1711 }); 1712 1713 if (!globalFooter) return; 1714 if (globalFooter.tagName.toLowerCase() !== 'footer' && !globalFooter.getAttribute('role')) { 1715 globalFooter.setAttribute('role', 'contentinfo'); 1716 } 1717 } 1718 1719 function findGlobalFooterElement() { 1720 var selectors = [ 1721 '#site-footer', 1722 '#colophon', 1723 '.site-footer', 1724 '.footer-main', 1725 '.footer-wrapper', 1726 '.elementor-location-footer', 1727 '.et-l--footer', 1728 '.x-colophon', 1729 '.x-site-footer', 1730 '.cs-footer', 1731 'body > footer' 1732 ]; 1733 1734 for (var i = 0; i < selectors.length; i++) { 1735 var footer = document.querySelector(selectors[i]); 1736 if (footer && isGlobalFooterCandidate(footer)) { 1737 return footer; 1738 } 1739 } 1740 1741 return null; 1742 } 1743 1744 function isGlobalFooterCandidate(el) { 1745 if (el.closest('main, article, aside, section, [role="main"], [role="article"], [role="complementary"], [role="region"]')) { 1746 return false; 1747 } 1748 1749 var classAndId = ((el.id || '') + ' ' + getClassString(el)).toLowerCase(); 1750 if (/(?:site-footer|colophon|global-footer|footer-main|footer-wrapper|elementor-location-footer|et-l--footer|x-colophon|x-site-footer|cs-footer)/i.test(classAndId)) { 1751 return true; 1752 } 1753 1754 return el.parentElement === document.body && el.tagName.toLowerCase() === 'footer'; 1755 } 1756 1757 function findMainLandmark() { 1758 return document.querySelector('main, [role="main"]'); 1759 } 1760 1761 function findMainContentContainer() { 1762 var selectors = [ 1763 '#main-content', 1764 '#content', 1765 '.site-content', 1766 '#primary', 1767 '.content-area', 1768 '#main', 1769 '.main-content' 1770 ]; 1771 1772 for (var i = 0; i < selectors.length; i++) { 1773 var el = document.querySelector(selectors[i]); 1774 if (el && !isInsideLandmark(el)) { 1775 return el; 1776 } 1777 } 1778 1779 return null; 1780 } 1781 1782 function wrapUnlandmarkedBodyContent() { 1783 var wrapper = null; 1784 var children = Array.prototype.slice.call(document.body.children); 1785 1786 children.forEach(function (child) { 1787 if (!needsLandmarkWrapper(child)) { 1788 wrapper = null; 1789 return; 1790 } 1791 1792 if (!wrapper) { 1793 wrapper = document.createElement('div'); 1794 wrapper.setAttribute('role', 'region'); 1795 wrapper.setAttribute('aria-label', 'Page content'); 1796 wrapper.className = 'atm-a11y-landmark-region'; 1797 child.parentNode.insertBefore(wrapper, child); 1798 } 1799 1800 wrapper.appendChild(child); 1801 }); 1802 } 1803 1804 function needsLandmarkWrapper(el) { 1805 if (isIgnoredBodyChild(el)) return false;
1806 if (isSkipLinkElement(el)) return false; 1807 if (isLandmark(el) || isInsideLandmark(el)) return false; 1808 if (hasLandmarkDescendant(el)) return false; 1809 if (el.id === 'wpadminbar') return false; 1810 if (el.classList.contains('atm-a11y-landmark-region')) return false; 1811 return hasMeaningfulContent(el); 1812 } 1813 1814 function isIgnoredBodyChild(el) { 1815 return ['SCRIPT', 'STYLE', 'NOSCRIPT', 'LINK', 'META', 'TEMPLATE'].indexOf(el.tagName) !== -1; 1816 } 1817 1818 function isSkipLinkElement(el) { 1819 return el.classList.contains('atm-skip-link') || el.classList.contains('atm-skip-links'); 1820 } 1821 1822 function hasMeaningfulContent(el) { 1823 if (el.hasAttribute('hidden') || el.getAttribute('aria-hidden') === 'true') return false; 1824 if (el.textContent.trim() !== '') return true; 1825 return !!el.querySelector('img, svg, canvas, iframe, video, audio, input, button, select, textarea, a[href]'); 1826 } 1827 1828 function isInsideLandmark(el) { 1829 var selector = 'main, header, footer, nav, aside, section[aria-label], section[aria-labelledby], [role="main"], [role="banner"], [role="contentinfo"], [role="navigation"], [role="complementary"], [role="search"], [role="region"]'; 1830 return !!el.parentElement && !!el.parentElement.closest(selector); 1831 } 1832 1833 function hasLandmarkDescendant(el) { 1834 var selector = 'main, header, footer, nav, aside, section[aria-label], section[aria-labelledby], [role="main"], [role="banner"], [role="contentinfo"], [role="navigation"], [role="complementary"], [role="search"], [role="region"]'; 1835 return !!el.querySelector(selector); 1836 } 1837 1838 function isLandmark(el) { 1839 return isNativeLandmark(el) || !!el.matches('[role="main"], [role="banner"], [role="contentinfo"], [role="navigation"], [role="complementary"], [role="search"], [role="region"]'); 1840 } 1841 1842 function isNativeLandmark(el, expectedRole) { 1843 var tag = el.tagName.toLowerCase(); 1844 var roleMap = { 1845 main: 'main', 1846 header: 'banner', 1847 footer: 'contentinfo', 1848 nav: 'navigation', 1849 aside: 'complementary' 1850 }; 1851 1852 if (tag === 'section') { 1853 return !expectedRole && (!!el.getAttribute('aria-label') || !!el.getAttribute('aria-labelledby')); 1854 } 1855 1856 return expectedRole ? roleMap[tag] === expectedRole : !!roleMap[tag]; 1857 } 1858 1859 function fixFrameAccessibleNames() { 1860 var frames = document.querySelectorAll('iframe, frame'); 1861 frames.forEach(function (frame, index) { 1862 fixSingleFrameAccessibleName(frame, index); 1863 }); 1864 } 1865 1866 function frameHasPresentationRole(frame) { 1867 var role = (frame.getAttribute('role') || '').trim().toLowerCase(); 1868 return role === 'none' || role === 'presentation'; 1869 } 1870 1871 function hasFrameAccessibleNameOrPresentationRole(frame) { 1872 if (frameHasPresentationRole(frame)) return true; 1873 if ((frame.getAttribute('title') || '').trim() !== '') return true; 1874 if ((frame.getAttribute('aria-label') || '').trim() !== '') return true; 1875 return getFrameLabelledByText(frame) !== ''; 1876 } 1877 1878 function watchFrameAccessibleNames() { 1879 if (!window.MutationObserver) return; 1880 1881 var observer = new MutationObserver(function (mutations) { 1882 mutations.forEach(function (mutation) { 1883 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1884 if (!node || node.nodeType !== 1) return; 1885 1886 if (node.matches && node.matches('iframe, frame')) { 1887 fixSingleFrameAccessibleName(node); 1888 } 1889 1890 Array.prototype.slice.call(node.querySelectorAll('iframe, frame')).forEach(function (frame) { 1891 fixSingleFrameAccessibleName(frame); 1892 }); 1893 }); 1894 }); 1895 }); 1896 1897 observer.observe(document.body, { childList: true, subtree: true }); 1898 } 1899 1900 function fixSingleFrameAccessibleName(frame, index) { 1901 if (frameHasPresentationRole(frame)) return; 1902 1903 var title = (frame.getAttribute('title') || '').trim(); 1904 if (title !== '') { 1905 if ((frame.getAttribute('aria-label') || '').trim() !== title) { 1906 frame.setAttribute('aria-label', title); 1907 } 1908 return; 1909 } 1910 1911 if ((frame.getAttribute('aria-label') || '').trim() !== '') return; 1912 if (getFrameLabelledByText(frame) !== '') return; 1913 1914 title = getFrameTitle(frame, index || 0); 1915 frame.setAttribute('title', title); 1916 frame.setAttribute('aria-label', title); 1917 } 1918 1919 function getFrameLabelledByText(frame) { 1920 var labelledBy = (frame.getAttribute('aria-labelledby') || '').trim(); 1921 if (labelledBy === '') return ''; 1922 1923 return labelledBy.split(/\s+/).map(function (id) { 1924 var el = document.getElementById(id); 1925 return el ? el.textContent.trim() : ''; 1926 }
1926).filter(Boolean).join(' ').trim(); 1927 } 1928 1929 function getFrameTitle(frame, index) { 1930 var frameText = [ 1931 frame.getAttribute('src') || '', 1932 frame.getAttribute('data-src') || '', 1933 frame.getAttribute('data-lazy-src') || '', 1934 frame.getAttribute('data-original-src') || '', 1935 frame.getAttribute('allow') || '', 1936 frame.getAttribute('class') || '', 1937 frame.getAttribute('id') || '', 1938 frame.getAttribute('name') || '' 1939 ].join(' ').toLowerCase(); 1940 1941 if (frameText.indexOf('google.com/maps') !== -1 || frameText.indexOf('maps.google') !== -1) { 1942 return 'Map'; 1943 } 1944 if (frameText.indexOf('youtube.com') !== -1 || frameText.indexOf('youtu.be') !== -1) { 1945 return 'Video'; 1946 } 1947 if (frameText.indexOf('vimeo.com') !== -1) { 1948 return 'Video'; 1949 } 1950 if (frameText.indexOf('picture-in-picture') !== -1 || frameText.indexOf('fullscreen') !== -1 || frameText.indexOf('fitvid') !== -1 || frameText.indexOf('video') !== -1) { 1951 return 'Video'; 1952 } 1953 if (frameText.indexOf('recaptcha') !== -1 || frameText.indexOf('captcha') !== -1) { 1954 return 'Verification challenge'; 1955 } 1956 1957 return 'Embedded content ' + (index + 1); 1958 } 1959 1960 function fixSecondaryNavRole() { 1961 var secondaryNav = document.getElementById('secondary-navigation'); 1962 if (secondaryNav && secondaryNav.tagName.toLowerCase() !== 'nav') { 1963 secondaryNav.removeAttribute('role'); 1964 } 1965 } 1966 1967 function fixMinorNavigationLandmarks() { 1968 var navs = document.querySelectorAll('nav, [role="navigation"]'); 1969 navs.forEach(function (el) { 1970 demoteMinorNavigationLandmark(el); 1971 }); 1972 } 1973 1974 function watchMinorNavigationLandmarks() { 1975 if (!window.MutationObserver) return; 1976 1977 var observer = new MutationObserver(function (mutations) { 1978 mutations.forEach(function (mutation) { 1979 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 1980 if (!node || node.nodeType !== 1) return; 1981 1982 if (node.matches && node.matches('nav, [role="navigation"]')) { 1983 demoteMinorNavigationLandmark(node); 1984 } 1985 1986 Array.prototype.slice.call(node.querySelectorAll('nav, [role="navigation"]')).forEach(function (el) { 1987 demoteMinorNavigationLandmark(el); 1988 }); 1989 }); 1990 }); 1991 }); 1992 1993 observer.observe(document.body, { childList: true, subtree: true }); 1994 } 1995 1996 function demoteMinorNavigationLandmark(el) { 1997 if (!isMinorNavigationLandmark(el)) return; 1998 1999 el.removeAttribute('role'); 2000 el.setAttribute('data-atm-a11y-demoted-navigation', 'true'); 2001 2002 if (el.tagName.toLowerCase() === 'nav') { 2003 replaceElementTag(el, 'div', ['role']); 2004 } 2005 } 2006 2007 function isMinorNavigationLandmark(el) { 2008 if (el.hasAttribute('data-atm-a11y-preserve-navigation')) return false; 2009 2010 var text = ( 2011 el.tagName + ' ' + 2012 (el.id || '') + ' ' + 2013 getClassString(el) + ' ' + 2014 (el.getAttribute('aria-label') || '') + ' ' + 2015 getLabelledByText(el) + ' ' + 2016 getVisibleText(el) 2017 ).toLowerCase(); 2018 2019 if (/(?:secondary|utility|top-bar|topbar|footer|social|share|sharing|breadcrumb|breadcrumbs|pagination|pager|post-navigation|posts-navigation|image-navigation|comment-navigation|legal|privacy|terms|copyright|related|tag|tags|categor|archive|sidebar|widget|quick-links|quicklinks)/i.test(text)) { 2020 return true; 2021 } 2022 2023 if (isKeyNavigationLandmark(text)) { 2024 return false; 2025 } 2026 2027 return el.tagName.toLowerCase() === 'nav' && el.querySelectorAll('a[href]').length > 0; 2028 } 2029 2030 function isKeyNavigationLandmark(text) { 2031 return /(?:primary|main|site-navigation|main-navigation|header-navigation|primary-menu|main-menu|mobile-menu|mega-menu|site-menu|menu-primary|menu-main|nav-menu|menu-item|current-menu-item|\bhome\b|\babout\b|\bservices?\b|\bcontact\b|\blocations?\b|\bpatients?\b|\bdentistry\b|\bdental\b)/i.test(text); 2032 } 2033 2034 // ------------------------------------------------------------------------- 2035 // 11. Links without discernible text 2036 // ------------------------------------------------------------------------- 2037 function fixLinksWithoutDiscernibleText() { 2038 var links = document.querySelectorAll('a'); 2039 links.forEach(function (link, index) { 2040 fixLinkWithoutDiscernibleText(link, index); 2041 }); 2042 } 2043 2044 function watchLinksWithoutDiscernibleText() { 2045 if (!window.MutationObserver) return; 2046 2047 var observer = new MutationObserver(function (mutations) { 2048 mutations.forEach(function (mutation) {
2049 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 2050 if (!node || node.nodeType !== 1) return; 2051 2052 if (isLinkElement(node)) { 2053 fixLinkWithoutDiscernibleText(node, 0); 2054 } 2055 2056 Array.prototype.slice.call(node.querySelectorAll('a')).forEach(function (link, index) { 2057 fixLinkWithoutDiscernibleText(link, index); 2058 }); 2059 }); 2060 }); 2061 }); 2062 2063 observer.observe(document.body, { childList: true, subtree: true }); 2064 } 2065 2066 function fixLinkWithoutDiscernibleText(link, index) { 2067 if ((link.getAttribute('aria-label') || '').trim() !== '') return; 2068 if (getVisibleText(link) !== '') return; 2069 2070 var context = getLinkContextText(link); 2071 link.setAttribute('aria-label', context ? getContextualLinkAction(link, index) + ' ' + context : getFallbackLinkLabel(link, index)); 2072 2073 if (isFixEnabled('aria_label_visible_text')) { 2074 fixAriaLabelForVisibleText(link); 2075 } 2076 } 2077 2078 function getFallbackLinkLabel(link, index) { 2079 var labels = [ 2080 'Related link', 2081 'Additional information', 2082 'More information', 2083 'Details link', 2084 'Learn more link', 2085 'Read more link', 2086 'Continue link', 2087 'Open related content', 2088 'View additional details', 2089 'Explore related content', 2090 'More details' 2091 ]; 2092 2093 return labels[Math.abs(hashString((link.getAttribute('href') || '') + ' ' + index)) % labels.length]; 2094 } 2095 2096 function isLinkElement(el) { 2097 if (!el.matches) return false; 2098 return el.matches('a'); 2099 } 2100 2101 function hasDiscernibleText(link) { 2102 if ((link.getAttribute('aria-label') || '').trim() !== '') return true; 2103 if ((link.getAttribute('title') || '').trim() !== '') return true; 2104 if (getLabelledByText(link) !== '') return true; 2105 if (getLabelFromLinkedImage(link) !== '') return true; 2106 if (getLabelFromSvg(link) !== '') return true; 2107 return getVisibleText(link) !== '' && !isIconOnlyLink(link); 2108 } 2109 2110 function getLabelledByText(link) { 2111 var labelledBy = (link.getAttribute('aria-labelledby') || '').trim(); 2112 if (labelledBy === '') return ''; 2113 2114 var text = labelledBy.split(/\s+/).map(function (id) { 2115 var el = document.getElementById(id); 2116 return el ? el.textContent.trim() : ''; 2117 }).filter(Boolean).join(' '); 2118 2119 return text.trim(); 2120 } 2121 2122 function getVisibleText(link) { 2123 return link.textContent.replace(/\s+/g, ' ').trim(); 2124 } 2125 2126 function isIconOnlyLink(link) { 2127 if (link.querySelector('img[alt], svg[aria-label], svg title')) return false; 2128 if (link.querySelector('i[class], span[class*="icon"], span[class*="fa-"], span[class*="dashicons"]')) return true; 2129 2130 var text = getVisibleText(link); 2131 return text !== '' && /^[\uE000-\uF8FF\s]+$/.test(text); 2132 } 2133 2134 function getLabelFromLinkedImage(link) { 2135 var img = link.querySelector('img[alt]'); 2136 return img ? img.getAttribute('alt').trim() : ''; 2137 } 2138 2139 function getLabelFromSvg(link) { 2140 var labelledSvg = link.querySelector('svg[aria-label], svg title'); 2141 if (!labelledSvg) return ''; 2142 2143 if (labelledSvg.getAttribute && labelledSvg.getAttribute('aria-label')) { 2144 return labelledSvg.getAttribute('aria-label').trim(); 2145 } 2146 2147 return labelledSvg.textContent.trim(); 2148 } 2149 2150 function getLabelFromNearbyHeading(link) { 2151 var heading = findPrecedingHeading(link); 2152 return heading ? 'Learn more about ' + heading : ''; 2153 } 2154 2155 function getLabelFromButtonLikeClasses(link) { 2156 var classText = (link.className && typeof link.className === 'string') ? link.className : ''; 2157 classText = classText.replace(/\b(button|btn|link|icon|fa|fas|far|fab)\b/g, '').replace(/[-_]+/g, ' ').replace(/\s+/g, ' ').trim(); 2158 return classText ? 'Open ' + classText : ''; 2159 } 2160 2161 function getLabelFromHref(href) { 2162 if (!href || href === '#') return ''; 2163 if (href.indexOf('tel:') === 0) return 'Call ' + href.replace('tel:', '').replace(/[^\d+]/g, ''); 2164 if (href.indexOf('mailto:') === 0) return 'Email ' + href.replace('mailto:', '').split('?')[0]; 2165 2166 var cleaned = href.split('#')[0].split('?')[0].replace(/\/$/, ''); 2167 var segment = cleaned.split('/').pop(); 2168 if (!segment) return ''; 2169 2170 return 'Open ' + segment.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' '); 2171 } 2172 2173 // ------------------------------------------------------------------------- 2174 // 12. PDF links should warn users before opening a PDF reader 2175 // ------------------------------------------------------------------------- 2176 function fixPdfLinkWarnings() { 2177 var links = document.querySelectorAll('a[href]'); 2178 links.forEach(function (link) { 2179 fixPdfLinkWarning(link); 2180 }); 2181 } 2182 2183 function watchPdfLinkWarnings() { 2184 if (!window.MutationObserver) return; 2185 2186 var observer = new MutationObserver(function (mutations) { 2187 mutations.forEach(function (mutation) {
2188 Array.prototype.slice.call(mutation.addedNodes).forEach(function (node) { 2189 if (!node || node.nodeType !== 1) return; 2190 2191 if (isLinkElement(node)) { 2192 fixPdfLinkWarning(node); 2193 } 2194 2195 Array.prototype.slice.call(node.querySelectorAll('a[href]')).forEach(function (link) { 2196 fixPdfLinkWarning(link); 2197 }); 2198 }); 2199 }); 2200 }); 2201 2202 observer.observe(document.body, { childList: true, subtree: true }); 2203 } 2204 2205 function fixPdfLinkWarning(link) { 2206 var href = link.getAttribute('href') || ''; 2207 if (!isPdfHref(href)) return; 2208 2209 var label = (link.getAttribute('aria-label') || '').trim() || 2210 getVisibleText(link) || 2211 (link.getAttribute('title') || '').trim() || 2212 getLabelFromHref(href) || 2213 'Open document'; 2214 2215 if (!textMentionsPdf(label)) { 2216 label += ' (opens a PDF file)'; 2217 } 2218 2219 link.setAttribute('aria-label', label); 2220 appendPdfWarningText(link); 2221 2222 var title = (link.getAttribute('title') || '').trim(); 2223 if (title === '') { 2224 link.setAttribute('title', 'Opens a PDF file'); 2225 } else if (!textMentionsPdf(title)) { 2226 link.setAttribute('title', title + ' - opens a PDF file'); 2227 } 2228 } 2229 2230 function isPdfHref(href) { 2231 try { 2232 return /\.pdf$/i.test(new URL(href, window.location.href).pathname); 2233 } catch (e) { 2234 return /\.pdf(?:$|[?#])/i.test(href); 2235 } 2236 } 2237 2238 function textMentionsPdf(text) { 2239 return /\bpdf\b/i.test(text || ''); 2240 } 2241 2242 function appendPdfWarningText(link) { 2243 if (link.querySelector('.atm-a11y-pdf-warning')) return; 2244 if (textMentionsPdf(getVisibleText(link))) return; 2245 2246 var warning = document.createElement('span'); 2247 warning.className = 'atm-a11y-sr-only atm-a11y-pdf-warning'; 2248 warning.textContent = ' (opens a PDF file)'; 2249 link.appendChild(warning); 2250 } 2251 2252})();
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.