PageSourceSearch

https://mrchicken.pl/wp-content/cache/wpfc-minified/ma4obble/hpv0c.js

js mrchicken.pl collected 2026-10-02 02:05:24 UTC 25,103 bytes, 801 lines download raw bytes

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

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.