1/** 2 * Muzeum Child - poprawki dostepnosci WCAG 2.1 AA. 3 * 4 * Zakres: 5 * - naprawa hierarchii naglowkow w banerze (H3 -> span, H2 -> H2 lub H1) 6 * - przycisk pauzy/odtwarzania dla karuzeli banera (WCAG 2.2.2) 7 * - aria-hidden i tabindex="-1" na sklonowanych slajdach Owl (WCAG 4.1.2) 8 * - aria-expanded toggling dla dropdown menu (WCAG 4.1.2, 2.1.1) 9 * - poprawa atrybutow alt=Image / alt=logo (WCAG 1.1.1) 10 * - poprawa href="tel:" i "mailto:" w stopce (jakosc danych) 11 * - rel="noopener noreferrer" na linkach target=_blank 12 * - aria-expanded na toggler'ach menu/wyszukiwarki 13 * - usuniecie preloadera po zaladowaniu 14 */ 15(function ($) { 16 'use strict'; 17 18 var TEXT = (window.muzeumA11yL10n || {}); 19 var t = function (key, fallback) { 20 return TEXT[key] || fallback; 21 }; 22 23 /** 24 * 1. Semantyczna korekta naglowkow w banerze (WCAG 1.3.1, 2.4.6). 25 * Tytul slajdu <h3> -> <span class="slide-tag"> 26 * Glowne haslo <h2> pozostaje <h2> (jest juz <h1> screen-reader-text na stronie glownej) 27 */ 28 function fixBannerHeadings() { 29 $('.banner-section .slide-item').each(function () { 30 var $slide = $(this); 31 $slide.find('.content-box > h3').each(function () { 32 var $h3 = $(this); 33 var $span = $('<span class="slide-tag" />').html($h3.html()); 34 $h3.replaceWith($span); 35 }); 36 }); 37 } 38 39 /** 40 * 2. Przycisk pauzy/odtwarzania karuzeli banera (WCAG 2.2.2 Pause, Stop, Hide). 41 */ 42 function addBannerPauseControl() { 43 var $banner = $('.banner-carousel').first(); 44 if (!$banner.length) { 45 return; 46 } 47 48 var playLabel = t('play', 'Odtworz karuzele'); 49 var pauseLabel = t('pause', 'Zatrzymaj karuzele'); 50 51 var $controls = $('<div class="banner-carousel-controls" />'); 52 var $btn = $( 53 '<button type="button" class="banner-carousel-toggle" aria-pressed="false">' + 54 '<span class="icon" aria-hidden="true"><i class="fa fa-pause"></i></span>' + 55 '<span class="screen-reader-text">' + pauseLabel + '</span>' + 56 '</button>' 57 ); 58 $btn.attr('aria-label', pauseLabel); 59 $controls.append($btn); 60 $banner.closest('.banner-section').css('position', 'relative').append($controls); 61 62 var isPaused = false; 63 64 // Uszanuj prefers-reduced-motion: wylacz autoplay od razu. 65 if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 66 $banner.trigger('stop.owl.autoplay'); 67 isPaused = true; 68 $btn.attr('aria-pressed', 'true') 69 .attr('aria-label', playLabel) 70 .find('.icon').html('<i class="fa fa-play"></i>'); 71 $btn.find('.screen-reader-text').text(playLabel); 72 } 73 74 $btn.on('click', function () { 75 if (isPaused) { 76 $banner.trigger('play.owl.autoplay'); 77 $btn.attr('aria-pressed', 'false') 78 .attr('aria-label', pauseLabel) 79 .find('.icon').html('<i class="fa fa-pause"></i>'); 80 $btn.find('.screen-reader-text').text(pauseLabel); 81 isPaused = false; 82 } else { 83 $banner.trigger('stop.owl.autoplay'); 84 $btn.attr('aria-pressed', 'true') 85 .attr('aria-label', playLabel) 86 .find('.icon').html('<i class="fa fa-play"></i>'); 87 $btn.find('.screen-reader-text').text(playLabel); 88 isPaused = true; 89 } 90 }); 91 92 // Zatrzymuj autoplay gdy uzytkownik wchodzi fokusem w slajd. 93 $banner.on('focusin', function () { 94 if (!isPaused) { 95 $banner.trigger('stop.owl.autoplay'); 96 } 97 }); 98 $banner.on('focusout', function () { 99 if (!isPaused) { 100 $banner.trigger('play.owl.autoplay'); 101 } 102 }); 103 } 104 105 /** 106 * 3. Klony Owl carousel powinny byc ukryte dla AT (WCAG 4.1.2). 107 */ 108 function hideClonedCarouselSlides() { 109 $('.owl-carousel .owl-item.cloned').attr('aria-hidden', 'true'); 110 $('.owl-carousel .owl-item.cloned a, .owl-carousel .owl-item.cloned button').attr('tabindex', '-1'); 111 112 // Obsluguje takze dynamicznie zmieniane klony (Owl przebudowuje DOM). 113 if (window.MutationObserver) { 114 var targets = document.querySelectorAll('.owl-carousel'); 115 targets.forEach(function (target) { 116 var obs = new MutationObserver(function () { 117 $(target).find('.owl-item.cloned').attr('aria-hidden', 'true'); 118 $(target).find('.owl-item.cloned a, .owl-item.cloned button').attr('tabindex', '-1'); 119 }); 120 obs.observe(target, { childList: true, subtree: true }); 121 }); 122 } 123 } 124
125 /** 126 * 4. Poprawa ogolnych atrybutow alt na stronie (WCAG 1.1.1). 127 * Zamieniamy wartosci typu "Image", "image", "logo" na sensowne 128 * albo na pusty alt (dla obrazow dekoracyjnych w tlach/ksztaltach). 129 */ 130 function fixGenericAltAttributes() { 131 $('img').each(function () { 132 var $img = $(this); 133 var alt = ($img.attr('alt') || '').trim(); 134 var src = $img.attr('src') || ''; 135 136 if (alt.toLowerCase() === 'image') { 137 // Decorative cues po sciezce: shape/bg/pattern/arrow/decor/separator/line 138 if (/shape|bg[-_]|pattern|arrow|decor|separator|line|dot|spark/i.test(src)) { 139 $img.attr('alt', '').attr('role', 'presentation'); 140 return; 141 } 142 // Jesli obrazek jest wewnatrz linku ze znanym tytulem, uzyj go. 143 var $parentLink = $img.closest('a'); 144 if ($parentLink.length) { 145 var title = ($parentLink.attr('title') || $parentLink.text() || '').trim(); 146 if (title) { 147 $img.attr('alt', title); 148 return; 149 } 150 } 151 // Zaufany figcaption. 152 var $fig = $img.closest('figure').find('figcaption').first(); 153 if ($fig.length && $fig.text().trim()) { 154 $img.attr('alt', $fig.text().trim()); 155 return; 156 } 157 // Pusty alt -> traktuj jako dekoracyjny. 158 $img.attr('alt', '').attr('role', 'presentation'); 159 } else if (alt.toLowerCase() === 'logo') { 160 $img.attr('alt', t('logo_alt', 'Muzeum Zamkowe w Sandomierzu - logo')); 161 } 162 }); 163 } 164 165 /** 166 * 5. aria-expanded toggling dla toggler'ow (menu, wyszukiwarka, submenu). 167 */ 168 function wireAriaExpandedToggles() { 169 // Side menu toggler (hamburger). 170 $('.side-menu__toggler:not(.side-menu__close-btn)').on('click.muzeumA11y', function () { 171 var $self = $(this); 172 var isActive = $('.side-menu__block').hasClass('active'); 173 $self.attr('aria-expanded', isActive ? 'true' : 'false'); 174 }); 175 $('.side-menu__block-overlay, .side-menu__close-btn').on('click.muzeumA11y', function () { 176 $('.side-menu__toggler:not(.side-menu__close-btn)').attr('aria-expanded', 'false'); 177 }); 178 179 // Search popup toggler. 180 $('.search-popup__toggler').on('click.muzeumA11y', function () { 181 var isActive = $('.search-popup').hasClass('active'); 182 $(this).attr('aria-expanded', isActive ? 'true' : 'false'); 183 if (isActive) { 184 window.setTimeout(function () { 185 $('#site-search-popup').trigger('focus'); 186 }, 50); 187 } 188 }); 189 $('.search-popup__overlay').on('click.muzeumA11y', function () { 190 $('.search-popup__toggler').attr('aria-expanded', 'false'); 191 }); 192 // Zamykanie popupu klawiszem Escape. 193 $(document).on('keydown.muzeumA11y', function (e) { 194 if (e.key === 'Escape' || e.keyCode === 27) { 195 if ($('.search-popup').hasClass('active')) { 196 $('.search-popup').removeClass('active'); 197 $('.search-popup__toggler').attr('aria-expanded', 'false').trigger('focus'); 198 } 199 if ($('.side-menu__block').hasClass('active')) { 200 $('.side-menu__block').removeClass('active'); 201 $('.side-menu__toggler:not(.side-menu__close-btn)').attr('aria-expanded', 'false').trigger('focus'); 202 } 203 } 204 }); 205 206 // Submenu togglers w menu glownym. 207 $('.main-nav-one .menu-item-has-children > a, .main-nav-one li.dropdown > a').each(function () { 208 var $link = $(this); 209 var $submenu = $link.siblings('ul').first(); 210 if (!$submenu.length) return; 211 212 // Dodajemy aria-haspopup i aria-expanded. 213 $link.attr('aria-haspopup', 'true').attr('aria-expanded', 'false'); 214 215 $link.on('keydown.muzeumA11y', function (e) { 216 if (e.key === 'Enter' || e.key === ' ' || e.keyCode === 13 || e.keyCode === 32) { 217 if ($link.attr('href') === '#' || !$link.attr('href')) { 218 e.preventDefault(); 219 } 220 var expanded = $link.attr('aria-expanded') === 'true'; 221 $link.attr('aria-expanded', expanded ? 'false' : 'true'); 222 } 223 }); 224 225 $link.parent().on('focusin.muzeumA11y mouseenter.muzeumA11y', function () { 226 $link.attr('aria-expanded', 'true'); 227 }); 228 $link.parent().on('focusout.muzeumA11y mouseleave.muzeumA11y', function () { 229 $link.attr('aria-expanded', 'false'); 230 }); 231 }); 232 } 233 234 /** 235 * 6. rel="noopener noreferrer" na wszystkich target="_blank" (bezpieczenstwo). 236 */ 237 function addNoopenerToExternalLinks() { 238 $('a[target="_blank"]').each(function () { 239 var $a = $(this); 240 var rel = ($a.attr('rel') || '').split(/\s+/); 241 var needed = ['noopener', 'noreferrer']; 242 needed.forEach(function (r) { 243 if (rel.indexOf(r) === -1) rel.push(r); 244 }); 245 $a.attr('rel', rel.filter(Boolean).join(' ').trim()); 246 }); 247 } 248 249 /** 250 * 7. Naprawa brednych tel: / mailto: w stopce. 251 * (Plugin widget uzywa danych z bazy; tu dokonujemy ostatecznej korekty.) 252 */ 253 function fixFooterContactLinks() { 254 // Widzialny tekst sluzy za zrodlo prawdy dla numeru telefonu. 255 $('a[href^="tel:"]').each(function () { 256 var $a = $(this); 257 var shown = $a.text().replace(/\s+/g, ' ').trim(); 258 var digits = shown.replace(/[^\d+]/g, ''); 259 if (digits.length >= 9) { 260 if (digits.charAt(0) !== '+' && digits.length === 9) { 261 digits = '+48' + digits; 262 } 263 $a.attr('href', 'tel:' + digits); 264 } 265 }); 266 267 // mailto: -> uzyj widocznego adresu e-mail. 268 $('a[href^="mailto:"]').each(function () { 269 var $a = $(this); 270 var shown = $a.text().trim(); 271 if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(shown)) { 272 $a.attr('href', 'mailto:' + shown); 273 } 274 }); 275 } 276 277 /** 278 * 8. Preloader nie moze blokowac czytnikow ekranu - usuwamy po load. 279 */ 280 function cleanupPreloader() { 281 $(window).on('load', function () { 282 window.setTimeout(function () { 283 $('.preloader').attr('aria-hidden', 'true').attr('aria-busy', 'false'); 284 }, 100); 285 }); 286 } 287 288 /** 289 * 9. Gdy mapa iframe istnieje, upewnij sie ze ma title. 290 */ 291 function ensureMapTitle() { 292 $('iframe[src*="google.com/maps"], iframe[src*="maps.google"]').each(function () { 293 var $ifr = $(this); 294 if (!$ifr.attr('title')) { 295 $ifr.attr('title', t('map_title', 'Mapa dojazdu do Muzeum Zamkowego w Sandomierzu')); 296 } 297 }); 298 } 299 300 $(function () { 301 fixBannerHeadings(); 302 fixGenericAltAttributes(); 303 wireAriaExpandedToggles(); 304 addNoopenerToExternalLinks(); 305 fixFooterContactLinks(); 306 ensureMapTitle(); 307 cleanupPreloader(); 308 }); 309 310 $(window).on('load', function () { 311 // Owl inicjalizuje sie na window.load - te poprawki musza byc pozniej. 312 window.setTimeout(function () { 313 addBannerPauseControl(); 314 hideClonedCarouselSlides(); 315 fixGenericAltAttributes(); 316 addNoopenerToExternalLinks(); 317 }, 200); 318 }); 319 320})(jQuery);
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.