PageSourceSearch

https://www.lightstalking.com/wp-content/themes/lightstalking/assets/js/index.js?ver=1.5.11

js lightstalking.com collected 2026-10-01 10:32:20 UTC 25,603 bytes, 810 lines download raw bytes

1/*	-----------------------------------------------------------------------------------------------
2	Namespace
3--------------------------------------------------------------------------------------------------- */
4
5var lightstalking = lightstalking || {};
6
7// Set a default value for scrolled.
8lightstalking.scrolled = 0;
9
10// polyfill closest
11// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
12if ( ! Element.prototype.closest ) {
13	Element.prototype.closest = function( s ) {
14		var el = this;
15
16		do {
17			if ( el.matches( s ) ) {
18				return el;
19			}
20
21			el = el.parentElement || el.parentNode;
22		} while ( el !== null && el.nodeType === 1 );
23
24		return null;
25	};
26}
27
28// polyfill forEach
29// https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
30if ( window.NodeList && ! NodeList.prototype.forEach ) {
31	NodeList.prototype.forEach = function( callback, thisArg ) {
32		var i;
33		var len = this.length;
34
35		thisArg = thisArg || window;
36
37		for ( i = 0; i < len; i++ ) {
38			callback.call( thisArg, this[ i ], i, this );
39		}
40	};
41}
42
43// event "polyfill"
44lightstalking.createEvent = function( eventName ) {
45	var event;
46	if ( typeof window.Event === 'function' ) {
47		event = new Event( eventName );
48	} else {
49		event = document.createEvent( 'Event' );
50		event.initEvent( eventName, true, false );
51	}
52	return event;
53};
54
55// matches "polyfill"
56// https://developer.mozilla.org/es/docs/Web/API/Element/matches
57if ( ! Element.prototype.matches ) {
58	Element.prototype.matches =
59		Element.prototype.matchesSelector ||
60		Element.prototype.mozMatchesSelector ||
61		Element.prototype.msMatchesSelector ||
62		Element.prototype.oMatchesSelector ||
63		Element.prototype.webkitMatchesSelector ||
64		function( s ) {
65			var matches = ( this.document || this.ownerDocument ).querySelectorAll( s ),
66				i = matches.length;
67			while ( --i >= 0 && matches.item( i ) !== this ) {}
68			return i > -1;
69		};
70}
71
72// Add a class to the body for when touch is enabled for browsers that don't support media queries
73// for interaction media features. Adapted from <https://codepen.io/Ferie/pen/vQOMmO>
74lightstalking.touchEnabled = {
75	init: function() {
76		var matchMedia = function() {
77			// Include the 'heartz' as a way to have a non matching MQ to help terminate the join. See <https://git.io/vznFH>.
78			var prefixes = [ '-webkit-', '-moz-', '-o-', '-ms-' ];
79			var query = [ '(', prefixes.join( 'touch-enabled),(' ), 'heartz', ')' ].join( '' );
80			return window.matchMedia && window.matchMedia( query ).matches;
81		};
82		if ( ( 'ontouchstart' in window ) || ( window.DocumentTouch && document instanceof window.DocumentTouch ) || matchMedia() ) {
83			document.body.classList.add( 'touch-enabled' );
84		}
85	}
86}; // lightstalking.touchEnabled
87
88/*	-----------------------------------------------------------------------------------------------
89	Cover Modals
90--------------------------------------------------------------------------------------------------- */
91
92lightstalking.coverModals = {
93
94	init: function() {
95		if ( document.querySelector( '.cover-modal' ) ) {
96			// Handle cover modals when they're toggled
97			this.onToggle();
98
99			// When toggled, untoggle if visitor clicks on the wrapping element of the modal
100			this.outsideUntoggle();
101
102			// Close on escape key press
103			this.closeOnEscape();
104
105			// Hide and show modals before and after their animations have played out
106			this.hideAndShowModals();
107		}
108	},
109
110	// Handle cover modals when they're toggled
111	onToggle: function() {
112		document.querySelectorAll( '.cover-modal' ).forEach( function( element ) {
113			element.addEventListener( 'toggled', function( event ) {
114				var modal = event.target,
115					body = document.body;
116
117				if ( modal.classList.contains( 'active' ) ) {
118					body.classList.add( 'showing-modal' );
119				} else {
120					body.classList.remove( 'showing-modal' );
121					body.classList.add( 'hiding-modal' );
122
123					// Remove the hiding class after a delay, when animations have been run
124					setTimeout( function() {
125						body.classList.remove( 'hiding-modal' );
126					}, 500 );
127				}
128			} );
129		} );
130	},
131
132	// Close modal on outside click
133	outsideUntoggle: function() {
134		document.addEventListener( 'click', function( event ) {
135			var target = event.target;
136			var modal = document.querySelector( '.cover-modal.active' );
137
138			if ( target === modal ) {
139				this.untoggleModal( target );
140			}
141		}.bind( this ) );
142	},
143
144	// Close modal on escape key press
145	closeOnEscape: function() {
146		document.addEventListener( 'keydown', function( event ) {
147			if ( event.keyCode === 27 ) {
148				event.preventDefault();
149				document.querySelectorAll( '.cover-modal.active' ).forEach( function( element ) {
150					this.untoggleModal( element );
151				}.bind( this ) );
152			}
153		}.bind( this ) );
154	},
155
156	// Hide and show modals before and after their animations have played out
157	hideAndShowModals: function() {
158		var _doc = document,
159			_win = window,
160			modals = _doc.querySelectorAll( '.cover-modal' ),
161			htmlStyle = _doc.documentElement.style,
162			adminBar = _doc.querySelector( '#wpadminbar' );
163
164		function getAdminBarHeight( negativeValue ) {
165			var height,
166				currentScroll = _win.pageYOffset;
167
168			if ( adminBar ) {
169				height = currentScroll + adminBar.getBoundingClientRect().height;
170
171				return negativeValue ? -height : height;
172			}
173
174			return currentScroll === 0 ? 0 : -currentScroll;
175		}
176
177		function htmlStyles() {
178			var overflow = _win.innerHeight > _doc.documentElement.getBoundingClientRect().height;
179
180			return {
181				'overflow-y': overflow ? 'hidden' : 'scroll',
182				position: 'fixed',
183				width: '100%',
184				top: getAdminBarHeight( true ) + 'px',
185				left: 0
186			};
187		}
188
189		// Show the modal
190		modals.forEach( function( modal ) {
191			modal.addEventListener( 'toggle-target-before-inactive', function( event ) {
192				var styles = htmlStyles(),
193					offsetY = _win.pageYOffset,
194					paddingTop = ( Math.abs( getAdminBarHeight() ) - offsetY ) + 'px',
195					mQuery = _win.matchMedia( '(max-width: 600px)' );
196
197				if ( event.target !== modal ) {
198					return;
199				}
200
201				Object.keys( styles ).forEach( function( styleKey ) {
202					htmlStyle.setProperty( styleKey, styles[ styleKey ] );
203				} );
204
205				_win.lightstalking.scrolled = parseInt( styles.top, 10 );
206
207				if ( adminBar ) {
208					_doc.body.style.setProperty( 'padding-top', paddingTop );
209
210					if ( mQuery.matches ) {
211						if ( offsetY >= getAdminBarHeight() ) {
212							modal.style.setProperty( 'top', 0 );
213						} else {
214							modal.style.setProperty( 'top', ( getAdminBarHeight() - offsetY ) + 'px' );
215						}
216					}
217				}
218
219				modal.classList.add( 'show-modal' );
220			} );
221
222			// Hide the modal after a delay, so animations have time to play out
223			modal.addEventListener( 'toggle-target-after-inactive', function( event ) {
224				if ( event.target !== modal ) {
225					return;
226				}
227
228				setTimeout( function() {
229					var clickedEl = lightstalking.toggles.clickedEl;
230
231					modal.classList.remove( 'show-modal' );
232
233					Object.keys( htmlStyles() ).forEach( function( styleKey ) {
234						htmlStyle.removeProperty( styleKey );
235					} );
236
237					if ( adminBar ) {
238						_doc.body.style.removeProperty( 'padding-top' );
239						modal.style.removeProperty( 'top' );
240					}
241
242					if ( clickedEl !== false ) {
243						clickedEl.focus();
244						clickedEl = false;
245					}
246
247					_win.scrollTo( 0, Math.abs( _win.lightstalking.scrolled + getAdminBarHeight() ) );
248
249					_win.lightstalking.scrolled = 0;
250				}, 500 );
251			} );
252		} );
253	},
254
255	// Untoggle a modal
256	untoggleModal: function( modal ) {
257		var modalTargetClass,
258			modalToggle = false;
259
260		// If the modal has specified the string (ID or class) used by toggles to target it, untoggle the toggles with that target string
261		// The modal-target-string must match the string toggles use to target the modal
262		if ( modal.dataset.modalTargetString ) {
263			modalTargetClass = modal.dataset.modalTargetString;
264
265			modalToggle = document.querySelector( '*[data-toggle-target="' + modalTargetClass + '"]' );
266		}
267
268		// If a modal toggle exists, trigger it so all of the toggle options are included
269		if ( modalToggle ) {
270			modalToggle.click();
271
272			// If one doesn't exist, just hide the modal
273		} else {
274			modal.classList.remove( 'active' );
275		}
276	}
277
278}; // lightstalking.coverModals
279
280/*	-----------------------------------------------------------------------------------------------
281	Intrinsic Ratio Embeds
282--------------------------------------------------------------------------------------------------- */
283
284lightstalking.intrinsicRatioVideos = {
285
286	init: function() {
287		this.makeFit();
288
289		window.addEventListener( 'resize', function() {
290			this.makeFit();
291		}.bind( this ) );
292	},
293
294	makeFit: function() {
295		document.querySelectorAll( 'iframe, object, video' ).forEach( function( video ) {
296			var ratio, iTargetWidth,
297				container = video.parentNode;
298
299			// Skip videos we want to ignore
300			if ( video.classList.contains( 'intrinsic-ignore' ) || video.parentNode.classList.contains( 'intrinsic-ignore' ) ) {
301				return true;
302			}
303
304			if ( ! video.dataset.origwidth ) {
305				// Get the video element proportions
306				video.setAttribute( 'data-origwidth', video.width );
307				video.setAttribute( 'data-origheight', video.height );
308			}
309
310			iTargetWidth = container.offsetWidth;
311
312			// Get ratio from proportions
313			ratio = iTargetWidth / video.dataset.origwidth;
314
315			// Scale based on ratio, thus retaining proportions
316			video.style.width = iTargetWidth + 'px';
317			video.style.height = ( video.dataset.origheight * ratio ) + 'px';
318		} );
319	}
320
321}; // lightstalking.instrinsicRatioVideos
322
323/*	-----------------------------------------------------------------------------------------------
324	Modal Menu
325--------------------------------------------------------------------------------------------------- */
326lightstalking.modalMenu = {
327
328	init: function() {
329		// If the current menu item is in a sub level, expand all the levels higher up on load
330		this.expandLevel();
331		this.keepFocusInModal();
332	},
333
334	expandLevel: function() {
335		var modalMenus = document.querySelectorAll( '.modal-menu' );
336
337		modalMenus.forEach( function( modalMenu ) {
338			var activeMenuItem = modalMenu.querySelector( '.current-menu-item' );
339
340			if ( activeMenuItem ) {
341				lightstalkingFindParents( activeMenuItem, 'li' ).forEach( function( element ) {
342					var subMenuToggle = element.querySelector( '.sub-menu-toggle' );
343					if ( subMenuToggle ) {
344						lightstalking.toggles.performToggle( subMenuToggle, true );
345					}
346				} );
347			}
348		} );
349	},
350
351	keepFocusInModal: function() {
352		var _doc = document;
353
354		_doc.addEventListener( 'keydown', function( event ) {
355			var toggleTarget, modal, selectors, elements, menuType, bottomMenu, activeEl, lastEl, firstEl, tabKey, shiftKey,
356				clickedEl = lightstalking.toggles.clickedEl;
357
358			if ( clickedEl && _doc.body.classList.contains( 'showing-modal' ) ) {
359				toggleTarget = clickedEl.dataset.toggleTarget;
360				selectors = 'input, a, button';
361				modal = _doc.querySelector( toggleTarget );
362
363				elements = modal.querySelectorAll( selectors );
364				elements = Array.prototype.slice.call( elements );
365
366				if ( '.menu-modal' === toggleTarget ) {
367					menuType = window.matchMedia( '(min-width: 1000px)' ).matches;
368					menuType = menuType ? '.expanded-menu' : '.mobile-menu';
369
370					elements = elements.filter( function( element ) {
371						return null !== element.closest( menuType ) && null !== element.offsetParent;
372					} );
373
374					elements.unshift( _doc.querySelector( '.close-nav-toggle' ) );
375
376					bottomMenu = _doc.querySelector( '.menu-bottom > nav' );
377
378					if ( bottomMenu ) {
379						bottomMenu.querySelectorAll( selectors ).forEach( function( element ) {
380							elements.push( element );
381						} );
382					}
383				}
384
385				lastEl = elements[ elements.length - 1 ];
386				firstEl = elements[0];
387				activeEl = _doc.activeElement;
388				tabKey = event.keyCode === 9;
389				shiftKey = event.shiftKey;
390
391				if ( ! shiftKey && tabKey && lastEl === activeEl ) {
392					event.preventDefault();
393					firstEl.focus();
394				}
395
396				if ( shiftKey && tabKey && firstEl === activeEl ) {
397					event.preventDefault();
398					lastEl.focus();
399				}
400			}
401		} );
402	}
403}; // lightstalking.modalMenu
404
405/*	-----------------------------------------------------------------------------------------------
406	Primary Menu
407--------------------------------------------------------------------------------------------------- */
408
409lightstalking.primaryMenu = {
410
411	init: function() {
412		this.focusMenuWithChildren();
413	},
414
415	// The focusMenuWithChildren() function implements Keyboard Navigation in the Primary Menu
416	// by adding the '.focus' class to all 'li.menu-item-has-children' when the focus is on the 'a' element.
417	focusMenuWithChildren: function() {
418		// Get all the link elements within the primary menu.
419		var links, i, len,
420			menu = document.querySelector( '.primary-menu-wrapper' );
421
422		if ( ! menu ) {
423			return false;
424		}
425
426		links = menu.getElementsByTagName( 'a' );
427
428		// Each time a menu link is focused or blurred, toggle focus.
429		for ( i = 0, len = links.length; i < len; i++ ) {
430			links[i].addEventListener( 'focus', toggleFocus, true );
431			links[i].addEventListener( 'blur', toggleFocus, true );
432		}
433
434		//Sets or removes the .focus class on an element.
435		function toggleFocus() {
436			var self = this;
437
438			// Move up through the ancestors of the current link until we hit .primary-menu.
439			while ( -1 === self.className.indexOf( 'primary-menu' ) ) {
440				// On li elements toggle the class .focus.
441				if ( 'li' === self.tagName.toLowerCase() ) {
442					if ( -1 !== self.className.indexOf( 'focus' ) ) {
443						self.className = self.className.replace( ' focus', '' );
444					} else {
445						self.className += ' focus';
446					}
447				}
448				self = self.parentElement;
449			}
450		}
451	}
452}; // lightstalking.primaryMenu
453
454/*	-----------------------------------------------------------------------------------------------
455	Toggles
456--------------------------------------------------------------------------------------------------- */
457
458lightstalking.toggles = {
459
460	clickedEl: false,
461
462	init: function() {
463		// Do the toggle
464		this.toggle();
465
466		// Check for toggle/untoggle on resize
467		this.resizeCheck();
468
469		// Check for untoggle on escape key press
470		this.untoggleOnEscapeKeyPress();
471	},
472
473	performToggle: function( element, instantly ) {
474		var target, timeOutTime, classToToggle,
475			self = this,
476			_doc = document,
477			// Get our targets
478			toggle = element,
479			targetString = toggle.dataset.toggleTarget,
480			activeClass = 'active';
481
482		// Elements to focus after modals are closed
483		if ( ! _doc.querySelectorAll( '.show-modal' ).length ) {
484			self.clickedEl = _doc.activeElement;
485		}
486
487		if ( targetString === 'next' ) {
488			target = toggle.nextSibling;
489		} else {
490			target = _doc.querySelector( targetString );
491		}
492
493		// Trigger events on the toggle targets before they are toggled
494		if ( target.classList.contains( activeClass ) ) {
495			target.dispatchEvent( lightstalking.createEvent( 'toggle-target-before-active' ) );
496		} else {
497			target.dispatchEvent( lightstalking.createEvent( 'toggle-target-before-inactive' ) );
498		}
499
500		// Get the class to toggle, if specified
501		classToToggle = toggle.dataset.classToToggle ? toggle.dataset.classToToggle : activeClass;
502
503		// For cover modals, set a short timeout duration so the class animations have time to play out
504		timeOutTime = 0;
505
506		if ( target.classList.contains( 'cover-modal' ) ) {
507			timeOutTime = 10;
508		}
509
510		setTimeout( function() {
511			var focusElement,
512				subMenued = target.classList.contains( 'sub-menu' ),
513				newTarget = subMenued ? toggle.closest( '.menu-item' ).querySelector( '.sub-menu' ) : target,
514				duration = toggle.dataset.toggleDuration;
515
516			// Toggle the target of the clicked toggle
517			if ( toggle.dataset.toggleType === 'slidetoggle' && ! instantly && duration !== '0' ) {
518				lightstalkingMenuToggle( newTarget, duration );
519			} else {
520				newTarget.classList.toggle( classToToggle );
521			}
522
523			// If the toggle target is 'next', only give the clicked toggle the active class
524			if ( targetString === 'next' ) {
525				toggle.classList.toggle( activeClass );
526			} else if ( target.classList.contains( 'sub-menu' ) ) {
527				toggle.classList.toggle( activeClass );
528			} else {
529				// If not, toggle all toggles with this toggle target
530				_doc.querySelector( '*[data-toggle-target="' + targetString + '"]' ).classList.toggle( activeClass );
531			}
532
533			// Toggle aria-expanded on the toggle
534			lightstalkingToggleAttribute( toggle, 'aria-expanded', 'true', 'false' );
535
536			if ( self.clickedEl && -1 !== toggle.getAttribute( 'class' ).indexOf( 'close-' ) ) {
537				lightstalkingToggleAttribute( self.clickedEl, 'aria-expanded', 'true', 'false' );
538			}
539
540			// Toggle body class
541			if ( toggle.dataset.toggleBodyClass ) {
542				_doc.body.classList.toggle( toggle.dataset.toggleBodyClass );
543			}
544
545			// Check whether to set focus
546			if ( toggle.dataset.setFocus ) {
547				focusElement = _doc.querySelector( toggle.dataset.setFocus );
548
549				if ( focusElement ) {
550					if ( target.classList.contains( activeClass ) ) {
551						focusElement.focus();
552					} else {
553						focusElement.blur();
554					}
555				}
556			}
557
558			// Trigger the toggled event on the toggle target
559			target.dispatchEvent( lightstalking.createEvent( 'toggled' ) );
560
561			// Trigger events on the toggle targets after they are toggled
562			if ( target.classList.contains( activeClass ) ) {
563				target.dispatchEvent( lightstalking.createEvent( 'toggle-target-after-active' ) );
564			} else {
565				target.dispatchEvent( lightstalking.createEvent( 'toggle-target-after-inactive' ) );
566			}
567		}, timeOutTime );
568	},
569
570	// Do the toggle
571	toggle: function() {
572		var self = this;
573
574		document.querySelectorAll( '*[data-toggle-target]' ).forEach( function( element ) {
575			element.addEventListener( 'click', function( event ) {
576				event.preventDefault();
577				self.performToggle( element );
578			} );
579		} );
580	},
581
582	// Check for toggle/untoggle on screen resize
583	resizeCheck: function() {
584		if ( document.querySelectorAll( '*[data-untoggle-above], *[data-untoggle-below], *[data-toggle-above], *[data-toggle-below]' ).length ) {
585			window.addEventListener( 'resize', function() {
586				var winWidth = window.innerWidth,
587					toggles = document.querySelectorAll( '.toggle' );
588
589				toggles.forEach( function( toggle ) {
590					var unToggleAbove = toggle.dataset.untoggleAbove,
591						unToggleBelow = toggle.dataset.untoggleBelow,
592						toggleAbove = toggle.dataset.toggleAbove,
593						toggleBelow = toggle.dataset.toggleBelow;
594
595					// If no width comparison is set, continue
596					if ( ! unToggleAbove && ! unToggleBelow && ! toggleAbove && ! toggleBelow ) {
597						return;
598					}
599
600					// If the toggle width comparison is true, toggle the toggle
601					if (
602						( ( ( unToggleAbove && winWidth > unToggleAbove ) ||
603							( unToggleBelow && winWidth < unToggleBelow ) ) &&
604							toggle.classList.contains( 'active' ) ) ||
605						( ( ( toggleAbove && winWidth > toggleAbove ) ||
606							( toggleBelow && winWidth < toggleBelow ) ) &&
607							! toggle.classList.contains( 'active' ) )
608					) {
609						toggle.click();
610					}
611				} );
612			} );
613		}
614	},
615
616	// Close toggle on escape key press
617	untoggleOnEscapeKeyPress: function() {
618		document.addEventListener( 'keyup', function( event ) {
619			if ( event.key === 'Escape' ) {
620				document.querySelectorAll( '*[data-untoggle-on-escape].active' ).forEach( function( element ) {
621					if ( element.classList.contains( 'active' ) ) {
622						element.click();
623					}
624				} );
625			}
626		} );
627	}
628
629}; // lightstalking.toggles
630
631/**
632 * Is the DOM ready
633 *
634 * this implementation is coming from https://gomakethings.com/a-native-javascript-equivalent-of-jquerys-ready-method/
635 *
636 * @param {Function} fn Callback function to run.
637 */
638function lightstalkingDomReady( fn ) {
639	if ( typeof fn !== 'function' ) {
640		return;
641	}
642
643	if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
644		return fn();
645	}
646
647	document.addEventListener( 'DOMContentLoaded', fn, false );
648}
649
650lightstalkingDomReady( function() {
651	lightstalking.toggles.init();	// Handle toggles
652	lightstalking.coverModals.init();	// Handle cover modals
653	lightstalking.intrinsicRatioVideos.init();	// Retain aspect ratio of videos on window resize
654	lightstalking.modalMenu.init();	// Modal Menu
655	lightstalking.primaryMenu.init();	// Primary Menu
656	lightstalking.touchEnabled.init();   // Add class to body if device is touch-enabled
657} );
658
659/*	-----------------------------------------------------------------------------------------------
660	Helper functions
661--------------------------------------------------------------------------------------------------- */
662
663/* Toggle an attribute ----------------------- */
664
665function lightstalkingToggleAttribute( element, attribute, trueVal, falseVal ) {
666	if ( trueVal === undefined ) {
667		trueVal = true;
668	}
669	if ( falseVal === undefined ) {
670		falseVal = false;
671	}
672	if ( element.getAttribute( attribute ) !== trueVal ) {
673		element.setAttribute( attribute, trueVal );
674	} else {
675		element.setAttribute( attribute, falseVal );
676	}
677}
678
679/**
680 * Toggle a menu item on or off.
681 *
682 * @param {HTMLElement} target
683 * @param {number} duration
684 */
685function lightstalkingMenuToggle( target, duration ) {
686	var initialParentHeight, finalParentHeight, menu, menuItems, transitionListener,
687		initialPositions = [],
688		finalPositions = [];
689
690	if ( ! target ) {
691		return;
692	}
693
694	menu = target.closest( '.menu-wrapper' );
695
696	// Step 1: look at the initial positions of every menu item.
697	menuItems = menu.querySelectorAll( '.menu-item' );
698
699	menuItems.forEach( function( menuItem, index ) {
700		initialPositions[ index ] = { x: menuItem.offsetLeft, y: menuItem.offsetTop };
701	} );
702	initialParentHeight = target.parentElement.offsetHeight;
703
704	target.classList.add( 'toggling-target' );
705
706	// Step 2: toggle target menu item and look at the final positions of every menu item.
707	target.classList.toggle( 'active' );
708
709	menuItems.forEach( function( menuItem, index ) {
710		finalPositions[ index ] = { x: menuItem.offsetLeft, y: menuItem.offsetTop };
711	} );
712	finalParentHeight = target.parentElement.offsetHeight;
713
714	// Step 3: close target menu item again.
715	// The whole process happens without giving the browser a chance to render, so it's invisible.
716	target.classList.toggle( 'active' );
717
718	// Step 4: prepare animation.
719	// Position all the items with absolute offsets, at the same starting position.
720	// Shouldn't result in any visual changes if done right.
721	menu.classList.add( 'is-toggling' );
722	target.classList.toggle( 'active' );
723	menuItems.forEach( function( menuItem, index ) {
724		var initialPosition = initialPositions[ index ];
725		if ( initialPosition.y === 0 && menuItem.parentElement === target ) {
726			initialPosition.y = initialParentHeight;
727		}
728		menuItem.style.transform = 'translate(' + initialPosition.x + 'px, ' + initialPosition.y + 'px)';
729	} );
730
731	// The double rAF is unfortunately needed, since we're toggling CSS classes, and
732	// the only way to ensure layout completion here across browsers is to wait twice.
733	// This just delays the start of the animation by 2 frames and is thus not an issue.
734	requestAnimationFrame( function() {
735		requestAnimationFrame( function() {
736			// Step 5: start animation by moving everything to final position.
737			// All the layout work has already happened, while we were preparing for the animation.
738			// The animation now runs entirely in CSS, using cheap CSS properties (opacity and transform)
739			// that don't trigger the layout or paint stages.
740			menu.classList.add( 'is-animating' );
741			menuItems.forEach( function( menuItem, index ) {
742				var finalPosition = finalPositions[ index ];
743				if ( finalPosition.y === 0 && menuItem.parentElement === target ) {
744					finalPosition.y = finalParentHeight;
745				}
746				if ( duration !== undefined ) {
747					menuItem.style.transitionDuration = duration + 'ms';
748				}
749				menuItem.style.transform = 'translate(' + finalPosition.x + 'px, ' + finalPosition.y + 'px)';
750			} );
751			if ( duration !== undefined ) {
752				target.style.transitionDuration = duration + 'ms';
753			}
754		} );
755
756		// Step 6: finish toggling.
757		// Remove all transient classes when the animation ends.
758		transitionListener = function() {
759			menu.classList.remove( 'is-animating' );
760			menu.classList.remove( 'is-toggling' );
761			target.classList.remove( 'toggling-target' );
762			menuItems.forEach( function( menuItem ) {
763				menuItem.style.transform = '';
764				menuItem.style.transitionDuration = '';
765			} );
766			target.style.transitionDuration = '';
767			target.removeEventListener( 'transitionend', transitionListener );
768		};
769
770		target.addEventListener( 'transitionend', transitionListener );
771	} );
772}
773
774/**
775 * traverses the DOM up to find elements matching the query
776 *
777 * @param {HTMLElement} target
778 * @param {string} query
779 * @return {NodeList} parents matching query
780 */
781function lightstalkingFindParents( target, query ) {
782	var parents = [];
783
784	// recursively go up the DOM adding matches to the parents array
785	function traverse( item ) {
786		var parent = item.parentNode;
787		if ( parent instanceof HTMLElement ) {
788			if ( parent.matches( query ) ) {
789				parents.push( parent );
790			}
791			traverse( parent );
792		}
793	}
794
795	traverse( target );
796
797	return parents;
798}
799
800// CUSTOM SHOW MORE JAVASCRIPT - Melody Fassino
801let showMoreButton = document.getElementById('custom-home-more-categories');
802let customHomeCategoriesList = document.body.querySelector('.custom-home-categories-list');
803showMoreButton.onclick = function(){
804	if (customHomeCategoriesList.classList.contains('custom-home-categories-show-more-height')) {
805        showMoreButton.innerHTML = "Show Less";
806    } else {
807        showMoreButton.innerHTML = "Show More";
808	}
809	customHomeCategoriesList.classList.toggle('custom-home-categories-show-more-height');
810};

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.