PageSourceSearch

https://onthisdateinphotography.com/_static/??-eJx9jkkOgzAQBD8U4yx…EFyENmtOvMnevUr+mWDdXEf7n5pmq5r2/paTx/gfH0/

js onthisdateinphotography.com collected 2026-10-02 07:55:56 UTC 66,015 bytes, 2,163 lines download raw bytes

1/* global wpcom_reblog */
2
3var jetpackLikesWidgetBatch = [];
4var jetpackLikesMasterReady = false;
5
6// Due to performance problems on pages with a large number of widget iframes that need to be loaded,
7// we are limiting the processing at any instant to unloaded widgets that are currently in viewport,
8// plus this constant that will allow processing of widgets above and bellow the current fold.
9// This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users.
10var jetpackLikesLookAhead = 2000; // pixels
11
12// Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view.
13var jetpackCommentLikesLoadedWidgets = [];
14
15var jetpackLikesDocReadyPromise = new Promise( resolve => {
16	if ( document.readyState !== 'loading' ) {
17		resolve();
18	} else {
19		window.addEventListener( 'DOMContentLoaded', () => resolve() );
20	}
21} );
22
23function JetpackLikesPostMessage( message, target ) {
24	if ( typeof message === 'string' ) {
25		try {
26			message = JSON.parse( message );
27		} catch ( e ) {
28			return;
29		}
30	}
31
32	if ( target && typeof target.postMessage === 'function' ) {
33		try {
34			target.postMessage(
35				JSON.stringify( {
36					type: 'likesMessage',
37					data: message,
38				} ),
39				'*'
40			);
41		} catch ( e ) {
42			return;
43		}
44	}
45}
46
47function JetpackLikesBatchHandler() {
48	const requests = [];
49	document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => {
50		if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) {
51			return;
52		}
53
54		if ( ! jetpackIsScrolledIntoView( widget ) ) {
55			return;
56		}
57
58		jetpackLikesWidgetBatch.push( widget.id );
59
60		var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/,
61			match = regex.exec( widget.id ),
62			info;
63
64		if ( ! match || match.length !== 5 ) {
65			return;
66		}
67
68		info = {
69			blog_id: match[ 2 ],
70			width: widget.width,
71		};
72
73		if ( 'post' === match[ 1 ] ) {
74			info.post_id = match[ 3 ];
75		} else if ( 'comment' === match[ 1 ] ) {
76			info.comment_id = match[ 3 ];
77		}
78
79		info.obj_id = match[ 4 ];
80
81		requests.push( info );
82	} );
83
84	if ( requests.length > 0 ) {
85		JetpackLikesPostMessage(
86			{ event: 'initialBatch', requests: requests },
87			window.frames[ 'likes-master' ]
88		);
89	}
90}
91
92function JetpackLikesMessageListener( event ) {
93	let message = event && event.data;
94	if ( typeof message === 'string' ) {
95		try {
96			message = JSON.parse( message );
97		} catch ( err ) {
98			return;
99		}
100	}
101
102	const type = message && message.type;
103	const data = message && message.data;
104
105	if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) {
106		return;
107	}
108
109	// We only allow messages from one origin
110	const allowedOrigin = 'https://widgets.wp.com';
111	if ( allowedOrigin !== event.origin ) {
112		return;
113	}
114
115	switch ( data.event ) {
116		case 'masterReady':
117			jetpackLikesDocReadyPromise.then( () => {
118				jetpackLikesMasterReady = true;
119
120				const stylesData = {
121					event: 'injectStyles',
122				};
123				const sdTextColor = document.querySelector( '.sd-text-color' );
124				const sdLinkColor = document.querySelector( '.sd-link-color' );
125				const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {};
126				const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {};
127
128				// enable reblogs if they are enabled for the page
129				if ( document.body.classList.contains( 'jetpack-reblog-enabled' ) ) {
130					JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] );
131				}
132
133				stylesData.textStyles = {
134					color: sdTextColorStyles[ 'color' ],
135					fontFamily: sdTextColorStyles[ 'font-family' ],
136					fontSize: sdTextColorStyles[ 'font-size' ],
137					direction: sdTextColorStyles[ 'direction' ],
138					fontWeight: sdTextColorStyles[ 'font-weight' ],
139					fontStyle: sdTextColorStyles[ 'font-style' ],
140					textDecoration: sdTextColorStyles[ 'text-decoration' ],
141				};
142
143				stylesData.linkStyles = {
144					color: sdLinkColorStyles[ 'color' ],
145					fontFamily: sdLinkColorStyles[ 'font-family' ],
146					fontSize: sdLinkColorStyles[ 'font-size' ],
147					textDecoration: sdLinkColorStyles[ 'text-decoration' ],
148					fontWeight: sdLinkColorStyles[ 'font-weight' ],
149					fontStyle: sdLinkColorStyles[ 'font-style' ],
150				};
151
152				JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] );
153
154				JetpackLikesBatchHandler();
155			} );
156
157			break;
158
159		// We're keeping this for planned future follow ups.
160		// @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
161		case 'showLikeWidget':
162			break;
163
164		// We're keeping this for planned future follow ups.
165		// @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
166		case 'showCommentLikeWidget':
167			break;
168
169		case 'killCommentLikes':
170			// If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders.
171			document
172				.querySelectorAll( '.jetpack-comment-likes-widget-wrapper' )
173				.forEach( wrapper => wrapper.remove() );
174			break;
175
176		case 'clickReblogFlair':
177			if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) {
178				wpcom_reblog.toggle_reblog_box_flair( data.obj_id, data.post_id );
179			}
180			break;
181
182		case 'hideOtherGravatars': {
183			hideLikersPopover();
184			break;
185		}
186
187		case 'showOtherGravatars': {
188			const container = document.querySelector( '#likes-other-gravatars' );
189
190			if ( ! container ) {
191				break;
192			}
193
194			const list = container.querySelector( 'ul' );
195
196			container.style.display = 'none';
197			list.innerHTML = '';
198
199			container
200				.querySelectorAll( '.likes-text span' )
201				.forEach( item => ( item.textContent = data.totalLikesLabel ) );
202
203			( data.likers || [] ).forEach( async ( liker, index ) => {
204				if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) {
205					// We only display gravatars with http or https schema
206					return;
207				}
208
209				const element = document.createElement( 'li' );
210				list.append( element );
211
212				element.innerHTML = `
213				<a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker">
214					<img src="${ encodeURI( liker.avatar_URL ) }"
215						alt=""
216						style="width: 28px; height: 28px;" />
217					<span></span>
218				</a>
219				`;
220
221
222				// Add some extra attributes through native methods, to ensure strings are sanitized.
223				element.classList.add( liker.css_class );
224				element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name );
225				element.querySelector( 'span' ).innerText = liker.name;
226
227				if ( index === data.likers.length - 1 ) {
228					element.addEventListener( 'keydown', ( e ) => {
229						if ( e.key === 'Tab' && ! e.shiftKey ) {
230							e.preventDefault();
231							hideLikersPopover();
232
233							JetpackLikesPostMessage(
234								{ event: 'focusLikesCount', parent: data.parent },
235								window.frames[ 'likes-master' ]
236							);
237						}
238					} );
239				}
240			} );
241
242			const positionPopup = function() {
243				const containerStyle = getComputedStyle(container);
244				const isRtl = containerStyle.direction === 'rtl';
245
246				const el = document.querySelector( `*[name='${ data.parent }']` );
247				const rect = el.getBoundingClientRect();
248				const win = el.ownerDocument.defaultView;
249
250				const offset = {
251					top: rect.top + win.pageYOffset,
252					left: rect.left + win.pageXOffset,
253				};
254
255				// don't display yet or we get skewed window.innerWidth later
256				container.style.display = 'none';
257
258				let containerLeft = 0;
259				container.style.top = offset.top + data.position.top - 1 + 'px';
260
261				if ( isRtl ) {
262					const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0;
263					// 24px is the width of the avatar + 4px is the padding between avatars
264					containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4;
265					container.style.transform = 'translateX(-100%)';
266				} else {
267					containerLeft = offset.left + data.position.left;
268				}
269				container.style.left = containerLeft + 'px';
270
271				// Container width - padding
272				const initContainerWidth = data.width - 20;
273				const rowLength = Math.floor( initContainerWidth / 37 );
274				// # of rows + (avatar + avatar padding) + text above + container padding
275				let height = Math.ceil( data.likers.length / rowLength ) * 37 + 17 + 22;
276				if ( height > 204 ) {
277					height = 204;
278				}
279
280				// If the popup is overflows viewport width, we should show it on the next line
281				// Push it offscreen to calculated rendered width
282				const windowWidth = win.innerWidth;
283				container.style.left = '-9999px';
284				container.style.display = 'block';
285
286				// If the popup exceeds the viewport width,
287				// flip the position of the popup.
288				const containerWidth = container.offsetWidth;
289				const containerRight = containerLeft + containerWidth;
290
291				if ( containerRight > windowWidth && ! isRtl) {
292					containerLeft = rect.left + rect.width - containerWidth;
293				} else if ( containerLeft - containerWidth < 0 && isRtl ) {
294					container.style.transform = 'none';
295					containerLeft = rect.left;
296				}
297				// Set the container left
298				container.style.left = containerLeft + 'px';
299
300				container.setAttribute( 'aria-hidden', 'false' );
301			}
302
303			positionPopup();
304			container.focus();
305
306			const debounce = function( func, wait ) {
307				var timeout;
308				return function() {
309					var context = this;
310					var args = arguments;
311					clearTimeout( timeout );
312					timeout = setTimeout( function() {
313						func.apply( context, args );
314					}, wait );
315				};
316			};
317
318			const debouncedPositionPopup = debounce( positionPopup, 100 );
319
320			// Keep a reference of this function in the element itself
321			// so that we can destroy it later
322			container.__resizeHandler = debouncedPositionPopup;
323
324			// When window is resized, resize the popup.
325			window.addEventListener( "resize", debouncedPositionPopup );
326		}
327	}
328}
329
330window.addEventListener( 'message', JetpackLikesMessageListener );
331
332function hideLikersPopover() {
333	const container = document.querySelector( '#likes-other-gravatars' );
334
335	if ( container ) {
336		container.style.display = 'none';
337		container.setAttribute( 'aria-hidden', 'true' );
338
339		// Remove the resize event listener and cleanup.
340		const resizeHandler = container.__resizeHandler;
341		if ( resizeHandler ) {
342			window.removeEventListener( "resize", resizeHandler );
343			delete container.__resizeHandler;
344		}
345	}
346}
347
348document.addEventListener( 'click', hideLikersPopover );
349
350function JetpackLikesWidgetQueueHandler() {
351	var wrapperID;
352
353	if ( ! jetpackLikesMasterReady ) {
354		setTimeout( JetpackLikesWidgetQueueHandler, 500 );
355		return;
356	}
357
358	// Restore widgets to initial unloaded state when they are scrolled out of view.
359	jetpackUnloadScrolledOutWidgets();
360
361	var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView();
362
363	if ( unloadedWidgetsInView.length > 0 ) {
364		// Grab any unloaded widgets for a batch request
365		JetpackLikesBatchHandler();
366	}
367
368	for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) {
369		wrapperID = unloadedWidgetsInView[ i ].id;
370
371		if ( ! wrapperID ) {
372			continue;
373		}
374
375		jetpackLoadLikeWidgetIframe( wrapperID );
376	}
377}
378
379function jetpackLoadLikeWidgetIframe( wrapperID ) {
380	if ( typeof wrapperID === 'undefined' ) {
381		return;
382	}
383
384	const wrapper = document.querySelector( '#' + wrapperID );
385	wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() );
386
387	const placeholder = wrapper.querySelector( '.likes-widget-placeholder' );
388
389	// Post like iframe
390	if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) {
391		const postLikesFrame = document.createElement( 'iframe' );
392
393		postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' );
394		postLikesFrame.name = wrapper.dataset.name;
395		postLikesFrame.src = wrapper.dataset.src;
396		postLikesFrame.height = '55px';
397		postLikesFrame.width = '100%';
398		postLikesFrame.frameBorder = '0';
399		postLikesFrame.scrolling = 'no';
400		postLikesFrame.title = wrapper.dataset.title;
401
402		placeholder.after( postLikesFrame );
403	}
404
405	// Comment like iframe
406	if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) {
407		const commentLikesFrame = document.createElement( 'iframe' );
408
409		commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame';
410		commentLikesFrame.name = wrapper.dataset.name;
411		commentLikesFrame.src = wrapper.dataset.src;
412		commentLikesFrame.height = '18px';
413		commentLikesFrame.width = '100%';
414		commentLikesFrame.frameBorder = '0';
415		commentLikesFrame.scrolling = 'no';
416
417		wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame );
418
419		jetpackCommentLikesLoadedWidgets.push( commentLikesFrame );
420	}
421
422	wrapper.classList.remove( 'jetpack-likes-widget-unloaded' );
423	wrapper.classList.add( 'jetpack-likes-widget-loading' );
424
425	wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => {
426		JetpackLikesPostMessage(
427			{ event: 'loadLikeWidget', name: e.target.name, width: e.target.width },
428			window.frames[ 'likes-master' ]
429		);
430
431		wrapper.classList.remove( 'jetpack-likes-widget-loading' );
432		wrapper.classList.add( 'jetpack-likes-widget-loaded' );
433	} );
434}
435
436function jetpackGetUnloadedWidgetsInView() {
437	const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' );
438
439	return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) );
440}
441
442function jetpackIsScrolledIntoView( element ) {
443	const top = element.getBoundingClientRect().top;
444	const bottom = element.getBoundingClientRect().bottom;
445
446	// Allow some slack above and bellow the fold with jetpackLikesLookAhead,
447	// with the aim of hiding the transition from unloaded to loaded widget from users.
448	return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead;
449}
450
451function jetpackUnloadScrolledOutWidgets() {
452	for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) {
453		const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ];
454
455		if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) {
456			const widgetWrapper =
457				currentWidgetIframe &&
458				currentWidgetIframe.parentElement &&
459				currentWidgetIframe.parentElement.parentElement;
460
461			// Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed.
462			widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' );
463			widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' );
464			widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' );
465
466			// Bring back the loading placeholder into view.
467			widgetWrapper
468				.querySelectorAll( '.comment-likes-widget-placeholder' )
469				.forEach( item => ( item.style.display = 'block' ) );
470
471			// Remove it from the list of loaded widgets.
472			jetpackCommentLikesLoadedWidgets.splice( i, 1 );
473
474			// Remove comment like widget iFrame.
475			currentWidgetIframe.remove();
476		}
477	}
478}
479
480var jetpackWidgetsDelayedExec = function ( after, fn ) {
481	var timer;
482	return function () {
483		clearTimeout( timer );
484		timer = setTimeout( fn, after );
485	};
486};
487
488var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler );
489
490// Load initial batch of widgets, prior to any scrolling events.
491JetpackLikesWidgetQueueHandler();
492
493// Add event listener to execute queue handler after scroll.
494window.addEventListener( 'scroll', jetpackOnScrollStopped, true );
495;
496/**
497 * Comment Likes - JavaScript
498 *
499 * This handles liking and unliking comments, as well as viewing who has
500 * liked a particular comment.
501 *
502 * @dependency  Swipe (dynamically loaded when needed)
503 *
504 * @package     Comment_Likes
505 * @subpackage  JavaScript
506 */
507(function () {
508	function init() {
509		let extWin;
510		let extWinCheck;
511		let commentLikeEvent;
512
513		// Only run once.
514		if (window.comment_likes_loaded) {
515			return;
516		}
517		window.comment_likes_loaded = true;
518
519		// Client-side cache of who liked a particular comment to avoid
520		// having to hit the server multiple times for the same data.
521		const commentLikeCache = {};
522
523		let swipeLibPromise;
524
525		// Load the Swipe library, if it's not already loaded.
526		function swipeLibLoader() {
527			if (!swipeLibPromise) {
528				swipeLibPromise = new Promise((resolve, reject) => {
529					if (window.Swipe) {
530						resolve(window.Swipe);
531					} else {
532						const swipeScript = document.createElement('script');
533						swipeScript.src = comment_like_text.swipeUrl;
534						swipeScript.async = true;
535						document.body.appendChild(swipeScript);
536						swipeScript.addEventListener('load', () => resolve(window.Swipe));
537						swipeScript.addEventListener('error', error => reject(error));
538					}
539				});
540			}
541			return swipeLibPromise;
542		}
543
544		/**
545		 * Parse the comment ID from a comment like link.
546		 */
547		function getCommentId(link) {
548			const commentId =
549				link && link.getAttribute('href') && link.getAttribute('href').split('like_comment=');
550			return commentId[1].split('&_wpnonce=')[0];
551		}
552
553		/**
554		 * Handle an ajax action on the comment like link.
555		 */
556		function handleLinkAction(link, action, commentId, callback) {
557			const nonce =
558				link && link.getAttribute('href') && link.getAttribute('href').split('_wpnonce=')[1];
559
560			fetch('/wp-admin/admin-ajax.php', {
561				method: 'POST',
562				body: new URLSearchParams({
563					action: action,
564					_wpnonce: nonce,
565					like_comment: commentId,
566					blog_id: Number(link.dataset.blog),
567				}),
568				headers: {
569					'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
570					'X-Requested-With': 'XMLHttpRequest',
571					Accept: 'application/json',
572					'cache-control': 'no-cache',
573					pragma: 'no-cache',
574				},
575			})
576				.then(response => response.json())
577				.then(callback);
578		}
579
580		function startPolling() {
581			// Append cookie polling login iframe to this window to wait for user to finish logging in (or cancel)
582			const loginIframe = document.createElement('iframe');
583			loginIframe.id = 'wp-login-polling-iframe';
584			loginIframe.src = 'https://wordpress.com/public.api/connect/?iframe=true';
585			document.body.appendChild(loginIframe);
586			loginIframe.style.display = 'none';
587		}
588
589		function stopPolling() {
590			const iframe = document.querySelector('#wp-login-polling-iframe');
591			if (iframe) {
592				iframe.remove();
593			}
594		}
595
596		function hide(el) {
597			if (el && el.style) {
598				el.style.display = 'none';
599			}
600		}
601
602		function show(el) {
603			if (el && el.style) {
604				el.style.removeProperty('display');
605			}
606		}
607
608		// Overlay used for displaying comment like info.
609		class Overlay {
610			constructor() {
611				// Overlay element.
612				this.el = document.createElement('div');
613				this.el.classList.add('comment-likes-overlay');
614				document.body.appendChild(this.el);
615				hide(this.el);
616
617				this.el.addEventListener('mouseenter', () => {
618					// Don't hide the overlay if the user is mousing over it.
619					overlay.cancelHide();
620				});
621
622				this.el.addEventListener('mouseleave', () => overlay.requestHide());
623
624				// Inner contents of overlay.
625				this.innerEl = null;
626
627				// Instance of the Swipe library.
628				this.swipe = null;
629
630				// Timeout used for hiding the overlay.
631				this.hideTimeout = null;
632			}
633
634			// Initialise the overlay for use, removing any old content.
635			clear() {
636				// Unload any previous instance of Swipe (to avoid leaking a global
637				// event handler). This is done before clearing the contents of the
638				// overlay because Swipe expects the slides to still be present.
639				if (this.swipe) {
640					this.swipe.kill();
641					this.swipe = null;
642				}
643				this.el.innerHTML = '';
644				this.innerEl = document.createElement('div');
645				this.innerEl.classList.add('inner');
646				this.el.appendChild(this.innerEl);
647			}
648
649			/**
650			 * Construct a list (<ul>) of user (gravatar, name) details.
651			 *
652			 * @param  data     liker data returned from the server
653			 * @param  klass    CSS class to apply to the <ul> element
654			 * @param  start    index of user to start at
655			 * @param  length   number of users to include in the list
656			 *
657			 * @return          A container element with the list
658			 */
659			getUserBits(data, klass, start, length) {
660				start = start || 0;
661				let last = start + (length || data.length);
662				last = last > data.length ? data.length : last;
663				const container = document.createElement('div');
664				container.classList.add('liker-list');
665				let html = `<ul class="${klass || ''}">`;
666				for (let i = start; i < last; ++i) {
667					const user = data[i];
668					html += `
669						<li>
670							<a rel="nofollow" title="${user.display_name_esc}" href="${user.profile_url_esc}">
671								<img src="${user.avatar_url_esc}" alt="${user.display_name_esc}" />
672								<span class="user-name">${user.display_name_esc}</span>
673							</a>
674						</li>
675					`;
676				}
677				html += '</ul>';
678				container.innerHTML = html;
679				return container;
680			}
681
682			/**
683			 * Render the display of who has liked this comment. The type of
684			 * display depends on how many people have liked the comment.
685			 * If more than 10 people have liked the comment, this function
686			 * renders navigation controls and sets up the Swipe library for
687			 * changing between pages.
688			 *
689			 * @param link  the element over which the user is hovering
690			 * @param data  the results retrieved from the server
691			 */
692			showLikes(link, data) {
693				this.clear();
694
695				link.dataset.likeCount = data.length;
696				if (data.length === 0) {
697					// No likers after all.
698					hide(this.el);
699					return;
700				}
701
702				this.innerEl.style.padding = '12px';
703
704				if (data.length < 6) {
705					// Only one column needed.
706					this.innerEl.style.maxWidth = '200px';
707					this.innerEl.innerHTML = '';
708					this.innerEl.appendChild(this.getUserBits(data, 'single'));
709					this.setPosition(link);
710				} else if (data.length < 11) {
711					// Two columns, but only one page.
712					this.innerEl.innerHTML = '';
713					this.innerEl.appendChild(this.getUserBits(data, 'double'));
714					this.setPosition(link);
715				} else {
716					// Multiple pages.
717					this.renderLikesWithPagination(data, link);
718				}
719			}
720
721			/**
722			 * Render multiple pages of likes with pagination controls.
723			 * This function is intended to be called by `showLikes` above.
724			 *
725			 * @param data  the results retrieved from the server
726			 */
727			renderLikesWithPagination(data, link) {
728				swipeLibLoader().then(() => {
729					const page_count = Math.ceil(data.length / 10);
730					// Swipe requires two nested containers.
731					const swipe = document.createElement('div');
732					swipe.classList.add('swipe');
733					this.innerEl.appendChild(swipe);
734
735					const wrap = document.createElement('div');
736					wrap.classList.add('swipe-wrap');
737					swipe.appendChild(wrap);
738
739					for (let i = 0; i < page_count; ++i) {
740						wrap.appendChild(this.getUserBits(data, 'double', i * 10, 10));
741					}
742
743					/**
744					 * Navigation controls.
745					 * This is based on the Newdash controls found in
746					 *    reader/recommendations-templates.php
747					 */
748					const nav = document.createElement('nav');
749					nav.classList.add('slider-nav');
750
751					let navContents = `
752						<a href="#" class="prev">
753							<span class="noticon noticon-previous" title="Previous" alt="<"></span>
754						</a>
755						<span class="position">
756					`;
757					for (let i = 0; i < page_count; ++i) {
758						navContents += `<em data-page="${i}" class="${i === 0 ? 'on' : ''}">&bull;</em>`;
759					}
760					navContents += `
761						</span>
762						<a href="#" class="next">
763							<span class="noticon noticon-next" title="Next" alt=">"></span>
764						</a>
765					`;
766					this.innerEl.appendChild(nav);
767					nav.innerHTML = navContents;
768
769					/** Set up Swipe. **/
770					// Swipe cannot be set up successfully unless its container
771					// is visible, so we show it now.
772					show(this.el);
773					this.setPosition(link);
774
775					this.swipe = new Swipe(swipe, {
776						callback: function (pos) {
777							// Update the pagination indicators.
778							//
779							// If there are exactly two pages, Swipe has a weird
780							// special case where it duplicates both pages and
781							// can return index 2 and 3 even though those aren't
782							// real pages (see swipe.js, line 47). To deal with
783							// this, we use the expression `pos % page_count`.
784							pos = pos % page_count;
785							nav.querySelectorAll('em').forEach(em => {
786								const page = Number(em.dataset.page);
787								em.setAttribute('class', pos === page ? 'on' : '');
788							});
789						},
790					});
791
792					nav.querySelectorAll('em').forEach(em => {
793						em.addEventListener('click', e => {
794							// Go to the page corresponding to the indicator clicked.
795							this.swipe.slide(Number(em.dataset.page));
796							e.preventDefault();
797						});
798					});
799					// Previous and next buttons.
800					nav.querySelector('.prev').addEventListener('click', e => {
801						this.swipe.prev();
802						e.preventDefault();
803					});
804					nav.querySelector('.next').addEventListener('click', e => {
805						this.swipe.next();
806						e.preventDefault();
807					});
808				});
809			}
810
811			/**
812			 * Open the overlay and show a loading message.
813			 */
814			showLoadingMessage(link) {
815				this.clear();
816				this.innerEl.textContent = comment_like_text.loading;
817				this.setPosition(link);
818			}
819
820			/**
821			 * Position the overlay near the current comment.
822			 *
823			 * @param link  element near which to position the overlay
824			 */
825			setPosition(link) {
826				// Prepare a down arrow icon for the bottom of the overlay.
827				const icon = document.createElement('span');
828				this.el.appendChild(icon);
829				icon.classList.add('icon', 'noticon', 'noticon-downarrow');
830				icon.style.textShadow = '0px 1px 1px rgb(223, 223, 223)';
831
832				const rect = link.getBoundingClientRect();
833				const win = document.defaultView;
834				const offset = {
835					top: rect.top + win.scrollY,
836					left: rect.left + win.scrollX,
837				};
838
839				// Take measurements with the element fully visible.
840				show(this.el);
841				let left = offset.left - (this.el.offsetWidth - link.offsetWidth) / 2;
842				left = left < 5 ? 5 : left;
843				let top = offset.top - this.el.offsetHeight + 5;
844				hide(this.el);
845
846				const adminBar = document.querySelector('#wpadminbar');
847
848				// Check if the overlay would appear off the screen.
849				if (top < win.scrollY + ((adminBar && adminBar.offsetHeight) || 0)) {
850					// We'll display the overlay beneath the link instead.
851					top = offset.top + link.offsetHeight;
852					// Instead of using the down arrow icon, use an up arrow.
853					icon.remove();
854					this.el.prepend(icon);
855					icon.classList.remove('noticon-downarrow');
856					icon.classList.add('noticon-uparrow');
857					icon.style.textShadow = '0px -1px 1px rgb(223, 223, 223)';
858					icon.style.verticalAlign = 'bottom';
859				}
860
861				this.el.style.left = `${left}px`;
862				this.el.style.top = `${top}px`;
863				show(this.el);
864
865				// The height of the arrow icon differs slightly between browsers,
866				// so we compute the margin here to make sure it isn't disjointed
867				// from the overlay.
868				icon.style.marginTop = `${icon.scrollHeight - 26}px`;
869				icon.style.marginBottom = `${20 - icon.scrollHeight}px`;
870
871				// Position the arrow to be horizontally centred on the link.
872				icon.style.paddingLeft = `${
873					offset.left - left + (link.offsetWidth - icon.scrollWidth) / 2
874				}px`;
875			}
876
877			/**
878			 * Return whether the overlay is visible.
879			 */
880			isVisible() {
881				return this.el.style.getPropertyValue('display') !== 'none';
882			}
883
884			/**
885			 * Request that the overlay be hidden after a short delay.
886			 */
887			requestHide() {
888				if (this.hideTimeout !== null) {
889					return;
890				}
891				this.hideTimeout = setTimeout(() => {
892					hide(this.el);
893					this.clear();
894				}, 300);
895			}
896
897			/**
898			 * Cancel a request to hide the overlay.
899			 */
900			cancelHide() {
901				if (this.hideTimeout !== null) {
902					clearTimeout(this.hideTimeout);
903					this.hideTimeout = null;
904				}
905			}
906		}
907
908		// Overlay used for displaying comment like info.
909		const overlay = new Overlay();
910
911		// The most recent comment for which the user has requested to see
912		// who liked it.
913		var relevantComment;
914
915		// Precache after this timeout.
916		var precacheTimeout = null;
917
918		/**
919		 * Fetch the like data for a particular comment.
920		 */
921		function fetchLikeData(link, commentId) {
922			commentLikeCache[commentId] = null;
923
924			const container = link && link.parentElement && link.parentElement.parentElement;
925			const star = container.querySelector('a.comment-like-link');
926			star &&
927				handleLinkAction(star, 'view_comment_likes', commentId, data => {
928					// Populate the cache.
929					commentLikeCache[commentId] = data;
930
931					// Only show the overlay if the user is interested.
932					if (overlay.isVisible() && relevantComment === commentId) {
933						overlay.showLikes(link, data);
934					}
935				});
936		}
937
938		function readCookie(c) {
939			const nameEQ = c + '=';
940			const cookieStrings = document.cookie.split(';');
941
942			for (let i = 0; i < cookieStrings.length; i++) {
943				let cookieString = cookieStrings[i];
944				while (cookieString.charAt(0) === ' ') {
945					cookieString = cookieString.substring(1, cookieString.length);
946				}
947				if (cookieString.indexOf(nameEQ) === 0) {
948					const chunk = cookieString.substring(nameEQ.length, cookieString.length);
949					const pairs = chunk.split('&');
950					const cookieData = {};
951					for (let num = pairs.length - 1; num >= 0; num--) {
952						const pair = pairs[num].split('=');
953						cookieData[pair[0]] = decodeURIComponent(pair[1]);
954					}
955					return cookieData;
956				}
957			}
958			return null;
959		}
960
961		function getServiceData() {
962			const data = readCookie('wpc_wpc');
963			if (data === null || typeof data.access_token === 'undefined' || !data.access_token) {
964				return false;
965			}
966			return data;
967		}
968
969		function readMessage(msg) {
970			const event = msg.data;
971
972			if (typeof event.event === 'undefined') {
973				return;
974			}
975
976			if (event.event === 'login' && event.success) {
977				extWinCheck = setInterval(function () {
978					if (!extWin || extWin.closed) {
979						clearInterval(extWinCheck);
980						if (getServiceData()) {
981							// Load page in an iframe to get the current comment nonce
982							const nonceIframe = document.createElement('iframe');
983							nonceIframe.id = 'wp-login-comment-nonce-iframe';
984							nonceIframe.style.display = 'none';
985							nonceIframe.src = commentLikeEvent + '';
986							document.body.appendChild(nonceIframe);
987
988							const commentLikeId = (commentLikeEvent + '')
989								.split('like_comment=')[1]
990								.split('&_wpnonce=')[0];
991							let c;
992
993							// Set a 5 second timeout to redirect to the comment page without doing the Like as a fallback
994							const commentLikeTimeout = setTimeout(() => {
995								window.location = commentLikeEvent;
996							}, 5000);
997
998							// Check for a new nonced redirect and use that if available before timing out
999							const commentLikeCheck = setInterval(() => {
1000								const iframe = document.querySelector('#wp-login-comment-nonce-iframe');
1001								if (iframe) {
1002									c = iframe.querySelector(`#comment-like-${commentLikeId} .comment-like-link`);
1003								}
1004								if (c && typeof c.href !== 'undefined') {
1005									clearTimeout(commentLikeTimeout);
1006									clearInterval(commentLikeCheck);
1007									window.location = c.href;
1008								}
1009							}, 100);
1010						}
1011					}
1012				}, 100);
1013
1014				if (extWin) {
1015					if (!extWin.closed) {
1016						extWin.close();
1017					}
1018					extWin = false;
1019				}
1020
1021				stopPolling();
1022			}
1023		}
1024
1025		if (typeof window.postMessage !== 'undefined') {
1026			window.addEventListener('message', e => {
1027				let message = e && e.data;
1028				if (typeof message === 'string') {
1029					try {
1030						message = JSON.parse(message);
1031					} catch (err) {
1032						return;
1033					}
1034				}
1035
1036				const type = message && message.type;
1037				if (type === 'loginMessage') {
1038					readMessage(message);
1039				}
1040			});
1041		}
1042
1043		document.body.addEventListener('click', e => {
1044			let target = e.target;
1045
1046			// Don't do anything when clicking on the "X people" link.
1047			if (target.matches('p.comment-likes a.view-likers')) {
1048				e.preventDefault();
1049				return;
1050			}
1051
1052			// Retrieve the surrounding paragraph to the star, if it hasn't been liked.
1053			const notLikedPar = target.closest('p.comment-not-liked');
1054
1055			// Return if not clicking on star or surrounding paragraph.
1056			if (!target.matches('a.comment-like-link') && !notLikedPar) {
1057				return;
1058			}
1059
1060			// When a comment hasn't been liked, make the text clickable, too.
1061			if (notLikedPar) {
1062				target = notLikedPar.querySelector('a.comment-like-link');
1063				if (!target) {
1064					return;
1065				}
1066			}
1067
1068			if (target.classList.contains('needs-login')) {
1069				e.preventDefault();
1070				commentLikeEvent = target;
1071				if (extWin) {
1072					if (!extWin.closed) {
1073						extWin.close();
1074					}
1075					extWin = false;
1076				}
1077
1078				stopPolling();
1079
1080				const url = 'https://wordpress.com/public.api/connect/?action=request&service=wordpress';
1081				extWin = window.open(
1082					url,
1083					'likeconn',
1084					'status=0,toolbar=0,location=1,menubar=0,directories=0,resizable=1,scrollbars=1,height=560,width=500'
1085				);
1086
1087				startPolling();
1088
1089				return false;
1090			}
1091
1092			// Record that the user likes or does not like this comment.
1093			const commentId = getCommentId(target);
1094			target.classList.add('loading');
1095
1096			let commentEl = document.querySelector(`p#comment-like-${commentId}`);
1097			// Determine whether to like or unlike based on whether the comment is
1098			// currently liked.
1099			const action =
1100				commentEl && commentEl.dataset.liked === 'comment-liked'
1101					? 'unlike_comment'
1102					: 'like_comment';
1103			handleLinkAction(target, action, commentId, data => {
1104				// Invalidate the like cache for this comment.
1105				delete commentLikeCache[commentId];
1106
1107				const countEl = document.querySelector(`#comment-like-count-${data.context}`);
1108				if (countEl) {
1109					countEl.innerHTML = data.display;
1110				}
1111
1112				commentEl = document.querySelector(`p#comment-like-${data.context}`);
1113				if (action === 'like_comment') {
1114					commentEl.classList.remove('comment-not-liked');
1115					commentEl.classList.add('comment-liked');
1116					commentEl.dataset.liked = 'comment-liked';
1117				} else {
1118					commentEl.classList.remove('comment-liked');
1119					commentEl.classList.add('comment-not-liked');
1120					commentEl.dataset.liked = 'comment-not-liked';
1121				}
1122
1123				// Prefetch new data for this comment (if there are likers left).
1124				const parent = target.closest('.comment-likes');
1125				const link = parent && parent.querySelector('a.view-likers');
1126				if (link) {
1127					fetchLikeData(link, commentId);
1128				}
1129
1130				target.classList.remove('loading');
1131			});
1132			e.preventDefault();
1133			e.stopPropagation();
1134		});
1135
1136		document.body.addEventListener(
1137			'mouseenter',
1138			function (e) {
1139				if (!e.target.matches('p.comment-likes a.view-likers')) {
1140					return;
1141				}
1142				// Show the user a list of who has liked this comment.
1143
1144				const link = e.target;
1145				if (Number(link.dataset.likeCount || 0) === 0) {
1146					// No one has liked this comment.
1147					return;
1148				}
1149
1150				// Don't hide the overlay.
1151				overlay.cancelHide();
1152
1153				// Get the comment ID.
1154				const container = link.parentElement && link.parentElement.parentElement;
1155				const star = container && container.querySelector('a.comment-like-link');
1156				const commentId = star && getCommentId(star);
1157				relevantComment = commentId;
1158
1159				// Check if the list of likes for this comment is already in
1160				// the cache.
1161				if (commentId in commentLikeCache) {
1162					const entry = commentLikeCache[commentId];
1163					// Only display the likes if the ajax request is
1164					// actually done.
1165					if (entry !== null) {
1166						overlay.showLikes(link, entry);
1167					} else {
1168						// Make sure the overlay is visible (in case
1169						// the user moved the mouse away while loading
1170						// but then came back before it finished
1171						// loading).
1172						overlay.showLoadingMessage(link);
1173					}
1174					return;
1175				}
1176
1177				// Position the "Loading..." overlay.
1178				overlay.showLoadingMessage(link);
1179
1180				// Fetch the data.
1181				fetchLikeData(link, commentId);
1182			},
1183			true
1184		);
1185
1186		document.body.addEventListener(
1187			'mouseleave',
1188			e => {
1189				if (!e.target.matches('p.comment-likes a.view-likers')) {
1190					return;
1191				}
1192				// User has moved cursor away - hide the overlay.
1193				overlay.requestHide();
1194			},
1195			true
1196		);
1197
1198		document.body.addEventListener(
1199			'mouseenter',
1200			e => {
1201				if (!e.target.matches('.comment') || !e.target.querySelector('a.comment-like-link')) {
1202					return;
1203				}
1204				// User is moving over a comment - precache the comment like data.
1205				if (precacheTimeout !== null) {
1206					clearTimeout(precacheTimeout);
1207					precacheTimeout = null;
1208				}
1209
1210				const star = e.target.querySelector('a.comment-like-link');
1211				const parent = star.closest('.comment-likes');
1212				const link = parent && parent.querySelector('a.view-likers');
1213				if (!link || Number(link.dataset.likeCount || 0) === 0) {
1214					// No likes.
1215					return;
1216				}
1217				const commentId = getCommentId(star);
1218				if (commentId in commentLikeCache) {
1219					// Already in cache.
1220					return;
1221				}
1222
1223				precacheTimeout = setTimeout(() => {
1224					precacheTimeout = null;
1225					if (commentId in commentLikeCache) {
1226						// Was cached in the interim.
1227						return;
1228					}
1229					fetchLikeData(link, commentId);
1230				}, 1000);
1231			},
1232			true
1233		);
1234	}
1235
1236	if (document.readyState !== 'loading') {
1237		init();
1238	} else {
1239		document.addEventListener('DOMContentLoaded', init);
1240	}
1241})();
1242;
1243( function() {
1244	var is_webkit = navigator.userAgent.toLowerCase().indexOf( 'webkit' ) > -1,
1245	    is_opera  = navigator.userAgent.toLowerCase().indexOf( 'opera' )  > -1,
1246	    is_ie     = navigator.userAgent.toLowerCase().indexOf( 'msie' )   > -1;
1247
1248	if ( ( is_webkit || is_opera || is_ie ) && document.getElementById && window.addEventListener ) {
1249		window.addEventListener( 'hashchange', function() {
1250			var element = document.getElementById( location.hash.substring( 1 ) );
1251
1252			if ( element ) {
1253				if ( ! /^(?:a|select|input|button|textarea)$/i.test( element.tagName ) )
1254					element.tabIndex = -1;
1255
1256				element.focus();
1257			}
1258		}, false );
1259	}
1260})();
1261;
1262( function () {
1263	'use strict';
1264
1265	function parsePx( val ) {
1266		return parseInt( ( val || '' ).replace( 'px', '' ), 10 ) || 0;
1267	}
1268
1269	function getOffset( el ) {
1270		var rect = el.getBoundingClientRect();
1271		return {
1272			top: rect.top + ( window.scrollY || window.pageYOffset || 0 ),
1273			left: rect.left + ( window.scrollX || window.pageXOffset || 0 ),
1274		};
1275	}
1276
1277	function getBubbleStyles( el ) {
1278		el.style.display = 'block';
1279		var styles = JSON.parse( JSON.stringify( getComputedStyle( el ) ) );
1280		el.style.removeProperty( 'display' );
1281		return {
1282			width: styles.width,
1283			paddingLeft: styles.paddingLeft,
1284			paddingRight: styles.paddingRight,
1285		};
1286	}
1287
1288	function delayBubbleHide( bubble, gravatar ) {
1289		// Small delay, so mouseenter has a chance to run and set hover to true
1290		// if the mouse is back on the bubble.
1291		setTimeout( function () {
1292			if (
1293				( ! bubble.hasAttribute( 'data-hover' ) ||
1294					bubble.getAttribute( 'data-hover' ) === 'false' ) &&
1295				( ! gravatar.hasAttribute( 'data-hover' ) ||
1296					gravatar.getAttribute( 'data-hover' ) === 'false' )
1297			) {
1298				bubble.classList.remove( 'fadein' );
1299			}
1300		}, 100 );
1301	}
1302
1303	function setupAvatarTips() {
1304		document.querySelectorAll( '.wpcom-follow-gravatar > a' ).forEach( function ( link ) {
1305			link.setAttribute( 'data-hover', 'false' );
1306		} );
1307
1308		document.querySelectorAll( '.wpcom-follow-gravatar > a' ).forEach( function ( link ) {
1309			link.addEventListener( 'mouseenter', function () {
1310				link.setAttribute( 'data-hover', 'true' );
1311				var id = ( link.getAttribute( 'data-id' ) || '' ).replace( /[^a-z0-9_-]/gi, '' );
1312
1313				if ( ! id ) {
1314					return;
1315				}
1316
1317				var bubble = document.querySelector( '#' + id );
1318				var img = link.querySelector( 'img' );
1319				if ( ! bubble || ! img ) {
1320					return;
1321				}
1322
1323				var avatarWidth = img.clientWidth;
1324				var offset = getOffset( img );
1325				var top = offset.top + avatarWidth;
1326
1327				var bubbleWidth =	parsePx( getBubbleStyles( bubble ).width );
1328				var bubbleLeft = offset.left - ( bubbleWidth - avatarWidth ) / 2;
1329				var windowCollisionPadding = 10;
1330
1331				bubble.classList.remove( 'bubble-left' );
1332				bubble.classList.remove( 'bubble-right' );
1333
1334				// Detect a collision on the left of the screen, and place the bubble at
1335				// the edge of the window with a bit of padding.
1336				if ( bubbleLeft < 0 ) {
1337					bubbleLeft = offset.left - windowCollisionPadding;
1338					bubble.classList.add( 'bubble-left' );
1339				}
1340
1341				// Detect a collision on the right of the screen, and place the bubble at
1342				// the edge of the window with a bit of padding.
1343				if ( bubbleLeft + bubbleWidth > document.documentElement.clientWidth ) {
1344					var avatarPosition = getOffset( link );
1345					var avatarRight = avatarPosition.left + avatarWidth;
1346					bubbleLeft = avatarRight - bubbleWidth - windowCollisionPadding;
1347					bubble.classList.add( 'bubble-right' );
1348				}
1349
1350				bubble.style.left = bubbleLeft + 'px';
1351				bubble.style.top = top + 'px';
1352				bubble.classList.add( 'fadein' );
1353			} );
1354		} );
1355
1356		document.querySelectorAll( '.wpcom-follow-bubbles > .wpcom-bubble' ).forEach( function ( div ) {
1357			div.addEventListener( 'mouseenter', function () {
1358				div.setAttribute( 'data-hover', 'true' );
1359			} );
1360		} );
1361
1362		document
1363			.querySelectorAll( '.wpcom-follow-bubbles > .wpcom-bubble' )
1364			.forEach( function ( bubble ) {
1365				bubble.addEventListener( 'mouseleave', function () {
1366					var id = ( bubble.getAttribute( 'id' ) || '' ).replace( /[^a-z0-9_-]/gi, '' );
1367					if ( ! id ) {
1368						return;
1369					}
1370
1371					var gravatar = document.querySelector( '.wpcom-follow-gravatar a[data-id="' + id + '"]' );
1372					bubble.setAttribute( 'data-hover', 'false' );
1373					delayBubbleHide( bubble, gravatar );
1374				} );
1375			} );
1376
1377		document.querySelectorAll( '.wpcom-follow-gravatar > a' ).forEach( function ( link ) {
1378			link.addEventListener( 'mouseleave', function () {
1379				var id = ( link.getAttribute( 'data-id' ) || '' ).replace( /[^a-z0-9_-]/gi, '' );
1380				if ( ! id ) {
1381					return;
1382				}
1383
1384				var bubble = document.querySelector( '#' + id );
1385				link.setAttribute( 'data-hover', 'false' );
1386				delayBubbleHide( bubble, link );
1387			} );
1388		} );
1389	}
1390
1391	if ( document.readyState !== 'loading' ) {
1392		setupAvatarTips();
1393	} else {
1394		document.addEventListener( 'DOMContentLoaded', setupAvatarTips );
1395	}
1396} )();
1397;
1398( function( $ ) {
1399
1400	/*
1401	 * A function to help debouncing.
1402	 */
1403	var debounce = function( func, wait ) {
1404		var timeout, args, context, timestamp;
1405		return function() {
1406			context = this;
1407			args = [].slice.call( arguments, 0 );
1408			timestamp = new Date();
1409			var later = function() {
1410				var last = ( new Date() ) - timestamp;
1411				if ( last < wait ) {
1412					timeout = setTimeout( later, wait - last );
1413				} else {
1414					timeout = null;
1415					func.apply( context, args );
1416				}
1417			};
1418			if ( ! timeout ) {
1419				timeout = setTimeout( later, wait );
1420			}
1421		};
1422	};
1423
1424	/*
1425	 * Remove body "blog" class when a user is logged in and doesn't have a post yet.
1426	 */
1427	if ( $( '.site-main' ).children().hasClass( 'not-found' ) && $( 'body' ).hasClass( 'blog' ) ) {
1428		$( 'body' ).removeClass( 'blog' ).addClass( 'search-no-results' );
1429	}
1430
1431	/*
1432	 * Move the Page Links before Sharedaddy.
1433	 */
1434	$( '.single .hentry' ).each( function() {
1435		$( this ).find( '.page-links' ).insertBefore( $( this ).find( '.sharedaddy' ).first() );
1436	} );
1437
1438	/*
1439	 * Add a class of "pace-done" to body when Sharedaddy official sharing buttons are being displayed.
1440	 */
1441	if ( $('.sd-social-official').length > 0 ) {
1442		$( 'body' ).addClass( 'pace-done' );
1443	}
1444
1445	/*
1446	 * Format Video: Move videos above the Entry Header.
1447	 */
1448	$( '.single .format-video' ).find( 'embed, iframe, object, video' ).parent().each( function() {
1449		if ( ! $( this ).hasClass( 'entry-content' ) ) {
1450			$( this ).addClass( 'entry-media' )
1451			         .insertBefore( $( '.entry-header' ) );
1452		}
1453	} );
1454
1455	$( '.single .format-video' ).find( 'iframe[src*="videopress.com"]' ).each( function() {
1456			$( this ).wrap( '<div class="entry-media" />' );
1457			$( this ).parent( '.entry-media' ).insertBefore( $( '.entry-header' ) );
1458	} );
1459
1460	/*
1461	 * Make sure tables don't overflow in Entry Content.
1462	 */
1463	$( '.entry-content' ).find( 'table' ).each( function() {
1464		if ( $( this ).width() > $( this ).parent().width() ) {
1465			$( this ).css( 'table-layout', 'fixed' );
1466		}
1467	} );
1468
1469	/*
1470	 * Remove border from linked images.
1471	 */
1472	$( '.entry-content a' ).each( function() {
1473		$( this ).has( 'img' ).addClass( 'no-border' );
1474	} );
1475
1476	/*
1477	 * Add hover class to Search Submit.
1478	 */
1479	function search_add_class() {
1480		$( this ).closest( '.search-form' ).addClass( 'hover' );
1481	}
1482	function search_remove_class() {
1483		$( this ).closest( '.search-form' ).removeClass( 'hover' );
1484	}
1485	var search_submit = $( '.search-submit' );
1486	search_submit.hover( search_add_class, search_remove_class );
1487	search_submit.focusin( search_add_class );
1488	search_submit.focusout( search_remove_class );
1489
1490
1491	/*
1492	 * Remove Comment Reply if empty.
1493	 */
1494	$( '.comment .reply' ).each( function() {
1495		if ( $.trim( $( this ).text() ) === '' ) {
1496			$( this ).remove();
1497		}
1498	} );
1499
1500	/*
1501	 * Remove Byline if hidden and Entry Footer if empty.
1502	 */
1503	if ( $( '.byline' ).is( ':hidden') ) {
1504		$( '.byline' ).remove();
1505	}
1506	$( '.entry-footer' ).filter( function() {
1507		return $.trim( $( this ).text() ) === '';
1508	} ).addClass( 'empty' );
1509
1510	/*
1511	 * Add dropdown toggle that display child menu items.
1512	 */
1513	$( '.main-navigation .page_item_has_children > a, .main-navigation .menu-item-has-children > a, .widget_nav_menu .page_item_has_children > a, .widget_nav_menu .menu-item-has-children > a' ).append( '<button class="dropdown-toggle" aria-expanded="false"/>' );
1514	$( '.dropdown-toggle' ).click( function( event ) {
1515		event.preventDefault();
1516		$( this ).toggleClass( 'toggle-on' );
1517		$( this ).parent().next( '.children, .sub-menu' ).toggleClass( 'toggle-on' );
1518		$( this ).attr( 'aria-expanded', $( this ).attr( 'aria-expanded' ) === 'false' ? 'true' : 'false' );
1519	} );
1520
1521	/*
1522	 * Sidebar toggle.
1523	 */
1524	var scroll_top_position = 0;
1525	$( '.sidebar-toggle' ).click( function( event ) {
1526		event.preventDefault();
1527
1528		// Remove mejs players from sidebar
1529		$( '#secondary .mejs-container' ).each( function( i, el ) {
1530			if ( mejs.players[ el.id ] ) {
1531				mejs.players[ el.id ].remove();
1532			}
1533		} );
1534
1535		if ( ! $( 'body' ).hasClass( 'sidebar-open' ) ) {
1536			scroll_top_position = $( 'body' ).scrollTop();
1537		}
1538		$( this ).toggleClass( 'toggle-on' )
1539		         .attr( 'aria-expanded', $( this ).attr( 'aria-expanded' ) === 'false' ? 'true' : 'false' );
1540		$( 'body' ).toggleClass( 'sidebar-open' )
1541		           .trigger( 'resize' );
1542		$( '#sidebar' ).attr( 'aria-hidden', $( '#sidebar' ).attr( 'aria-hidden' ) === 'false' ? 'true' : 'false' );
1543		if ( $( 'body' ).hasClass( 'sidebar-open' ) ) {
1544			$( '.site-main' ).hide();
1545			$( 'html, body' ).animate( {
1546				scrollTop: 0
1547			}, 0 );
1548
1549			// Re-initialize mediaelement players.
1550			setTimeout( function() {
1551				if ( window.wp && window.wp.mediaelement ) {
1552					window.wp.mediaelement.initialize();
1553				}
1554			} );
1555
1556			// Trigger resize event to display VideoPress player.
1557			setTimeout( function(){
1558				if ( typeof( Event ) === 'function' ) {
1559					window.dispatchEvent( new Event( 'resize' ) );
1560				} else {
1561					var event = window.document.createEvent( 'UIEvents' );
1562					event.initUIEvent( 'resize', true, false, window, 0 );
1563					window.dispatchEvent( event );
1564				}
1565			} );
1566		} else {
1567			$( '.site-main' ).show();
1568			$( 'html, body' ).animate( {
1569				scrollTop: scroll_top_position
1570			}, 0 );
1571		}
1572	} );
1573
1574	/*
1575	 * Close Sidebar with escape key.
1576	 */
1577	$( document ).keyup( function( event ) {
1578		if ( event.keyCode === 27 && $( '.sidebar-toggle' ).hasClass( 'toggle-on' ) ) {
1579			$( 'body' ).removeClass( 'sidebar-open' );
1580			$( '.sidebar-toggle' ).removeClass( 'toggle-on' )
1581			                      .attr( 'aria-expanded', 'false' );
1582			$( '#sidebar' ).attr( 'aria-hidden', 'true' );
1583			$( '.site-main' ).show();
1584			$( 'html, body' ).animate( {
1585				scrollTop: scroll_top_position
1586			}, 0 );
1587		}
1588	} );
1589
1590	/*
1591	 * Make sure to remove all image attributes to fit perfectly.
1592	 */
1593	function remove_image_attributes() {
1594		if ( ( $( 'body' ).hasClass( 'page' ) || $( 'body' ).hasClass( 'single' ) ) && $( '.site-main > .hentry' ).hasClass( 'has-post-thumbnail' ) ) {
1595			$( '.entry-thumbnail' ).find( 'img' ).removeAttr( 'width' ).removeAttr( 'height' );
1596		}
1597	}
1598
1599	/*
1600	 * Define theme heights.
1601	 */
1602	function heights() {
1603		var site_header_height = $( '.site-header' ).outerHeight(),
1604		    site_header_height_backup = site_header_height,
1605		    site_footer_height = $( '.site-footer' ).outerHeight(),
1606		    admin_bar_height = 0;
1607		if ( $( 'body' ).hasClass( 'admin-bar' ) || $( 'body' ).hasClass( 'has-marketing-bar' ) ) {
1608			if ( $( 'body' ).hasClass( 'admin-bar' ) ) {
1609				admin_bar_height += $( '#wpadminbar' ).outerHeight();
1610			}
1611
1612			if ( $( 'body' ).hasClass( 'has-marketing-bar' ) ) {
1613				admin_bar_height += $( '#marketingbar' ).outerHeight();
1614			}
1615
1616			site_header_height += admin_bar_height;
1617			$( '.site-header' ).css( 'top', admin_bar_height );
1618		}
1619
1620		if ( $( window ).width() > 767 ) {
1621			$( '.archive .page-header, .search-results .page-header' ).each( function() {
1622				$( this ).css( 'top', - $( this ).outerHeight() );
1623			} );
1624		}
1625		$( '.sidebar-toggle' ).css( 'height', site_header_height_backup - 1 );
1626		$( '.sidebar' ).css( 'top', site_header_height );
1627		$( '.sidebar, .single .attachment-boardwalk-hero-image' ).css( 'min-height', $( window ).height() - site_header_height );
1628		if ( $( 'body' ).hasClass( 'archive' ) || $( 'body' ).hasClass( 'search-results' ) ) {
1629			site_header_height += $( '.page-header' ).outerHeight();
1630			site_header_height_backup += $( '.page-header' ).outerHeight();
1631		}
1632		var content_area_height = $( window ).height() - site_header_height - site_footer_height;
1633		if ( $( window ).width() > 767 ) {
1634			$( '.site-main' ).css( {
1635				'margin-top': site_header_height_backup,
1636				'min-height': content_area_height
1637			} );
1638		} else {
1639			$( '.site-main' ).css( {
1640				'margin-top': '',
1641				'min-height': ''
1642			} );
1643		}
1644		$( 'body.page .entry-thumbnail, body.single .entry-thumbnail, .entry-media iframe, .entry-media object, .entry-media embed, .entry-media video, .entry-media .wp-video-shortcode' ).height( $( window ).height() - site_header_height );
1645		if ( ( $( 'body' ).hasClass( 'single' ) || $( 'body' ).hasClass( 'page' ) ) && $( '.hentry' ).hasClass( 'has-post-thumbnail' ) ) {
1646			if ( $( '.entry-header' ).outerHeight() + 48 >  $( window ).height() - site_header_height ) {
1647				$( '.entry-thumbnail' ).css( 'min-height', $( '.entry-header' ).outerHeight() + 48 );
1648			} else {
1649				$( '.entry-thumbnail' ).css( 'min-height', '' );
1650			}
1651		}
1652		$( '.search-no-results .site-main' ).css( 'min-height', content_area_height );
1653		$( '.single .hentry' ).css( 'min-height', content_area_height - $( '.post-navigation' ).outerHeight() );
1654
1655		// Ratio Entry Thumbnail
1656		if ( ( $( 'body' ).hasClass( 'page' ) || $( 'body' ).hasClass( 'single' ) ) && $( '.site-main > .hentry' ).hasClass( 'has-post-thumbnail' ) ) {
1657			$( '.wp-post-image' ).each( function() {
1658				var img = this,
1659						$img = $( img );
1660				function image_ready() {
1661					var ratio_screen = $( '.entry-thumbnail' ).width() / $( '.entry-thumbnail' ).height(),
1662					    ratio_image = img.naturalWidth / img.naturalHeight;
1663					if ( ratio_image >= ratio_screen ) {
1664						$img.css( {
1665							'height': site_header_height,
1666							'max-width': 'none'
1667						} );
1668					} else {
1669						$img.css( {
1670							'height': '',
1671							'max-width': '100%'
1672						} );
1673					}
1674				}
1675				if ( img.complete ) {
1676					image_ready();
1677				} else {
1678					$img.load( image_ready );
1679				}
1680			} );
1681		}
1682	}
1683
1684	/*
1685	 * Add a class to big image and caption > 767px.
1686	 */
1687	function image_big() {
1688		$( '.page .entry-content, .single .entry-content' ).find( 'img:not(.latex)' ).each( function() {
1689			// Ignore table, VideoPress, portfolio and recipe images
1690			if ( $( this ).parents( 'table' ).length || $( this ).parents( '.video-player' ).length || $( this ).parents( '.portfolio-entry' ).length || $( this ).parents( '.jetpack-recipe' ).length || $( this ).parents( '.jetpack-slideshow-slide' ).length ) {
1691				$( this ).addClass( 'resized' );
1692			}
1693			if ( ! $( this ).closest( '.gallery, .tiled-gallery, .wp-block-gallery, .wp-block-jetpack-tiled-gallery, .wp-block-jetpack-slideshow, .wp-block-media-text, .wp-block-columns' ).length ) {
1694				var img = this,
1695						$img = $( img ),
1696				    $caption = $( this ).closest( 'figure' );
1697
1698				function image_ready() {
1699					var width_attr = parseInt( img.getAttribute( "width" ) || 0, 10 );
1700					var is_large =
1701						$img.hasClass( 'resized' ) ||
1702						Math.max( width_attr, img.naturalWidth ) > 767;
1703
1704					if ( is_large && ! $img.hasClass( 'resized' ) ) {
1705						$img.wrap( '<span class="image-big" />' );
1706						$img.addClass( 'resized' );
1707					}
1708					if ( is_large && $caption.hasClass( 'wp-caption' ) ) {
1709						$caption.addClass( 'caption-big' ).css( 'width', '' );
1710					}
1711				}
1712
1713				if ( img.complete ) {
1714					image_ready();
1715				} else {
1716					$img.load( image_ready );
1717				}
1718			}
1719		} );
1720	}
1721
1722	/*
1723	 * Force a page to appear after 5 sconds document is ready to avoid
1724	 * whitescreen of death with hanging JS.
1725	 */
1726	$( document ).ready( function() {
1727		var body = $( document.body );
1728		setTimeout( function() {
1729			if (  body.hasClass( 'pace-running' ) ) {
1730				body.removeClass( 'pace-running' ).addClass( 'pace-done' )
1731			}
1732		}, 5000 );
1733	} );
1734
1735	/*
1736	 * Load all the functions once it's ready.
1737	 */
1738	$( window ).load( function() {
1739
1740		// Load the functions.
1741		remove_image_attributes();
1742		heights();
1743		image_big();
1744
1745	} ).resize( debounce( function() {
1746
1747		// Reset the Sidebar Toggle height.
1748		$( '.sidebar-toggle' ).height( 'auto' );
1749
1750		// Load the functions.
1751		remove_image_attributes();
1752		heights();
1753		image_big();
1754
1755	}, 500 ) );
1756
1757} )( jQuery );
1758;
1759/**
1760 * Observe how the user enters content into the comment form in order to determine whether it's a bot or not.
1761 *
1762 * Note that no actual input is being saved here, only counts and timings between events.
1763 */
1764
1765( function() {
1766	// Passive event listeners are guaranteed to never call e.preventDefault(),
1767	// but they're not supported in all browsers.  Use this feature detection
1768	// to determine whether they're available for use.
1769	var supportsPassive = false;
1770
1771	try {
1772		var opts = Object.defineProperty( {}, 'passive', {
1773			get : function() {
1774				supportsPassive = true;
1775			}
1776		} );
1777
1778		window.addEventListener( 'testPassive', null, opts );
1779		window.removeEventListener( 'testPassive', null, opts );
1780	} catch ( e ) {}
1781
1782	function init() {
1783		var input_begin = '';
1784
1785		var keydowns = {};
1786		var lastKeyup = null;
1787		var lastKeydown = null;
1788		var keypresses = [];
1789
1790		var modifierKeys = [];
1791		var correctionKeys = [];
1792
1793		var lastMouseup = null;
1794		var lastMousedown = null;
1795		var mouseclicks = [];
1796		var mouseclickCoordinates = [];
1797
1798		var mousemoveTimer = null;
1799		var lastMousemoveX = null;
1800		var lastMousemoveY = null;
1801		var lastMousemoveTime = null;
1802		var mousemoveStart = null;
1803		var mousemoves = [];
1804		var intervalsBetweenMousemovesAndClicks = [];
1805
1806		var touchmoveCountTimer = null;
1807		var touchmoveCount = 0;
1808
1809		var lastTouchEnd = null;
1810		var lastTouchStart = null;
1811		var touchEvents = [];
1812
1813		var scrollCountTimer = null;
1814		var scrollCount = 0;
1815
1816		var correctionKeyCodes = [ 'Backspace', 'Delete', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown' ];
1817		var modifierKeyCodes = [ 'Shift', 'CapsLock' ];
1818
1819		var forms = document.querySelectorAll( 'form[method=post]' );
1820
1821		for ( var i = 0; i < forms.length; i++ ) {
1822			var form = forms[i];
1823
1824			var formAction = form.getAttribute( 'action' );
1825
1826			// Ignore forms that POST directly to other domains; these could be things like payment forms.
1827			if ( formAction ) {
1828				// Check that the form is posting to an external URL, not a path.
1829				if ( formAction.indexOf( 'http://' ) == 0 || formAction.indexOf( 'https://' ) == 0 ) {
1830					if ( formAction.indexOf( 'http://' + window.location.hostname + '/' ) != 0 && formAction.indexOf( 'https://' + window.location.hostname + '/' ) != 0 ) {
1831						continue;
1832					}
1833				}
1834			}
1835
1836			form.addEventListener( 'submit', function () {
1837				var ak_bkp = prepare_array_for_request( keypresses );
1838				var ak_bmc = prepare_array_for_request( mouseclicks );
1839				var ak_bte = prepare_array_for_request( touchEvents );
1840				var ak_bmm = prepare_array_for_request( mousemoves );
1841				var ak_bcc = prepare_array_for_request( mouseclickCoordinates );
1842				var ak_bibmac = intervalsBetweenMousemovesAndClicks.slice( 0, 100 ).join( ';' );
1843
1844				var input_fields = {
1845					// When did the user begin entering any input?
1846					'bib': input_begin,
1847
1848					// When was the form submitted?
1849					'bfs': Date.now(),
1850
1851					// How many keypresses did they make?
1852					'bkpc': keypresses.length,
1853
1854					// How quickly did they press a sample of keys, and how long between them?
1855					'bkp': ak_bkp,
1856
1857					// How quickly did they click the mouse, and how long between clicks?
1858					'bmc': ak_bmc,
1859
1860					// How many mouseclicks did they make?
1861					'bmcc': mouseclicks.length,
1862
1863					// When did they press modifier keys (like Shift or Capslock)?
1864					'bmk': modifierKeys.join( ';' ),
1865
1866					// When did they correct themselves? e.g., press Backspace, or use the arrow keys to move the cursor back
1867					'bck': correctionKeys.join( ';' ),
1868
1869					// How many times did they move the mouse?
1870					'bmmc': mousemoves.length,
1871
1872					// How many times did they move around using a touchscreen?
1873					'btmc': touchmoveCount,
1874
1875					// How many times did they scroll?
1876					'bsc': scrollCount,
1877
1878					// How quickly did they perform touch events, and how long between them?
1879					'bte': ak_bte,
1880
1881					// How many touch events were there?
1882					'btec' : touchEvents.length,
1883
1884					// How quickly did they move the mouse, and how long between moves?
1885					'bmm' : ak_bmm,
1886
1887					// Click coordinates
1888					'bcc' : ak_bcc,
1889
1890					// Milliseconds between last mouse movement and each click
1891					'bibmac' : ak_bibmac
1892				};
1893
1894				var akismet_field_prefix = 'ak_';
1895
1896				if ( this.getElementsByClassName ) {
1897					// Check to see if we've used an alternate field name prefix. We store this as an attribute of the container around some of the Akismet fields.
1898					var possible_akismet_containers = this.getElementsByClassName( 'akismet-fields-container' );
1899
1900					for ( var containerIndex = 0; containerIndex < possible_akismet_containers.length; containerIndex++ ) {
1901						var container = possible_akismet_containers.item( containerIndex );
1902
1903						if ( container.getAttribute( 'data-prefix' ) ) {
1904							akismet_field_prefix = container.getAttribute( 'data-prefix' );
1905							break;
1906						}
1907					}
1908				}
1909
1910				for ( var field_name in input_fields ) {
1911					var field = document.createElement( 'input' );
1912					field.setAttribute( 'type', 'hidden' );
1913					field.setAttribute( 'name', akismet_field_prefix + field_name );
1914					field.setAttribute( 'value', input_fields[ field_name ] );
1915					this.appendChild( field );
1916				}
1917			}, supportsPassive ? { passive: true } : false  );
1918
1919			form.addEventListener( 'keydown', function ( e ) {
1920				// If you hold a key down, some browsers send multiple keydown events in a row.
1921				// Ignore any keydown events for a key that hasn't come back up yet.
1922				if ( e.key in keydowns ) {
1923					return;
1924				}
1925
1926				var keydownTime = ( new Date() ).getTime();
1927				keydowns[ e.key ] = [ keydownTime ];
1928
1929				if ( ! input_begin ) {
1930					input_begin = keydownTime;
1931				}
1932
1933				// In some situations, we don't want to record an interval since the last keypress -- for example,
1934				// on the first keypress, or on a keypress after focus has changed to another element. Normally,
1935				// we want to record the time between the last keyup and this keydown. But if they press a
1936				// key while already pressing a key, we want to record the time between the two keydowns.
1937
1938				var lastKeyEvent = Math.max( lastKeydown, lastKeyup );
1939
1940				if ( lastKeyEvent ) {
1941					keydowns[ e.key ].push( keydownTime - lastKeyEvent );
1942				}
1943
1944				lastKeydown = keydownTime;
1945			}, supportsPassive ? { passive: true } : false  );
1946
1947			form.addEventListener( 'keyup', function ( e ) {
1948				if ( ! ( e.key in keydowns ) ) {
1949					// This key was pressed before this script was loaded, or a mouseclick happened during the keypress, or...
1950					return;
1951				}
1952
1953				var keyupTime = ( new Date() ).getTime();
1954
1955				if ( 'TEXTAREA' === e.target.nodeName || 'INPUT' === e.target.nodeName ) {
1956					if ( -1 !== modifierKeyCodes.indexOf( e.key ) ) {
1957						modifierKeys.push( keypresses.length - 1 );
1958					} else if ( -1 !== correctionKeyCodes.indexOf( e.key ) ) {
1959						correctionKeys.push( keypresses.length - 1 );
1960					} else {
1961						// ^ Don't record timings for keys like Shift or backspace, since they
1962						// typically get held down for longer than regular typing.
1963
1964						var keydownTime = keydowns[ e.key ][0];
1965
1966						var keypress = [];
1967
1968						// Keypress duration.
1969						keypress.push( keyupTime - keydownTime );
1970
1971						// Amount of time between this keypress and the previous keypress.
1972						if ( keydowns[ e.key ].length > 1 ) {
1973							keypress.push( keydowns[ e.key ][1] );
1974						}
1975
1976						keypresses.push( keypress );
1977					}
1978				}
1979
1980				delete keydowns[ e.key ];
1981
1982				lastKeyup = keyupTime;
1983			}, supportsPassive ? { passive: true } : false  );
1984
1985			form.addEventListener( "focusin", function ( e ) {
1986				lastKeydown = null;
1987				lastKeyup = null;
1988				keydowns = {};
1989			}, supportsPassive ? { passive: true } : false  );
1990
1991			form.addEventListener( "focusout", function ( e ) {
1992				lastKeydown = null;
1993				lastKeyup = null;
1994				keydowns = {};
1995			}, supportsPassive ? { passive: true } : false  );
1996		}
1997
1998		document.addEventListener( 'mousedown', function ( e ) {
1999			lastMousedown = ( new Date() ).getTime();
2000
2001			if ( lastMousemoveTime ) {
2002				intervalsBetweenMousemovesAndClicks.push( lastMousedown - lastMousemoveTime );
2003			}
2004
2005			var mouseclickCoordinate = [];
2006
2007			var rect = e.target.getBoundingClientRect();
2008			var relativeX = e.clientX - rect.left;
2009			var relativeY = e.clientY - rect.top;
2010
2011			// Pixel offset of the click within the target element.
2012			mouseclickCoordinate.push( Math.round( relativeX ) );
2013			mouseclickCoordinate.push( Math.round( relativeY ) );
2014
2015			// Percentage offset of the click within the target element.
2016			mouseclickCoordinate.push( rect.width > 0 ? Math.round( relativeX / rect.width * 100 ) : 0 );
2017			mouseclickCoordinate.push( rect.height > 0 ? Math.round( relativeY / rect.height * 100 ) : 0 );
2018
2019			mouseclickCoordinates.push( mouseclickCoordinate );
2020		}, supportsPassive ? { passive: true } : false  );
2021
2022		document.addEventListener( 'mouseup', function ( e ) {
2023			if ( ! lastMousedown ) {
2024				// If the mousedown happened before this script was loaded, but the mouseup happened after...
2025				return;
2026			}
2027
2028			var now = ( new Date() ).getTime();
2029
2030			var mouseclick = [];
2031			mouseclick.push( now - lastMousedown );
2032
2033			if ( lastMouseup ) {
2034				mouseclick.push( lastMousedown - lastMouseup );
2035			}
2036
2037			mouseclicks.push( mouseclick );
2038
2039			lastMouseup = now;
2040
2041			// If the mouse has been clicked, don't record this time as an interval between keypresses.
2042			lastKeydown = null;
2043			lastKeyup = null;
2044			keydowns = {};
2045		}, supportsPassive ? { passive: true } : false  );
2046
2047		document.addEventListener( 'mousemove', function ( e ) {
2048			lastMousemoveTime = ( new Date() ).getTime();
2049
2050			if ( mousemoveTimer ) {
2051				clearTimeout( mousemoveTimer );
2052				mousemoveTimer = null;
2053			}
2054			else {
2055				mousemoveStart = lastMousemoveTime;
2056				lastMousemoveX = e.offsetX;
2057				lastMousemoveY = e.offsetY;
2058			}
2059
2060			mousemoveTimer = setTimeout( function ( theEvent, originalMousemoveStart ) {
2061				var now = ( new Date() ).getTime() - 500; // To account for the timer delay.
2062
2063				var mousemove = [];
2064				mousemove.push( now - originalMousemoveStart );
2065				mousemove.push(
2066					Math.round(
2067						Math.sqrt(
2068							Math.pow( theEvent.offsetX - lastMousemoveX, 2 ) +
2069							Math.pow( theEvent.offsetY - lastMousemoveY, 2 )
2070						)
2071					)
2072				);
2073
2074				if ( mousemove[1] > 0 ) {
2075					// If there was no measurable distance, then it wasn't really a move.
2076					mousemoves.push( mousemove );
2077				}
2078
2079				mousemoveStart = null;
2080				mousemoveTimer = null;
2081			}, 500, e, mousemoveStart );
2082		}, supportsPassive ? { passive: true } : false  );
2083
2084		document.addEventListener( 'touchmove', function ( e ) {
2085			if ( touchmoveCountTimer ) {
2086				clearTimeout( touchmoveCountTimer );
2087			}
2088
2089			touchmoveCountTimer = setTimeout( function () {
2090				touchmoveCount++;
2091			}, 500 );
2092		}, supportsPassive ? { passive: true } : false );
2093
2094		document.addEventListener( 'touchstart', function ( e ) {
2095			lastTouchStart = ( new Date() ).getTime();
2096		}, supportsPassive ? { passive: true } : false );
2097
2098		document.addEventListener( 'touchend', function ( e ) {
2099			if ( ! lastTouchStart ) {
2100				// If the touchstart happened before this script was loaded, but the touchend happened after...
2101				return;
2102			}
2103
2104			var now = ( new Date() ).getTime();
2105
2106			var touchEvent = [];
2107			touchEvent.push( now - lastTouchStart );
2108
2109			if ( lastTouchEnd ) {
2110				touchEvent.push( lastTouchStart - lastTouchEnd );
2111			}
2112
2113			touchEvents.push( touchEvent );
2114
2115			lastTouchEnd = now;
2116
2117			// Don't record this time as an interval between keypresses.
2118			lastKeydown = null;
2119			lastKeyup = null;
2120			keydowns = {};
2121		}, supportsPassive ? { passive: true } : false );
2122
2123		document.addEventListener( 'scroll', function ( e ) {
2124			if ( scrollCountTimer ) {
2125				clearTimeout( scrollCountTimer );
2126			}
2127
2128			scrollCountTimer = setTimeout( function () {
2129				scrollCount++;
2130			}, 500 );
2131		}, supportsPassive ? { passive: true } : false );
2132	}
2133
2134	/**
2135	 * For the timing/coordinate data that is collected, don't send more than `limit` data points in the request.
2136	 * Choose a random slice and send those, with each batch separated by semicolons and the items in each batch
2137	 * separated by commas.
2138	 */
2139	function prepare_array_for_request( a, limit ) {
2140		if ( ! limit ) {
2141			limit = 100;
2142		}
2143
2144		var rv = '';
2145
2146		if ( a.length > 0 ) {
2147			var random_starting_point = Math.max( 0, Math.floor( Math.random() * a.length - limit ) );
2148
2149			for ( var i = 0; i < limit && i < a.length; i++ ) {
2150				var entry = a[ random_starting_point + i ];
2151				rv += entry.join( ',' ) + ';';
2152			}
2153		}
2154
2155		return rv;
2156	}
2157
2158	if ( document.readyState !== 'loading' ) {
2159		init();
2160	} else {
2161		document.addEventListener( 'DOMContentLoaded', init );
2162	}
2163})();;

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.