PageSourceSearch

https://mywpcustomize.com/wp-content/themes/Total/assets/js/frontend/core.js?ver=5.18.2

js mywpcustomize.com collected 2026-10-02 03:44:53 UTC 100,050 bytes, 3,512 lines download raw bytes

1/* jshint esversion: 6 */
2var wpex = {};
3
4( function( l10n ) {
5
6	'use strict';
7
8	wpex = {
9
10		/**
11		 * Main init function.
12		 */
13		init: function() {
14			this.config();
15			this.replaceNoJsClass();
16			this.bindEvents();
17		},
18
19		/**
20		 * Define vars for caching.
21		 */
22		config: function() {
23			this.config = {
24				localScrollSections: []
25			};
26		},
27
28		/**
29		 * Replaces the "wpex-no-js" body class with "wpex-js".
30		 */
31		replaceNoJsClass: function() {
32			let bodyClass = document.body.className;
33			bodyClass = bodyClass.replace(/wpex-no-js/, 'wpex-js');
34			document.body.className = bodyClass;
35		},
36
37		/**
38		 * Bind Events.
39		 */
40		bindEvents: function() {
41			const self = this;
42
43			self.setScrollBarWidth();
44
45			/*** Fires as soon as the document is loaded ***/
46			self.domReady( () => {
47				document.body.classList.add( 'wpex-docready' );
48
49				if ( self.retinaCheck() ) {
50					document.body.classList.add( 'wpex-is-retina' );
51				}
52
53				if ( self.mobileCheck() ) {
54					document.body.classList.add( 'wpex-is-mobile-device' );
55				}
56
57				self.localScrollSections();
58				self.megaMenuAddClasses();
59				self.dropdownMenuOnclick();
60				self.dropdownMenuTouch();
61				self.inlineHeaderLogo();
62				self.menuSearch();
63				self.skipToContent();
64				self.backTopLink();
65				self.backTopButton();
66				self.goBackButton();
67				self.smoothCommentScroll();
68				self.dialogs();
69				self.toggleElements();
70				self.localScrollLinks();
71				self.customSelects();
72				self.hoverStyles();
73				self.overlaysMobileSupport();
74				self.accessibility();
75				self.wpbakery();
76			} );
77
78			/*** Fires once the window has been loaded ***/
79			window.addEventListener( 'load', () => {
80				document.body.classList.add( 'wpex-window-loaded' );
81
82				// Main.
83				self.megaMenusWidth();
84				self.megaMenusTop();
85				self.stickyTopBar();
86				self.headerOverlayOffset(); // Add before sticky header ( important ).
87				self.equalHeights();
88				self.localScrollHighlight();
89
90				// Sticky functions.
91				self.stickyHeaderMenu(); // must go before stickyHeader to prevent issues with dyn-styles
92				self.stickyHeader();
93				self.stickyElements();
94
95				// Run methods after sticky header.
96				self.footerReveal(); // Footer Reveal => Must run before fixed footer!!!
97				self.fixedFooter(); // Fixed Footer => Must run after footerReveal!!!
98
99				// Scroll to hash (must be last).
100				if ( l10n.scrollToHash ) {
101					window.setTimeout( function() {
102						self.scrollToHash( self );
103					}
103, parseInt( l10n.scrollToHashTimeout ) );
104				}
105			} );
106
107			/*** Fires on window resize ***/
108			window.addEventListener( 'resize', () => {
109				self.setScrollBarWidth();
110			} );
111		},
112
113		/**
114		 * Set scrollbar width variable to fix issues with 100vw
115		 */
116		setScrollBarWidth: function() {
117			const self = this;
118			const elements = document.querySelectorAll( '.wpex-vc-full-width-row:not(.wpex-vc-full-width-row--centered)' );
119			if ( ! elements ) {
120				return;
121			}
122			const scrollbarWidth = self.getScrollBarWidth();
123			elements.forEach( ( element ) => {
124				element.style.setProperty('--scrollbar-width', `${scrollbarWidth}px`);
125			} );
126		},
127
128		/**
129		 * Onclick dropdown menu.
130		 */
131		dropdownMenuOnclick: function() {
132
133			/*
134			// @todo should we implement this?
135			document.querySelectorAll( '.wpex-dropdown-menu--onclick .menu-item-has-children > a' ).forEach( function( element ) {
136				if ( element.closest( '.megamenu__inner-ul' ) ) {
137					return;
138				}
139				element.setAttribute( 'aria-expanded', 'false' );
140				if ( l10n.dropdownMenuAriaLabel ) {
141					var ariaLabel = l10n.dropdownMenuAriaLabel;
142					element.setAttribute( 'aria-label', ariaLabel.replace( '%s', element.textContent.trim() ) );
143				}
144			} );*/
145
146			document.addEventListener( 'click', ( event ) => {
147				const target = event.target;
148
149				if ( ! target.closest( '.wpex-dropdown-menu--onclick .menu-item-has-children > a' ) ) {
150					document.querySelectorAll( '.wpex-dropdown-menu--onclick .menu-item-has-children' ).forEach( ( element ) => {
151						element.classList.remove( 'wpex-active' );
152						//element.querySelector( 'a' ).setAttribute( 'aria-expanded', 'false' );
153					} );
154					return;
155				}
156
157				document.querySelectorAll( '.wpex-dropdown-menu--onclick .menu-item-has-children' ).forEach( ( element ) => {
158					if ( ! element.contains( target ) ) {
159						element.classList.remove( 'wpex-active' );
160						//element.querySelector( 'a' ).setAttribute( 'aria-expanded', 'false' );
161					}
162				} );
163
164				const li = target.closest( '.menu-item-has-children' );
165				const a = target.closest( 'a' );
166
167				if ( li.classList.contains( 'wpex-active' ) ) {
168					li.classList.remove( 'wpex-active' );
169					//a.setAttribute( 'aria-expanded', 'false' );
170					if ( '#' === a.getAttribute( 'href' ) ) {
171						event.preventDefault();
172					}
173				} else {
174					li.classList.add( 'wpex-active' );
175					//a.setAttribute( 'aria-expanded', 'true' );
176					event.preventDefault();
177				}
178
179			} );
180
181			document.addEventListener( 'keydown', ( event ) => {
182				const target = event.target.closest( '.wpex-dropdown-menu--onclick .menu-item-has-children.wpex-active' );
183				if ( 'Escape' !== event.key || ! target ) {
184					return;
185				}
186				target.classList.remove( 'wpex-active' );
187				//target.querySelector( 'a' ).setAttribute( 'aria-expanded', 'false' );
188			} );
189
190			document.querySelectorAll( '.wpex-dropdown-menu--onclick .sub-menu' ).forEach( ( dropdown ) => {
191				dropdown.addEventListener( 'keydown', ( event ) => {
192					if ( 'Escape' === event.key ) {
193						const closestParent = event.target.closest( '.menu-item-has-children.wpex-active' );
194						if ( closestParent ) {
195							closestParent.classList.remove( 'wpex-active' );
196							const closestParentA = closestParent.querySelector( 'a' );
197							//closestParentA.setAttribute( 'aria-expanded', 'false' );
198							closestParentA.focus();
199							event.stopPropagation();
200						}
201					}
202				} );
203			} );
204		},
205
206		/*
207		 * Dropdown menu touch support.
208		 */
209		dropdownMenuTouch: function() {
210			const self = this;
211			let dragging = false;
212
213			document.querySelectorAll( '.wpex-dropdown-menu--onhover .menu-item-has-children > a' ).forEach( ( element ) => {
214				element.addEventListener( 'touchend', ( event ) => {
215					if ( dragging ) {
216						// hide dropdowns here?
217						return;
218					}
219
220					const parent = element.closest( '.menu-item-has-children' );
221
222					if ( parent.classList.contains( 'wpex-touched' ) ) {
223						return; // already open allow clicking.
224					}
225
226					event.preventDefault();
227
228					parent.classList.add( 'wpex-touched' );
229				} );
230
231				element.addEventListener( 'touchmove', ( event ) => {
232					dragging = true;
233				}, { passive: true } );
234
235				element.addEventListener( 'touchstart', ( event ) => {
236					dragging = false;
237				}, { passive: true } );
238			} );
239
240			const clickingOutside = ( event ) => {
241				const target = event.target;
242				// not this code will only run if menus are open so it's efficient.
243				document.querySelectorAll( '.menu-item-has-children.wpex-touched' ).forEach( ( element ) => {
244					if ( element.contains( target ) ) {
245						return;
246					}
247					element.classList.remove( 'wpex-touched' );
248				} );
249			};
250
251			document.addEventListener( 'touchstart', clickingOutside, { passive: true } );
252			document.addEventListener( 'touchmove', clickingOutside, { passive: true } );
253		},
254
255		/**
256		 * Add "megamenu__inner-ul" classname to megamenu inner UL elements.
257		 */
258		megaMenuAddClasses: function() {
259			document.querySelectorAll( '.main-navigation-ul .megamenu > .sub-menu' ).forEach( ( element ) => {
260				element.querySelectorAll( '.sub-menu' ).forEach( ( element ) => {
261					element.classList.add( 'megamenu__inner-ul' );
262				} );
263			} );
264		},
265
266		/**
267		 * MegaMenus Width.
268		 *
269		 * @todo Instead of listening on resize perhaps we should run when resize ends.
270		 *       since usually dropdowns won't be open while resizing.
271		 */
272		megaMenusWidth: function() {
273			const self = this;
274
275			const init = () => {
276				const navWrap = document.querySelector( '#site-navigation-wrap.wpex-stretch-megamenus' );
277				let navLeft;
278				let headerInnerLeft;
279
280				if ( ! self.isVisible( navWrap ) ) {
281					return;
282				}
283
284				const megaMenus = navWrap.querySelectorAll( '.megamenu:not(.no-stretch) > ul' );
285
286				if ( ! megaMenus.length ) {
287					return;
288				}
289
290				const headerInner = document.querySelector( '#site-header-inner.container' );
291
292				if ( ! headerInner ) {
293					return;
294				}
295
296				const addNegativeMargin = navWrap.classList.contains( 'navbar-style-one' );
297				const headerInnerWidth = headerInner.getBoundingClientRect().width;
298				if ( addNegativeMargin ) {
299					headerInnerLeft = parseInt( headerInner.getBoundingClientRect().left );
300					navLeft = navWrap.getBoundingClientRect().left;
301				}
302
303				megaMenus.forEach( ( element ) => {
304					element.style.width = headerInnerWidth + 'px';
305					if ( addNegativeMargin ) {
306						element.style.marginLeft = -(navLeft-headerInnerLeft) + 'px';
307					}
308				} );
309			};
310
311			// Add events.
312			init();
313			window.addEventListener( 'resize', init );
314		},
315
316		/**
317		 * MegaMenus Top Position.
318		 */
319		megaMenusTop: function() {
320			const self = this;
321			const header = document.querySelector( '#site-header.header-one' );
322
323			if ( ! header ) {
324				return;
325			}
326
327			const navWrap = header.querySelector( '#site-navigation-wrap.wpex-stretch-megamenus:not(.wpex-flush-dropdowns)' );
328
329			if ( ! this.isVisible( navWrap ) ) {
330				return;
331			}
332
333			const megaMenus = navWrap.querySelectorAll( '.megamenu > ul');
334
335			if ( ! megaMenus ) {
336				return;
337			}
338
339			const setTop = () => {
340				if ( ! self.isVisible( navWrap ) ) {
341					return;
342				}
343				const navHeight = navWrap.getBoundingClientRect().height;
344				const topVal = ( ( header.getBoundingClientRect().height - navHeight ) / 2 ) + navHeight;
345				megaMenus.forEach( ( element ) => {
346					element.style.top = topVal + 'px';
347				} );
348			};
349
350			// Add events.
351			setTop();
352			window.addEventListener( 'scroll', setTop, { passive: true } );
353			window.addEventListener( 'resize', setTop );
354			navWrap.querySelectorAll( '.megamenu > a' ).forEach( ( element ) => {
355				element.addEventListener( 'mouseenter', setTop, false );
356			} );
357		},
358
359		/**
360		 * Parses Megamenu HTML for mobile.
361		 */
362		megaMenusMobile: function( menu ) {
363			if ( ! menu ) {
364				return;
365			}
366
367			const megaMenusWithoutHeadings = menu.classList.contains( 'sidr-class-dropdown-menu' ) ? '.sidr-class-megamenu.sidr-class-hide-headings' : '.megamenu.hide-headings';
368
369			// Loop through mega menus with the hide-headings class to remove top level child ul elements.
370			menu.querySelectorAll( megaMenusWithoutHeadings ).forEach( ( megamenu ) => {
371
372				if ( megamenu.classList.contains( 'show-headings-mobile' ) || megamenu.classList.contains( 'sidr-class-show-headings-mobile' ) ) {
373					return;
374				}
375
376				// Loop through top level megamenu li elements (aka columns).
377				megamenu.querySelectorAll( ':scope > ul > li' ).forEach( ( headingLi ) => {
378
379					// Remove the heading link.
380					const headingA = headingLi.querySelector( 'a' );
381					if ( headingA ) {
382						headingA.parentNode.removeChild( headingA );
383					}
384
385					// Remove ul wrapper around direct heading li elements.
386					const headingUL = headingLi.querySelector( 'ul' );
387					if ( headingUL ) {
388						headingUL.outerHTML = headingUL.innerHTML;
389					}
390
391					// Remove classes.
392					headingLi.classList.remove( 'sidr-class-menu-item-has-children' );
393					headingLi.classList.remove( 'menu-item-has-children' );
394				} );
395
396			} );
397		},
398
399		/**
400		 * Header Search.
401		 */
402		menuSearch: function() {
403			const searchWrap = document.querySelector( '.header-searchform-wrap' );
404
405			if ( ! searchWrap ) {
406				return;
407			}
408
409			const searchInput = searchWrap.querySelector( 'input[type="search"]' );
410
411			if ( ! searchInput ) {
412				return;
413			}
414
415			if ( searchWrap ) {
416				if ( searchWrap.dataset.placeholder ) {
417					searchInput.setAttribute( 'placeholder', searchWrap.dataset.placeholder );
418				}
419				if ( searchWrap.dataset.disableAutocomplete ) {
420					searchInput.setAttribute( 'autocomplete', 'off' );
421				}
422			}
423
424			this.menuSearchDropdown();
425			this.menuSearchOverlay();
426			this.menuSearchHeaderReplace();
427		},
428
429		/**
430		 * Dropdown search.
431		 */
432		menuSearchDropdown: function() {
433			const self = this;
434			const searchDropdownForm = document.querySelector( '#searchform-dropdown' );
435
436			if ( ! searchDropdownForm ) {
437				return;
438			}
439
440			let isOpen = false;
441			let onCloseFocus = false;
442			let toggleBtn = null;
443			const searchInput = searchDropdownForm.querySelector( 'input[type="search"]' );
444			const targets = 'a.search-dropdown-toggle, a.mobile-menu-search, .wpex-header-search-icon button';
445
446			const show = () => {
447				document.body.classList.add( 'header-search-is-open' );
448				searchDropdownForm.classList.add( 'show' );
449				searchDropdownForm.classList.remove( 'wpex-invisible', 'wpex-opacity-0' );
450				document.querySelectorAll( targets ).forEach( ( element ) => {
451					element.setAttribute( 'aria-expanded', 'true' );
452					var elementLi = element.closest( 'li' );
453					if ( elementLi ) {
454						elementLi.classList.add( 'active' );
455					}
456				} );
457
458				searchInput.value = '';
459
460				if ( 'function' === typeof jQuery ) {
461					jQuery( document ).trigger( 'show.wpex.menuSearch' );
462				}
463
464				const focus = ( event ) => {
465					self.focusOnElement( searchDropdownForm, searchInput );
466					searchDropdownForm.removeEventListener( 'transitionend', focus ); // remove event as it keeps triggering.
467				};
468
469				// Focus on the search when opening after transition is complete to prevent issues.
470				searchDropdownForm.addEventListener( 'transitionend', focus );
471
472				isOpen = true;
473				onCloseFocus = false;
474			};
475
476			const hide = () => {
477				document.body.classList.remove( 'header-search-is-open' );
478				searchDropdownForm.classList.remove( 'show' );
479				searchDropdownForm.classList.add( 'wpex-invisible', 'wpex-opacity-0' );
480				document.querySelectorAll( targets ).forEach( ( element ) => {
481					element.setAttribute( 'aria-expanded', 'false' );
482					var elementLi = element.closest( 'li' );
483					if ( elementLi ) {
484						elementLi.classList.remove( 'active' );
485					}
486				} );
487				if ( toggleBtn && onCloseFocus ) {
488					toggleBtn.focus();
489				}
490				isOpen = false;
491			};
492
493			const onDocClick = ( event ) => {
494				toggleBtn = event.target.closest( targets );
495				if ( ! toggleBtn ) {
496					if ( ! event.target.closest( '#searchform-dropdown' ) && isOpen ) {
497						hide();
498					}
499					return;
500				}
501				event.preventDefault();
502				if ( isOpen ) {
503					hide();
504				} else {
505					show();
506				}
507			};
508
509			const onKeyDown = ( event ) => {
510				const key = event.key;
511				if ( 'Escape' === key && isOpen ) {
512					onCloseFocus = true;
513					hide();
514				}
515			};
516
517			// Add events.
518			document.addEventListener( 'click', onDocClick );
519			searchDropdownForm.addEventListener( 'keydown', onKeyDown );
520		},
521
522		/**
523		 * Overlay search.
524		 */
525		menuSearchOverlay: function() {
526			const self = this;
527			const searchOverlay = document.querySelector( '#wpex-searchform-overlay' );
528
529			if ( ! searchOverlay ) {
530				return;
531			}
532
533			let isOpen = false;
534			let toggleBtn = null;
535			let onCloseFocus = false;
536			const searchInput = searchOverlay.querySelector( 'input[type="search"]' );
537			const targets = 'a.search-overlay-toggle, a.mobile-menu-search, li.search-overlay-toggle > a, .wpex-header-search-icon button';
538
539			const setFocus = () => {
540				const focus = ( event ) => {
541					self.focusOnElement( searchOverlay, searchInput );
542					searchOverlay.removeEventListener( 'transitionend', focus ); // remove event as it keeps triggering.
543				};
544
545				// Focus on the search when opening after transition is complete to prevent issues.
546				searchOverlay.addEventListener( 'transitionend', focus );
547			};
548
549			const returnFocus = () => {
550				if ( toggleBtn && onCloseFocus ) {
551					toggleBtn.focus();
552				}
553			};
554
555			const show = () => {
556				isOpen = true;
557				onCloseFocus = false;
558				document.body.classList.add( 'header-search-is-open' );
559				searchOverlay.classList.add( 'active' );
560				self.htmlNoScroll( 'add' );
561
562				document.querySelectorAll( targets ).forEach( ( element ) => {
563					element.setAttribute( 'aria-expanded', 'true' );
564					const elementLi = element.closest( 'li' );
565					if ( elementLi ) {
566						elementLi.classList.add( 'active' );
567					}
568				} );
569
570				searchInput.value = ''; // reset value on re-open.
571
572				if ( 'function' === typeof jQuery ) {
573					jQuery( document ).trigger( 'show.wpex.menuSearch' );
574				}
575
576				setFocus();
577			};
578
579			const hide = ( event ) => {
580				isOpen = false;
581				document.body.classList.remove( 'header-search-is-open' );
582				searchOverlay.classList.remove( 'active' );
583				document.querySelectorAll( targets ).forEach( ( element ) => {
584					element.setAttribute( 'aria-expanded', 'false' );
585					const elementLi = element.closest( 'li' );
586					if ( elementLi ) {
587						elementLi.classList.remove( 'active' );
588					}
589				} );
590				returnFocus();
591				// after animation has finished.
592				setTimeout( () => {
593					self.htmlNoScroll( 'remove' );
594				}, self.getElTransitionDuration( searchOverlay ) );
595			};
596
597			const onDocClick = ( event ) => {
598				const target = event.target.closest( targets );
599				if ( ! target ) {
600					if ( isOpen && event.target.closest( '#wpex-searchform-overlay .wpex-close' ) ) {
601						hide();
602					}
603					return;
604				}
605				toggleBtn = target; // store toggle button for future focus.
606				event.preventDefault();
607				if ( isOpen ) {
608					hide();
609				} else {
610					show();
611				}
612			};
613
614			const onKeyDown = ( event ) => {
615				const key = event.key;
616				if ( 'Escape' === key && isOpen ) {
617					onCloseFocus = true;
618					return hide();
619				} else if ( event.target.closest( '#wpex-searchform-overlay .wpex-close' ) && ( 'Space' === key || 'Enter' === key ) ) {
620					onCloseFocus = true;
621				}
622			};
623
624			// Add events.
625			document.addEventListener( 'click', onDocClick );
626			document.addEventListener( 'keydown', onKeyDown );
627		},
628
629		/**
630		 * Header Replace Search.
631		 */
632		menuSearchHeaderReplace: function() {
633			const self = this;
634			const headerReplace = document.querySelector( '#searchform-header-replace' );
635
636			if ( ! headerReplace ) {
637				return;
638			}
639
640			let isOpen = false;
641			let onCloseFocus = false;
642			let toggleBtn = null;
643			const headerInner = document.querySelector( '#site-header-inner' );
644			const slideDown = headerReplace.classList.contains( '-wpex-translate-y-50' );
645			const searchInput = headerReplace.querySelector( 'input[type="search"]' );
646			const targets = 'a.search-header-replace-toggle, a.mobile-menu-search, .wpex-header-search-icon button';
647
648			const show = () => {
649				document.body.classList.add( 'header-search-is-open' );
650				headerReplace.classList.add( 'show' );
651				headerReplace.classList.remove( 'wpex-invisible', 'wpex-opacity-0' );
652				if ( headerInner ) {
653					headerInner.classList.add( 'wpex-overflow-hidden' );
654				//	headerInner.classList.add( 'wpex-invisible' ); // will break Poppy demo, don't do this - would require extra option.
655				}
656				if ( slideDown ) {
657					headerReplace.classList.remove( '-wpex-translate-y-50' );
658				}
659				document.querySelectorAll( targets ).forEach( ( element ) => {
660					element.setAttribute( 'aria-expanded', 'true' );
661					const elementLi = element.closest( 'li' );
662					if ( elementLi ) {
663						elementLi.classList.add( 'active' );
664					}
665				} );
666
667				searchInput.value = '';
668
669				if ( 'function' === typeof jQuery ) {
670					jQuery( document ).trigger( 'show.wpex.menuSearch' );
671				}
672
673				const focus = ( event ) => {
674					self.focusOnElement( headerReplace, searchInput );
675					headerReplace.removeEventListener( 'transitionend', focus ); // remove event as it keeps triggering.
676				};
677
678				// Focus on the search when opening after transition is complete to prevent issues.
679				headerReplace.addEventListener( 'transitionend', focus );
680
681				isOpen = true;
682				onCloseFocus = false;
683			};
684
685			const hide = () => {
686				document.body.classList.remove( 'header-search-is-open' );
687				headerReplace.classList.remove( 'show' );
688				headerReplace.classList.add( 'wpex-invisible', 'wpex-opacity-0' );
689				if ( headerInner ) {
690					headerInner.classList.remove( 'wpex-overflow-hidden' );
691				//	headerInner.classList.remove( 'wpex-invisible' );
692				}
693				if ( slideDown ) {
694					headerReplace.classList.add( '-wpex-translate-y-50' );
695				}
696
697				document.querySelectorAll( targets ).forEach( ( element ) => {
698					element.setAttribute( 'aria-expanded', 'false' );
699					const elementLi = element.closest( 'li' );
700					if ( elementLi ) {
701						elementLi.classList.remove( 'active' );
702					}
703				} );
704				if ( toggleBtn && onCloseFocus ) {
705					toggleBtn.focus();
706				}
707				isOpen = false;
708			};
709
710			const onDocClick = ( event ) => {
711				const target = event.target.closest( targets );
712				if ( ! target ) {
713					if ( ! event.target.closest( '#searchform-header-replace .searchform' ) && isOpen ) {
714						hide();
715					}
716					return;
717				}
718				toggleBtn = target;
719				event.preventDefault();
720				if ( isOpen ) {
721					hide();
722				} else {
723					show();
724				}
725			};
726
727			const onKeyDown = ( event ) => {
728				if ( ! isOpen ) {
729					return;
730				}
731				const key = event.key;
732				if ( 'Escape' === key ) {
733					onCloseFocus = true;
734					hide();
735				} else if ( event.target.closest( '#searchform-header-replace-close' ) && ( 'Space' === key || 'Enter' === key ) ) {
736					onCloseFocus = true;
737				}
738			};
739
740			// Add events.
741			document.addEventListener( 'click', onDocClick );
742			headerReplace.addEventListener( 'keydown', onKeyDown );
743		},
744
745		/**
746		 * Automatically add padding to row to offset header.
747		 */
748		headerOverlayOffset: function() {
749			const header = document.querySelector( '#site-header' );
750
751			if ( ! header || ! header.classList.contains( 'overlay-header' ) || header.classList.contains( 'header-fixed-height' ) ) {
752				return;
753			}
754
755			const setHeight = () => {
756				const headerHeight = header.getBoundingClientRect().height;
757				document.querySelectorAll( '.overlay-header-offset-div' ).forEach( ( el ) => {
758					el.style.height = headerHeight + 'px';
759				} );
760			};
761
762			// Add events.
763			setHeight();
764			window.addEventListener( 'resize', setHeight );
765		},
766
767		/**
768		 * Header 5 - Inline Logo.
769		 *
770		 * @todo move to it's own file.
771		 */
772		inlineHeaderLogo: function() {
773			const self = this;
774			const header = document.querySelector( '#site-header' );
775
776			if ( ! header || ! header.classList.contains( 'header-five' ) ) {
777				return;
778			}
779
780			const logo = document.querySelector( '#site-header.header-five #site-header-inner > .header-five-logo' );
781			const nav = document.querySelector( '#site-header.header-five .navbar-style-five' );
782			let menuLogoLi = null;
783
784			if ( ! logo || ! nav ) {
785				return;
786			}
787
788			const getLogoBeforeEl = () => {
789				const navLiElements = document.querySelectorAll( '.navbar-style-five .main-navigation-ul > li' );
790				let navLiElementsVisible = [];
791
792				for ( let i=0; i < navLiElements.length; i++ ) {
793					if ( self.isVisible( navLiElements[i] ) ) {
794						navLiElementsVisible.push( navLiElements[i] );
795					}
796				}
797
798				const navLiCount = navLiElementsVisible.length;
799				const insertLogoAfterIndex = Math.round( navLiCount / 2 ) - parseInt( l10n.headerFiveSplitOffset );
800
801				return navLiElementsVisible[insertLogoAfterIndex];
802			};
803
804			// Insert Logo into Menu.
805			const insertLogo = () => {
806				const insertLogoBeforeEl = getLogoBeforeEl();
807				if ( ! insertLogoBeforeEl ) {
808					return;
809				}
810				if ( self.viewportWidth() >
810 l10n.mobileMenuBreakpoint ) {
811					if ( ! menuLogoLi ) {
812						menuLogoLi = document.createElement( 'li' );
813						menuLogoLi.className = 'menu-item-logo wpex-px-40';
814					}
815					menuLogoLi.appendChild( logo );
816					insertLogoBeforeEl.parentNode.insertBefore( menuLogoLi, insertLogoBeforeEl.nextSibling );
817					logo.classList.add( 'display' );
818				}
819			};
820
821			const onResize = () => {
822				const inlineLogo = document.querySelector( '.menu-item-logo .header-five-logo' );
823				if ( self.viewportWidth() <= l10n.mobileMenuBreakpoint ) {
824					if ( inlineLogo ) {
825						const headerInner = document.querySelector( '#site-header-inner' );
826						if ( headerInner ) {
827							headerInner.insertBefore( inlineLogo, headerInner.firstChild );
828						}
829						if ( menuLogoLi ) {
830							menuLogoLi.parentNode.removeChild( menuLogoLi );
831						}
832					}
833				} else if ( ! inlineLogo ) {
834					insertLogo(); // Insert logo to menu.
835				}
836			};
837
838			// Add Events.
839			insertLogo();
840			window.addEventListener( 'resize', onResize );
841		},
842
843		/**
844		 * Handles the skip to content link.
845		 */
846		skipToContent: function() {
847			const self = this;
848
849			const onDocClick = ( event ) => {
850				if ( ! event.target.classList.contains( 'skip-to-content' ) ) {
851					return;
852				}
853				const target = document.querySelector( event.target.getAttribute( 'href' ) );
854				if ( target ) {
855					target.setAttribute( 'tabIndex', '-1' );
856					self.scrollTo( target );
857				}
858				event.preventDefault();
859				event.stopPropagation();
860			};
861
862			document.addEventListener( 'click', onDocClick );
863		},
864
865		/**
866		 * Back to top link.
867		 */
868		backTopLink: function() {
869			const self = this;
870
871			const onDocClick = ( event ) => {
872				let target = event.target;
873
874				if ( ! target.closest( 'a#site-scroll-top, a.wpex-scroll-top, .wpex-scroll-top a' ) ) {
875					return;
876				}
877
878				const mainLink = target.closest( '#site-scroll-top' );
879
880				if ( mainLink ) {
881					target = mainLink;
882				}
883
884				if ( 0 !== window.scrollY ) {
885					const topSpan = document.querySelector( '[data-ls_id="#site_top"]' );
886					if ( topSpan ) {
887						topSpan.focus( {
888							preventScroll: true,
889							focusVisible: false
890						} );
891					}
892					const speed = parseInt( target.dataset.scrollSpeed || parseInt( l
89210n.localScrollSpeed ) );
893					const easing = self.getEasing( target.dataset.scrollEasing );
894					if ( easing && 'function' === typeof jQuery ) {
895						jQuery( 'html, body' ).stop( true, true ).animate( {
896							scrollTop: 0
897						}, speed, easing );
898					} else {
899						window.scrollTo( {
900							top: 0,
901							behavior: self.getScrollToBehavior()
902						} );
903					}
904				}
905
906				event.preventDefault();
907				event.stopPropagation();
908			};
909
910			// Add events.
911			document.addEventListener( 'click', onDocClick );
912		},
913
914		/**
915		 * Back to top link.
916		 */
917		backTopButton: function() {
918			const self = this;
919			const backTopButton = document.querySelector( '#site-scroll-top' );
920
921			if ( ! backTopButton ) {
922				return;
923			}
924
925			const offset = parseInt( backTopButton.dataset.scrollOffset || 100 );
926
927			if ( 0 === offset ) {
928				return;
929			}
930
931			const showHide = ( event ) => {
932				if ( window.pageYOffset > offset ) {
933					backTopButton.classList.add( 'show');
934					backTopButton.classList.remove( 'wpex-invisible', 'wpex-opacity-0' );
935				} else {
936					backTopButton.classList.remove( 'show' );
937					backTopButton.classList.add( 'wpex-invisible', 'wpex-opacity-0' );
938				}
939			};
940
941			// Add events.
942			window.addEventListener( 'scroll', showHide, { passive: true } );
943		},
944
945		/**
946		 * Go back button.
947		 */
948		goBackButton: function() {
949			document.addEventListener( 'click', ( event ) => {
950				if ( event.target.closest( '.wpex-go-back' ) ) {
951					event.preventDefault();
952					history.back();
953				}
954			} );
955		},
956
957		/**
958		 * Smooth Comment Scroll.
959		 */
960		smoothCommentScroll: function() {
961			const self = this;
962
963			const onDocClick = ( event ) => {
964				if ( ! event.target.closest( '.comments-link' ) ) {
965					return;
966				}
967				const target = document.querySelector( '#comments' );
968				if ( ! target ) {
969					return;
970				}
971				self.scrollTo( target, -20 );
972				event.preventDefault();
973				event.stopPropagation();
974			};
975
976			// Add events.
977			document.addEventListener( 'click', onDocClick );
978		},
979
980		/**
981		 * Dialogs.
982		 */
983		dialogs: function() {
984			const self = this;
985
986			const dialogOnClick = ( event ) => {
987				const target = event.target;
988				if ( ! target.closest( '.wpex-modal__inner' ) ) {
989					const dialog = target.closest( 'dialog' );
990					if ( dialog ) {
991						closeDialog( dialog );
992					}
993				}
994			};
995
996			const openDialog = ( modalId ) => {
997				if ( ! modalId ) {
998					return;
999				}
1000
1001				const dialog = document.querySelector( `#${modalId.replace('#', '')}` );
1002
1003				if ( ! dialog ) {
1004					return;
1005				}
1006
1007				if ( dialog.classList.contains( 'wpex-modal' ) ) {
1008					dialog.showModal();
1009					const inner = dialog.querySelector( '.wpex-modal__inner' );
1010					if ( inner ) {
1011						dialog.addEventListener( 'click', dialogOnClick );
1012					}
1013					document.body.classList.add( 'modal-is-open' );
1014					if ( ! document.body.classList.contains( 'vc_editor' ) ) {
1015						self.htmlNoScroll( 'add' );
1016					}
1017				} else {
1018					dialog.show();
1019				}
1020			};
1021
1022			const closeDialog = ( dialog ) => {
1023				if ( dialog.classList.contains( 'wpex-modal' ) ) {
1024					dialog.removeEventListener( 'click', dialogOnClick );
1025				}
1026				dialog.close();
1027			};
1028
1029			const onDocClick = ( event ) => {
1030				const openButton = event.target.closest( '.wpex-open-modal,.wpex-open-dialog' );
1031				const closeButton = event.target.closest( '.wpex-close-modal' );
1032
1033				if ( ! openButton && ! closeButton ) {
1034					return;
1035				}
1036
1037				event.preventDefault();
1038
1039				if ( openButton ) {
1040					const dialog = openButton.getAttribute( 'aria-controls' );
1041					if ( dialog ) {
1042						return openDialog( dialog );
1043					}
1044				}
1045
1046				if ( closeButton ) {
1047					const dialog = event.target.closest( 'dialog' );
1048					if ( dialog ) {
1049						return closeDialog( dialog );
1050					}
1051				}
1052			};
1053
1054			const onModalClose = ( event ) => {
1055				document.body.classList.remove( 'modal-is-open' );
1056				self.htmlNoScroll( 'remove' );
1057			};
1058
1059			const addOnCloseEvents = () => {
1060				document.querySelectorAll( '.wpex-modal' ).forEach( dialog => {
1061					dialog.addEventListener( 'close', onModalClose );
1062				} );
1063			};
1064
1065			// Register event listeners.
1066			addOnCloseEvents(); // note: dialog close event does not bubble.
1067			document.addEventListener( 'click', onDocClick );
1068			window.addEventListener( 'totaltheme_loadmore_posts', addOnCloseEvents );
1069			window.addEventListener( 'vcexAfterAjaxSuccess', addOnCloseEvents );
1070		},
1071
1072		/**
1073		 * Toggle Elements.
1074		 */
1075		toggleElements: function() {
1076			const self = this;
1077		//	const hasToggleElements = document.querySelector( '.wpex-toggle-element' );
1078
1079			const getTargetElement = ( trigger ) => {
1080				const target = trigger.getAttribute( 'aria-controls' );
1081				if ( target ) {
1082					return document.querySelector( `#${target.replace('#', '')}` );
1083				}
1084			};
1085
1086			const enableTrigger = ( trigger ) => {
1087				trigger.setAttribute( 'aria-expanded', 'true' );
1088				if ( trigger.classList.contains( 'vcex-button' ) || trigger.classList.contains( 'theme-button' ) ) {
1089					trigger.classList.add( 'active' );
1090				}
1091				const closeText = trigger.getAttribute( 'data-close-text' );
1092				const openText = trigger.getAttribute( 'data-open-text' );
1093				const textEl = trigger.querySelector( '.theme-button-text' ) || trigger;
1094				if ( openText && closeText && textEl ) {
1095					textEl.textContent = closeText;
1096				}
1097			};
1098
1099			const disableTrigger = ( trigger ) => {
1100				trigger.setAttribute( 'aria-expanded', 'false' );
1101				if ( trigger.classList.contains( 'vcex-button' ) || trigger.classList.contains( 'theme-button' ) ) {
1102					trigger.classList.remove( 'active' );
1103				}
1104				const closeText = trigger.getAttribute( 'data-close-text' );
1105				const openText = trigger.getAttribute( 'data-open-text' );
1106				const textEl = trigger.querySelector( '.theme-button-text' ) || trigger;
1107				if ( openText && closeText && textEl ) {
1108					textEl.textContent = openText;
1109				}
1110			};
1111
1112			const showToggleEl = ( toggleEl, container ) => {
1113				container = container || document;
1114				const toggleElId = toggleEl.getAttribute( 'id' );
1115				if ( ! toggleElId ) {
1116					return;
1117				}
1118
1119				toggleEl.classList.add( 'wpex-toggle-element--visible' );
1120
1121				const elParent = toggleEl.parentNode;
1122
1123				// Fix issues with toggle elements inside toggle elements (@see vcex_icon_box).
1124				if ( elParent.classList.contains( 'wpex-toggle-element' ) ) {
1125					elParent.classList.add( 'wpex-toggle-element--visible' );
1126					elParent.setAttribute( 'tabIndex', '-1' );
1127					elParent.focus( {
1128						preventScroll: true,
1129						focusVisible: false
1130					} );
1131				} else {
1132					toggleEl.setAttribute( 'tabIndex', '-1' );
1133					toggleEl.focus( {
1134						preventScroll: true,
1135						focusVisible: false
1136					} );
1137				}
1138
1139				container.querySelectorAll( `.wpex-toggle-element-trigger[aria-controls="${toggleElId}"]` ).forEach( ( trigger ) => {
1140					enableTrigger( trigger );
1141				} );
1142
1143				window.dispatchEvent( new Event( 'resize' ) );
1144				toggleEl.dispatchEvent( new CustomEvent( 'wpex_toggle_element_open' ) );
1145			};
1146
1147			const closeToggleEl = ( toggleEl, container ) => {
1148				container = container || document;
1149				const toggleElId = toggleEl.getAttribute( 'id' );
1150				if ( ! toggleElId ) {
1151					return;
1152				}
1153				toggleEl.classList.remove( 'wpex-toggle-element--visible' );
1154				toggleEl.dispatchEvent( new CustomEvent( 'wpex_toggle_element_close' ) );
1155				container.querySelectorAll( `.wpex-toggle-element-trigger[aria-controls="${toggleElId}"]` ).forEach( ( trigger ) => {
1156					disableTrigger( trigger );
1157				} );
1158			};
1159
1160			/*
1161			const onLoad = () => {
1162				const hash = location.hash;
1163				if ( ! hash || 'undefined' === hash || '#' === hash || ! self.isSelectorValid( hash ) ) {
1164					return;
1165				}
1166				const element = document.querySelector( hash );
1167				if ( ! element || ! element.classList.contains( 'wpex-toggle-element' ) ) {
1168					return;
1169				}
1170				showToggleEl( element, null );
1171				window.setTimeout( function() {
1172					self.scrollTo( element, -20 );
1173				}
1173, parseInt( l10n.scrollToHashTimeout ) );
1174			};
1175			*/
1176
1177			const onDocClick = ( event ) => {
1178				const button = event.target.closest( 'a.wpex-toggle-element-trigger,button.wpex-toggle-element-trigger' );
1179
1180				if ( ! button ) {
1181					return;
1182				}
1183
1184				const toggleEl = getTargetElement( button );
1185
1186				if ( ! toggleEl || ! toggleEl.classList.contains( 'wpex-toggle-element' ) ) {
1187					return;
1188				}
1189
1190				event.preventDefault();
1191
1192				const toggleElClass = toggleEl.classList;
1193				const triggerParent = button.closest( '.vc_section' ) || button.closest( '.vc_row' );
1194				let isToggleElementContained = false;
1195
1196				if ( triggerParent && triggerParent.contains( toggleEl ) ) {
1197					isToggleElementContained = true;
1198				}
1199
1200				const targetSection = isToggleElementContained ? triggerParent : document;
1201
1202				targetSection.querySelectorAll( '.wpex-toggle-element--visible' ).forEach( ( element ) => {
1203					if ( ! element.isSameNode( toggleEl ) ) {
1204						closeToggleEl( element, targetSection );
1205					}
1206				} );
1207
1208				if ( toggleElClass.contains( 'wpex-toggle-element--visible' ) ) {
1209					if ( ! toggleElClass.contains( 'wpex-toggle-element--persist' ) ) {
1210						closeToggleEl( toggleEl, targetSection );
1211					}
1212				} else {
1213					showToggleEl( toggleEl, targetSection );
1214					if ( 'true' === button.getAttribute( 'data-wpex-toggle-localscroll' ) && ! button.classList.contains( 'local-scroll-link' ) ) {
1215						self.scrollTo( toggleEl );
1216					}
1217				}
1218			};
1219
1220			// Focus on toggle when clicking escape inside a toggle element.
1221			const onKeyDown = ( event ) => {
1222				if ( 'Escape' !== event.key ) {
1223					return;
1224				}
1225				const toggleEl = event.target.closest( '.wpex-toggle-element--visible' );
1226				if ( ! toggleEl ) {
1227					return;
1228				}
1229				const toggleElId = toggleEl.getAttribute( 'id' );
1230				if ( ! toggleElId ) {
1231					return;
1232				}
1233				let container = toggleEl.closest( '.vc_row' ) || toggleEl.closest( '.vc_section' );
1234				let trigger = container.querySelector( `.wpex-toggle-element-trigger[aria-controls="${toggleElId}"]` );
1235				if ( ! trigger ) {
1236					trigger = document.querySelector( `.wpex-toggle-element-trigger[aria-controls="${toggleElId}"]` );
1237				}
1238				if ( trigger ) {
1239					trigger.focus();
1240				}
1241			};
1242
1243			// Register event listeners.
1244			document.addEventListener( 'click', onDocClick );
1245			document.addEventListener( 'keydown', onKeyDown );
1246			/*if ( hasToggleElements ) {
1247				window.addEventListener( 'load', onLoad );
1248			}*/
1249		},
1250
1251		/**
1252		 * Local Scroll Offset.
1253		 */
1254		getLocalScrollOffset: function( instance ) {
1255			if ( l10n.localScrollOffset ) {
1256				return parseInt( l10n.localScrollOffset ); // custom user defined offset in pixels.
1257			}
1258
1259			const self = this;
1260			let offset = 0;
1261
1262			// Adds extra offset via filter.
1263			if ( l10n.localScrollExtraOffset ) {
1264				offset = parseInt( offset ) + parseInt( l10n.localScrollExtraOffset );
1265			}
1266
1267			// Fixed header.
1268			// !!!important!!! must target the #site-header to prevent issues with the Transparent header which
1269			// will return 0 for the sticky-wrapper height.
1270			const stickyHeader = document.querySelector( '#site-header-sticky-wrapper.wpex-can-sticky #site-header' );
1271			if ( stickyHeader ) {
1272
1273				// Return 0 for small screens if mobile fixed header is disabled.
1274				if ( ! l10n.hasStickyMobileHeader && self.viewportWidth() < l10n.stickyHeaderBreakPoint ) {
1275					offset = parseInt( offset ) + 0;
1276				}
1277
1278				// Return header height.
1279				else {
1280
1281					// Shrink header.
1282					if ( stickyHeader.classList.contains( 'shrink-sticky-header' ) ) {
1283						// @todo can we remove the instance and use a different check?
1284						// Maybe we can add a data attribute to the header like data-sticky-init="false"
1285						// that we can check.
1286						if ( 'init' === instance || self.isVisible( stickyHeader ) ) {
1287							offset = parseInt( offset ) + parseInt( l10n.shrinkHeaderHeight );
1288						}
1289					}
1290
1291					// Standard header.
1292					else {
1293						offset = parseInt( offset ) + stickyHeader.getBoundingClientRect().height;
1294					}
1295
1296				}
1297
1298			}
1299
1300			// Loop through extra items.
1301			// @todo update elements to have the "wpex-ls-offset classname where possible".
1302			document.querySelectorAll( '.wpex-ls-offset,#wpadminbar,#top-bar-wrap-sticky-wrapper.wpex-can-sticky,#site-navigation-sticky-wrapper.wpex-can-sticky' ).forEach( ( element ) => {
1303				if ( self.isVisible( element ) ) {
1304					offset = parseInt( offset ) + element.getBoundingClientRect().height;
1305				}
1306			} );
1307
1308			/**
1309			 * Remove 1 pixel to prevent cross browser rounding issues (firefox).
1310			 *
1311			 * @todo anyway to fix this?
1312			 */
1313			offset = offset ? offset - 1 : 0;
1314
1315			return offset;
1316		},
1317
1318		/**
1319		 * Scroll to function.
1320		 *
1321		 * @note: Important never change this function since it's used by 3rd party scripts.
1322		 */
1323		scrollTo: function( hash, addedOffset = 0, callback ) {
1324			const self = this;
1325
1326			if ( ! hash ) {
1327				return;
1328			}
1329
1330			let target = null;
1331			let isLsDataLink = false;
1332			let localSection = null;
1333			let offset = 0;
1334			const lsOffset = self.getLocalScrollOffset();
1335			const lsSpeed = parseInt( l10n.localScrollSpeed );
1336			const sections = document.querySelectorAll( '[data-ls_id]' );
1337			const easing = self.getEasing();
1338
1339			// Use standard for loop since forEach doesn't support breaks.
1340			for ( let i=0; i < sections.length; i++ ) {
1341				if ( hash === sections[i].dataset.ls_id ) {
1342					localSection = sections[i];
1343					break;
1344				}
1345			}
1346
1347			if ( localSection ) {
1348				target = localSection;
1349				isLsDataLink = true;
1350			} else if ( 'string' === typeof hash ) {
1351				if ( self.isSelectorValid( hash ) ) {
1352					target = document.querySelector( hash );
1353				}
1354			} else if ( hash.nodeType ) {
1355				target = hash;
1356			}
1357
1358			if ( ! target ) {
1359				return; // bail early since there isn't any target.
1360			}
1361
1362			const run = () => {
1363				if ( target && target.nodeType ) {
1364					target.focus( {
1365						preventScroll: true,
1366						focusVisible: false
1367					} );
1368				}
1369				if ( easing && 'function' === typeof jQuery ) {
1370					jQuery( 'html, body' ).stop( true, true ).animate( {
1371						scrollTop: offset
1372					}, lsSpeed, easing );
1373				} else {
1374					window.scrollTo( {
1375						top: offset,
1376						behavior: self.getScrollToBehavior()
1377					} );
1378				}
1379			};
1380
1381			// Change the target to parent tabs when linking to tab item and open the tab.
1382			if ( target.classList.contains( 'vc_tta-panel' ) ) {
1383				const tabs = target.closest( '.vc_tta-tabs' );
1384				if ( tabs ) {
1385					if ( target.id ) {
1386						const tabLink = tabs.querySelector(`.vc_tta-tab:not(.vc_active) a[href="#${target.id}"]`);
1387						if ( tabLink ) {
1388							tabLink.click();
1389						}
1390					}
1391					target = tabs;
1392					addedOffset = -20;
1393				}
1394			}
1395
1396			// Set offset if 0.
1397			if ( ! offset ) {
1398				offset = self.offset( target ).top - lsOffset + addedOffset;
1399			}
1400
1401			// Update hash.
1402			if ( l10n.localScrollUpdateHash && 'string' === typeof hash && isLsDataLink ) {
1403				window.location.hash = hash;
1404			}
1405
1406			/**
1407			 * Mobile toggle Menu needs it's own code so it closes before the event fires
1408			 * to make sure we end up in the right place.
1409			 *
1410			 * @todo move into the toggle.js file, so we listen to events there and then run the scrollTo class after the element is closed.
1411			 * we simply need to add an extra check to localScrollLinks > onDocClick to exclude the links int his menu.
1412			 */
1413			const mobileToggleNav = document.querySelector( '.mobile-toggle-nav' );
1414			if ( mobileToggleNav && mobileToggleNav.classList.contains( 'visible' ) ) {
1415
1416				// Make sure the nav height is removed from the offset height, but only
1417				// when its not absolutely positioned.
1418				if ( 'absolute' !== window.getComputedStyle( mobileToggleNav ).position ) {
1419					offset = offset - mobileToggleNav.getBoundingClientRect().height;
1420				}
1421
1422				document.querySelectorAll( 'a.mobile-menu-toggle, button.mobile-menu-toggle, li.mobile-menu-toggle > a' ).forEach( ( element ) => {
1423					element.classList.remove( 'wpex-active' );
1424					element.setAttribute( 'aria-expanded', 'false' );
1425				} );
1426
1427				self.setHamburgerIconState();
1428
1429				if ( mobileToggleNav.classList.contains( 'mobile-toggle-nav--animate' ) ) {
1430					self.slideUp( mobileToggleNav, 300, function() {
1431						mobileToggleNav.classList.remove( 'visible' );
1432						document.body.classList.remove( 'mobile-toggle-nav-open' );
1433						run();
1434					} );
1435				} else {
1436					mobileToggleNav.classList.remove( 'visible' );
1437					document.body.classList.remove( 'mobile-toggle-nav-open' );
1438					run();
1439				}
1440			}
1441			// Scroll to section.
1442			else {
1443				run();
1444			}
1445		},
1446
1447		/**
1448		 * Scroll to Hash.
1449		 */
1450		scrollToHash: function( self ) {
1451			let target;
1452			let hash = location.hash;
1453
1454			if ( '' === hash || '#' === hash || 'undefined' === hash ) {
1455				return;
1456			}
1457
1458			// Scroll to comments.
1459			if ( '#view_comments' === hash || '#comments_reply' === hash ) {
1460				target = document.querySelector( '#comments' );
1461				if ( target ) {
1462					return self.scrollTo( target, -20 );
1463				}
1464			}
1465
1466			// Scroll to specific comment, fix for sticky header.
1467			if ( -1 !== hash.indexOf( 'comment-' ) && document.querySelector( '#site-header.fixed-scroll' ) ) {
1468				target = document.querySelector( hash );
1469				if ( target ) {
1470					return self.scrollTo( target, -20 );
1471				}
1472			}
1473
1474			// Custom local scroll using #localscroll-{ID} for targeting elements on the page.
1475			if ( -1 !== hash.indexOf( 'localscroll-' ) ) {
1476				hash = hash.replace( 'localscroll-', '' );
1477			}
1478
1479			try {
1480				const element = document.querySelector( `[data-ls_id="${hash}"], ${hash}` );
1481				if ( element
1482					&& 'dialog' !== element.getAttribute( 'roles' )
1483					&& ! element.classList.contains( 'vcex-toggle__content' )
1484					&& ! element.classList.contains( 'wpex-toggle-element' )
1485					&& ! element.classList.contains( 'wpex-card-modal' )
1486				) {
1487					self.scrollTo( hash );
1488				}
1489			} catch ( error ) {
1490				return false;
1491			}
1492		},
1493
1494		/**
1495		 * Local scroll links array.
1496		 */
1497		localScrollSections: function() {
1498			const self = this;
1499			const links = document.querySelectorAll( l10n.localScrollTargets );
1500			const array = [];
1501
1502			// Loop through links.
1503			links.forEach( ( link ) => {
1504				const href = link.getAttribute( 'href' );
1505				const hash = href ? `#${href.replace( /^.*?(#|$)/, '' )}` : null;
1506
1507				if ( ! hash || '#' === hash ) {
1508					return;
1509				}
1510
1511				if ( ! link.hasAttribute( 'data-ls_linkto' ) ) {
1512					link.setAttribute( 'data-ls_linkto', hash );
1513				}
1514
1515				let section = document.querySelector( `[data-ls_id="${hash}"]` );
1516				if ( ! section && 'string' === typeof hash && self.isSelectorValid( hash ) ) {
1517					section = document.querySelector( hash );
1518				}
1519
1520				if ( section && -1 === array.indexOf( hash ) ) {
1521					array.push( hash );
1522				}
1523
1524			} );
1525
1526			self.config.localScrollSections = array;
1527
1528			return self.config.localScrollSections;
1529		},
1530
1531		/**
1532		 * Local Scroll link.
1533		 */
1534		localScrollLinks: function() {
1535			const self = this;
1536
1537			const findLinks = () => {
1538				document.querySelectorAll( 'a[href^="#"]:not([href="#"]):not(.local-scroll-link):not([target]):not([aria-selected]):not([aria-pressed]):not([aria-expanded]):not([data-vc-tabs]):not(.skip-to-content):not(#site-scroll-top):not([data-vc-accordion]):not([role="button"]):not(.wpex-lightbox):not(.wpex-lightbox-group-item)' ).forEach( ( link ) => {
1539					if ( link.closest( '.local-scroll, .local-scroll-link, [aria-controls]' ) ) {
1540						return;
1541					}
1542					let href = link.getAttribute( 'href' );
1543					try {
1544						if ( document.querySelector( `[data-ls_id="${href}"], ${href}` ) ) {
1545							link.classList.add( 'local-scroll-link' );
1546						}
1547					} catch ( error ) {
1548						return false;
1549					}
1550				} );
1551			};
1552
1553			const onDocClick = ( event ) => {
1554				const target = event.target;
1555
1556				if ( target.closest( '.wpex-open-submenu' ) ) {
1557					return;
1558				}
1559
1560				const link = target.closest( l10n.localScrollTargets );
1561
1562				if ( ! link ) {
1563					return;
1564				}
1565
1566				let hash = link.dataset.ls_linkto || link.hash; // this.hash needed as fallback.
1567				let linkExists = false;
1568
1569				if ( self.config.localScrollSections && -1 !== self.config.localScrollSections.indexOf( hash ) ) {
1570					linkExists = true;
1571				} else {
1572					linkExists = ( self.isSelectorValid( hash ) ) ? document.querySelector( `[data-ls_id="${hash}"], ${hash}` ) : false;
1573				}
1574
1575				if ( ! linkExists ) {
1576					return;
1577				}
1578
1579				if ( link.closest( '.sfHover' ) ) {
1580					link.parentNode.classList.remove( 'sfHover' );
1581				}
1582
1583				if ( link.classList.contains( 'wpex-toggle-element-trigger' ) && 'false' === link.getAttribute( 'aria-expanded' ) ) {
1584					return;
1585				}
1586
1587				self.scrollTo( hash );
1588
1589				if ( link.closest( '.full-screen-overlay-nav-menu .local-scroll > a' ) ) {
1590					return;
1591				}
1592
1593				event.preventDefault();
1594				event.stopPropagation();
1595			};
1596
1597			// Add events.
1598			if ( l10n.localScrollFindLinks ) {
1599				findLinks();
1600			}
1601			document.addEventListener( 'click', onDocClick );
1602		},
1603
1604		/**
1605		 * Local Scroll Highlight on scroll.
1606		 *
1607		 * @todo can this be optimized somehow? Maybe using Observer API perhaps
1608		 * by checking if the element is visible on the screen (at least 50%) and excluding the
1609		 * self.getLocalScrollOffset() in the calculation as this is whitespace.
1610		 */
1611		localScrollHighlight: function() {
1612			const self = this;
1613			const localScrollSections = self.config.localScrollSections;
1614
1615			if ( ! l10n.localScrollHighlight || ! localScrollSections.length ) {
1616				return;
1617			}
1618
1619			// Highlight active items.
1620			const highlightSection = ( section ) => {
1621				const targetDiv = document.querySelector( `[data-ls_id="${section}"], ${section}` );
1622
1623				if ( ! targetDiv ) {
1624					return;
1625				}
1626
1627				let highlightSection = false;
1628				const winTop = self.winScrollTop();
1629				const lsOffset = self.getLocalScrollOffset();
1630				const divPos = self.offset( targetDiv ).top - lsOffset - 1;
1631				const divHeight = targetDiv.offsetHeight;
1632				const highlightLinks = document.querySelectorAll( `[data-ls_linkto="${section}"] `);
1633
1634				if ( winTop >= divPos && winTop < ( divPos + divHeight ) ) {
1635					highlightSection = true;
1636				} else {
1637					highlightSection = false;
1638				}
1639
1640				if ( highlightSection ) {
1641					targetDiv.classList.add( 'wpex-ls-inview' );
1642					document.querySelectorAll( '.local-scroll.menu-item' ).forEach( ( element ) => {
1643						element.classList.remove( 'current-menu-item' );
1644					} );
1645				} else {
1646					targetDiv.classList.remove( 'wpex-ls-inview' );
1647				}
1648
1649				highlightLinks.forEach( ( element ) => {
1650					if ( highlightSection ) {
1651						element.classList.add( 'active' );
1652					} else {
1653						element.classList.remove( 'active' );
1654					}
1655					const li = element.closest( 'li' );
1656					if ( li ) {
1657						if ( highlightSection ) {
1658							li.classList.add( 'current-menu-item' );
1659						} else {
1660							li.classList.remove( 'current-menu-item' );
1661						}
1662					}
1663				} );
1664			};
1665
1666			const highlightSections = () => {
1667				for ( var i=0; i < localScrollSections.length; i++ ) {
1668					highlightSection( localScrollSections[i] );
1669				}
1670			};
1671
1672			window.addEventListener( 'scroll', highlightSections, { passive: true } );
1673		},
1674
1675		/**
1676		 * Equal heights function.
1677		 */
1678		equalHeights: function( context ) {
1679			if ( 'function' !== typeof window.wpexEqualHeights ) {
1680				return;
1681			}
1682			wpexEqualHeights( '.match-height-grid', '.match-height-content', context );
1683			wpexEqualHeights( '.match-height-row', '.match-height-content', context );
1684			wpexEqualHeights( '.vc_row', '.equal-height-column', context );
1685			wpexEqualHeights( '.vc_row', '.equal-height-content', context );
1686		},
1687
1688		/**
1689		 * Footer Reveal Display on Load.
1690		 */
1691		footerReveal: function() {
1692			const self = this;
1693			const footerReveal = document.querySelector( '#footer-reveal' );
1694			const wrap = document.querySelector( '#wrap' );
1695			const main = document.querySelector( '#main' );
1696
1697			if ( ! footerReveal || ! wrap || ! main ) {
1698				return;
1699			}
1700
1701			const showHide = () => {
1702				if ( self.viewportWidth() < 960 ) {
1703					if ( footerReveal.classList.contains( 'footer-reveal' ) ) {
1704						footerReveal.classList.remove( 'footer-reveal' );
1705						footerReveal.classList.add( 'footer-reveal-visible' );
1706						wrap.style.removeProperty( 'margin-bottom' );
1707					}
1708					return;
1709				}
1710
1711				let heightCheck = 0;
1712				let revealHeight = footerReveal.offsetHeight;
1713				const windowHeight = window.innerHeight;
1714
1715				if ( footerReveal.classList.contains( 'footer-reveal' ) ) {
1716					heightCheck = wrap.offsetHeight + self.getLocalScrollOffset();
1717				} else {
1718					heightCheck = wrap.offsetHeight + self.getLocalScrollOffset() - revealHeight;
1719				}
1720
1721				// Hide the footer.
1722				if ( ( windowHeight > revealHeight ) && ( heightCheck > windowHeight ) ) {
1723					if ( footerReveal.classList.contains( 'footer-reveal-visible' ) ) {
1724						wrap.style.marginBottom = revealHeight + 'px';
1725						footerReveal.classList.remove( 'footer-reveal-visible' );
1726						footerReveal.classList.add( 'footer-reveal' );
1727					}
1728				}
1729
1730				// Display the footer.
1731				else {
1732					if ( footerReveal.classList.contains( 'footer-reveal' ) ) {
1733						wrap.style.removeProperty( 'margin-bottom' );
1734						footerReveal.classList.remove( 'footer-reveal' );
1735						footerReveal.classList.remove( 'wpex-visible' );
1736						footerReveal.classList.add( 'footer-reveal-visible' );
1737					}
1738				}
1739
1740			};
1741
1742			const reveal = () => {
1743				if ( ! footerReveal.classList.contains( 'footer-reveal' ) ) {
1744					return;
1745				}
1746				if ( self.scrolledToBottom( main ) ) {
1747					footerReveal.classList.add( 'wpex-visible' );
1748				} else {
1749					footerReveal.classList.remove( 'wpex-visible' );
1750				}
1751			};
1752
1753			// Fire right away.
1754			showHide();
1755			reveal();
1756
1757			// Fire on events.
1758			window.addEventListener( 'scroll', reveal, { passive: true } );
1759			window.addEventListener( 'resize', showHide );
1760		},
1761
1762		/**
1763		 * Set min height on main container to prevent issue with extra space below footer.
1764		 */
1765		fixedFooter: function() {
1766			if ( ! document.body.classList.contains( 'wpex-has-fixed-footer' ) ) {
1767				return;
1768			}
1769
1770			const main = document.querySelector( '#main' );
1771
1772			if ( ! main ) {
1773				return;
1774			}
1775
1776			const run = () => {
1777				main.style.minHeight = ( main.offsetHeight + ( window.innerHeight - document.documentElement.offsetHeight ) ) + 'px';
1778			};
1779
1780			// Add Events
1781			run();
1782			window.addEventListener( 'resize', run );
1783		},
1784
1785		/**
1786		 * Custom Selects.
1787		 */
1788		customSelects: function( context ) {
1789			if ( ! l10n.customSelects ) {
1790				return;
1791			}
1792
1793			const self = this;
1794
1795			if ( ! context || ! context.childNodes ) {
1796				context = document;
1797			}
1798
1799			context.querySelectorAll( l10n.customSelects ).forEach( ( element ) => {
1800				const parent = element.parentNode;
1801				if ( parent.classList.contains( 'wpex-select-wrap' ) || parent.classList.contains( 'wpex-multiselect-wrap' ) ) {
1802					return;
1803				}
1804				const elementId = element.id;
1805				let addIcon = false;
1806				if ( self.isVisible( element ) ) { // @todo is this really needed?
1807					// Wrap the select
1808					const div = document.createElement( 'div' );
1809					if ( element.hasAttribute( 'multiple' ) ) {
1810						if ( elementId ) {
1811							div.className = `wpex-multiselect-wrap wpex-multiselect-wrap--${elementId} wpex-${elementId}`;
1812						} else {
1813							div.className = `wpex-multiselect-wrap`;
1814						}
1815					} else {
1816						if ( elementId ) {
1817							div.className = `wpex-select-wrap wpex-select-wrap--${elementId} wpex-${elementId}`;
1818						} else {
1819							div.className = `wpex-select-wrap`;
1820						}
1821						addIcon = true;
1822					}
1823					element = self.wrap( element, div );
1824					// Add icon to wrapper
1825					if ( addIcon && l10n.selectArrowIcon ) {
1826						const arrow = document.createElement( 'span' );
1827						arrow.className = 'wpex-select-arrow';
1828						const icon = document.createDocumentFragment();
1829						const temp = document.createElement( 'div' );
1830						temp.innerHTML  = l10n.selectArrowIcon;
1831						icon.appendChild(temp.firstChild);
1832						arrow.appendChild( icon );
1833						div.appendChild( arrow );
1834					}
1835				}
1836			} );
1837		},
1838
1839		/**
1840		 * Inline hover styles.
1841		 *
1842		 * @todo switch to using inline style var() where possible.
1843		 */
1844		hoverStyles: function() {
1845			let oldStyle,
1846				items,
1847				itemsLength,
1848				headCSS = '',
1849				cssObj = {},
1850				style,
1851				head;
1852
1853			oldStyle = document.querySelector( '.wpex-hover-data' ); // querySelector will return null if nothing is found.
1854			if ( oldStyle ) {
1855				oldStyle.remove(); // prevent dups in VC front end.
1856			}
1857
1858			items = document.querySelectorAll( '[data-wpex-hover]' );
1859			itemsLength = items.length;
1860
1861			// No need to do anything if we don't have any items to style.
1862			if ( ! itemsLength ) {
1863				return;
1864			}
1865
1866			const parseItem = ( index ) => {
1867				let element,
1868					data,
1869					uniqueClass,
1870					classList,
1871					hoverCSS = '',
1872					target = '',
1873					parent;
1874
1875				element = items[index];
1876				data = element.dataset.wpexHover;
1877
1878				if ( ! data ) {
1879					return;
1880				}
1881
1882				data = JSON.parse( data );
1883
1884				// Remove any wpex-dhover-{int} classname that may have been previously added.
1885				// This is a fix for AJAX functions and front-end editor edits.
1886				classList = element.classList;
1887				for ( let i = 0; i < classList.length; i++ ) {
1888					if ( -1 !== classList[i].indexOf( 'wpex-dhover-' ) ) {
1889						element.classList.remove( classList[i] );
1890					}
1891				}
1892
1893				// New unique classname based on index.
1894				uniqueClass = `wpex-dhover-${index}`;
1895
1896				if ( data.parent ) {
1897					parent = element.closest( data.parent );
1898					if ( parent ) {
1899						parent.classList.add( uniqueClass + '-p' );
1900						element.classList.add( uniqueClass );
1901						target = `.${uniqueClass}-p:hover .${uniqueClass}`;
1902					}
1903				} else {
1904					element.classList.add( uniqueClass );
1905					target = `.${uniqueClass}:hover`;
1906				}
1907
1908				for ( let key in data ) {
1909					if ( data.hasOwnProperty( key ) ) {
1910						if ( 'target' === key || 'parent' === key ) {
1911							continue;
1912						}
1913						hoverCSS += `${key}:${data[key]}!important;`;
1914					}
1915				}
1916
1917				if ( hoverCSS ) {
1918					if ( hoverCSS in cssObj ) {
1919						cssObj[hoverCSS] = `${cssObj[hoverCSS]},${target}`;
1920					} else {
1921						cssObj[hoverCSS] = target;
1922					}
1923				}
1924			};
1925
1926			for (let i = 0; i < itemsLength; i++) {
1927				parseItem( i );
1928			}
1929
1930			if ( cssObj ) {
1931				for ( let css in cssObj ) {
1932					if ( cssObj.hasOwnProperty( css ) ) {
1933						headCSS += `${cssObj[css]}{${css}}`;
1934					}
1935				}
1936			}
1937
1938			if ( headCSS ) {
1939				style = document.createElement( 'style' );
1940				style.classList.add( 'wpex-hover-data' );
1941				style.appendChild( document.createTextNode( headCSS ) );
1942				head = document.head || document.getElementsByTagName( 'head' )[0];
1943				head.appendChild( style );
1944			}
1945		},
1946
1947		/**
1948		 * Overlay Mobile Support.
1949		 */
1950		overlaysMobileSupport: function() {
1951			const self = this;
1952			const supportsTouch = (window.matchMedia("(any-pointer: coarse)").matches);
1953
1954			if ( ! supportsTouch ) {
1955				return;
1956			}
1957
1958			let dragging = false;
1959
1960			// Remove overlays completely if mobile support is disabled.
1961			document.querySelectorAll( '.overlay-parent.overlay-hh' ).forEach( ( element ) => {
1962				if ( ! element.classList.contains( 'overlay-ms' ) ) {
1963					const overlay = element.querySelector( '.theme-overlay' );
1964					if ( overlay ) {
1965						element.parentNode.removeChild( element );
1966					}
1967				}
1968			} );
1969
1970			const hideAllOverlays = () => {
1971				document.querySelectorAll( '.wpex-touched' ).forEach( ( element ) => {
1972					element.classList.remove( 'wpex-touched' );
1973				} );
1974			};
1975
1976			const touchEnd = ( event ) => {
1977				const target = event.target;
1978				let overlay = target.closest( '.overlay-parent.overlay-ms.overlay-h,.vcex-image-swap,.wpex-card-touch-support' );
1979
1980				if ( ! overlay ) {
1981					return;
1982				}
1983
1984				if ( dragging ) {
1985					hideAllOverlays();
1986					return;
1987				}
1988
1989				if ( overlay.classList.contains( 'wpex-touched' ) ) {
1990					overlay.classList.remove( 'wpex-touched' );
1991					return; // overlay is open, allow clicking inside.
1992				}
1993
1994				event.preventDefault();
1995
1996				hideAllOverlays();
1997				overlay.classList.add( 'wpex-touched' );
1998			};
1999
2000			const touchMove = ( event ) => {
2001				const target = event.target;
2002				if ( ! target.closest( '.wpex-touched' ) ) {
2003					hideAllOverlays();
2004				}
2005				const overlay = target.closest( '.overlay-parent.overlay-ms.overlay-h,.vcex-image-swap,.wpex-card-touch-support' );
2006				if ( overlay ) {
2007					dragging = true;
2008				}
2009			};
2010
2011			const touchStart = ( event ) => {
2012				const target = event.target;
2013				if ( ! target.closest( '.wpex-touched' ) ) {
2014					hideAllOverlays();
2015				}
2016				const overlay = target.closest( '.overlay-parent.overlay-ms.overlay-h,.vcex-image-swap,.wpex-card-touch-support' );
2017				if ( overlay ) {
2018					dragging = false;
2019				}
2020			};
2021
2022			// Add events.
2023			document.addEventListener( 'touchend', touchEnd, false ); // must have false event listeners.
2024			document.addEventListener( 'touchmove', touchMove, { passive: true } );
2025			document.addEventListener( 'touchstart', touchStart, { passive: true } );
2026		},
2027
2028		/**
2029		 * Adds CSS variables to the body tag based on element heights.
2030		 *
2031		 * @todo enable this functionality. It's disabled because curently it causes severe lag
2032		 * on the animated shrink header.
2033		 *
2034		stickyElCssVar: function( el, varName ) {
2035			const setHeight = ( height ) => {
2036				document.body.style.setProperty(varName, `${height}px`);
2037			};
2038			let prevHeight = 0;
2039			const resize_ob = new ResizeObserver( function( entries ) {
2040				let rect = entries[0].contentRect;
2041				let height = rect.height;
2042				if (typeof height === 'number' && height !== prevHeight) {
2043					prevHeight = height;
2044					setHeight( height );
2045				}
2046			});
2047			resize_ob.observe( el ); // @note the observer API will fire on init.
2048		},
2049		*/
2050
2051		/**
2052		 * Sticky Topbar.
2053		 */
2054		stickyTopBar: function() {
2055			const self = this;
2056			let isSticky = false;
2057			let offset = 0;
2058			const stickyTopbar = document.querySelector( '#top-bar-wrap.wpex-top-bar-sticky' );
2059			const brkPoint = l10n.stickyTopBarBreakPoint;
2060
2061			if ( ! stickyTopbar ) {
2062				return;
2063			}
2064
2065		//	self.stickyElCssVar( stickyTopbar, '--wpex-topbar-height' );
2066
2067			// Add wrapper.
2068			const stickyWrap = document.createElement( 'div' );
2069			stickyWrap.id = 'top-bar-wrap-sticky-wrapper';
2070			stickyWrap.className = 'wpex-sticky-top-bar-holder not-sticky wpex-print-hidden';
2071
2072			self.wrap( stickyTopbar, stickyWrap );
2073
2074			// Get offset.
2075			const getOffset = () => {
2076				offset = 0; // Reset offset for resize.
2077
2078				document.querySelectorAll( '.wpex-sticky-el-offset,#wpadminbar' ).forEach( ( element ) => {
2079					const elPosition = window.getComputedStyle( element ).position;
2080					if ( self.isBeforeElement( element, stickyWrap ) && self.isVisible( element ) && ( 'fixed' === elPosition || 'sticky' === elPosition ) ) {
2081						offset += element.getBoundingClientRect().height;
2082					}
2083				} );
2084
2085				return offset;
2086			};
2087
2088			// Stick the TopBar.
2089			const setSticky = () => {
2090				if ( isSticky ) {
2091					return;
2092				}
2093
2094				// Tweak wrap class.
2095				stickyWrap.style.height = stickyTopbar.getBoundingClientRect().height + 'px';
2096				stickyWrap.classList.remove( 'not-sticky' );
2097				stickyWrap.classList.add( 'is-sticky' );
2098
2099				// Tweak topbar class.
2100				stickyTopbar.classList.remove( 'wpex-z-99' );
2101				stickyTopbar.classList.add( 'wpex-z-999' );
2102
2103				// Add CSS to topbar.
2104				stickyTopbar.style.top = getOffset() + 'px';
2105				stickyTopbar.style.width = stickyWrap.getBoundingClientRect().width + 'px';
2106				stickyTopbar.style.position = 'fixed'; // don't use CSS class to prevent possible custom CSS issues.
2107
2108				// Set sticky to true.
2109				isSticky = true;
2110			};
2111
2112			// Unstick the TopBar.
2113			const destroySticky = () => {
2114				if ( ! isSticky ) {
2115					return;
2116				}
2117
2118				// Tweak wrap class.
2119				stickyWrap.style.height = '';
2120				stickyWrap.classList.remove( 'is-sticky' );
2121				stickyWrap.classList.add( 'not-sticky' );
2122
2123				// Tweak topbar classes.
2124				stickyTopbar.classList.add( 'wpex-z-99' );
2125				stickyTopbar.classList.remove( 'wpex-z-999' );
2126
2127				// Remove topbar css.
2128				stickyTopbar.style.width = '';
2129				stickyTopbar.style.top = '';
2130				stickyTopbar.style.position = '';
2131
2132				// Set sticky to false.
2133				isSticky = false;
2134			};
2135
2136			// Runs on load and resize.
2137			const initSticky = () => {
2138				if ( ! l10n.hasStickyTopBarMobile && ( self.viewportWidth() < brkPoint ) ) {
2139					stickyWrap.classList.remove( 'wpex-can-sticky' );
2140					destroySticky();
2141					return;
2142				}
2143
2144				const windowTop = self.winScrollTop();
2145
2146				stickyWrap.classList.add( 'wpex-can-sticky' );
2147
2148				if ( isSticky ) {
2149
2150					// Update sticky wrapper height incase it changed on resize.
2151					stickyWrap.style.height = stickyTopbar.getBoundingClientRect().height + 'px';
2152
2153					// Update topbar top position and width incase it changed on resize.
2154					stickyTopbar.style.top = getOffset() + 'px';
2155					stickyTopbar.style.width = stickyWrap.getBoundingClientRect().width + 'px';
2156
2157				} else {
2158
2159					// Set sticky based on original offset.
2160					offset = self.offset( stickyWrap ).top - getOffset();
2161
2162					// Set or destroy sticky.
2163					if ( 0 !== windowTop && windowTop > offset ) {
2164						setSticky();
2165					} else {
2166						destroySticky();
2167					}
2168
2169				}
2170			};
2171
2172			// On scroll actions for sticky topbar.
2173			const onScroll = () => {
2174				if ( ! stickyWrap || ! stickyWrap.classList.contains( 'wpex-can-sticky' ) ) {
2175					return;
2176				}
2177
2178				const windowTop = self.winScrollTop();
2179
2180				// Set or destroy sticky based on offset.
2181				if ( ( 0 !== windowTop ) && ( windowTop >= ( self.offset( stickyWrap ).top - getOffset() ) ) ) {
2182					setSticky();
2183				} else {
2184					destroySticky();
2185				}
2186			};
2187
2188			// Add events.
2189			initSticky();
2190			window.addEventListener( 'scroll', onScroll, { passive: true } );
2191			window.addEventListener( 'resize', initSticky );
2192		},
2193
2194		/**
2195		 * Sticky Header Menu.
2196		 */
2197		stickyHeaderMenu: function() {
2198			const self = this;
2199			const stickyNav = document.querySelector( '#site-navigation-wrap.fixed-nav' );
2200
2201			if ( ! stickyNav ) {
2202				return;
2203			}
2204
2205		//	self.stickyElCssVar( stickyNav, '--wpex-header-menu-height' );
2206
2207			let isSticky = false;
2208			const header = document.querySelector( '#site-header' );
2209
2210			const stickyWrap = document.createElement( 'div' );
2211			stickyWrap.id = 'site-navigation-sticky-wrapper';
2212			stickyWrap.className = 'wpex-sticky-navigation-holder not-sticky';
2213
2214			self.wrap( stickyNav, stickyWrap );
2215
2216			const getOffset = () => {
2217				let offset = 0;
2218
2219				document.querySelectorAll( '.wpex-sticky-el-offset,#wpadminbar,.wpex-top-bar-sticky' ).forEach( ( element ) => {
2220					const elPosition = window.getComputedStyle( element ).position;
2221					if ( self.isVisible( element ) && self.isBeforeElement( element, stickyWrap ) && ( 'fixed' === elPosition || 'sticky' === elPosition ) ) {
2222						offset += element.getBoundingClientRect().height;
2223					}
2224				} );
2225
2226				if ( l10n.addStickyHeaderOffset ) {
2227					offset += l10n.addStickyHeaderOffset;
2228				}
2229
2230				return offset;
2231			};
2232
2233			// Add offsets.
2234			const stickyWrapTop = self.offset( stickyWrap ).top;
2235			const setStickyPos = stickyWrapTop - getOffset();
2236
2237			const setSticky = () => {
2238				if ( isSticky ) {
2239					return;
2240				}
2241
2242				// Add wrap class and toggle sticky class.
2243				stickyWrap.style.height = stickyNav.getBoundingClientRect().height + 'px';
2244				stickyWrap.classList.remove( 'not-sticky' );
2245				stickyWrap.classList.add( 'is-sticky' );
2246
2247				// Add CSS to topbar.
2248				stickyNav.style.top = getOffset() + 'px';
2249				stickyNav.style.width = stickyWrap.getBoundingClientRect().width + 'px';
2250
2251				// Remove header dynamic styles.
2252				if ( header ) {
2253					header.classList.remove( 'dyn-styles' );
2254				}
2255
2256				// Update shrunk var.
2257				isSticky = true;
2258			};
2259
2260			const destroySticky = () => {
2261				if ( ! isSticky ) {
2262					return;
2263				}
2264
2265				// Remove sticky wrap height and toggle sticky class.
2266				stickyWrap.style.height = '';
2267				stickyWrap.classList.remove( 'is-sticky' );
2268				stickyWrap.classList.add( 'not-sticky' );
2269
2270				// Remove navbar width.
2271				stickyNav.style.top = '';
2272				stickyNav.style.width = '';
2273
2274				// Re-add dynamic header styles.
2275				if ( header ) {
2276					header.classList.add( 'dyn-styles' );
2277				}
2278
2279				// Update shrunk var.
2280				isSticky = false;
2281			};
2282
2283			const initResizeSetSticky = () => {
2284				if ( self.viewportWidth() <= l10n.stickyNavbarBreakPoint ) {
2285					destroySticky();
2286					stickyWrap.classList.remove( 'wpex-can-sticky' );
2287					return;
2288				}
2289
2290				const windowTop = self.winScrollTop();
2291
2292				stickyWrap.classList.add( 'wpex-can-sticky' );
2293
2294				if ( isSticky ) {
2295					// Already sticky, lets update height, width and offsets.
2296					stickyWrap.style.height = stickyNav.getBoundingClientRect().height + 'px';
2297					stickyNav.style.top = getOffset() + 'px';
2298					stickyNav.style.width = stickyWrap.getBoundingClientRect().width + 'px';
2299				} else {
2300					if ( windowTop >= setStickyPos && 0 !== windowTop ) {
2301						setSticky();
2302					} else {
2303						destroySticky();
2304					}
2305
2306				}
2307			};
2308
2309			const onScroll = () => {
2310				if ( ! stickyWrap.classList.contains( 'wpex-can-sticky' ) ) {
2311					return;
2312				}
2313
2314				const windowTop = self.winScrollTop();
2315
2316				// Sticky menu.
2317				if ( 0 !== windowTop && windowTop >= setStickyPos ) {
2318					setSticky();
2319				} else {
2320					destroySticky();
2321				}
2322			};
2323
2324			const onOrientationChange = () => {
2325				destroySticky();
2326				initResizeSetSticky();
2327			};
2328
2329			// Add events.
2330			initResizeSetSticky();
2331			window.addEventListener( 'scroll', onScroll, { passive: true } );
2332			window.addEventListener( 'resize', initResizeSetSticky );
2333			window.addEventListener( 'orientationchange', onOrientationChange );
2334		},
2335
2336		/**
2337		 * Sticky Header.
2338		 */
2339		stickyHeader: function() {
2340			const self = this;
2341			let stickyStyle = l10n.stickyHeaderStyle;
2342			if ( 'standard' !== stickyStyle && 'shrink' !== stickyStyle && 'shrink_animated' !== stickyStyle ) {
2343				return;
2344			}
2345
2346			const header = document.querySelector( '#site-header.fixed-scroll' );
2347
2348			if ( ! header ) {
2349				return;
2350			}
2351
2352		//	self.stickyElCssVar( header, '--wpex-header-height' );
2353
2354			const mobileOnly = header.classList.contains( 'fixed-scroll--mobile-only' );
2355
2356			const startPoint = () => {
2357				var startPosition = l10n.stickyHeaderStartPosition;
2358				if ( startPosition && ! isNaN( startPosition ) ) {
2359					return startPosition;
2360				}
2361				var el = document.querySelector( startPosition );
2362				if ( el ) {
2363					return this.offset( el ).top;
2364				}
2365				return 0;
2366			};
2367
2368			let isSticky = false;
2369			let isShrunk = false;
2370
2371			// Add sticky wrap.
2372			const stickyWrap = document.createElement( 'div' );
2373			stickyWrap.id = 'site-header-sticky-wrapper';
2374			stickyWrap.className = 'wpex-sticky-header-holder not-sticky wpex-print-hidden';
2375
2376			self.wrap( header, stickyWrap );
2377
2378			const getOffset = () => {
2379				let offset = 0;
2380
2381				document.querySelectorAll( '.wpex-sticky-el-offset,.wpex-top-bar-sticky,#wpadminbar' ).forEach( ( element ) => {
2382					const elPosition = window.getComputedStyle( element ).position;
2383					if ( self.isVisible( element ) && self.isBeforeElement( element, stickyWrap ) && ( 'fixed' === elPosition || 'sticky' === elPosition ) ) {
2384						offset += element.getBoundingClientRect().height;
2385					}
2386				} );
2387
2388				if ( l10n.addStickyHeaderOffset ) {
2389					offset += l10n.addStickyHeaderOffset;
2390				}
2391
2392				return offset;
2393			};
2394
2395			const getBreakpoint = () => {
2396				let brkPoint = parseInt( l10n.stickyHeaderBreakPoint );
2397				return mobileOnly ? brkPoint -1 : brkPoint;
2398			};
2399
2400			const getBreakpointComparisonOperator = () => {
2401				return mobileOnly ? '>' : '<';
2402			};
2403
2404			// Define main vars for sticky function.
2405			const brkPoint = getBreakpoint();
2406			const mobileSupport = l10n.hasStickyMobileHeader;
2407			const customStart = startPoint();
2408
2409			// Check if we are on mobile size.
2410			const pastBreakPoint = () => {
2411				switch ( getBreakpointComparisonOperator() ) {
2412					case '<':
2413						return ( self.viewportWidth() < brkPoint );
2414					case '>':
2415						return ( self.viewportWidth() > brkPoint );
2416				}
2417			};
2418
2419			let pastHeaderBottomCheck = 0;
2420			const overlayHeader = document.querySelector( '#site-header.overlay-header' );
2421			if ( overlayHeader && 'absolute' === window.getComputedStyle( overlayHeader ).position ) {
2422				pastHeaderBottomCheck = self.offset( header ).top + header.getBoundingClientRect().height;
2423			} else {
2424				pastHeaderBottomCheck = self.offset( stickyWrap ).top + stickyWrap.getBoundingClientRect().height;
2425			}
2426
2427			// Check if we are past the header.
2428			function pastheader() {
2429				if ( self.winScrollTop() > pastHeaderBottomCheck ) {
2430					return true;
2431				}
2432				return false;
2433			}
2434
2435			// Check start position.
2436			const start_position = () => {
2437				let startPosition = customStart || self.offset( stickyWrap ).top;
2438				return startPosition - getOffset();
2439			};
2440
2441			const getShrunkDuration = () => {
2442				let elDuration = window.getComputedStyle( header ).transitionDuration;
2443				let duration = '';
2444				if ( ! elDuration || '0s' !== elDuration ) {
2445					duration = parseFloat( elDuration ) * ( elDuration.indexOf( 'ms' ) >- 1 ? 1 : 1000 );
2446				}
2447
2448				if ( ! duration ) {
2449					duration = 300;
2450				}
2451				return duration;
2452			};
2453
2454			// Transform.
2455			const transformPrepare = () => {
2456				var windowTop = self.winScrollTop();
2457				if ( isSticky ) {
2458					header.classList.add( 'transform-go' ); // prevent issues when scrolling.
2459				}
2460				if ( windowTop <= 0 ) {
2461					header.classList.remove( 'transform-prepare' );
2462				} else if ( pastheader() ) {
2463					header.classList.add( 'transform-prepare' );
2464				} else {
2465					header.classList.remove( 'transform-prepare' );
2466				}
2467			};
2468
2469			// Set sticky.
2470			const setSticky = () => {
2471				if ( isSticky ) {
2472					return;
2473				}
2474
2475				// Set wrapper height before toggling sticky classes.
2476				stickyWrap.style.height = header.getBoundingClientRect().height + 'px';
2477
2478				// Toggle sticky classes.
2479				stickyWrap.classList.remove( 'not-sticky' );
2480				stickyWrap.classList.add( 'is-sticky' );
2481				header.classList.remove( 'dyn-styles' );
2482
2483				// Tweak header styles.
2484				header.style.top = getOffset() + 'px';
2485				header.style.width = stickyWrap.getBoundingClientRect().width + 'px';
2486
2487				// Add transform go class.
2488				if ( header.classList.contains( 'transform-prepare' ) ) {
2489					header.classList.add( 'transform-go' );
2490				}
2491
2492				// Set sticky to true.
2493				isSticky = true;
2494			};
2495
2496			// Shrink/unshrink header.
2497			const shrink = () => {
2498				let check = true; // we already check if it's enabled before running this function.
2499
2500				if ( mobileOnly ) {
2501					check = true;
2502				} else if ( pastBreakPoint() ) {
2503					if ( mobileSupport && l10n.hasStickyMobileHeaderShrink ) {
2504						check = true;
2505					} else {
2506						check = false;
2507					}
2508				}
2509
2510				if ( check && pastheader() ) {
2511					if ( ! isShrunk && isSticky ) {
2512						header.classList.add( 'sticky-header-shrunk' );
2513						isShrunk = true;
2514					}
2515				} else {
2516					header.classList.remove( 'sticky-header-shrunk' );
2517					isShrunk = false;
2518				}
2519			};
2520
2521			// Destroy actions.
2522			const destroyActions = () => {
2523
2524				// Remove sticky wrap height and toggle sticky class.
2525				stickyWrap.classList.remove( 'is-sticky' );
2526				stickyWrap.classList.add( 'not-sticky' );
2527
2528				if ( header.classList.contains( 'shrink-sticky-header' ) ) {
2529					let duration = getShrunkDuration();
2530					setTimeout( function() {
2531						if ( ! isSticky ) {
2532							stickyWrap.style.height = '';
2533						}
2534					}, duration );
2535				} else {
2536					stickyWrap.style.height = ''; //@todo remove for shrink as well?
2537				}
2538
2539				// Reset header.
2540				header.classList.add( 'dyn-styles' );
2541				header.style.width = '';
2542				header.style.top = '';
2543				header.classList.remove( 'transform-go' );
2544
2545				// Set sticky to false.
2546				isSticky = false;
2547
2548				// Make sure shrink header is removed.
2549				header.classList.remove( 'sticky-header-shrunk' ); // Fixes some bugs with really fast scrolling.
2550				isShrunk = false;
2551
2552				// Reset sticky wrapper (causes weird animations with shrink sticky)...@todo enable?
2553				//stickyWrap.style.height = '';
2554			};
2555
2556			// Destroy sticky.
2557			function destroySticky() {
2558				if ( ! isSticky ) {
2559					return;
2560				}
2561
2562				if ( customStart ) {
2563					header.classList.remove( 'transform-go' );
2564					if ( isShrunk ) {
2565						header.classList.remove( 'sticky-header-shrunk' );
2566						isShrunk = false;
2567					}
2568				} else {
2569					header.classList.remove( 'transform-prepare' );
2570				}
2571
2572				destroyActions();
2573			}
2574
2575			// On load check.
2576			const initResizeSetSticky = () => {
2577				let windowTop = self.winScrollTop();
2578
2579				if ( ( mobileOnly || ! mobileSupport ) && pastBreakPoint() ) {
2580					destroySticky();
2581					stickyWrap.classList.remove( 'wpex-can-sticky' );
2582					header.classList.remove( 'transform-prepare' );
2583					return;
2584				}
2585
2586				//header.classList.add( 'transform-go' );
2587				stickyWrap.classList.add( 'wpex-can-sticky' );
2588
2589				if ( isSticky ) {
2590
2591					// Update header height on resize incase it's changed height.
2592					// Can't update shrink as it may cause issues.
2593					if ( ! header.classList.contains( 'shrink-sticky-header' ) ) {
2594						stickyWrap.style.height = header.getBoundingClientRect().height + 'px';
2595					}
2596
2597					header.style.top = getOffset() + 'px';
2598					header.style.width = stickyWrap.getBoundingClientRect().width + 'px';
2599
2600				} else {
2601
2602					if ( 0 !== windowTop && windowTop > start_position() ) {
2603						setSticky();
2604					} else {
2605						destroySticky();
2606					}
2607
2608				}
2609
2610				if ( l10n.hasStickyHeaderShrink ) {
2611					shrink();
2612				}
2613			};
2614
2615			// On scroll function.
2616			const onScroll = () => {
2617				let windowTop = self.winScrollTop();
2618
2619				if ( ! stickyWrap.classList.contains( 'wpex-can-sticky' ) ) {
2620					return;
2621				}
2622
2623				// Animate scroll with custom start.
2624				if ( customStart ) {
2625					transformPrepare();
2626				}
2627
2628				// Set or destroy sticky.
2629				if ( 0 != windowTop && windowTop >= start_position() ) {
2630					setSticky();
2631				} else {
2632					destroySticky();
2633				}
2634
2635				// Shrink.
2636				if ( l10n.hasStickyHeaderShrink ) {
2637					shrink();
2638				}
2639			};
2640
2641			// Add Events.
2642			initResizeSetSticky();
2643			window.addEventListener( 'scroll', onScroll, { passive: true } );
2644			window.addEventListener( 'resize', initResizeSetSticky );
2645			window.addEventListener( 'orientationchange', () => {
2646				destroySticky();
2647				initResizeSetSticky();
2648			} );
2649		},
2650
2651		/**
2652		 * Custom Sticky elements.
2653		 */
2654		stickyElements: function() {
2655			const bodyClass = document.body.classList;
2656			if ( bodyClass.contains( 'compose-mode' ) || bodyClass.contains( 'wp-admin' ) ) {
2657				return;
2658			}
2659
2660			const stickyElements = document.querySelectorAll( '.wpex-js-sticky' );
2661
2662			if ( ! stickyElements ) {
2663				return;
2664			}
2665
2666			const self = this;
2667
2668			const addInnerWrap = ( el ) => {
2669				const wrapper = document.createElement( 'div' );
2670				let wrapClassname = 'wpex-js-sticky-wrap';
2671				const lsOffset = el.dataset.wpexStickyLsOffset || true;
2672				if ( 'true' == lsOffset || '1' == lsOffset ) {
2673					wrapClassname += ' wpex-ls-offset';
2674				}
2675				wrapper.className = wrapClassname;
2676				if ( el.nextSibling ) {
2677					el.parentNode.insertBefore( wrapper, el.nextSibling );
2678				} else {
2679					el.parentNode.appendChild( wrapper );
2680				}
2681				wrapper.appendChild( el );
2682				return wrapper;
2683			};
2684
2685			const addHolder = ( el ) => {
2686				const wrapper = document.createElement( 'div' );
2687				wrapper.className = 'wpex-js-sticky-holder';
2688				if ( el.nextSibling ) {
2689					el.parentNode.insertBefore( wrapper, el.nextSibling );
2690				} else {
2691					el.parentNode.appendChild( wrapper );
2692				}
2693				wrapper.appendChild( el );
2694				return wrapper;
2695			};
2696
2697			stickyElements.forEach( ( stickyEl ) => {
2698				let isSticky = false;
2699				let stickyEndPoint = stickyEl.dataset.wpexStickyEndpoint || null;
2700				const stickyBreakpoint = parseInt( stickyEl.dataset.wpexStickyBreakpoint );
2701
2702				if ( stickyEndPoint && isNaN( stickyEndPoint ) ) {
2703					stickyEndPoint = document.querySelector( stickyEndPoint );
2704				}
2705
2706				const stickyWrapper = addInnerWrap( stickyEl );
2707				const stickyHolder = addHolder( stickyWrapper );
2708
2709				const getStickyOffset = () => {
2710					let offset = stickyEl.dataset.wpexStickyOffset ? parseInt( stickyEl.dataset.wpexStickyOffset ) : 0;
2711					document.querySelectorAll( '#top-bar-wrap-sticky-wrapper.wpex-can-sticky #top-bar-wrap,#site-header-sticky-wrapper.wpex-can-sticky #site-header,#site-navigation-sticky-wrapper.wpex-can-sticky #site-navigation-wrap,#wpadminbar,.wpex-sticky-el-offset,.wpex-js-sticky-wrap' ).forEach( ( item ) => {
2712						const itemPosition = window.getComputedStyle( item ).position;
2713						if ( ! stickyEl.isEqualNode( item ) && self.isVisible( item ) && self.isBeforeElement( item, stickyEl ) && ( 'fixed' === itemPosition || 'sticky' === itemPosition ) ) {
2714							offset += self.elHeight( item );
2715						}
2716					} );
2717					return offset;
2718				};
2719
2720				const stickyCheck = () => {
2721					if ( stickyBreakpoint && self.viewportWidth() <= stickyBreakpoint ) {
2722						return destroySticky();
2723					}
2724
2725					const windowTop = self.winScrollTop();
2726					const stickyOffset = getStickyOffset();
2727					const stickyWrapTop = self.offset( stickyHolder ).top;
2728					const setStickyPos = stickyWrapTop - stickyOffset;
2729
2730					if ( windowTop > setStickyPos && 0 !== windowTop ) {
2731						setSticky( stickyOffset );
2732						if ( stickyEndPoint && self.isVisible( stickyEndPoint ) ) {
2733							if ( windowTop > ( self.offset( stickyEndPoint ).top - stickyOffset - self.elHeight( stickyEl ) ) ) {
2734								stickyWrapper.style.position = 'relative';
2735							} else {
2736								stickyWrapper.style.position = 'fixed';
2737							}
2738						}
2739					} else {
2740						destroySticky();
2741					}
2742				};
2743
2744				const setWidth = () => {
2745					const wrapWidth = stickyHolder.getBoundingClientRect().width;
2746					if ( wrapWidth ) {
2747						stickyWrapper.style.setProperty( 'width', wrapWidth + 'px' );
2748					}
2749				};
2750
2751				const setSticky = ( offset ) => {
2752					if ( ! self.isVisible( stickyEl ) ) {
2753						destroySticky();
2754						return;
2755					}
2756
2757					if ( isSticky ) {
2758						stickyWrapper.style.top = getStickyOffset() + 'px'; // fix for items that may have changed sized like shrink header.
2759						return;
2760					}
2761
2762					stickyHolder.style.height = self.elHeight( stickyEl ) + 'px';
2763
2764					stickyWrapper.classList.add( 'wpex-z-99' );
2765					stickyWrapper.style.position = 'fixed';
2766					stickyWrapper.style.top = offset + 'px';
2767
2768					stickyEl.classList.add( 'wpex-js-sticky--stuck' );
2769
2770					setWidth();
2771
2772					isSticky = true;
2773				};
2774
2775				// Unsticks element.
2776				 const destroySticky = () => {
2777					if ( ! isSticky ) {
2778						return;
2779					}
2780					stickyHolder.style.height = '';
2781					stickyWrapper.classList.remove( 'wpex-z-99' );
2782					stickyWrapper.style.position = '';
2783					stickyWrapper.style.top = '';
2784					stickyWrapper.style.width = '';
2785
2786					stickyEl.classList.remove( 'wpex-js-sticky--stuck' );
2787
2788					isSticky = false;
2789				};
2790
2791				const onResize = () => {
2792					stickyCheck();
2793					if ( ! isSticky ) {
2794						return;
2795					}
2796					if ( ! self.isVisible( stickyEl ) ) {
2797						destroySticky();
2798					}
2799					stickyHolder.style.height = self.elHeight( stickyEl ) + 'px';
2800					stickyWrapper.style.top = getStickyOffset() + 'px';
2801					setWidth( stickyEl );
2802				};
2803
2804				const onFlip = () => {
2805					destroySticky();
2806					stickyCheck();
2807				};
2808
2809				// Run functions and add event listeners.
2810				stickyCheck();
2811				window.addEventListener( 'scroll', stickyCheck, { passive: true } );
2812				window.addEventListener( 'resize', onResize );
2813				window.addEventListener( 'orientationchange', onFlip );
2814			} );
2815		},
2816
2817		/**
2818		 * Accessability fixes/enhancements.
2819		 */
2820		accessibility: function() {
2821
2822			// Add tabindex -1 to nav-no-click links.
2823			document.querySelectorAll( '#site-navigation li.nav-no-click:not(.menu-item-has-children) > a' ).forEach( ( element ) => {
2824				element.setAttribute( 'tabIndex', '-1' );
2825			} );
2826
2827			// Add tabindex -1 to megamenu headings that are using "#" for the href.
2828			document.querySelectorAll( '#site-navigation li.megamenu > ul.sub-menu > li.menu-item.menu-item-has-children > a' ).forEach( ( megaMenuHeading ) => {
2829				if ( '#' === megaMenuHeading.getAttribute( 'href' ) ) {
2830					megaMenuHeading.setAttribute( 'tabIndex', '-1' );
2831				}
2832			} );
2833
2834			// Allow for opening WPBakery FAQ elements with the enter button.
2835			document.querySelectorAll( '.vc_toggle .vc_toggle_title' ).forEach( ( element ) => {
2836				element.setAttribute( 'tabIndex', 0 );
2837				element.addEventListener( 'keydown', ( event ) => {
2838					if ( 'Enter' === event.key ) {
2839						event.target.click();
2840					}
2841				} );
2842			} );
2843
2844		},
2845
2846		/**
2847		 * WPBakery tweaks.
2848		 */
2849		wpbakery: function() {
2850			// Disable tab animation.
2851			if ( l10n.disable_vc_tta_animation
2852				&& document.querySelector( '.vc_tta' )
2853				&& 'function' === typeof jQuery
2854				&& 'function' === typeof jQuery.fn.vcAccordion
2855			) {
2856				jQuery.fn.vcAccordion.Constructor.prototype.isAnimated = function() {
2857					return 0;
2858				}
2859			}
2860		},
2861
2862		/* Helpers.
2863		------------------------------------------------------------------------------ */
2864
2865		/**
2866		 * Is the DOM ready?
2867		 */
2868		domReady: function( fn ) {
2869			if ( typeof fn !== 'function' || 'undefined' === typeof document ) {
2870				return;
2871			}
2872
2873			const readyState = document.readyState;
2874
2875			// If document is already loaded, run method.
2876			if ( readyState === 'interactive' || readyState === 'complete' ) {
2877				return setTimeout( fn ); // Timeout prevents issues with dependencies when using async.
2878			}
2879
2880			// Otherwise, wait until document is loaded.
2881			document.addEventListener( 'DOMContentLoaded', fn, false );
2882		},
2883
2884		/**
2885		 * Retina Check.
2886		 */
2887		retinaCheck: function() {
2888			const mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)';
2889			if ( window.devicePixelRatio > 1 ) {
2890				return true;
2891			}
2892			if ( window.matchMedia && window.matchMedia( mediaQuery ).matches ) {
2893				return true;
2894			}
2895			return false;
2896		},
2897
2898		/**
2899		 * Mobile Check.
2900		 */
2901		mobileCheck: function() {
2902			if ( /Android|webOS|iPhone|iPad|iPod|iPad Simulator|iPhone Simulator|iPod Simulator|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ) ) {
2903				return true;
2904			}
2905		},
2906
2907		/**
2908		 * Get easing value.
2909		 */
2910		getEasing: function( customEasing ) {
2911			if ( ! l10n.localScrollEasing || 'function' !== typeof jQuery || 'undefined' === typeof jQuery.easing || 'function' !== typeof jQuery.easing.jswing ) {
2912				return;
2913			}
2914			let easing = customEasing || l10n.localScrollEasing;
2915			if ( ! jQuery.easing.hasOwnProperty( easing ) ) {
2916				easing = 'swing';
2917			}
2918			return easing;
2919		},
2920
2921		/**
2922		 * Viewport width.
2923		 */
2924		viewportWidth: function() {
2925			let a = 'inner';
2926			let e = window;
2927			if ( ! ( 'innerWidth' in window ) ) {
2928				a = 'client';
2929				e = document.documentElement || document.body;
2930			}
2931			return e[ a+'Width' ];
2932		},
2933
2934		/**
2935		 * Check if a given selector is valid.
2936		 */
2937		isSelectorValid: function( selector ) {
2938			const queryCheck = ( s ) => {
2939				document.createDocumentFragment().querySelector( s );
2940			};
2941			try {
2942				queryCheck( selector );
2943			} catch( error ) {
2944				return false;
2945			}
2946			return true;
2947		},
2948
2949		/**
2950		 * SlideUp element.
2951		 */
2952		slideUp: function( target, duration, callback ) {
2953			if ( ! target ) {
2954				return;
2955			}
2956
2957			const display = window.getComputedStyle( target ).display;
2958
2959			// Element already closed.
2960			if ( 'none' === display ) {
2961				return;
2962			}
2963
2964			// Allow for CSS defined transition duration.
2965			const elDuration = window.getComputedStyle( target ).transitionDuration;
2966
2967			if ( ! elDuration || '0s' !== elDuration ) {
2968				duration = parseFloat( elDuration ) * ( elDuration.indexOf( 'ms' ) >- 1 ? 1 : 1000 );
2969			}
2970
2971			if ( ! duration ) {
2972				duration = 300;
2973			}
2974
2975			// Add classname that we can check to prevent from doing other things during transition.
2976			target.classList.add( 'wpex-transitioning' );
2977
2978			// Set transition duration for animation.
2979			target.style.transitionProperty = 'height, margin, padding';
2980			target.style.transitionDuration = duration + 'ms';
2981
2982			// Set element height.
2983			target.style.height = target.offsetHeight + 'px';
2984			target.offsetHeight; // get height so that browser re-paints element.
2985			target.style.overflow = 'hidden';
2986
2987			// Reset element height.
2988			target.style.height = 0;
2989			target.style.paddingTop = 0;
2990			target.style.paddingBottom = 0;
2991			target.style.marginTop = 0;
2992			target.style.marginBottom = 0;
2993
2994			// Remove properties after animation has finished
2995			setTimeout( () => {
2996				target.style.display = 'none';
2997				target.style.removeProperty( 'height' );
2998				target.style.removeProperty( 'padding-top' );
2999				target.style.removeProperty( 'padding-bottom' );
3000				target.style.removeProperty( 'margin-top' );
3001				target.style.removeProperty( 'margin-bottom' );
3002				target.style.removeProperty( 'overflow' );
3003				target.style.removeProperty( 'transition-duration' );
3004				target.style.removeProperty( 'transition-property' );
3005				target.classList.remove( 'wpex-transitioning' );
3006				if ( callback ) {
3007					callback();
3008				}
3009			}, duration );
3010		},
3011
3012		/**
3013		 * SlideDown element.
3014		 */
3015		slideDown: function( target, duration, callback ) {
3016			if ( ! target ) {
3017				return;
3018			}
3019
3020			let display = window.getComputedStyle( target ).display;
3021
3022			// Already open.
3023			if ( 'block' === display ) {
3024				return;
3025			}
3026
3027			// Allow for CSS defined transition duration.
3028			const elDuration = window.getComputedStyle( target ).transitionDuration;
3029
3030			if ( ! elDuration || '0s' !== elDuration ) {
3031				duration = parseFloat( elDuration ) * ( elDuration.indexOf( 'ms' ) >- 1 ? 1 : 1000 );
3032			}
3033
3034			if ( ! duration ) {
3035				duration = 300;
3036			}
3037
3038			// Add classname that we can check to prevent from doing other things during transition.
3039			target.classList.add( 'wpex-transitioning' );
3040
3041			// Remove inline display if it had previously been toggled.
3042			target.style.removeProperty( 'display' );
3043
3044			// Element needs to be visible to calculate height and animate.
3045			if ( 'none' === display ) {
3046				display = 'block';
3047			}
3048
3049			target.style.display = display;
3050			target.style.transitionProperty = 'none'; // prevent possible animation when calculating height.
3051
3052			const height = target.offsetHeight;
3053
3054			// Reset height so we can animate it.
3055			target.style.overflow = 'hidden';
3056			target.style.height = 0;
3057			target.style.paddingTop = 0;
3058			target.style.paddingBottom = 0;
3059			target.style.marginTop = 0;
3060			target.style.marginBottom = 0;
3061			target.offsetHeight; // get height so that browser re-paints element.
3062			target.style.boxSizing = 'border-box';
3063
3064			// Add transition duration for animation.
3065			target.style.transitionProperty = 'height, margin, padding';
3066			target.style.transitionDuration = duration + 'ms';
3067
3068			// Set element height using a timeout otherwise animation won't work.
3069			target.style.height = height + 'px';
3070			target.style.removeProperty( 'padding-top' );
3071			target.style.removeProperty( 'padding-bottom' );
3072			target.style.removeProperty( 'margin-top' );
3073			target.style.removeProperty( 'margin-bottom' );
3074
3075			// Remove properties after animation has finished.
3076			setTimeout( () => {
3077				target.style.removeProperty( 'height' );
3078				target.style.removeProperty( 'overflow' );
3079				target.style.removeProperty( 'transition-duration' );
3080				target.style.removeProperty( 'transition-property' );
3081				target.classList.remove( 'wpex-transitioning' );
3082				if ( callback ) {
3083					callback();
3084				}
3085			}, duration );
3086		},
3087
3088		/**
3089		 * Set correct focus states for custom elements.
3090		 *
3091		 * @param {HTMLElement} el
3092		 */
3093		focusOnElement: function( element, initialFocus ) {
3094			const self = this;
3095			const focusElements = element.querySelectorAll( 'button, [href], input, select, textarea, a,[tabindex]:not([tabindex="-1"])' );
3096
3097			if ( ! focusElements.length ) {
3098				return;
3099			}
3100
3101			const focus = [];
3102
3103			for ( let i=0; i < focusElements.length; i++ ) {
3104				if ( self.isVisible( focusElements[i] ) ){
3105					focus.push( focusElements[i] );
3106				}
3107			}
3108
3109			if ( ! focus.length ) {
3110				return;
3111			}
3112
3113			const firstFocus = focus[0];
3114			const lastFocus = focus[focus.length - 1];
3115
3116			// Add initial focus.
3117			if ( initialFocus ) {
3118				initialFocus.focus();
3119			} else {
3120				firstFocus.focus();
3121			}
3122
3123			// Redirect last tab to first input.
3124			lastFocus.addEventListener( 'keydown', ( event ) => {
3125				if ( 'Tab' === event.key && ! event.shiftKey ) {
3126					event.preventDefault();
3127					firstFocus.focus();
3128				}
3129			} );
3130
3131			// Redirect first shift+tab to last input.
3132			firstFocus.addEventListener( 'keydown', ( event ) => {
3133				if ( 'Tab' === event.key && event.shiftKey ) {
3134					event.preventDefault();
3135					lastFocus.focus();
3136				}
3137			} );
3138		},
3139
3140		/**
3141		 * Wrap an element.
3142		 */
3143		wrap: function( element, wrapper ) {
3144			if ( ! element.childNodes ) {
3145				element = [element];
3146			}
3147			if ( element.nextSibling ) {
3148				element.parentNode.insertBefore( wrapper, element.nextSibling );
3149			} else {
3150				element.parentNode.appendChild( wrapper );
3151			}
3152			wrapper.appendChild( element );
3153		},
3154
3155		/**
3156		 * Insert element after another.
3157		 */
3158		insertAfter: function( newNode, referenceNode ) {
3159			referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling );
3160		},
3161
3162		/**
3163		 * Get element offset.
3164		 */
3165		offset: function( element ) {
3166			const rect = element.getBoundingClientRect();
3167			return {
3168				top: rect.top + this.winScrollTop(),
3169				left: rect.left + this.winScrollTop(),
3170				bottom: rect.bottom + this.winScrollTop(),
3171			};
3172		},
3173
3174		/**
3175		 * Get element height.
3176		 */
3177		elHeight: function( element ) {
3178			return element.getBoundingClientRect().height;
3179		},
3180
3181		/**
3182		 * Check if element is visible.
3183		 */
3184		isVisible: function( element ) {
3185			if ( ! element ) {
3186				return false;
3187			}
3188			return !!( element.offsetWidth || element.offsetHeight || element.getClientRects().length );
3189		},
3190
3191		/**
3192		 * Check if element is empty
3193		 */
3194		isEmpty: function( element ) {
3195			return ! element || '' === element.innerHTML;
3196		},
3197
3198		/**
3199		 * Check if an element is before another one.
3200		 */
3201		isBeforeElement: function( firstEl, secondEl ) {
3202			if ( 'wpadminbar' === firstEl.id ) {
3203				return true; //  incase some plugin moved it's position.
3204			}
3205			if ( ! firstEl || ! secondEl ) {
3206				return;
3207			}
3208			const position = firstEl.compareDocumentPosition( secondEl );
3209			return ( 4 === position );
3210		},
3211
3212		/**
3213		 * Grabs content and inserts into another element.
3214		 */
3215		insertExtras: function( element, target, method ) {
3216			if ( ! element || ! target ) {
3217				return;
3218			}
3219			switch ( method ) {
3220				case 'append':
3221					target.appendChild( element );
3222					break;
3223				case 'prepend':
3224					target.insertBefore( element, target.firstChild );
3225					break;
3226			}
3227			element.classList.remove( 'wpex-hidden' );
3228		},
3229
3230		/**
3231		 * Returns the window scrollTop position.
3232		 */
3233		winScrollTop: function() {
3234			let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
3235			if ( scrollTop < 0 ) {
3236				scrollTop = 0; // return 0 if negative to prevent issues with elastic scrolling in Safari.
3237			}
3238			return scrollTop;
3239		},
3240
3241		/**
3242		 * Check if window has scrolled to bottom of element.
3243		 */
3244		scrolledToBottom: function( element ) {
3245			return this.winScrollTop() >= element.offsetTop + element.offsetHeight - window.innerHeight;
3246		},
3247
3248		/**
3249		 * Remove class prefix.
3250		 */
3251		removeClassPrefix: function( elements, regex, prefix ) {
3252			elements.forEach( ( element ) => {
3253				const classes = element.classList;
3254				for ( let j=0; j < classes.length; j++ ) {
3255					if ( regex.test( classes[j] ) ) {
3256						const newclass = classes[j].replace( prefix, '' );
3257						element.classList.replace( classes[j], newclass );
3258					}
3259				}
3260			} );
3261		},
3262
3263		/**
3264		 * Creates accordion menu.
3265		 */
3266		menuAccordion: function( menu ) {
3267			const self = this;
3268
3269			if ( ! menu ) {
3270				return;
3271			}
3272
3273			let isAnimating = false;
3274			const isMobileMenu = ( menu.classList.contains( 'mobile-toggle-nav' ) || menu.classList.contains( 'sidr-class-dropdown-menu' ) );
3275
3276			// Add toggle buttons.
3277			menu.querySelectorAll( '.menu-item-has-children, .sidr-class-menu-item-has-children' ).forEach( ( menuItem ) => {
3278				const link = menuItem.querySelector( 'a' );
3279				if ( ! link ) {
3280					return;
3281				}
3282
3283				const wrap = link.closest( 'span' );
3284				const toggleBtn = document.createElement( 'button' );
3285
3286				if ( isMobileMenu ) {
3287					toggleBtn.className = 'wpex-open-submenu wpex-unstyled-button wpex-flex wpex-items-center wpex-justify-end wpex-absolute wpex-top-0 wpex-right-0 wpex-h-100 wpex-cursor-pointer wpex-opacity-80 wpex-overflow-hidden';
3288				} else {
3289					toggleBtn.className = 'wpex-open-submenu wpex-unstyled-button wpex-block';
3290				}
3291
3292				toggleBtn.setAttribute( 'aria-haspopup', 'true' );
3293				toggleBtn.setAttribute( 'aria-expanded', 'false' );
3294				toggleBtn.setAttribute( 'role', 'button' );
3295				toggleBtn.setAttribute( 'aria-label', l10n.i18n.openSubmenu.replace( '%s', link.textContent.trim() ) );
3296
3297				if ( isMobileMenu ) {
3298					if ( l10n.mobileMenuOpenSubmenuIcon ) {
3299						const temp = document.createElement( 'div' );
3300						temp.innerHTML = l10n.mobileMenuOpenSubmenuIcon;
3301						toggleBtn.appendChild(temp.firstChild);
3302					}
3303					if ( l10n.mobileMenuOpenSubmenuIconActive ) {
3304						const temp = document.createElement( 'div' );
3305						temp.innerHTML = l10n.mobileMenuOpenSubmenuIconActive;
3306						toggleBtn.appendChild(temp.firstChild);
3307					}
3308				} else {
3309					const toggleBtnIcon = document.createElement( 'span' );
3310					toggleBtnIcon.className = 'ticon ticon-angle-down';
3311					toggleBtnIcon.setAttribute( 'aria-hidden', 'true' );
3312					toggleBtn.appendChild( toggleBtnIcon );
3313				}
3314				if ( wrap ) {
3315					wrap.appendChild( toggleBtn );
3316				} else {
3317					link.appendChild( toggleBtn );
3318				}
3319			} );
3320
3321			const closeSubmenu = ( submenu ) => {
3322				const li = submenu.closest( 'li.active' );
3323				li.classList.remove( 'active' );
3324				const link = li.querySelector( 'a' );
3325				const toggle = li.querySelector( '.wpex-open-submenu' );
3326				toggle.setAttribute( 'aria-expanded', 'false' );
3327				toggle.setAttribute( 'aria-label', l10n.i18n.openSubmenu.replace( '%s', link.textContent.trim() ) );
3328				isAnimating = true;
3329				const toggleIcon = toggle.querySelector( '.wpex-open-submenu__icon' );
3330				if ( toggleIcon ) {
3331					const toggleIconOpen = toggle.querySelector( '.wpex-open-submenu__icon--open' );
3332					if ( toggleIconOpen ) {
3333						toggleIcon.classList.remove( 'wpex-hidden' );
3334						toggleIconOpen.classList.add( 'wpex-hidden' );
3335					} else {
3336						toggleIcon.classList.remove( 'wpex-rotate-180' );
3337					}
3338				}
3339				self.slideUp( submenu, null, function() {
3340					isAnimating = false;
3341				} );
3342			};
3343
3344			const onDocClick = ( event ) => {
3345				const button = event.target.closest( '.wpex-open-submenu' );
3346				if ( ! button || ! menu.contains( button ) ) {
3347					return;
3348				}
3349
3350				const li = button.closest( 'li' );
3351
3352				if ( ! li ) {
3353					return;
3354				}
3355
3356				const ul = li.querySelector( 'ul' );
3357
3358				if ( ! ul ) {
3359					return; // no dropdowns.
3360				}
3361
3362				const link = li.querySelector( 'a' );
3363
3364				if ( ! link ) {
3365					return; // no link.
3366				}
3367
3368				event.preventDefault();
3369				event.stopPropagation(); // !!important!! needed since button is inside link.
3370
3371				if ( isAnimating ) {
3372					return; // prevent click spam.
3373				}
3374
3375				// Closing.
3376				if ( li.classList.contains( 'active' ) ) {
3377
3378					// Close child submenus.
3379					li.querySelectorAll( 'li.active > ul' ).forEach( ( submenu ) => {
3380						closeSubmenu( submenu );
3381					} );
3382
3383					// Close self.
3384					li.classList.remove( 'active' );
3385					button.setAttribute( 'aria-expanded', 'false' );
3386					button.setAttribute( 'aria-label', l10n.i18n.openSubmenu.replace( '%s', link.textContent.trim() ) );
3387					isAnimating = true;
3388					self.slideUp( ul, null, function() {
3389						isAnimating = false;
3390					} );
3391				}
3392
3393				// Opening.
3394				else {
3395					button.setAttribute( 'aria-expanded', 'true' );
3396					button.setAttribute( 'aria-label', l10n.i18n.closeSubmenu.replace( '%s', link.textContent.trim() ) );
3397
3398					const toggleIcon = button.querySelector( '.wpex-open-submenu__icon' );
3399					if ( toggleIcon ) {
3400						const toggleIconOpen = button.querySelector( '.wpex-open-submenu__icon--open' );
3401						if ( toggleIconOpen ) {
3402							toggleIcon.classList.add( 'wpex-hidden' );
3403							toggleIconOpen.classList.remove( 'wpex-hidden' );
3404						} else {
3405							toggleIcon.classList.add( 'wpex-rotate-180' );
3406						}
3407					}
3408
3409					// Close all open submenus that arent parents of this submenu.
3410					menu.querySelectorAll( 'li.active > ul' ).forEach( ( submenu ) => {
3411						if ( ! submenu.contains( ul ) ) {
3412							closeSubmenu( submenu );
3413						}
3414					} );
3415
3416					// Open self.
3417					isAnimating = true;
3418					self.slideDown( ul, null, function() {
3419						isAnimating = false;
3420					} );
3421					li.classList.add( 'active' );
3422				}
3423			};
3424
3425			// Add events - @todo instead of adding event to document we should add it to menu?
3426			document.addEventListener( 'click', onDocClick );
3427		},
3428
3429		/**
3430		 * Set hamburger icon state.
3431		 */
3432		setHamburgerIconState: function( el ) {
3433			if ( ! el ) {
3434				const toggleBtn = document.querySelector( 'a.mobile-menu-toggle, button.mobile-menu-toggle, li.mobile-menu-toggle > a' );
3435				el = toggleBtn ? toggleBtn.querySelector( '.wpex-hamburger-icon' ) : null;
3436			}
3437			if ( ! el ) {
3438				return;
3439			}
3440			if ( el.classList.contains( 'wpex-hamburger-icon--active' ) ) {
3441				el.classList.remove( 'wpex-hamburger-icon--active' );
3442				el.classList.add( 'wpex-hamburger-icon--inactive' );
3443			} else if ( el.classList.contains( 'wpex-hamburger-icon--inactive' ) ) {
3444				el.classList.remove( 'wpex-hamburger-icon--inactive' );
3445				el.classList.add( 'wpex-hamburger-icon--active' );
3446			}
3447		},
3448
3449		/**
3450		 * Returns scrollbar width.
3451		 */
3452		getScrollBarWidth: function() {
3453			return window.innerWidth - document.body.clientWidth;
3454		},
3455
3456		/**
3457		 * Returns scrollbar width.
3458		 */
3459		getElTransitionDuration: function( el ) {
3460			let duration = window.getComputedStyle( el ).transitionDuration;
3461
3462			if ( ! duration || '0s' !== duration ) {
3463				duration = parseFloat( duration ) * ( duration.indexOf( 'ms' ) >- 1 ? 1 : 1000 );
3464			}
3465
3466			return parseFloat( duration ) || 0;
3467		},
3468
3469		/**
3470		 * Set HTML no scroll.
3471		 */
3472		htmlNoScroll: function( action, duration ) {
3473			const html = document.querySelector( 'html' );
3474			switch ( action ) {
3475				case 'add':
3476					const scrollbarWidth = this.getScrollBarWidth();
3477					html.style.setProperty( '--html-noscroll-padding', `${scrollbarWidth}px` );
3478					html.classList.add( 'html-noscroll' );
3479				break;
3480				case 'remove':
3481					html.classList.remove( 'html-noscroll' );
3482					html.style.setProperty( '--html-noscroll-padding', '' );
3483					break;
3484			}
3485		},
3486
3487		/**
3488		 * Debounce function.
3489		 */
3490		debounce: function(callback, wait = 10) {
3491			let timeoutId = null;
3492			return (...args) => {
3493				window.clearTimeout(timeoutId);
3494				timeoutId = window.setTimeout(() => {
3495				  callback.apply(null, args);
3496				}, wait);
3497			};
3498		},
3499
3500		/**
3501		 * Returns scrollTo behavior.
3502		 */
3503		getScrollToBehavior: function() {
3504			return l10n.scrollToBehavior || 'smooth';
3505		},
3506
3507	};
3508
3509	// Start things up
3510	wpex.init();
3511
3512} ) ( wpex_theme_params );

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.