1(function () { 2 "use strict"; 3 4 var READY = "data-theme-ready"; 5 6 // Carousel 7 function initCarousel(root) { 8 var track = root.querySelector(".vcarousel__track"); 9 if (!track) return; 10 11 var dots = root.querySelector(".vcarousel__dots, .hl__dots"); 12 var prev = root.querySelector(".vcarousel__arrow--prev"); 13 var next = root.querySelector(".vcarousel__arrow--next"); 14 // Advances a full page: modules showing two items skip two at a time, 15 // which is what the dots below the carousel count 16 var step = function () { 17 var first = track.firstElementChild; 18 if (!first) return track.clientWidth; 19 20 var gap = parseFloat(getComputedStyle(track).columnGap) || 0; 21 var item = first.getBoundingClientRect().width + gap; 22 var perPage = Math.max(1, Math.round((track.clientWidth + gap) / item)); 23 24 return item * perPage; 25 }; 26 27 function overflows() { 28 return track.scrollWidth - track.clientWidth > 4; 29 } 30 31 function syncArrows() { 32 var on = overflows(); 33 if (prev) prev.hidden = !on; 34 if (next) next.hidden = !on; 35 } 36 37 function syncDots() { 38 if (!dots) return; 39 var marks = dots.querySelectorAll("span"); 40 if (!marks.length) return; 41 var max = track.scrollWidth - track.clientWidth; 42 var ratio = max > 0 ? track.scrollLeft / max : 0; 43 var idx = Math.round(ratio * (marks.length - 1)); 44 for (var i = 0; i < marks.length; i++) { 45 marks[i].classList.toggle("is-active", i === idx); 46 } 47 } 48 49 function maxScroll() { 50 return track.scrollWidth - track.clientWidth; 51 } 52 53 // Wraps around instead of stopping at the edges 54 function go(direction) { 55 var limit = maxScroll(); 56 var at = track.scrollLeft; 57 58 if (direction > 0 && at >= limit - 4) { 59 track.scrollTo({ left: 0, behavior: "smooth" }); 60 return; 61 } 62 63 if (direction < 0 && at <= 4) { 64 track.scrollTo({ left: limit, behavior: "smooth" }); 65 return; 66 } 67 68 track.scrollBy({ left: direction * step(), behavior: "smooth" }); 69 } 70 71 if (prev) prev.addEventListener("click", function () { go(-1); }); 72 if (next) next.addEventListener("click", function () { go(1); }); 73 74 track.addEventListener("scroll", syncDots, { passive: true }); 75 window.addEventListener("resize", syncArrows); 76 77 syncArrows(); 78 syncDots(); 79 autoplay(root, go, overflows); 80 } 81 82 // Slides walk on their own, pausing while the visitor is on them 83 function autoplay(root, go, overflows) { 84 var delay = parseInt(root.dataset.autoplay, 10); 85 if (!delay) return; 86 87 // Under four seconds there is no time to read what is on the slide 88 if (delay < 4000) delay = 4000; 89 90 var timer = null; 91 92 function stop() { clearInterval(timer); timer = null; } 93 94 function start() { 95 if (timer || !overflows() || document.hidden) return; 96 timer = setInterval(function () { go(1); }, delay); 97 } 98 99 root.addEventListener("mouseenter", stop); 100 root.addEventListener("mouseleave", start); 101 root.addEventListener("touchstart", stop, { passive: true }); 102 document.addEventListener("visibilitychange", function () { 103 if (document.hidden) { stop(); } else { start(); } 104 }); 105 106 start(); 107 } 108 109 // Slides module: the panel sets the effect, the animation length and the 110 // time each image stays, so it gets its own controller 111 function initSlides(root) { 112 var track = root.querySelector(".vcarousel__track"); 113 if (!track) return; 114 115 var items = Array.prototype.slice.call(track.children); 116 if (!items.length) return; 117 118 var dots = root.querySelectorAll(".vcarousel__dots span"); 119 var prev = root.querySelector(".vcarousel__arrow--prev"); 120 var next = root.querySelector(".vcarousel__arrow--next"); 121 var speed = parseInt(root.dataset.speed, 10) || 400; 122 var delay = parseInt(root.dataset.autoplay, 10) || 0; 123 if (delay && delay < 4000) delay = 4000; 124 var fade = root.dataset.effect === "fade"; 125 var index = 0; 126 var timer = null; 127 var animation = null; 128 129 function paint() {
130 items.forEach(function (item, i) { item.classList.toggle("is-on", i === index); }); 131 dots.forEach(function (dot, i) { dot.classList.toggle("is-active", i === index); }); 132 } 133 134 // Manual tween: native smooth scrolling ignores the configured duration 135 function slideTo(target) { 136 if (animation) cancelAnimationFrame(animation); 137 138 var from = track.scrollLeft; 139 var to = items[target].offsetLeft - track.offsetLeft; 140 var started = null; 141 142 function frame(now) { 143 if (started === null) started = now; 144 145 var progress = Math.min((now - started) / speed, 1); 146 var eased = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2; 147 148 track.scrollLeft = from + (to - from) * eased; 149 150 if (progress < 1) { animation = requestAnimationFrame(frame); } 151 } 152 153 animation = requestAnimationFrame(frame); 154 } 155 156 function go(step) { 157 index = (index + step + items.length) % items.length; 158 159 if (!fade) slideTo(index); 160 paint(); 161 } 162 163 function stop() { clearInterval(timer); timer = null; } 164 165 function start() { 166 if (timer || !delay || items.length < 2 || document.hidden) return; 167 timer = setInterval(function () { go(1); }, delay); 168 } 169 170 if (prev) prev.addEventListener("click", function () { stop(); go(-1); start(); }); 171 if (next) next.addEventListener("click", function () { stop(); go(1); start(); }); 172 173 dots.forEach(function (dot, i) { 174 dot.addEventListener("click", function () { 175 stop(); 176 index = i; 177 if (!fade) slideTo(index); 178 paint(); 179 start(); 180 }); 181 }); 182 183 root.addEventListener("mouseenter", stop); 184 root.addEventListener("mouseleave", start); 185 root.addEventListener("touchstart", stop, { passive: true }); 186 187 document.addEventListener("visibilitychange", function () { 188 if (document.hidden) { stop(); } else { start(); } 189 }); 190 191 paint(); 192 start(); 193 } 194 195 // Menu: panel on phones, overflow into "more" on wider screens 196 function initMenu(menu) { 197 var list = menu.querySelector(".menu__list"); 198 var more = menu.querySelector(".menu__more"); 199 var toggle = menu.querySelector(".menu__toggle"); 200 var phone = window.matchMedia("(max-width: 760px)"); 201 202 if (toggle) { 203 toggle.addEventListener("click", function (e) { 204 e.stopPropagation(); 205 var open = menu.classList.toggle("is-open"); 206 toggle.setAttribute("aria-expanded", open ? "true" : "false"); 207 }); 208 209 document.addEventListener("click", function (e) { 210 if (!menu.contains(e.target)) closeMenu(); 211 }); 212 213 document.addEventListener("keydown", function (e) { 214 if (e.key === "Escape") closeMenu(); 215 }); 216 217 list.addEventListener("click", function (e) { 218 if (e.target.closest("a")) closeMenu(); 219 }); 220 } 221 222 function closeMenu() { 223 menu.classList.remove("is-open"); 224 if (toggle) toggle.setAttribute("aria-expanded", "false"); 225 } 226 227 if (!list || !more) return; 228 229 var submenu = more.querySelector(".submenu"); 230 var pool = []; 231 232 list.querySelectorAll(":scope > li:not(.menu__more)").forEach(function (li) { 233 pool.push(li); 234 }); 235 236 function reflow() { 237 pool.forEach(function (li) { list.insertBefore(li, more); }); 238 submenu.innerHTML = ""; 239 more.hidden = true; 240 241 // On a phone every item lives in the panel, nothing overflows 242 if (phone.matches) return; 243 244 var style = getComputedStyle(list); 245 var gap = parseFloat(style.columnGap) || 0; 246 var limit = list.clientWidth - parseFloat(style.paddingLeft || 0) - parseFloat(style.paddingRight || 0) - 1; 247 248 function widthOf(nodes) { 249 var total = 0; 250 251 nodes.forEach(function (li, i) { 252 total += li.offsetWidth + (i ? gap : 0); 253 }); 254 255 return total; 256 } 257 258 if (widthOf(pool) <= limit) return; 259 260 // The row has to fit the items plus the "more" button itself 261 more.hidden = false; 262 263 var available = limit - more.offsetWidth - gap; 264 var used = 0; 265 var full = false;
266 var overflow = []; 267 268 pool.forEach(function (li) { 269 var next = used + li.offsetWidth + (used ? gap : 0); 270 271 // Once one item does not fit, the following ones go with it: the menu 272 // order has to stay the same as the panel 273 if (full || next > available) { 274 full = true; 275 overflow.push(li); 276 return; 277 } 278 279 used = next; 280 }); 281 282 overflow.forEach(function (li) { 283 var link = li.querySelector("a"); 284 var clone = document.createElement("li"); 285 var anchor = document.createElement("a"); 286 anchor.href = link.getAttribute("href"); 287 anchor.textContent = link.textContent; 288 if (link.classList.contains("is-active")) anchor.classList.add("is-active"); 289 clone.appendChild(anchor); 290 submenu.appendChild(clone); 291 li.remove(); 292 }); 293 } 294 295 // The panel belongs to the pointer: hover opens it. The click on the label 296 // is there for touch, where there is no hover, and it only opens, never 297 // takes the list away. 298 more.querySelector("a").addEventListener("click", function (e) { 299 e.preventDefault(); 300 more.classList.add("is-open"); 301 }); 302 303 // Choosing a page closes the panel even with the pointer still over it. The 304 // hold is released as soon as the pointer leaves, so hovering opens again. 305 more.addEventListener("click", function (e) { 306 if (!e.target.closest(".submenu")) return; 307 308 more.classList.remove("is-open"); 309 more.classList.add("is-picked"); 310 }); 311 312 more.addEventListener("mouseleave", function () { 313 more.classList.remove("is-open"); 314 more.classList.remove("is-picked"); 315 }); 316 317 document.addEventListener("click", function (e) { 318 if (!more.contains(e.target)) more.classList.remove("is-open"); 319 }); 320 321 window.addEventListener("pronto.render", function () { more.classList.remove("is-open"); }); 322 323 var timer; 324 window.addEventListener("resize", function () { 325 clearTimeout(timer); 326 timer = setTimeout(reflow, 150); 327 }); 328 329 // The web font lands after the first pass and every label gets wider: a 330 // second measurement keeps the row from wrapping 331 if (document.fonts && document.fonts.ready) { 332 document.fonts.ready.then(reflow); 333 } 334 335 reflow(); 336 } 337 338 // Active menu item 339 // The menu lives in the header, which pjax navigation never replaces, 340 // so the active state has to be refreshed on every render. 341 function syncMenu() { 342 var path = window.location.pathname.replace(/\/+$/, "") || "/"; 343 344 document.querySelectorAll(".menu__list a, .submenu a").forEach(function (link) { 345 var href = (link.getAttribute("href") || "").replace(/\/+$/, "") || "/"; 346 link.classList.toggle("is-active", href === path); 347 }); 348 } 349 350 // Init 351 function init() { 352 document.querySelectorAll(".vcarousel:not(.slides):not([" + READY + "])").forEach(function (el) { 353 el.setAttribute(READY, "1"); 354 initCarousel(el); 355 }); 356 357 document.querySelectorAll(".slides:not([" + READY + "])").forEach(function (el) { 358 el.setAttribute(READY, "1"); 359 initSlides(el); 360 }); 361 362 document.querySelectorAll(".menu:not([" + READY + "])").forEach(function (el) { 363 el.setAttribute(READY, "1"); 364 initMenu(el); 365 }); 366 367 syncMenu(); 368 } 369 370 if (document.readyState === "loading") { 371 document.addEventListener("DOMContentLoaded", init); 372 } else { 373 init(); 374 } 375 376 window.addEventListener("pronto.render", init); 377})();
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.