1/* JS for preset "Menu V2" */ 2(function() { 3 $(function() { 4 $('.menu-wrapper').each(function() { 5 initMenu($(this)) 6 }); 7 }); 8 9 // Make :active pseudo classes work on iOS 10 document.addEventListener("touchstart", function() {}, false); 11 12 const initMenu = function($menuWrapper) { 13 const $body = $('body'); 14 const $menu = $('.ed-menu', $menuWrapper); 15 const $menuLinks = $('a', $menu); 16 const $menuTrigger = $('.menu-trigger', $menuWrapper); 17 const $banner = $('.banner').first(); 18 19 const smoothScrollOffset = 20; 20 21 // Set aria attributes 22 $menuTrigger.attr({ 23 'aria-expanded': 'false', 24 'aria-controls': $menu.attr('id'), 25 }); 26 27 toggleClassOnClick($body.add($menu), $menuTrigger, null, 'open open-menu'); // Keep open on $menu for backward compatibility 28 activateSmoothScroll($menuLinks.add($('.scroll a')), smoothScrollOffset); 29 addClassOnVisibleLinkTargets($menuLinks, 'active', 2 / 3); 30 handleSticky($menuWrapper, 'sticky', $banner); 31 }; 32 33 /** 34 * Observe element's height changes and reload the initMenu() function 35 * 36 * @param {HTMLElement} elm Element to observe 37 * @param {function} callback to call when elmement's height changed 38 */ 39 const observeHeightChange = function(elm, callback) { 40 if (!('ResizeObserver' in window) || elm == null) return; 41 42 const ro = new ResizeObserver(callback); 43 ro.observe(elm); 44 } 45 46 /** 47 * Toggles class on a target when a trigger is clicked 48 * 49 * @param {jQuery} $target The target to apply the CSS class to 50 * @param {jQuery} $trigger The Trigger 51 * @param {jQuery} $closeTrigger Optional close trigger 52 * @param {string} cssClass CSS Class to toggle on the target 53 */ 54 const toggleClassOnClick = function($target, $trigger, $closeTrigger, cssClass) { 55 56 // Reset in case class "open" was saved accidentally 57 $target.removeClass(cssClass); 58 $trigger.removeClass(cssClass).attr('aria-expanded', 'false'); 59 60 // Click on trigger toggles class "open" 61 $trigger.off('.toggle').on('click.toggle', function() { 62 const isExpanded = $(this).attr('aria-expanded') === 'true'; 63 const $menu = $target.filter('.ed-menu'); 64 $(this).toggleClass(cssClass).attr('aria-expanded', !isExpanded); 65 $target.toggleClass(cssClass); 66 67 // Set focus to menu on open 68 if (!isExpanded) { 69 $menu.attr('tabindex', '0'); 70 const focusMenu = () => { 71 $menu.focus(); 72 $menu.off('transitionend', focusMenu); 73 }; 74 $menu.on('transitionend', focusMenu); 75 } 76 77 $menu[0].addEventListener( 78 'blur', 79 function() { 80 $menu[0].removeAttribute('tabindex'); 81 }, { 82 once: true 83 } 84 ); 85 }); 86 87 // Close target when link inside is clicked 88 $target.find('a').click(function() { 89 $target.removeClass(cssClass); 90 $trigger.removeClass(cssClass).attr('aria-expanded', 'false'); 91 }); 92 93 if (!$closeTrigger || !$closeTrigger.length) { 94 return; 95 } 96 97 $closeTrigger.click(function() { 98 $target.removeClass(cssClass); 99 $trigger.removeClass(cssClass).attr('aria-expanded', 'false'); 100 }); 101 }; 102 103 /** 104 * Smooth scroll to link targets 105 * 106 * @param {jQuery} $scrollLinks The links 107 * @param {jQuery} scrollOffset Offset to subtract from the scroll target position (e.g. for fixed positioned elements like a menu) 108 */ 109 const activateSmoothScroll = function($scrollLinks, scrollOffset) { 110 if (typeof scrollOffset === 'undefined') { 111 scrollOffset = 0; 112 } 113 114 const determineTarget = function($trigger, hash) { 115 if (hash == '#!next') { 116 return $trigger.closest('.ed-element').next(); 117 } 118 // Prevent invalid selectors 119 try { 120 return $(hash); 121 } catch (e) { 122 return $(); 123 } 124 } 125 126 $scrollLinks.click(function(e) { 127 const $target = determineTarget($(this), this.hash); 128 if (!$target.length) return; 129 e.preventDefault(); 130 131 viewport.scrollTo($target, 'top', 500, 0); 132 133 }); 134 }; 135 136 /** 137 * We are using the fill property on an element to pass user's choices from CSS to JavaScript 138 * 139 * @param {jQuery} $element 140 */ 141 const getStickyMode = function($element) { 142 const fillValue = getComputedStyle($element[0]).fill; 143 144 return fillValue === 'rgb(255, 0, 0)' ? 145 'sticky_banner' : 146 fillValue === 'rgb(0, 255, 0)' ? 147 'sticky_menu' : 148 fillValue === 'rgb(0, 0, 255)' ? 149 'sticky_instant' : 150 fillValue === 'rgb(255, 255, 255)' ? 151 'sticky_reverse' : 152 'sticky_none'; 153 }; 154 155 /** 156 * Adds a class to an element when not currently visible 157 * 158 * @param {jQuery} $element The element to handle stickyness for 159 * @param {string} cssClass The actual CSS class to be applied to the element when it's above a certain scroll position 160 * @param {jQuery} $banner A banner to reference the scroll position to 161 */ 162 const handleSticky = function($element, cssClass, $banner) { 163 let triggerPos = 0, 164 offset = 0; 165 let menuWrapperHeight = $element.outerHeight(); 166 let mode; 167 let prevScroll = 0; 168 $element.removeClass(cssClass); 169 170 const toggleSpacer = function(toggle) { 171 document.body.style.setProperty('--spacer-height', toggle ? menuWrapperHeight + 'px' : ''); 172 }; 173 174 const handleScroll = function() { 175 if (!$element.length || mode === 'sticky_none') return; 176 //if (!$element.length || mode === 'sticky_none' || mode === 'sticky_instant') return; 177 178 const isReverse = mode === 'sticky_reverse', 179 curScroll = viewport.getScrollTop(); 180 181 if (triggerPos <= curScroll && (!isReverse || prevScroll > curScroll)) { 182 $element.addClass(cssClass); 183 toggleSpacer(true); 184 } else { 185 $element.removeClass(cssClass); 186 toggleSpacer(false); 187 } 188 189 prevScroll = curScroll; 190 }; 191
192 const updateOffset = function() { 193 mode = getStickyMode($element); 194 menuWrapperHeight = $element.outerHeight(); 195 if (!$element.hasClass(cssClass)) { 196 offset = $element.offset().top; 197 } 198 if (mode === 'sticky_banner' && !$banner.length) { 199 mode = 'sticky_menu'; 200 } 201 if (mode === 'sticky_banner') { 202 triggerPos = $banner.offset().top + ($banner.length ? $banner.outerHeight() : $element.outerHeight()); 203 } 204 if (mode === 'sticky_menu' || mode === 'sticky_reverse') { 205 triggerPos = offset + $element.outerHeight(); 206 } 207 if (mode === 'sticky_instant') { 208 triggerPos = offset; 209 } 210 211 handleScroll(); 212 } 213 214 viewport.observe('resize', updateOffset); 215 viewport.observe('animation.end', updateOffset); 216 observeHeightChange($element[0], updateOffset); 217 updateOffset(); 218 219 viewport.observe('scroll', handleScroll); 220 handleScroll(); 221 }; 222 223 /** 224 * Adds a class to links whose target is currently inside the viewport 225 * 226 * @param {jQuery} $links Link(s) to be observed 227 * @param {string} cssClass CSS Class to be applied 228 * @param {float} sectionViewportRatio Ratio by which the target should be within the viewport 229 */ 230 const addClassOnVisibleLinkTargets = function($links, cssClass, sectionViewportRatio) { 231 if (typeof sectionViewportRatio === 'undefined') { 232 sectionViewportRatio = 1 / 2; 233 } 234 235 const menuTargets = []; 236 const activeLink = $links.filter('.active'); 237 238 const links = $links.filter(function() { 239 // Prevent invalid selectors 240 let $target; 241 try { 242 $target = $(this.hash); 243 } catch (e) { 244 return false; 245 } 246 if (!this.hash || !$target.length) { 247 return false; 248 } 249 250 // Cache offset position to improve performance (update on resize) 251 const updateOffset = function() { 252 $target.data('offset', $target.offset().top); 253 }; 254 255 viewport.observe('resize', updateOffset); 256 viewport.observe('animation.end', updateOffset); 257 updateOffset(); 258 259 menuTargets.push($target); 260 return true; 261 }); 262 263 // No hash links found, so don't handle it at all 264 if (!links.length) { 265 return; 266 } 267 268 const checkVisibility = function() { 269 $links.removeClass('active'); 270 271 // Check section position reversely 272 for (let i = menuTargets.length - 1; i >= 0; i--) { 273 const desiredScrollPosition = menuTargets[i].data('offset') - viewport.getHeight() * (1 - sectionViewportRatio); 274 if (viewport.getScrollTop() >= desiredScrollPosition && menuTargets[i][0].offsetParent !== null) { 275 links.eq(i).addClass(cssClass); 276 return; 277 } 278 } 279 280 // Fallback to originally active item 281 activeLink.addClass(cssClass); 282 }; 283 284 viewport.observe('scroll', checkVisibility); 285 checkVisibility(); 286 }; 287})(); 288/* End JS for preset "Menu V2" */ 289 290/* JS for preset "Countdown V3" */ 291$(function() { 292 var slice = Array.prototype.slice; 293 var valid = true; 294 295 var ready = function(callback) { 296 var fn = function() { 297 if (document.body.classList.contains('edit')) { 298 return; 299 } 300 callback(); 301 }; 302 303 if (window.readyState !== 'loading') { 304 fn(); 305 return; 306 } 307 308 document.addEventListener('DOMContentLoaded', fn); 309 }; 310 311 var countdown = function(date, tick) { 312 var now = new Date().getTime(), 313 running = false, 314 days = 0, 315 hours = 0, 316 minutes = 0, 317 seconds, 318 interval; 319 320 var updateCounter = function() { 321 if (!running) return; 322 323 now = new Date().getTime(); 324 seconds = Math.round((date - now) / 1000); 325 326 if (seconds > 86400) { 327 days = Math.floor(seconds / 86400); 328 seconds %= 86400; 329 } 330 331 if (seconds > 3600) { 332 hours = Math.floor(seconds / 3600); 333 seconds %= 3600; 334 } 335 336 if (seconds > 60) { 337 minutes = Math.floor(seconds / 60); 338 seconds %= 60; 339 } 340 341 tick(days, hours, minutes, seconds); 342 }; 343 344 if (isNaN(Date.parse(date))) { 345 date = date.replace(/\-/g, '/'); 346 } 347 348 if (isNaN(Date.parse(date))) { 349 date = date.replace(/\s/, 'T'); 350 } 351 352 date = new Date(date).getTime(); 353 354 if (now >= date) { 355 valid = false; 356 return; 357 } 358 359 tick = tick || (function() {}); 360 361 return { 362 start: function() { 363 interval = window.setInterval(updateCounter, 1000); 364 running = true; 365 updateCounter(); 366 }, 367 stop: function() { 368 if (interval) window.clearInterval(interval); 369 interval = undefined; 370 running = false;
371 } 372 }; 373 }; 374 375 var writeCountdown = function(element, days, hours, minutes, seconds) { 376 var daysElm = element.querySelector(".countdown-days"), 377 hoursElm = element.querySelector('.countdown-hours'), 378 minutesElm = element.querySelector('.countdown-minutes'), 379 secondsElm = element.querySelector('.countdown-seconds'); 380 381 if (daysElm) daysElm.innerHTML = days; 382 if (hoursElm) hoursElm.innerHTML = hours; 383 if (minutesElm) minutesElm.innerHTML = minutes; 384 if (secondsElm) secondsElm.innerHTML = seconds; 385 }; 386 387 var buildCountdown = function(element) { 388 var dataContent = window.getComputedStyle(element, ':before').content.slice(1, -1); 389 var date = dataContent; 390 391 392 element.countdown = countdown(date, function(days, hours, minutes, seconds) { 393 writeCountdown( 394 element, parseInt(days), 395 ("0" + parseInt(hours)).slice(-2), 396 ("0" + parseInt(minutes)).slice(-2), 397 ("0" + parseInt(seconds)).slice(-2) 398 ); 399 }); 400 401 if (valid) { 402 element.countdown.start(); 403 } 404 }; 405 406 var destroyCountdown = function() { 407 var instances = slice.call(document.querySelectorAll('.countdown-instance')), 408 len = instances.length, 409 i = 0, 410 element; 411 for (; i < len; i++) { 412 element = instances[i]; 413 writeCountdown(element, "0", "0", "0", "0"); 414 if (element.countdown) { 415 element.countdown.stop(); 416 } 417 } 418 }; 419 420 var observeInstances = function() { 421 const observer = new MutationObserver((mutations) => { 422 mutations.forEach((mutation) => { 423 mutation.addedNodes.forEach((node) => { 424 if (node.nodeType === 1 && node.classList.contains('countdown-instance')) { 425 if (document.body.classList.contains('preview') || !document.body.classList.contains('edit-full')) { 426 buildCountdown(node); 427 } else { 428 destroyCountdown(); 429 } 430 } 431 }); 432 }); 433 }); 434 435 observer.observe(document.body, { 436 childList: true, 437 subtree: true 438 }); 439 }; 440 441 var preview = false; 442 var listener = function() { 443 if (valid) { 444 if (!preview && document.body.classList.contains('preview') || !document.body.classList.contains('edit-full')) { 445 var instances = slice.call(document.querySelectorAll('.countdown-instance')); 446 instances.forEach(buildCountdown); 447 preview = true; 448 } else if (preview && !document.body.classList.contains('preview')) { 449 destroyCountdown(); 450 preview = false; 451 } 452 } 453 454 requestAnimationFrame(listener); 455 }; 456 457 requestAnimationFrame(listener); 458 ready(function() { 459 observeInstances(); 460 }); 461}); 462/* End JS for preset "Countdown V3" */ 463 464/* JS for preset "Collection Search" */ 465(function() { 466 function debounce(func, wait, immediate) { 467 var timeout; 468 return function() { 469 var context = this, 470 args = arguments; 471 var later = function() { 472 timeout = null; 473 if (!immediate) func.apply(context, args); 474 }; 475 var callNow = immediate && !timeout; 476 clearTimeout(timeout); 477 timeout = setTimeout(later, wait); 478 if (callNow) func.apply(context, args); 479 }; 480 } 481 document.addEventListener('DOMContentLoaded', () => { 482 var search = document.getElementById('collection-search'); 483 var clear = document.getElementById('collection-search-clear'); 484 if (!search || !clear) { 485 return; 486 } 487 488 var runSearch = function() { 489 window.location.hash = search.value ? '#!search=' + encodeURI(search.value) : '#!'; 490 }; 491 492 var debouncedFunc = debounce(runSearch, 400); 493 494 495 search.addEventListener('keyup', function(event) { 496 if (event.keyCode == 13) { 497 runSearch(); 498 } else { 499 debouncedFunc(); 500 } 501 }); 502 clear.addEventListener('click', function(event) { 503 search.value = ''; 504 }); 505 }); 506})(); 507/* End JS for preset "Collection Search" */ 508 509/* JS for preset "Stacked Boxes" */ 510(function() { 511 function updateIndexes($container) { 512 const numBoxes = $container.find('.stacked-box').length; 513 $container[0].style.setProperty('--numBoxes', numBoxes); 514 $container.find('.stacked-box').each(function(i) { 515 this.style.setProperty('--boxIndex', i + 1); 516 }); 517 } 518 519 $(function() { 520 $('.stacked-boxes-container').each(function() { 521 const $container = $(this); 522 updateIndexes($container); 523 524 // Observe changes 525 var observer = new MutationObserver(function() { 526 updateIndexes($container); 527 }); 528 529 observer.observe(this, { 530 childList: true, 531 subtree: true 532 }); 533 534 $container.data('observer', observer); 535 }); 536 }); 537})(); 538/* End JS for preset "Stacked Boxes" */
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.