PageSourceSearch

https://www.camarillopremierdental.com/wp-content/plugins/accessib…/js/accessibility-fixer.js?ver=1784718847

js camarillopremierdental.com collected 2026-10-04 03:15:48 UTC 80,455 bytes, 2,495 lines download raw bytes

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

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.