PageSourceSearch

https://www.qualite.fr/blog/wp-content/themes/twentytwenty/assets/js/index.js?ver=1.0

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

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.