PageSourceSearch

https://www.chesa.com/wp-content/themes/twentytwenty/assets/js/index.js?ver=1.0.0

js chesa.com collected 2026-09-24 09:28:46 UTC 27,950 bytes, 907 lines download raw bytes

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

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.