1/* ----------------------------------------------------------------------------------------------- 2 Namespace 3--------------------------------------------------------------------------------------------------- */ 4 5var twentytwenty = twentytwenty || {}; 6 7// Set a default value for scrolled. 8twentytwenty.scrolled = 0; 9 10// polyfill closest 11// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill 12if (!Element.prototype.closest) { 13 Element.prototype.closest = function (s) { 14 var el = this; 15 16 do { 17 if (el.matches(s)) { 18 return el; 19 } 20 21 el = el.parentElement || el.parentNode; 22 } while (el !== null && el.nodeType === 1); 23 24 return null; 25 }; 26} 27 28// polyfill forEach 29// https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill 30if (window.NodeList && !NodeList.prototype.forEach) { 31 NodeList.prototype.forEach = function (callback, thisArg) { 32 var i; 33 var len = this.length; 34 35 thisArg = thisArg || window; 36 37 for (i = 0; i < len; i++) { 38 callback.call(thisArg, this[i], i, this); 39 } 40 }; 41} 42 43// event "polyfill" 44twentytwenty.createEvent = function (eventName) { 45 var event; 46 if (typeof window.Event === 'function') { 47 event = new Event(eventName); 48 } else { 49 event = document.createEvent('Event'); 50 event.initEvent(eventName, true, false); 51 } 52 return event; 53}; 54 55// matches "polyfill" 56// https://developer.mozilla.org/es/docs/Web/API/Element/matches 57if (!Element.prototype.matches) { 58 Element.prototype.matches = 59 Element.prototype.matchesSelector || 60 Element.prototype.mozMatchesSelector || 61 Element.prototype.msMatchesSelector || 62 Element.prototype.oMatchesSelector || 63 Element.prototype.webkitMatchesSelector || 64 function (s) { 65 var matches = (this.document || this.ownerDocument).querySelectorAll(s), 66 i = matches.length; 67 while (--i >= 0 && matches.item(i) !== this) {} 68 return i > -1; 69 }; 70} 71 72// Add a class to the body for when touch is enabled for browsers that don't support media queries 73// for interaction media features. Adapted from <https://codepen.io/Ferie/pen/vQOMmO> 74(function () { 75 var matchMedia = function () { 76 // Include the 'heartz' as a way to have a non matching MQ to help terminate the join. See <https://git.io/vznFH>. 77 var prefixes = ['-webkit-', '-moz-', '-o-', '-ms-']; 78 var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join(''); 79 return window.matchMedia && window.matchMedia(query).matches; 80 }; 81 82 if (('ontouchstart' in window) || (window.DocumentTouch && document instanceof window.DocumentTouch) || matchMedia()) { 83 document.body.classList.add('touch-enabled'); 84 } 85}()); 86 87/* ----------------------------------------------------------------------------------------------- 88 Cover Modals 89--------------------------------------------------------------------------------------------------- */ 90 91twentytwenty.coverModals = { 92 93 init: function () { 94 if (document.querySelector('.cover-modal')) { 95 // Handle cover modals when they're toggled 96 this.onToggle(); 97 98 // When toggled, untoggle if visitor clicks on the wrapping element of the modal 99 this.outsideUntoggle(); 100 101 // Close on escape key press 102 this.closeOnEscape(); 103 104 // Hide and show modals before and after their animations have played out 105 this.hideAndShowModals(); 106 } 107 }, 108 109 // Handle cover modals when they're toggled 110 onToggle: function () { 111 document.querySelectorAll('.cover-modal').forEach(function (element) { 112 element.addEventListener('toggled', function (event) { 113 var modal = event.target, 114 body = document.body; 115 116 if (modal.classList.contains('active')) { 117 body.classList.add('showing-modal'); 118 } else { 119 body.classList.remove('showing-modal'); 120 body.classList.add('hiding-modal'); 121 122 // Remove the hiding class after a delay, when animations have been run 123 setTimeout(function () { 124 body.classList.remove('hiding-modal'); 125 }, 500); 126 } 127 }); 128 }); 129 }, 130 131 // Close modal on outside click 132 outsideUntoggle: function () { 133 document.addEventListener('click', function (event) { 134 var target = event.target; 135 var modal = document.querySelector('.cover-modal.active'); 136 137 if (target === modal) { 138 this.untoggleModal(target); 139 } 140 }.bind(this)); 141 }, 142 143 // Close modal on escape key press 144 closeOnEscape: function () { 145 document.addEventListener('keydown', function (event) { 146 if (event.keyCode === 27) { 147 event.preventDefault(); 148 document.querySelectorAll('.cover-modal.active').forEach(function (element) { 149 this.untoggleModal(element); 150 }.bind(this)); 151 } 152 }.bind(this)); 153 }, 154 155 // Hide and show modals before and after their animations have played out 156 hideAndShowModals: function () { 157 var _doc = document, 158 _win = window, 159 modals = _doc.querySelectorAll('.cover-modal'), 160 htmlStyle = _doc.documentElement.style, 161 adminBar = _doc.querySelector('#wpadminbar'); 162 163 function getAdminBarHeight(negativeValue) { 164 var height, 165 currentScroll = _win.pageYOffset; 166 167 if (adminBar) { 168 height = currentScroll + adminBar.getBoundingClientRect().height; 169 170 return negativeValue ? -height : height; 171 } 172 173 return currentScroll === 0 ? 0 : -currentScroll; 174 } 175 176 function htmlStyles() { 177 var overflow = _win.innerHeight > _doc.documentElement.getBoundingClientRect().height; 178 179 return { 180 'overflow-y': overflow ? 'hidden' : 'scroll', 181 position: 'fixed', 182 width: '100%', 183 top: getAdminBarHeight(true) + 'px', 184 left: 0 185 }; 186 } 187 188 // Show the modal
189 modals.forEach(function (modal) { 190 modal.addEventListener('toggle-target-before-inactive', function (event) { 191 var styles = htmlStyles(), 192 offsetY = _win.pageYOffset, 193 paddingTop = (Math.abs(getAdminBarHeight()) - offsetY) + 'px', 194 mQuery = _win.matchMedia('(max-width: 600px)'); 195 196 if (event.target !== modal) { 197 return; 198 } 199 200 Object.keys(styles).forEach(function (styleKey) { 201 htmlStyle.setProperty(styleKey, styles[styleKey]); 202 }); 203 204 _win.twentytwenty.scrolled = parseInt(styles.top, 10); 205 206 if (adminBar) { 207 _doc.body.style.setProperty('padding-top', paddingTop); 208 209 if (mQuery.matches) { 210 if (offsetY >= getAdminBarHeight()) { 211 modal.style.setProperty('top', 0); 212 } else { 213 modal.style.setProperty('top', (getAdminBarHeight() - offsetY) + 'px'); 214 } 215 } 216 } 217 218 modal.classList.add('show-modal'); 219 }); 220 221 // Hide the modal after a delay, so animations have time to play out 222 modal.addEventListener('toggle-target-after-inactive', function (event) { 223 if (event.target !== modal) { 224 return; 225 } 226 227 setTimeout(function () { 228 var clickedEl = twentytwenty.toggles.clickedEl; 229 230 modal.classList.remove('show-modal'); 231 232 Object.keys(htmlStyles()).forEach(function (styleKey) { 233 htmlStyle.removeProperty(styleKey); 234 }); 235 236 if (adminBar) { 237 _doc.body.style.removeProperty('padding-top'); 238 modal.style.removeProperty('top'); 239 } 240 241 if (clickedEl !== false) { 242 clickedEl.focus(); 243 clickedEl = false; 244 } 245 246 _win.scrollTo(0, Math.abs(_win.twentytwenty.scrolled + getAdminBarHeight())); 247 248 _win.twentytwenty.scrolled = 0; 249 }, 500); 250 }); 251 }); 252 }, 253 254 // Untoggle a modal 255 untoggleModal: function (modal) { 256 var modalTargetClass, 257 modalToggle = false; 258 259 // If the modal has specified the string (ID or class) used by toggles to target it, untoggle the toggles with that target string 260 // The modal-target-string must match the string toggles use to target the modal 261 if (modal.dataset.modalTargetString) { 262 modalTargetClass = modal.dataset.modalTargetString; 263 264 modalToggle = document.querySelector('*[data-toggle-target="' + modalTargetClass + '"]'); 265 } 266 267 // If a modal toggle exists, trigger it so all of the toggle options are included 268 if (modalToggle) { 269 modalToggle.click(); 270 271 // If one doesn't exist, just hide the modal 272 } else { 273 modal.classList.remove('active'); 274 } 275 } 276 277}; // twentytwenty.coverModals 278 279/* ----------------------------------------------------------------------------------------------- 280 Intrinsic Ratio Embeds 281--------------------------------------------------------------------------------------------------- */ 282 283twentytwenty.intrinsicRatioVideos = { 284 285 init: function () { 286 this.makeFit(); 287 288 window.addEventListener('resize', function () { 289 this.makeFit(); 290 }.bind(this)); 291 }, 292 293 makeFit: function () { 294 document.querySelectorAll('iframe, object, video').forEach(function (video) { 295 var ratio, iTargetWidth, 296 container = video.parentNode; 297 298 // Skip videos we want to ignore 299 if (video.classList.contains('intrinsic-ignore') || video.parentNode.classList.contains('intrinsic-ignore')) { 300 return true; 301 } 302 303 if (!video.dataset.origwidth) { 304 // Get the video element proportions 305 video.setAttribute('data-origwidth', video.width); 306 video.setAttribute('data-origheight', video.height); 307 } 308 309 iTargetWidth = container.offsetWidth; 310 311 // Get ratio from proportions 312 ratio = iTargetWidth / video.dataset.origwidth; 313 314 // Scale based on ratio, thus retaining proportions 315 video.style.width = iTargetWidth + 'px'; 316 video.style.height = (video.dataset.origheight * ratio) + 'px'; 317 }); 318 } 319 320}; // twentytwenty.instrinsicRatioVideos 321 322/* ----------------------------------------------------------------------------------------------- 323 Smooth Scroll 324--------------------------------------------------------------------------------------------------- */ 325 326twentytwenty.smoothScroll = { 327 328 init: function () { 329 // Scroll to anchor 330 this.scrollToAnchor(); 331 332 // Scroll to element 333 this.scrollToElement(); 334 }, 335 336 // Scroll to anchor 337 scrollToAnchor: function () { 338 var anchorElements = document.querySelectorAll('a[href*="#"]'); 339 var anchorElementsList = Array.prototype.slice.call(anchorElements); 340 anchorElementsList.filter(function (element) { 341 if (element.href === '#' || element.href === '#0' || element.i
341d === 'cancel-comment-reply-link' || element.classList.contains('do-not-scroll') || element.classList.contains('skip-link')) { 342 return false; 343 } 344 return true; 345 }).forEach(function (element) { 346 element.addEventListener('click', function (event) { 347 var target, scrollOffset, originalOffset, adminBar, scrollSpeed, additionalOffset; 348 349 // On-page links 350 if (window.location.hostname === event.target.hostname) { 351 // Figure out element to scroll to 352 target = window.location.hash !== '' && document.querySelector(window.location.hash); 353 target = target ? target : event.target.hash !== '' && document.querySelector(event.target.hash); 354 355 // Does a scroll target exist? 356 if (target) { 357 // Only prevent default if animation is actually gonna happen 358 event.preventDefault(); 359 360 // Get options 361 additionalOffset = event.target.dataset.additionalOffset; 362 scrollSpeed = event.target.dataset.scrollSpeed ? event.target.dataset.scrollSpeed : 500; 363 364 // Determine offset 365 366 adminBar = document.querySelector('#wpadminbar'); 367 368 originalOffset = target.getBoundingClientRect().top + window.pageYOffset; 369 scrollOffset = additionalOffset ? originalOffset + additionalOffset : originalOffset; 370 371 if (adminBar && event.target.className === 'to-the-top') { 372 scrollOffset = scrollOffset - adminBar.getBoundingClientRect().height; 373 } 374 375 twentytwentyScrollTo(scrollOffset, null, scrollSpeed); 376 377 window.location.hash = event.target.hash.slice(1); 378 } 379 } 380 }); 381 }); 382 }, 383 384 // Scroll to element 385 scrollToElement: function () { 386 var scrollToElement = document.querySelector('*[data-scroll-to]'); 387 388 if (scrollToElement) { 389 scrollToElement.addEventListener('click', function (event) { 390 var originalOffset, additionalOffset, scrollOffset, scrollSpeed, 391 // Figure out element to scroll to 392 target = event.target.dataset.twentytwentyScrollTo; 393 394 // Make sure said element exists 395 if (target) { 396 event.preventDefault(); 397 398 // Get options 399 additionalOffset = event.target.dataset.additionalOffset; 400 scrollSpeed = event.target.dataset.scrollSpeed ? event.target.dataset.scrollSpeed : 500; 401 402 // Determine offset 403 originalOffset = target.getBoundingClientRect().top + window.pageYOffset; 404 scrollOffset = additionalOffset ? originalOffset + additionalOffset : originalOffset; 405 406 twentytwentyScrollTo(scrollOffset, null, scrollSpeed); 407 } 408 }); 409 } 410 } 411 412}; // twentytwenty.smoothScroll 413 414/* ----------------------------------------------------------------------------------------------- 415 Modal Menu 416--------------------------------------------------------------------------------------------------- */ 417twentytwenty.modalMenu = { 418 419 init: function () { 420 // If the current menu item is in a sub level, expand all the levels higher up on load 421 this.expandLevel(); 422 this.keepFocusInModal(); 423 }, 424 425 expandLevel: function () { 426 var modalMenus = document.querySelectorAll('.modal-menu'); 427 428 modalMenus.forEach(function (modalMenu) { 429 var activeMenuItem = modalMenu.querySelector('.current-menu-item'); 430 431 if (activeMenuItem) { 432 twentytwentyFindParents(activeMenuItem, 'li').forEach(function (element) { 433 var subMenuToggle = element.querySelector('.sub-menu-toggle'); 434 if (subMenuToggle) { 435 twentytwenty.toggles.performToggle(subMenuToggle, true); 436 } 437 }); 438 } 439 }); 440 }, 441 442 keepFocusInModal: function () { 443 var _doc = document; 444 445 _doc.addEventListener('keydown', function (event) { 446 var toggleTarget, modal, selectors, elements, menuType, bottomMenu, activeEl, lastEl, firstEl, tabKey, shift
446Key, 447 clickedEl = twentytwenty.toggles.clickedEl; 448 449 if (clickedEl && _doc.body.classList.contains('showing-modal')) { 450 toggleTarget = clickedEl.dataset.toggleTarget; 451 selectors = 'input, a, button'; 452 modal = _doc.querySelector(toggleTarget); 453 454 elements = modal.querySelectorAll(selectors); 455 elements = Array.prototype.slice.call(elements); 456 457 if ('.menu-modal' === toggleTarget) { 458 menuType = window.matchMedia('(min-width: 1000px)').matches; 459 menuType = menuType ? '.expanded-menu' : '.mobile-menu'; 460 461 elements = elements.filter(function (element) { 462 return null !== element.closest(menuType) && null !== element.offsetParent; 463 }); 464 465 elements.unshift(_doc.querySelector('.close-nav-toggle')); 466 467 bottomMenu = _doc.querySelector('.menu-bottom > nav'); 468 469 if (bottomMenu) { 470 bottomMenu.querySelectorAll(selectors).forEach(function (element) { 471 elements.push(element); 472 }); 473 } 474 } 475 476 lastEl = elements[elements.length - 1]; 477 firstEl = elements[0]; 478 activeEl = _doc.activeElement; 479 tabKey = event.keyCode === 9; 480 shiftKey = event.shiftKey; 481 482 if (!shiftKey && tabKey && lastEl === activeEl) { 483 event.preventDefault(); 484 firstEl.focus(); 485 } 486 487 if (shiftKey && tabKey && firstEl === activeEl) { 488 event.preventDefault(); 489 lastEl.focus(); 490 } 491 } 492 }); 493 } 494}; // twentytwenty.modalMenu 495 496/* ----------------------------------------------------------------------------------------------- 497 Primary Menu 498--------------------------------------------------------------------------------------------------- */ 499 500twentytwenty.primaryMenu = { 501 502 init: function () { 503 this.focusMenuWithChildren(); 504 }, 505 506 // The focusMenuWithChildren() function implements Keyboard Navigation in the Primary Menu 507 // by adding the '.focus' class to all 'li.menu-item-has-children' when the focus is on the 'a' element. 508 focusMenuWithChildren: function () { 509 // Get all the link elements within the primary menu. 510 var links, i, len, 511 menu = document.querySelector('.primary-menu-wrapper'); 512 513 if (!menu) { 514 return false; 515 } 516 517 links = menu.getElementsByTagName('a'); 518 519 // Each time a menu link is focused or blurred, toggle focus. 520 for (i = 0, len = links.length; i < len; i++) { 521 links[i].addEventListener('focus', toggleFocus, true); 522 links[i].addEventListener('blur', toggleFocus, true); 523 } 524 525 //Sets or removes the .focus class on an element. 526 function toggleFocus() { 527 var self = this; 528 529 // Move up through the ancestors of the current link until we hit .primary-menu. 530 while (-1 === self.className.indexOf('primary-menu')) { 531 // On li elements toggle the class .focus. 532 if ('li' === self.tagName.toLowerCase()) { 533 if (-1 !== self.className.indexOf('focus')) { 534 self.className = self.className.replace(' focus', ''); 535 } else { 536 self.className += ' focus'; 537 } 538 } 539 self = self.parentElement; 540 } 541 } 542 } 543}; // twentytwenty.primaryMenu 544 545/* ----------------------------------------------------------------------------------------------- 546 Toggles 547--------------------------------------------------------------------------------------------------- */ 548 549twentytwenty.toggles = { 550 551 clickedEl: false, 552 553 init: function () { 554 // Do the toggle 555 this.toggle(); 556 557 // Check for toggle/untoggle on resize 558 this.resizeCheck(); 559 560 // Check for untoggle on escape key press 561 this.untoggleOnEscapeKeyPress(); 562 }, 563 564 performToggle: function (element, instantly) { 565 var target, timeOutTime, classToToggle, 566 self = this, 567 _doc = document, 568 // Get our targets 569 toggle = element, 570 targetString = toggle.dataset.toggleTarget, 571 activeClass = 'active'; 572 573 // Elements to focus after modals are closed 574 if (!_doc.querySelectorAll('.show-modal').length) { 575 self.clickedEl = _doc.activeElement; 576 } 577 578 if (targetString === 'next') { 579 target = toggle.nextSibling; 580 } else { 581 target = _doc.querySelector(targetString); 582 } 583
584 // Trigger events on the toggle targets before they are toggled 585 if (target.classList.contains(activeClass)) { 586 target.dispatchEvent(twentytwenty.createEvent('toggle-target-before-active')); 587 } else { 588 target.dispatchEvent(twentytwenty.createEvent('toggle-target-before-inactive')); 589 } 590 591 // Get the class to toggle, if specified 592 classToToggle = toggle.dataset.classToToggle ? toggle.dataset.classToToggle : activeClass; 593 594 // For cover modals, set a short timeout duration so the class animations have time to play out 595 timeOutTime = 0; 596 597 if (target.classList.contains('cover-modal')) { 598 timeOutTime = 10; 599 } 600 601 setTimeout(function () { 602 var focusElement, 603 subMenued = target.classList.contains('sub-menu'), 604 newTarget = subMenued ? toggle.closest('.menu-item').querySelector('.sub-menu') : target, 605 duration = toggle.dataset.toggleDuration; 606 607 // Toggle the target of the clicked toggle 608 if (toggle.dataset.toggleType === 'slidetoggle' && !instantly && duration !== '0') { 609 twentytwentyMenuToggle(newTarget, duration); 610 } else { 611 newTarget.classList.toggle(classToToggle); 612 } 613 614 // If the toggle target is 'next', only give the clicked toggle the active class 615 if (targetString === 'next') { 616 toggle.classList.toggle(activeClass); 617 } else if (target.classList.contains('sub-menu')) { 618 toggle.classList.toggle(activeClass); 619 } else { 620 // If not, toggle all toggles with this toggle target 621 _doc.querySelector('*[data-toggle-target="' + targetString + '"]').classList.toggle(activeClass); 622 } 623 624 // Toggle aria-expanded on the toggle 625 twentytwentyToggleAttribute(toggle, 'aria-expanded', 'true', 'false'); 626 627 if (self.clickedEl && -1 !== toggle.getAttribute('class').indexOf('close-')) { 628 twentytwentyToggleAttribute(self.clickedEl, 'aria-expanded', 'true', 'false'); 629 } 630 631 // Toggle body class 632 if (toggle.dataset.toggleBodyClass) { 633 _doc.body.classList.toggle(toggle.dataset.toggleBodyClass); 634 } 635 636 // Check whether to set focus 637 if (toggle.dataset.setFocus) { 638 focusElement = _doc.querySelector(toggle.dataset.setFocus); 639 640 if (focusElement) { 641 if (target.classList.contains(activeClass)) { 642 focusElement.focus(); 643 } else { 644 focusElement.blur(); 645 } 646 } 647 } 648 649 // Trigger the toggled event on the toggle target 650 target.dispatchEvent(twentytwenty.createEvent('toggled')); 651 652 // Trigger events on the toggle targets after they are toggled 653 if (target.classList.contains(activeClass)) { 654 target.dispatchEvent(twentytwenty.createEvent('toggle-target-after-active')); 655 } else { 656 target.dispatchEvent(twentytwenty.createEvent('toggle-target-after-inactive')); 657 } 658 }, timeOutTime); 659 }, 660 661 // Do the toggle 662 toggle: function () { 663 var self = this; 664 665 document.querySelectorAll('*[data-toggle-target]').forEach(function (element) { 666 element.addEventListener('click', function (event) { 667 event.preventDefault(); 668 self.performToggle(element); 669 }); 670 }); 671 }, 672 673 // Check for toggle/untoggle on screen resize 674 resizeCheck: function () { 675 if (document.querySelectorAll('*[data-untoggle-above], *[data-untoggle-below], *[data-toggle-above], *[data-toggle-below]').length) { 676 window.addEventListener('resize', function () { 677 var winWidth = window.innerWidth, 678 toggles = document.querySelectorAll('.toggle'); 679 680 toggles.forEach(function (toggle) { 681 var unToggleAbove = toggle.dataset.untoggleAbove, 682 unToggleBelow = toggle.dataset.untoggleBelow, 683 toggleAbove = toggle.dataset.toggleAbove, 684 toggleBelow = toggle.dataset.toggleBelow; 685 686 // If no width comparison is set, continue 687 if (!unToggleAbove && !unToggleBelow && !toggleAbove && !toggleBelow) { 688 return; 689 } 690 691 // If the toggle width comparison is true, toggle the toggle 692 if ( 693 (((unToggleAbove && winWidth > unToggleAbove) || 694 (unToggleBelow && winWidth < unToggleBelow)) && 695 toggle.classList.contains('active')) || 696 (((toggleAbove && winWidth > toggleAbove) || 697 (toggleBelow && winWidth < toggleBelow)) && 698 !toggle.classList.contains('active')) 699 ) { 700 toggle.click(); 701 } 702 }); 703 }); 704 } 705 }, 706 707 // Close toggle on escape key press 708 untoggleOnEscapeKeyPress: function () { 709 document.addEventListener('keyup', function (event) { 710 if (event.key === 'Escape') { 711 document.querySelectorAll('*[data-untoggle-on-escape].active').forEach(function (element) { 712 if (element.classList.contains('active')) { 713 element.click(); 714 } 715 }); 716 } 717 }); 718 } 719 720}; // twentytwenty.toggles 721 722/** 723 * Is the DOM ready 724 * 725 * this implementation is coming from https://gomakethings.com/a-native-javascript-equivalent-of-jquerys-ready-method/ 726 * 727 * @param {Function} fn Callback function to run. 728 */ 729function twentytwentyDomReady(fn) { 730 if (typeof fn !== 'function') { 731 return; 732 } 733 734 if (document.readyState === 'interactive' || document.readyState === 'complete') { 735 return fn(); 736 } 737 738 document.addEventListener('DOMContentLoaded', fn, false); 739} 740 741twentytwentyDomReady(function () { 742 twentytwenty.toggles.init(); // Handle toggles 743 twentytwenty.coverModals.init(); // Handle cover modals 744 twentytwenty.intrinsicRatioVideos.init(); // Retain aspect ratio of videos on window resize 745 twentytwenty.smoothScroll.init(); // Smooth scroll to anchor link or a specific element 746 twentytwenty.modalMenu.init(); // Modal Menu 747 twentytwenty.primaryMenu.init(); // Primary Menu 748}); 749 750/* ----------------------------------------------------------------------------------------------- 751 Helper functions 752--------------------------------------------------------------------------------------------------- */ 753 754/* Toggle an attribute ----------------------- */ 755 756function twentytwentyToggleAttribute(element, attribute, trueVal, falseVal) { 757 if (trueVal === undefined) { 758 trueVal = true; 759 } 760 if (falseVal === undefined) { 761 falseVal = false; 762 } 763 if (element.getAttribute(attribute) !== trueVal) { 764 element.setAttribute(attribute, trueVal); 765 } else { 766 element.setAttribute(attribute, falseVal); 767 } 768} 769 770/** 771 * Toggle a menu item on or off. 772 * 773 * @param {HTMLElement} target 774 * @param {number} duration 775 */ 776function twentytwentyMenuToggle(target, duration) { 777 var initialParentHeight, finalParentHeight, menu, menuItems, transitionListener, 778 initialPositions = [], 779 finalPositions = []; 780 781 if (!target) { 782 return; 783 } 784 785 menu = target.closest('.menu-wrapper'); 786 787 // Step 1: look at the initial positions of every menu item. 788 menuItems = menu.querySelectorAll('.menu-item'); 789
790 menuItems.forEach(function (menuItem, index) { 791 initialPositions[index] = { 792 x: menuItem.offsetLeft, 793 y: menuItem.offsetTop 794 }; 795 }); 796 initialParentHeight = target.parentElement.offsetHeight; 797 798 target.classList.add('toggling-target'); 799 800 // Step 2: toggle target menu item and look at the final positions of every menu item. 801 target.classList.toggle('active'); 802 803 menuItems.forEach(function (menuItem, index) { 804 finalPositions[index] = { 805 x: menuItem.offsetLeft, 806 y: menuItem.offsetTop 807 }; 808 }); 809 finalParentHeight = target.parentElement.offsetHeight; 810 811 // Step 3: close target menu item again. 812 // The whole process happens without giving the browser a chance to render, so it's invisible. 813 target.classList.toggle('active'); 814 815 // Step 4: prepare animation. 816 // Position all the items with absolute offsets, at the same starting position. 817 // Shouldn't result in any visual changes if done right. 818 menu.classList.add('is-toggling'); 819 target.classList.toggle('active'); 820 menuItems.forEach(function (menuItem, index) { 821 var initialPosition = initialPositions[index]; 822 if (initialPosition.y === 0 && menuItem.parentElement === target) { 823 initialPosition.y = initialParentHeight; 824 } 825 menuItem.style.transform = 'translate(' + initialPosition.x + 'px, ' + initialPosition.y + 'px)'; 826 }); 827 828 // The double rAF is unfortunately needed, since we're toggling CSS classes, and 829 // the only way to ensure layout completion here across browsers is to wait twice. 830 // This just delays the start of the animation by 2 frames and is thus not an issue. 831 requestAnimationFrame(function () { 832 requestAnimationFrame(function () { 833 // Step 5: start animation by moving everything to final position. 834 // All the layout work has already happened, while we were preparing for the animation. 835 // The animation now runs entirely in CSS, using cheap CSS properties (opacity and transform) 836 // that don't trigger the layout or paint stages. 837 menu.classList.add('is-animating'); 838 menuItems.forEach(function (menuItem, index) { 839 var finalPosition = finalPositions[index]; 840 if (finalPosition.y === 0 && menuItem.parentElement === target) { 841 finalPosition.y = finalParentHeight; 842 } 843 if (duration !== undefined) { 844 menuItem.style.transitionDuration = duration + 'ms'; 845 } 846 menuItem.style.transform = 'translate(' + finalPosition.x + 'px, ' + finalPosition.y + 'px)'; 847 }); 848 if (duration !== undefined) { 849 target.style.transitionDuration = duration + 'ms'; 850 } 851 }); 852 853 // Step 6: finish toggling. 854 // Remove all transient classes when the animation ends. 855 transitionListener = function () { 856 menu.classList.remove('is-animating'); 857 menu.classList.remove('is-toggling'); 858 target.classList.remove('toggling-target'); 859 menuItems.forEach(function (menuItem) { 860 menuItem.style.transform = ''; 861 menuItem.style.transitionDuration = ''; 862 }); 863 target.style.transitionDuration = ''; 864 target.removeEventListener('transitionend', transitionListener); 865 }; 866 867 target.addEventListener('transitionend', transitionListener); 868 }); 869} 870 871/** 872 * traverses the DOM up to find elements matching the query 873 * 874 * @param {HTMLElement} target 875 * @param {string} query 876 * @return {NodeList} parents matching query 877 */ 878function twentytwentyFindParents(target, query) { 879 var parents = []; 880 881 // recursively go up the DOM adding matches to the parents array 882 function traverse(item) { 883 var parent = item.parentNode; 884 if (parent instanceof HTMLElement) { 885 if (parent.matches(query)) { 886 parents.push(parent); 887 } 888 traverse(parent); 889 } 890 } 891 892 traverse(target); 893 894 return parents; 895} 896 897// twentytwentyEaseInOutQuad functions http://goo.gl/5HLl8 898function twentytwentyEaseInOutQuad(t, b, c, d) { 899 t /= d / 2; 900 if (t < 1) { 901 return (((c / 2) * t) * t) + b; 902 } 903 t--; 904 return ((-c / 2) * ((t * (t - 2)) - 1)) + b; 905} 906 907function twentytwentyScrollTo(to, callback, duration) { 908 var start, change, increment, currentTime; 909 910 function move(amount) { 911 document.documentElement.scrollTop = amount; 912 document.body.parentNode.scrollTop = amount; 913 document.body.scrollTop = amount; 914 } 915 916 start = document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop; 917 change = to - start; 918 increment = 20; 919 currentTime = 0; 920 921 duration = (typeof (duration) === 'undefined') ? 500 : duration; 922 923 function animateScroll() { 924 var val; 925 926 // increment the time 927 currentTime += increment; 928 // find the value with the quadratic in-out twentytwentyEaseInOutQuad function 929 val = twentytwentyEaseInOutQuad(currentTime, start, change, duration); 930 // move the document.body 931 move(val); 932 // do the animation unless its over 933 if (currentTime < duration) { 934 window.requestAnimationFrame(animateScroll); 935 } else if (callback && typeof (callback) === 'function') { 936 // the animation is done so lets callback 937 callback(); 938 } 939 } 940 animateScroll(); 941} 942 943 944jQuery(document).ready(function ($) { 945 jQuery(document).on("click", ".right_side_links_wrapper .custom_menu .menu_item.menu-item-has-children", function () { 946 if (jQuery(this).find(".sub-menu").is(":hidden")) { 947 jQuery(this).find(".sub-menu").slideDown(); 948 } else { 949 jQuery(this).find(".sub-menu").slideUp(); 950 } 951 }) 952 953 jQuery(document).on("click", ".close-nav-toggle", function () { 954 jQuery(".menu-item").removeClass("child_active sub_child_active"); 955 jQuery(".movable_menu_items").removeClass("sub_menu_child_open sub_menu_open"); 956 }) 957 958 jQuery(".menu-modal .mobile-menu .menu-item.menu-item-has-children").each(function () { 959 var menu_name = jQuery(this).find(">.ancestor-wrapper > a").text(); 960 if (jQuery(this).find(".menu_heading").length == 0) { 961 jQuery(this).find(">.sub-menu").prepend("<li class='menu_heading'>" + menu_name + "</li>"); 962 jQuery(this).find(">.sub-menu").prepend("<li class='menu_back_to_parent'> <svg width='8' height='14' viewBox='0 0 8 14' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M7.70711 1.70711C8.09763 1.31658 8.09763 0.683418 7.70711 0.292893
962C7.31658 -0.0976311 6.68342 -0.0976311 6.29289 0.292893L7.70711 1.70711ZM1 7L0.292893 6.29289C-0.0976312 6.68342 -0.0976313 7.31658 0.292893 7.70711L1 7ZM6.29289 13.7071C6.68342 14.0976 7.31658 14.0976 7.70711 13.7071C8.09763 13.3166 8.09763 12.6834 7.70711 12.2929L6.29289 13.7071ZM6.29289 0.292893L0.292893 6.29289L1.70711 7.70711L7.70711 1.70711L6.29289 0.292893ZM0.292893 7.70711L6.29289 13.7071L7.70711 12.2929L1.70711 6.29289L0.292893 7.70711Z' fill='currentColor'/></svg> Back</li>"); 963 } 964 }) 965 966 jQuery(document).on("click", ".menu-modal .mobile-menu > .modal-menu > .menu-item.menu-item-has-children > .ancestor-wrapper > a", function (e) { 967 e.preventDefault(); 968 jQuery(this).parent().parent().addClass("child_active"); 969 jQuery(".movable_menu_items").addClass("sub_menu_open"); 970 }) 971 972 jQuery(document).on("click", ".menu-modal .mobile-menu > .modal-menu > .menu-item > .sub-menu .menu-item-has-children > .ancestor-wrapper > a", function (e) { 973 e.preventDefault(); 974 jQuery(".movable_menu_items").addClass("sub_menu_child_open"); 975 jQuery(this).parent().parent().addClass("sub_child_active"); 976 }) 977 978 jQuery(document).on("click", ".menu_back_to_parent", function () { 979 if (jQuery(".movable_menu_items").hasClass("sub_menu_child_open")) { 980 jQuery(".movable_menu_items").removeClass("sub_menu_child_open"); 981 setTimeout(function () { 982 jQuery(".menu-modal .menu-modal-inner .menu-top .mobile-menu > .modal-menu > .menu-item > .sub-menu .menu-item-has-children").removeClass("sub_child_active"); 983 }, 400); 984 } else { 985 jQuery(".movable_menu_items").removeClass("sub_menu_open"); 986 setTimeout(function () { 987 jQuery(".menu-modal .menu-modal-inner .menu-top .mobile-menu > .modal-menu > .menu-item").removeClass("child_active"); 988 }, 400); 989 } 990 }) 991 992 if (jQuery(window).width() < 768) { 993 jQuery('.column_item.column_first .heading').addClass("active"); 994 jQuery(".column_item.column_first .menu_section").css("display", "block"); 995 } 996 997 jQuery(document).on("click", ".column_item .heading", function (e) { 998 if (jQuery(window).width() < 768) { 999 e.preventDefault(); 1000 if (jQuery(this).next().is(":hidden")) { 1001 jQuery(".menu_section").slideUp(400); 1002 jQuery(".heading").removeClass("active"); 1003 jQuery(this).addClass("active"); 1004 jQuery(this).next().slideDown(); 1005 } else { 1006 jQuery(this).removeClass("active"); 1007 jQuery(this).next().slideUp(); 1008 } 1009 } 1010 }); 1011}); 1012 1013jQuery(window).on("scroll", function () { 1014 if (jQuery(window).scrollTop() > 100) { 1015 jQuery("body").addClass("sticky_header"); 1016 } else { 1017 jQuery("body").removeClass("sticky_header"); 1018 } 1019})
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.