1/** 2* 3* Rodeo Utils 4* 5**/ 6 7document.addEventListener("DOMContentLoaded", () => { 8 "use strict"; 9 10 // Initializes slideshow for specific block 11 function initializeSlideshow(slideshowBlock) { 12 let columns = slideshowBlock.querySelectorAll(":scope > div:not(.arrow)"); 13 14 if (columns.length === 0) { 15 columns = slideshowBlock.querySelectorAll(":scope > a:not(.arrow)"); 16 } 17 18 const prevButton = slideshowBlock.querySelector(".arrow-prev"); 19 const nextButton = slideshowBlock.querySelector(".arrow-next"); 20 21 let currentIndex = 0; 22 let interval = null; // Stores autoplay interval 23 24 // Add __active class to first column 25 if (columns.length > 0) { 26 columns[0].classList.add("__active"); 27 } 28 29 function updateSlideshow() { 30 columns.forEach((col, index) => { 31 if (index === currentIndex) { 32 col.classList.add("__active", "__fade"); 33 col.addEventListener("animationend", () => { 34 col.classList.remove("__fade"); 35 }, { once: true }); 36 } else { 37 col.classList.remove("__active"); 38 } 39 }); 40 } 41 42 function nextColumn() { 43 currentIndex = (currentIndex + 1) % columns.length; 44 updateSlideshow(); 45 } 46 47 function prevColumn() { 48 currentIndex = (currentIndex - 1 + columns.length) % columns.length; 49 updateSlideshow(); 50 } 51 52 function startAutoplay() { 53 if (slideshowBlock.classList.contains("rodeo-autoplay")) { 54 interval = setInterval(() => { 55 nextColumn(); 56 }, 6000); // Autoplay every 6 seconds 57 } 58 } 59 60 function stopAutoplay() { 61 clearInterval(interval); 62 interval = null; 63 } 64 65 // Event listeners for navigation via arrows 66 if (prevButton) prevButton.addEventListener("click", () => { 67 stopAutoplay(); 68 prevColumn(); 69 }); 70 71 if (nextButton) nextButton.addEventListener("click", () => { 72 stopAutoplay(); 73 nextColumn(); 74 }); 75 76 // Event listener to stop autoplay on user interaction with column 77 columns.forEach((col) => { 78 col.addEventListener("click", stopAutoplay); 79 }); 80 81 // Start autoplay if class is present 82 startAutoplay(); 83 } 84 85 // Find all slideshow blocks and initialize them 86 const slideshowBlocks = document.querySelectorAll(".rodeo-slideshow"); 87 slideshowBlocks.forEach(initializeSlideshow); 88 89 // Accordion 90 document.querySelectorAll('.rodeo-accordion').forEach(accordion => { 91 const allowMultiple = accordion.dataset.multiple === "true"; 92 const defaultState = accordion.dataset.state; 93 94 const items = accordion.querySelectorAll('.rodeo-accordion-item'); 95 96 // Function to apply content's styles to the header 97 const applyContentStyleToHeader = (headerEl, contentEl) => { 98 const radius = contentEl.style.borderRadius || ''; // fallback if not set inline 99 const width = contentEl.style.borderBottomWidth || ''; 100 const color = contentEl.style.borderBottomColor || ''; 101 102 if (radius) { 103 headerEl.style.borderRadius = radius; 104 } 105 106 if (width && color) { 107 headerEl.style.borderBottom = `${width} solid ${color}`; 108 } 109 }; 110 111 // Function to restore the header's default styling 112 const restoreHeaderDefaults = (headerEl) => { 113 headerEl.style.borderRadius = headerEl.dataset.defaultRadius; 114 headerEl.style.borderBottom = headerEl.dataset.defaultBorderBottom; 115 }; 116 117 items.forEach((item, index) => { 118 const header = item.querySelector('.rodeo-accordion-header'); 119 const content = item.querySelector('.rodeo-accordion-content'); 120 const contentInner = item.querySelector('.rodeo-accordion-content-inner'); 121 const isLast = index === items.length - 1; 122 123 if (isLast) { 124 // Save current header border styles 125 header.dataset.defaultRadius = header.style.borderRadius || ''; 126 header.dataset.defaultBorderBottom = header.style.borderBottom || ''; 127 128 applyContentStyleToHeader(header, contentInner); 129 } 130 131 // Open the first item by default if specified 132 if (defaultState === 'FirstExpanded' && index === 0) { 133 header.classList.add('active'); 134 content.style.maxHeight = content.scrollHeight + 'px'; 135 } 136 137 header.addEventListener('click', () => { 138 const isOpen = header.classList.contains('active'); 139 140 if (!allowMultiple) {
141 items.forEach((i, idx) => { 142 const h = i.querySelector('.rodeo-accordion-header'); 143 const c = i.querySelector('.rodeo-accordion-content'); 144 const ci = i.querySelector('.rodeo-accordion-content-inner'); 145 146 h.classList.remove('active'); 147 c.style.maxHeight = null; 148 149 if (idx == items.length - 1) { 150 applyContentStyleToHeader(h, ci); 151 } 152 }); 153 } 154 155 if (!isOpen || allowMultiple) { 156 header.classList.toggle('active'); 157 content.style.maxHeight = isOpen ? null : content.scrollHeight + 'px'; 158 159 if (isLast) { 160 if (isOpen) { 161 setTimeout(() => { 162 applyContentStyleToHeader(header, contentInner); // closing 163 }, 300); 164 } else { 165 restoreHeaderDefaults(header); // opening 166 } 167 } 168 } 169 }); 170 }); 171 }); 172 173 // Image Zoom on Click 174 const zoomableImages = document.querySelectorAll(".img-zoom-trigger"); 175 176 if (zoomableImages.length > 0) { 177 let lastFocusedTrigger = null; 178 const overlay = document.createElement("div"); 179 overlay.className = "rodeo-zoom-overlay"; 180 document.body.appendChild(overlay); 181 182 let zoomedImg = null; 183 let spinner = null; 184 185 zoomableImages.forEach((img) => { 186 img.addEventListener("click", (e) => { 187 e.preventDefault(); 188 e.stopPropagation(); 189 190 lastFocusedTrigger = img; 191 const fullSrc = img.getAttribute("data-fullsrc"); 192 const alt = img.getAttribute("alt") || ""; 193 194 overlay.innerHTML = ""; // Clear old content 195 196 // Create and show spinner 197 spinner = document.createElement("div"); 198 spinner.className = "rodeo-zoom-spinner"; 199 spinner.setAttribute("role", "status"); 200 spinner.setAttribute("aria-label", "Loading full-size image"); 201 overlay.appendChild(spinner); 202 203 // Preload full image 204 zoomedImg = document.createElement("img"); 205 zoomedImg.src = fullSrc; 206 zoomedImg.alt = alt; 207 208 zoomedImg.onload = () => { 209 if (spinner && spinner.parentNode) { 210 spinner.remove(); 211 } 212 overlay.appendChild(zoomedImg); 213 zoomedImg.classList.add("zoom-in"); 214 }; 215 216 zoomedImg.onerror = () => { 217 if (spinner && spinner.parentNode) { 218 spinner.remove(); 219 } 220 const errorMsg = document.createElement("div"); 221 errorMsg.textContent = "Image failed to load."; 222 errorMsg.style.color = "#fff"; 223 overlay.appendChild(errorMsg); 224 }; 225 226 overlay.classList.add("active"); 227 }); 228 }); 229 230 function closeOverlay() { 231 if (!zoomedImg) return; 232 233 // Animate out 234 zoomedImg.classList.remove("zoom-in"); 235 236 zoomedImg.addEventListener("transitionend", function handleTransitionEnd(e) { 237 if (e.propertyName !== "transform") return; 238 239 zoomedImg.removeEventListener("transitionend", handleTransitionEnd); 240 overlay.classList.remove("active"); 241 242 setTimeout(() => { 243 overlay.innerHTML = ""; 244 zoomedImg = null; 245 if (lastFocusedTrigger) lastFocusedTrigger.focus(); 246 }, 300); 247 }); 248 } 249 250 overlay.addEventListener("click", closeOverlay); 251 252 document.addEventListener("keydown", (e) => { 253 // Handle Enter or Space on zoom-trigger 254 if ((e.key === "Enter" || e.key === " ") && e.target.classList.contains("img-zoom-trigger")) { 255 e.preventDefault(); 256 e.target.click(); 257 return; 258 } 259 260 // Always allow Escape to close the overlay 261 if (e.key === "Escape") { 262 closeOverlay(); 263 } 264 }); 265 } 266 267 (() => { 268 // Check if IntersectionObserver is supported 269 if (!('IntersectionObserver' in window)) { 270 console.warn('IntersectionObserver is not supported in this browser'); 271 document.querySelectorAll('.animate-on-scroll').forEach(el => el.classList.add('visible'));
272 return; 273 } 274 275 // Create the observer instance with rootMargin option 276 const observer = new IntersectionObserver((entries) => { 277 entries.forEach(entry => { 278 if (entry.isIntersecting) { 279 entry.target.classList.add('visible'); 280 observer.unobserve(entry.target); 281 } 282 }); 283 }, { 284 rootMargin: '0px 0px -100px 0px' // This creates a 100px margin from the bottom 285 }); 286 287 // Function to start observing elements 288 const observeElements = () => { 289 const elements = document.querySelectorAll('.animate-on-scroll'); 290 elements.forEach(element => { 291 observer.observe(element); 292 }); 293 }; 294 295 // Initialize when DOM is ready 296 if (document.readyState === 'loading') { 297 document.addEventListener('DOMContentLoaded', observeElements); 298 } else { 299 observeElements(); 300 } 301 302 // Cleanup function (useful for SPAs or when script needs to be destroyed) 303 const cleanup = () => { 304 observer.disconnect(); 305 }; 306 307 // Optional: Expose cleanup method to window (if needed) 308 window.cleanupScrollAnimations = cleanup; 309 })(); 310});
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.