1document.addEventListener("DOMContentLoaded", function () { 2 const carousel = document.querySelector(".carousel"); 3 if (!carousel) return; 4 carousel.setAttribute("tabindex", "0"); 5 6 const images = Array.from(carousel.querySelectorAll("img")).filter(function (img) { 7 return img.complete ? img.naturalWidth > 0 : true; 8 }); 9 10 // Nur Nachbarbilder preloaden: flüssig beim Klicken, aber mobile Daten sparen. 11 const preloadImage = function (index) { 12 const safeIndex = (index + images.length) % images.length; 13 const src = images[safeIndex].getAttribute("src"); 14 if (!src) return; 15 const preload = new Image(); 16 preload.src = src; 17 }; 18 19 if (images.length === 0) { 20 const emptyMessage = document.createElement("div"); 21 emptyMessage.className = "carousel-empty"; 22 emptyMessage.textContent = "Bilder folgen in Kürze."; 23 carousel.appendChild(emptyMessage); 24 return; 25 } 26 27 let currentIndex = 0; 28 images[currentIndex].classList.add("active"); 29 30 let lightbox = null; 31 let lightboxImage = null; 32 let lightboxClose = null; 33 let previousFocus = null; 34 35 const createLightbox = function () { 36 if (lightbox) return; 37 38 lightbox = document.createElement("div"); 39 lightbox.className = "lightbox-backdrop"; 40 lightbox.setAttribute("role", "dialog"); 41 lightbox.setAttribute("aria-modal", "true"); 42 lightbox.setAttribute("aria-label", "GroÃansicht des Bildes"); 43 44 const inner = document.createElement("div"); 45 inner.className = "lightbox-inner"; 46 47 lightboxImage = document.createElement("img"); 48 lightboxImage.className = "lightbox-image"; 49 lightboxImage.alt = ""; 50 51 lightboxClose = document.createElement("button"); 52 lightboxClose.type = "button"; 53 lightboxClose.className = "lightbox-close"; 54 lightboxClose.setAttribute("aria-label", "GroÃansicht schlieÃen"); 55 lightboxClose.innerHTML = "Ã"; 56 57 inner.appendChild(lightboxImage); 58 inner.appendChild(lightboxClose); 59 lightbox.appendChild(inner); 60 document.body.appendChild(lightbox); 61 62 lightbox.addEventListener("click", function (event) { 63 if (event.target === lightbox || event.target === lightboxClose) { 64 closeLightbox(); 65 } 66 }); 67 68 document.addEventListener("keydown", function (event) { 69 if (!lightbox || lightbox.getAttribute("data-open") !== "true") return; 70 if (event.key === "Escape") { 71 event.preventDefault(); 72 closeLightbox(); 73 if (lightboxClose) { 74 lightboxClose.blur(); 75 } 76 } 77 }); 78 }; 79 80 const openLightbox = function (index) { 81 createLightbox(); 82 if (!lightbox || !lightboxImage) return; 83 const safeIndex = (index + images.length) % images.length; 84 const src = images[safeIndex].getAttribute("src"); 85 if (!src) return; 86 87 previousFocus = document.activeElement; 88 lightboxImage.src = src; 89 lightbox.setAttribute("data-open", "true"); 90 lightbox.classList.add("open"); 91 if (lightboxClose) { 92 lightboxClose.focus(); 93 } 94 }; 95 96 const closeLightbox = function () { 97 if (!lightbox) return; 98 lightbox.setAttribute("data-open", "false"); 99 lightbox.classList.remove("open"); 100 if (previousFocus && typeof previousFocus.focus === "function") { 101 previousFocus.focus(); 102 } 103 }; 104 105 const counter = document.createElement("div"); 106 counter.className = "carousel-counter"; 107 counter.setAttribute("aria-live", "polite"); 108 carousel.appendChild(counter); 109 110 const updateCounter = function () { 111 counter.textContent = (currentIndex + 1) + " / " + images.length; 112 }; 113 114 const showImage = function (index) { 115 images[currentIndex].classList.remove("active"); 116 currentIndex = (index + images.length) % images.length; 117 images[currentIndex].classList.add("active"); 118 updateCounter(); 119 preloadImage(currentIndex + 1); 120 preloadImage(currentIndex - 1); 121 }; 122 123 const nav = document.createElement("div"); 124 nav.classList.add("nav"); 125 126 const prevButton = document.createElement("button"); 127 prevButton.type = "button"; 128 prevButton.innerHTML = "❮"; 129 prevButton.setAttribute("aria-label", "Vorheriges Bild"); 130 prevButton.addEventListener("click", function () { 131 showImage(currentIndex - 1); 132 }); 133 134 const nextButton = document.createElement("button"); 135 nextButton.type = "button"; 136 nextButton.innerHTML = "❯"; 137 nextButton.setAttribute("aria-label", "Nächstes Bild"); 138 nextButton.addEventListener("click", function () { 139 showImage(currentIndex + 1); 140 }); 141 142 nav.appendChild(prevButton); 143 nav.appendChild(nextButton); 144 carousel.appendChild(nav); 145 146 carousel.addEventListener("keydown", function (event) { 147 if (event.key === "ArrowLeft") { 148 event.preventDefault(); 149 showImage(currentIndex - 1); 150 } else if (event.key === "ArrowRight") { 151 event.preventDefault(); 152 showImage(currentIndex + 1); 153 } else if (event.key === "Enter" || event.key === " ") { 154 event.preventDefault(); 155 openLightbox(currentIndex); 156 } 157 }); 158
159 images.forEach(function (img, index) { 160 img.style.cursor = "zoom-in"; 161 img.addEventListener("click", function () { 162 openLightbox(index); 163 }); 164 }); 165 166 updateCounter(); 167 preloadImage(currentIndex + 1); 168 preloadImage(currentIndex - 1); 169});
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.