1var lastFocusedBeforeLightbox = null; 2 3document.addEventListener('DOMContentLoaded', () => { 4 enhanceOnclickElements(); 5 enhanceOnclickWithChildrenElements(); 6 enhanceTextElements(); 7 enhanceLinks(); 8 enhanceSearchForm(); 9 enhanceBusinessHourShortcode(); 10 enhanceProductCardsAccessibility(); 11 enhanceDestinationCardsAccessibility(); 12 setupLightboxFocusHandling(); 13 enhanceDcbBoxes(); 14 enhanceSMGShortcodes(); 15}); 16 17function enhanceOnclickElements() { 18 const selector = 'div[onclick], span[onclick], svg[onclick], img[onclick], div.et_clickable'; 19 const elements = document.querySelectorAll(selector); 20 21 elements.forEach((el) => { 22 // Tastaturfokus ermöglichen 23 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 24 25 // Rolle setzen 26 if (!el.hasAttribute('role')) el.setAttribute('role', 'button'); 27 28 // aria-label setzen (für Screenreader) 29 if (!el.hasAttribute('aria-label')) { 30 const label = 31 el.getAttribute('alt') || 32 el.getAttribute('title') || 33 el.innerText?.trim(); 34 if (label) el.setAttribute('aria-label', label); 35 } 36 37 // Tastatursteuerung ermöglichen 38 el.addEventListener('keydown', (event) => { 39 if (event.key === 'Enter' || event.key === ' ') { 40 event.preventDefault(); 41 el.click(); 42 } 43 }); 44 }); 45} 46 47function enhanceOnclickWithChildrenElements() { 48 const interactiveSelector = 'div[onclick], span[onclick], svg[onclick], img[onclick]'; 49 const textSelector = 'p, h1, h2, h3, h4, h5, h6, span'; 50 51 const interactiveElements = document.querySelectorAll(interactiveSelector); 52 const textElements = document.querySelectorAll(textSelector); 53 54 // Interaktive Elemente tastaturfähig machen 55 interactiveElements.forEach((el) => { 56 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 57 if (!el.hasAttribute('role')) el.setAttribute('role', 'button'); 58 59 if (!el.hasAttribute('aria-label')) { 60 const label = 61 el.getAttribute('alt') || 62 el.getAttribute('title') || 63 el.innerText?.trim(); 64 if (label) el.setAttribute('aria-label', label); 65 } 66 67 el.addEventListener('keydown', (event) => { 68 if (event.key === 'Enter' || event.key === ' ') { 69 event.preventDefault(); 70 el.click(); 71 } 72 }); 73 }); 74 75 // Textbereiche: nur zugänglich, wenn kein Kind interaktiv ist 76 textElements.forEach((el) => { 77 const child = el.querySelector('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'); 78 const text = el.innerText?.trim(); 79 80 if (child) { 81 // Doppelten Fokus verhindern 82 el.removeAttribute('tabindex'); 83 el.removeAttribute('role'); 84 el.removeAttribute('aria-label'); 85 return; 86 } 87 88 if (!text || text.length < 3) return; 89 90 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 91 if (!el.hasAttribute('role')) el.setAttribute('role', 'document'); 92 if (!el.hasAttribute('aria-label')) el.setAttribute('aria-label', text); 93 }); 94} 95 96function enhanceTextElements() { 97 const textElements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, span'); 98 99 textElements.forEach((el) => { 100 const interactiveChild = el.querySelector('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'); 101 102 if (interactiveChild) { 103 // Entferne Fokus vom Elternelement, falls vorhanden 104 el.removeAttribute('tabindex'); 105 el.removeAttribute('role'); 106 el.removeAttribute('aria-label'); 107 return; 108 } 109 110 const text = el.innerText?.trim(); 111 if (!text || text.length < 3) return; 112 113 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 114 if (!el.hasAttribute('role')) el.setAttribute('role', 'document'); 115 if (!el.hasAttribute('aria-label')) el.setAttribute('aria-label', text); 116 });
117} 118 119 120function enhanceLinks() { 121 document.querySelectorAll('a').forEach((link) => { 122 const onlyImage = link.children.length === 1 && link.firstElementChild.tagName === 'IMG'; 123 124 if (onlyImage && !link.hasAttribute('aria-label')) { 125 const img = link.firstElementChild; 126 const alt = img?.getAttribute('alt')?.trim(); 127 if (alt) { 128 link.setAttribute('aria-label', alt); 129 } 130 } 131 }); 132} 133 134function enhanceSearchForm() { 135 // 1. Labels (Spans) mit Inputs verbinden via aria-labelledby 136 var spans = document.querySelectorAll('.searchform-label[aria-label]'); 137 spans.forEach(function (span) { 138 var input = span.closest('.input-inner-wrapper'); 139 if (input) input = input.querySelector('input, select, textarea'); 140 if (input && !input.hasAttribute('aria-labelledby')) { 141 var id = span.id || 'label-' + Math.random().toString(36).slice(2, 8); 142 span.id = id; 143 input.setAttribute('aria-labelledby', id); 144 } 145 }); 146 147 // 2. Counter-Buttons tastaturfähig machen 148 var buttons = document.querySelectorAll('.down_count, .up_count'); 149 buttons.forEach(function (el) { 150 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 151 if (!el.hasAttribute('role')) el.setAttribute('role', 'button'); 152 if (!el.hasAttribute('aria-label')) { 153 var title = el.getAttribute('title') || el.innerText.trim(); 154 if (title) el.setAttribute('aria-label', title); 155 } 156 el.addEventListener('keydown', function (event) { 157 if (event.key === 'Enter' || event.key === ' ') { 158 event.preventDefault(); 159 el.click(); 160 } 161 }); 162 }); 163 164 // 3. Reiseart-Buttons aria-pressed setzen 165 var travelButtons = document.querySelectorAll('.booking-type-btn'); 166 travelButtons.forEach(function (btn) { 167 btn.setAttribute('aria-pressed', btn.classList.contains('selected') ? 'true' : 'false'); 168 btn.addEventListener('click', function () { 169 travelButtons.forEach(function (b) { 170 b.setAttribute('aria-pressed', 'false'); 171 }); 172 btn.setAttribute('aria-pressed', 'true'); 173 }); 174 }); 175 176 // 4. Inputs mit visuellen Labels beschriften 177 var inputs = document.querySelectorAll('input[data-input], input[readonly], input.travellers-input'); 178 inputs.forEach(function (input) { 179 var labelSpan = input.closest('.input-inner-wrapper'); 180 if (labelSpan) labelSpan = labelSpan.querySelector('span.searchform-label'); 181 if (labelSpan && !input.hasAttribute('aria-label')) { 182 var label = labelSpan.innerText.trim(); 183 if (label) input.setAttribute('aria-label', label); 184 } 185 }); 186 187 // 5. Auswahlboxen (Select2) mit aria-label 188 var selects = document.querySelectorAll('.select2-selection'); 189 selects.forEach(function (el) { 190 var text = el.innerText.trim(); 191 if (!el.hasAttribute('aria-label') && text.length > 0) { 192 el.setAttribute('aria-label', 'Auswahl: ' + text); 193 } 194 }); 195 196 // 6. Social-Media-Bild-Links beschriften 197 document.querySelectorAll('a').forEach(function (link) { 198 var img = link.querySelector('img[alt]'); 199 if (img && !link.hasAttribute('aria-label')) { 200 var alt = img.getAttribute('alt'); 201 if (alt) link.setAttribute('aria-label', alt.trim()); 202 } 203 }); 204} 205 206function enhanceBusinessHourShortcode() { 207 document.querySelectorAll('.business-hour-detail .row').forEach(function (row) { 208 if (!row.hasAttribute('tabindex')) row.setAttribute('tabindex', '0'); 209 if (!row.hasAttribute('role')) row.setAttribute('role', 'document'); 210 211 var dayEl = row.querySelector('.col-sm-4'); 212 var timeEl = row.querySelector('.col-sm-8'); 213 214 if (dayEl && timeEl) { 215 var label = dayEl.innerText.trim() + ' ' + timeEl.innerText.trim(); 216 row.setAttribute('aria-label', label); 217 } 218 });
219 220 document.querySelectorAll('section.smg-welcome-text').forEach(function (section) { 221 var h2 = section.querySelector('h2'); 222 if (h2) { 223 h2.removeAttribute('tabindex'); 224 h2.removeAttribute('role'); 225 h2.removeAttribute('aria-label'); 226 } 227 228 // Prüfe ob schon ein Screenreader-Block vorhanden ist 229 if (section.querySelector('.sr-text')) return; 230 231 // Alle Textknoten nach dem h2 einsammeln 232 var textParts = []; 233 var node = h2?.nextSibling; 234 while (node) { 235 if (node.nodeType === 3 && node.textContent.trim()) { 236 textParts.push(node.textContent.trim()); 237 } else if (node.nodeType === 1) { 238 textParts.push(node.innerText.trim()); 239 } 240 node = node.nextSibling; 241 } 242 243 var text = textParts.join(' ').trim(); 244 if (!text || text.length < 5) return; 245 246 // Neues <div> mit zugänglichem Text einfügen 247 var div = document.createElement('div'); 248 div.className = 'sr-text'; 249 div.setAttribute('tabindex', '0'); 250 div.setAttribute('role', 'document'); 251 div.setAttribute('aria-label', text); 252 div.style.position = 'relative'; // Optional: sichtbar positionieren 253 section.appendChild(div); 254 }); 255 document.querySelectorAll('.business-hour-detail .mt-3').forEach(function (el) { 256 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 257 if (!el.hasAttribute('role')) el.setAttribute('role', 'note'); 258 259 var text = el.innerText.trim(); 260 if (text && !el.hasAttribute('aria-label')) { 261 el.setAttribute('aria-label', text); 262 } 263 }); 264} 265 266function enhanceProductCardsAccessibility() { 267 const cards = document.querySelectorAll('.product-card'); 268 269 cards.forEach((card) => { 270 const cardLink = card.querySelector('a[href]'); 271 if (cardLink) { 272 card.setAttribute('tabindex', '0'); 273 card.setAttribute('role', 'link'); 274 275 const label = Array.from( 276 card.querySelectorAll('.product-title, .product-description, .product-price') 277 ).map(el => el.innerText.trim()).join(' '); 278 card.setAttribute('aria-label', label.replace(/\s+/g, ' ')); 279 } 280 281 card.addEventListener('keydown', (e) => { 282 if (e.key === 'Enter' || e.key === ' ') { 283 e.preventDefault(); 284 cardLink.click(); 285 } 286 }); 287 288 // Fokussierbare Textelemente 289 card.querySelectorAll('.product-title, .product-description, .product-price').forEach(el => { 290 el.setAttribute('tabindex', '0'); 291 el.setAttribute('role', 'document'); 292 293 if (!el.hasAttribute('aria-label')) { 294 const txt = el.innerText.trim(); 295 if (txt) el.setAttribute('aria-label', txt); 296 } 297 }); 298 299 // Aktionen: bleiben fokussierbar 300 card.querySelectorAll('.contact-share > button, .contact-mail > a, .contact-telephone > a').forEach(el => { 301 el.setAttribute('tabindex', '0'); 302 el.setAttribute('role', 'button'); 303 304 const label = el.getAttribute('title') || el.getAttribute('href') || el.innerText; 305 if (label && !el.hasAttribute('aria-label')) el.setAttribute('aria-label', label.trim()); 306 307 el.querySelectorAll('svg, object').forEach(svg => svg.removeAttribute('tabindex')); 308 }); 309 310 // Tooltip-Tags nicht fokussierbar 311 card.querySelectorAll('.tooltip-tags').forEach(tag => { 312 tag.removeAttribute('tabindex'); 313 tag.setAttribute('aria-hidden', 'true'); 314 315 const focusables = tag.querySelectorAll('svg, object, [tabindex]');
316 focusables.forEach(el => { 317 el.removeAttribute('tabindex'); 318 el.setAttribute('aria-hidden', 'true'); 319 }); 320 }); 321 }); 322} 323 324function enhanceDestinationCardsAccessibility() { 325 const destinationCards = document.querySelectorAll('.et_pb_column.hover-img'); 326 327 destinationCards.forEach(card => { 328 const textElement = card.querySelector('.et_pb_text_inner p'); 329 330 if (textElement) { 331 const labelText = textElement.innerText.trim(); 332 333 // Fokus auf die ganze Karte 334 card.setAttribute('tabindex', '0'); 335 card.setAttribute('role', 'button'); 336 card.setAttribute('aria-label', labelText); 337 338 // Event für Enter/Leertaste optional (wenn klickbar) 339 card.addEventListener('keydown', (e) => { 340 if (e.key === 'Enter' || e.key === ' ') { 341 e.preventDefault(); 342 card.click(); // falls onclick vorhanden 343 } 344 }); 345 346 // Verhindere doppelte Fokussierung 347 textElement.setAttribute('aria-hidden', 'true'); 348 textElement.removeAttribute('tabindex'); 349 } 350 }); 351} 352 353 354 355function setupLightboxFocusHandling() { 356 // Alle Lightbox-Links erkennen 357 const lightboxLinks = document.querySelectorAll('[data-lightbox]'); 358 359 lightboxLinks.forEach(link => { 360 link.addEventListener('click', () => { 361 // Fokus vor Ãffnung speichern 362 lastFocusedBeforeLightbox = document.activeElement; 363 }); 364 }); 365 366 // ESC schlieÃt Lightbox und setzt Fokus zurück 367 document.addEventListener('keydown', (e) => { 368 if (e.key === 'Escape') { 369 const openLightbox = document.querySelector('.lightbox, .fancybox-container, .lg-show'); 370 371 // Prüfen ob eine bekannte Lightbox sichtbar ist 372 if (openLightbox && typeof lastFocusedBeforeLightbox?.focus === 'function') { 373 setTimeout(() => { 374 lastFocusedBeforeLightbox.focus(); 375 }, 300); // Warten bis Lightbox entfernt wurde 376 } 377 } 378 }); 379} 380 381function enhanceDcbBoxes() { 382 // DCB Boxes 383 const dcbTagIcons = document.querySelectorAll('.smg_dynamic_content .hotel-icons .tooltip-tags'); 384 const selectors = '.smg_dynamic_content .hotel-icons .tooltip-tags object,' + 385 '.smg_dynamic_content .product-card > a,' + 386 '.smg_dynamic_content .product-card .product-content > a,' + 387 '.smg_subpage_link'; 388 389 // Product Card Issues in Firefox, resulting to being trapped 390 const wrongIndexes = document.querySelectorAll(selectors); 391 392 dcbTagIcons.forEach(el => { 393 // Tastaturfokus ermöglichen 394 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 395 396 // Rolle setzen 397 if (!el.hasAttribute('role')) el.setAttribute('role', 'img'); 398 399 // aria-label setzen (für Screenreader) 400 if (!el.hasAttribute('aria-label')) { 401 const label = 402 el.getAttribute('alt') || 403 el.getAttribute('title') || 404 el.getAttribute('data-orginal-title') || 405 el.innerText?.trim(); 406 if (label) el.setAttribute('aria-label', label); 407 } 408 409 if (el.getAttribute('aria-hidden') == true) { 410 el.setAttribute('aria-hidden', false); 411 } 412 }); 413 414 wrongIndexes.forEach(el => { 415 if (el.getAttribute('tabindex') === '0' || !el.hasAttribute('tabindex')) { 416 el.setAttribute('tabindex', '-1'); 417 } 418 }); 419} 420 421function enhanceSMGShortcodes() { 422 const elements = document.querySelectorAll('.contact-widget, .advantage-card-view'); 423 424 elements.forEach(el => { 425 if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); 426 }); 427}
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.