PageSourceSearch

https://sertgallery.ca/_static/??-eJyVkMkOwjAMRH+IYkAqnBCfgkJiURfb…/VRIa95G1vNV285my8Wi7a/Jwrix

js sertgallery.ca collected 2026-10-09 01:09:59 UTC 125,240 bytes, 3,779 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.
vendor: 24,810 bytes, lines 1205-2012
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/* Modernizr 2.7.1 (Custom Build) | MIT & BSD
1244 * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
1245 */
1246;
1247
1248window.Modernizr = (function( window, document, undefined ) {
1249
1250    var version = '2.7.1',
1251
1252    Modernizr = {},
1253
1254    enableClasses = true,
1255
1256    docElement = document.documentElement,
1257
1258    mod = 'modernizr',
1259    modElem = document.createElement(mod),
1260    mStyle = modElem.style,
1261
1262    inputElem  = document.createElement('input')  ,
1263
1264    smile = ':)',
1265
1266    toString = {}.toString,
1267
1268    prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
1269
1270
1271
1272    omPrefixes = 'Webkit Moz O ms',
1273
1274    cssomPrefixes = omPrefixes.split(' '),
1275
1276    domPrefixes = omPrefixes.toLowerCase().split(' '),
1277
1278    ns = {'svg': 'http://www.w3.org/2000/svg'},
1279
1280    tests = {},
1281    inputs = {},
1282    attrs = {},
1283
1284    classes = [],
1285
1286    slice = classes.slice,
1287
1288    featureName,
1289
1290
1291    injectElementWithStyles = function( rule, callback, nodes, testnames ) {
1292
1293      var style, ret, node, docOverflow,
1294          div = document.createElement('div'),
1295                body = document.body,
1296                fakeBody = body || document.createElement('body');
1297
1298      if ( parseInt(nodes, 10) ) {
1299                      while ( nodes-- ) {
1300              node = document.createElement('div');
1301              node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
1302              div.appendChild(node);
1303          }
1304      }
1305
1306                style = ['&#173;','<style id="s', mod, '">', rule, '</style>'].join('');
1307      div.id = mod;
1308          (body ? div : fakeBody).innerHTML += style;
1309      fakeBody.appendChild(div);
1310      if ( !body ) {
1311                fakeBody.style.background = '';
1312                fakeBody.style.overflow = 'hidden';
1313          docOverflow = docElement.style.overflow;
1314          docElement.style.overflow = 'hidden';
1315          docElement.appendChild(fakeBody);
1316      }
1317
1318      ret = callback(div, rule);
1319        if ( !body ) {
1320          fakeBody.parentNode.removeChild(fakeBody);
1321          docElement.style.overflow = docOverflow;
1322      } else {
1323          div.parentNode.removeChild(div);
1324      }
1325
1326      return !!ret;
1327
1328    },
1329
1330    testMediaQuery = function( mq ) {
1331
1332      var matchMedia = window.matchMedia || window.msMatchMedia;
1333      if ( matchMedia ) {
1334        return matchMedia(mq).matches;
1335      }
1336
1337      var bool;
1338
1339      injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) {
1340        bool = (window.getComputedStyle ?
1341                  getComputedStyle(node, null) :
1342                  node.currentStyle)['position'] == 'absolute';
1343      });
1344
1345      return bool;
1346
1347     },
1348
1349
1350    isEventSupported = (function() {
1351
1352      var TAGNAMES = {
1353        'select': 'input', 'change': 'input',
1354        'submit': 'form', 'reset': 'form',
1355        'error': 'img', 'load': 'img', 'abort': 'img'
1356      };
1357
1358      function isEventSupported( eventName, element ) {
1359
1360        element = element || document.createElement(TAGNAMES[eventName] || 'div');
1361        eventName = 'on' + eventName;
1362
1363            var isSupported = eventName in element;
1364
1365        if ( !isSupported ) {
1366                if ( !element.setAttribute ) {
1367            element = document.createElement('div');
1368          }
1369          if ( element.setAttribute && element.removeAttribute ) {
1370            element.setAttribute(eventName, '');
1371            isSupported = is(element[eventName], 'function');
1372
1373                    if ( !is(element[eventName], 'undefined') ) {
1374              element[eventName] = undefined;
1375            }
1376            element.removeAttribute(eventName);
1377          }
1378        }
1379
1380        element = null;
1381        return isSupported;
1382      }
1383      return isEventSupported;
1384    })(),
1385
1386
1387    _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
1388
1389    if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
1390      hasOwnProp = function (object, property) {
1391        return _hasOwnProperty.call(object, property);
1392      };
1393    }
1394    else {
1395      hasOwnProp = function (object, property) {
1396        return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
1397      };
1398    }
1399
1400
1401    if (!Function.prototype.bind) {
1402      Function.prototype.bind = function bind(that) {
1403
1404        var target = this;
1405
1406        if (typeof target != "function") {
1407            throw new TypeError();
1408        }
1409
1410        var args = slice.call(arguments, 1),
1411            bound = function () {
1412
1413            if (this instanceof bound) {
1414
1415              var F = function(){};
1416              F.prototype = target.prototype;
1417              var self = new F();
1418
1419              var result = target.apply(
1420                  self,
1421                  args.concat(slice.call(arguments))
1422              );
1423              if (Object(result) === result) {
1424                  return result;
1425              }
1426              return self;
1427
1428            } else {
1429
1430              return target.apply(
1431                  that,
1432                  args.concat(slice.call(arguments))
1433              );
1434
1435            }
1436
1437        };
1438
1439        return bound;
1440      };
1441    }
1442
1443    function setCss( str ) {
1444        mStyle.cssText = str;
1445    }
1446
1447    function setCssAll( str1, str2 ) {
1448        return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
1449    }
1450
1451    function is( obj, type ) {
1452        return typeof obj === type;
1453    }
1454
1455    function contains( str, substr ) {
1456        return !!~('' + str).indexOf(substr);
1457    }
1458
1459    function testProps( props, prefixed ) {
1460        for ( var i in props ) {
1461            var prop = props[i];
1462            if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
1463                return prefixed == 'pfx' ? prop : true;
1464            }
1465        }
1466        return false;
1467    }
1468
1469    function testDOMProps( props, obj, elem ) {
1470        for ( var i in props ) {
1471            var item = obj[props[i]];
1472            if ( item !== undefined) {
1473
1474                            if (elem === false) return props[i];
1475
1476                            if (is(item, 'function')){
1477                                return item.bind(elem || obj);
1478                }
1479
1480                            return item;
1481            }
1482        }
1483        return false;
1484    }
1485
1486    function testPropsAll( prop, prefixed, elem ) {
1487
1488        var ucProp  = prop.charAt(0).toUpperCase() + prop.slice(1),
1489            props   = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
1490
1491            if(is(prefixed, "string") || is(prefixed, "undefined")) {
1492          return testProps(props, prefixed);
1493
1494            } else {
1495          props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
1496          return testDOMProps(props, prefixed, elem);
1497        }
1498    }    tests['flexbox'] = function() {
1499      return testPropsAll('flexWrap');
1500    };    tests['canvas'] = function() {
1501        var elem = document.createElement('canvas');
1502        return !!(elem.getContext && elem.getContext('2d'));
1503    };
1504
1505    tests['canvastext'] = function() {
1506        return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
1507    };
1508
1509
1510
1511    tests['webgl'] = function() {
1512        return !!window.WebGLRenderingContext;
1513    };
1514
1515
1516    tests['touch'] = function() {
1517        var bool;
1518
1519        if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
1520          bool = true;
1521        } else {
1522          injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
1523            bool = node.offsetTop === 9;
1524          });
1525        }
1526
1527        return bool;
1528    };
1529
1530
1531
1532    tests['geolocation'] = function() {
1533        return 'geolocation' in navigator;
1534    };
1535
1536
1537    tests['postmessage'] = function() {
1538      return !!window.postMessage;
1539    };
1540
1541
1542    tests['websqldatabase'] = function() {
1543      return !!window.openDatabase;
1544    };
1545
1546    tests['indexedDB'] = function() {
1547      return !!testPropsAll("indexedDB", window);
1548    };
1549
1550    tests['hashchange'] = function() {
1551      return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
1552    };
1553
1554    tests['history'] = function() {
1555      return !!(window.history && history.pushState);
1556    };
1557
1558    tests['draganddrop'] = function() {
1559        var div = document.createElement('div');
1560        return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
1561    };
1562
1563    tests['websockets'] = function() {
1564        return 'WebSocket' in window || 'MozWebSocket' in window;
1565    };
1566
1567
1568    tests['rgba'] = function() {
1569        setCss('background-color:rgba(150,255,150,.5)');
1570
1571        return contains(mStyle.backgroundColor, 'rgba');
1572    };
1573
1574    tests['hsla'] = function() {
1575            setCss('background-color:hsla(120,40%,100%,.5)');
1576
1577        return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
1578    };
1579
1580    tests['multiplebgs'] = function() {
1581                setCss('background:url(https://),url(https://),red url(https://)');
1582
1583            return (/(url\s*\(.*?){3}/).test(mStyle.background);
1584    };    tests['backgroundsize'] = function() {
1585        return testPropsAll('backgroundSize');
1586    };
1587
1588    tests['borderimage'] = function() {
1589        return testPropsAll('borderImage');
1590    };
1591
1592
1593
1594    tests['borderradius'] = function() {
1595        return testPropsAll('borderRadius');
1596    };
1597
1598    tests['boxshadow'] = function() {
1599        return testPropsAll('boxShadow');
1600    };
1601
1602    tests['textshadow'] = function() {
1603        return document.createElement('div').style.textShadow === '';
1604    };
1605
1606
1607    tests['opacity'] = function() {
1608                setCssAll('opacity:.55');
1609
1610                    return (/^0.55$/).test(mStyle.opacity);
1611    };
1612
1613
1614    tests['cssanimations'] = function() {
1615        return testPropsAll('animationName');
1616    };
1617
1618
1619    tests['csscolumns'] = function() {
1620        return testPropsAll('columnCount');
1621    };
1622
1623
1624    tests['cssgradients'] = function() {
1625        var str1 = 'background-image:',
1626            str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
1627            str3 = 'linear-gradient(left top,#9f9, white);';
1628
1629        setCss(
1630                       (str1 + '-webkit- '.split(' ').join(str2 + str1) +
1631                       prefixes.join(str3 + str1)).slice(0, -str1.length)
1632        );
1633
1634        return contains(mStyle.backgroundImage, 'gradient');
1635    };
1636
1637
1638    tests['cssreflections'] = function() {
1639        return testPropsAll('boxReflect');
1640    };
1641
1642
1643    tests['csstransforms'] = function() {
1644        return !!testPropsAll('transform');
1645    };
1646
1647
1648    tests['csstransforms3d'] = function() {
1649
1650        var ret = !!testPropsAll('perspective');
1651
1652                        if ( ret && 'webkitPerspective' in docElement.style ) {
1653
1654                      injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
1655            ret = node.offsetLeft === 9 && node.offsetHeight === 3;
1656          });
1657        }
1658        return ret;
1659    };
1660
1661
1662    tests['csstransitions'] = function() {
1663        return testPropsAll('transition');
1664    };
1665
1666
1667
1668    tests['fontface'] = function() {
1669        var bool;
1670
1671        injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) {
1672          var style = document.getElementById('smodernizr'),
1673              sheet = style.sheet || style.styleSheet,
1674              cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
1675
1676          bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
1677        });
1678
1679        return bool;
1680    };
1681
1682    tests['generatedcontent'] = function() {
1683        var bool;
1684
1685        injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) {
1686          bool = node.offsetHeight >= 3;
1687        });
1688
1689        return bool;
1690    };
1691    tests['video'] = function() {
1692        var elem = document.createElement('video'),
1693            bool = false;
1694
1695            try {
1696            if ( bool = !!elem.canPlayType ) {
1697                bool      = new Boolean(bool);
1698                bool.ogg  = elem.canPlayType('video/ogg; codecs="theora"')      .replace(/^no$/,'');
1699
1700                            bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
1701
1702                bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
1703            }
1704
1705        } catch(e) { }
1706
1707        return bool;
1708    };
1709
1710    tests['audio'] = function() {
1711        var elem = document.createElement('audio'),
1712            bool = false;
1713
1714        try {
1715            if ( bool = !!elem.canPlayType ) {
1716                bool      = new Boolean(bool);
1717                bool.ogg  = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
1718                bool.mp3  = elem.canPlayType('audio/mpeg;')               .replace(/^no$/,'');
1719
1720                                                    bool.wav  = elem.canPlayType('audio/wav; codecs="1"')     .replace(/^no$/,'');
1721                bool.m4a  = ( elem.canPlayType('audio/x-m4a;')            ||
1722                              elem.canPlayType('audio/aac;'))             .replace(/^no$/,'');
1723            }
1724        } catch(e) { }
1725
1726        return bool;
1727    };
1728
1729
1730    tests['localstorage'] = function() {
1731        try {
1732            localStorage.setItem(mod, mod);
1733            localStorage.removeItem(mod);
1734            return true;
1735        } catch(e) {
1736            return false;
1737        }
1738    };
1739
1740    tests['sessionstorage'] = function() {
1741        try {
1742            sessionStorage.setItem(mod, mod);
1743            sessionStorage.removeItem(mod);
1744            return true;
1745        } catch(e) {
1746            return false;
1747        }
1748    };
1749
1750
1751    tests['webworkers'] = function() {
1752        return !!window.Worker;
1753    };
1754
1755
1756    tests['applicationcache'] = function() {
1757        return !!window.applicationCache;
1758    };
1759
1760
1761    tests['svg'] = function() {
1762        return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
1763    };
1764
1765    tests['inlinesvg'] = function() {
1766      var div = document.createElement('div');
1767      div.innerHTML = '<svg/>';
1768      return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
1769    };
1770
1771    tests['smil'] = function() {
1772        return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
1773    };
1774
1775
1776    tests['svgclippaths'] = function() {
1777        return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
1778    };
1779
1780    function webforms() {
1781                                            Modernizr['input'] = (function( props ) {
1782            for ( var i = 0, len = props.length; i < len; i++ ) {
1783                attrs[ props[i] ] = !!(props[i] in inputElem);
1784            }
1785            if (attrs.list){
1786                                  attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
1787            }
1788            return attrs;
1789        })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
1790                            Modernizr['inputtypes'] = (function(props) {
1791
1792            for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
1793
1794                inputElem.setAttribute('type', inputElemType = props[i]);
1795                bool = inputElem.type !== 'text';
1796
1797                                                    if ( bool ) {
1798
1799                    inputElem.value         = smile;
1800                    inputElem.style.cssText = 'position:absolute;visibility:hidden;';
1801
1802                    if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
1803
1804                      docElement.appendChild(inputElem);
1805                      defaultView = document.defaultView;
1806
1807                                        bool =  defaultView.getComputedStyle &&
1808                              defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
1809                                                                                  (inputElem.offsetHeight !== 0);
1810
1811                      docElement.removeChild(inputElem);
1812
1813                    } else if ( /^(search|tel)$/.test(inputElemType) ){
1814                                                                                    } else if ( /^(url|email)$/.test(inputElemType) ) {
1815                                        bool = inputElem.checkValidity && inputElem.checkValidity() === false;
1816
1817                    } else {
1818                                        bool = inputElem.value != smile;
1819                    }
1820                }
1821
1822                inputs[ props[i] ] = !!bool;
1823            }
1824            return inputs;
1825        })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
1826        }
1827    for ( var feature in tests ) {
1828        if ( hasOwnProp(tests, feature) ) {
1829                                    featureName  = feature.toLowerCase();
1830            Modernizr[featureName] = tests[feature]();
1831
1832            classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
1833        }
1834    }
1835
1836    Modernizr.input || webforms();
1837
1838
1839     Modernizr.addTest = function ( feature, test ) {
1840       if ( typeof feature == 'object' ) {
1841         for ( var key in feature ) {
1842           if ( hasOwnProp( feature, key ) ) {
1843             Modernizr.addTest( key, feature[ key ] );
1844           }
1845         }
1846       } else {
1847
1848         feature = feature.toLowerCase();
1849
1850         if ( Modernizr[feature] !== undefined ) {
1851                                              return Modernizr;
1852         }
1853
1854         test = typeof test == 'function' ? test() : test;
1855
1856         if (typeof enableClasses !== "undefined" && enableClasses) {
1857           docElement.className += ' ' + (test ? '' : 'no-') + feature;
1858         }
1859         Modernizr[feature] = test;
1860
1861       }
1862
1863       return Modernizr;
1864     };
1865
1866
1867    setCss('');
1868    modElem = inputElem = null;
1869
1870    ;(function(window, document) {
1871                var version = '3.7.0';
1872
1873            var options = window.html5 || {};
1874
1875            var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
1876
1877            var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
1878
1879            var supportsHtml5Styles;
1880
1881            var expando = '_html5shiv';
1882
1883            var expanID = 0;
1884
1885            var expandoData = {};
1886
1887            var supportsUnknownElements;
1888
1889        (function() {
1890          try {
1891            var a = document.createElement('a');
1892            a.innerHTML = '<xyz></xyz>';
1893                    supportsHtml5Styles = ('hidden' in a);
1894
1895            supportsUnknownElements = a.childNodes.length == 1 || (function() {
1896                        (document.createElement)('a');
1897              var frag = document.createDocumentFragment();
1898              return (
1899                typeof frag.cloneNode == 'undefined' ||
1900                typeof frag.createDocumentFragment == 'undefined' ||
1901                typeof frag.createElement == 'undefined'
1902              );
1903            }());
1904          } catch(e) {
1905                    supportsHtml5Styles = true;
1906            supportsUnknownElements = true;
1907          }
1908
1909        }());
1910
1911            function addStyleSheet(ownerDocument, cssText) {
1912          var p = ownerDocument.createElement('p'),
1913          parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
1914
1915          p.innerHTML = 'x<style>' + cssText + '</style>';
1916          return parent.insertBefore(p.lastChild, parent.firstChild);
1917        }
1918
1919            function getElements() {
1920          var elements = html5.elements;
1921          return typeof elements == 'string' ? elements.split(' ') : elements;
1922        }
1923
1924            function getExpandoData(ownerDocument) {
1925          var data = expandoData[ownerDocument[expando]];
1926          if (!data) {
1927            data = {};
1928            expanID++;
1929            ownerDocument[expando] = expanID;
1930            expandoData[expanID] = data;
1931          }
1932          return data;
1933        }
1934
1935            function createElement(nodeName, ownerDocument, data){
1936          if (!ownerDocument) {
1937            ownerDocument = document;
1938          }
1939          if(supportsUnknownElements){
1940            return ownerDocument.createElement(nodeName);
1941          }
1942          if (!data) {
1943            data = getExpandoData(ownerDocument);
1944          }
1945          var node;
1946
1947          if (data.cache[nodeName]) {
1948            node = data.cache[nodeName].cloneNode();
1949          } else if (saveClones.test(nodeName)) {
1950            node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
1951          } else {
1952            node = data.createElem(nodeName);
1953          }
1954
1955                                                    return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
1956        }
1957
1958            function createDocumentFragment(ownerDocument, data){
1959          if (!ownerDocument) {
1960            ownerDocument = document;
1961          }
1962          if(supportsUnknownElements){
1963            return ownerDocument.createDocumentFragment();
1964          }
1965          data = data || getExpandoData(ownerDocument);
1966          var clone = data.frag.cloneNode(),
1967          i = 0,
1968          elems = getElements(),
1969          l = elems.length;
1970          for(;i<l;i++){
1971            clone.createElement(elems[i]);
1972          }
1973          return clone;
1974        }
1975
1976            function shivMethods(ownerDocument, data) {
1977          if (!data.cache) {
1978            data.cache = {};
1979            data.createElem = ownerDocument.createElement;
1980            data.createFrag = ownerDocument.createDocumentFragment;
1981            data.frag = data.createFrag();
1982          }
1983
1984
1985          ownerDocument.createElement = function(nodeName) {
1986                    if (!html5.shivMethods) {
1987              return data.createElem(nodeName);
1988            }
1989            return createElement(nodeName, ownerDocument, data);
1990          };
1991
1992          ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
1993                                                          'var n=f.cloneNode(),c=n.createElement;' +
1994                                                          'h.shivMethods&&(' +
1995                                                                                                                getElements().join().replace(/[\w\-]+/g, function(nodeName) {
1996            data.createElem(nodeName);
1997            data.frag.createElement(nodeName);
1998            return 'c("' + nodeName + '")';
1999          }) +
2000            ');return n}'
2001                                                         )(html5, data.frag);
2002        }
2003
2004            function shivDocument(ownerDocument) {
2005          if (!ownerDocument) {
2006            ownerDocument = document;
2007          }
2008          var data = getExpandoData(ownerDocument);
2009
2010          if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
2011            data.hasCSS = !!addStyleSheet(ownerDocument,
2012                                                                                'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{dis
vendor: 9,057 bytes, lines 2012-2194
2012play:block}' +
2013                                                                                    'mark{background:#FF0;color:#000}' +
2014                                                                                    'template{display:none}'
2015                                         );
2016          }
2017          if (!supportsUnknownElements) {
2018            shivMethods(ownerDocument, data);
2019          }
2020          return ownerDocument;
2021        }
2022
2023            var html5 = {
2024
2025                'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video',
2026
2027                'version': version,
2028
2029                'shivCSS': (options.shivCSS !== false),
2030
2031                'supportsUnknownElements': supportsUnknownElements,
2032
2033                'shivMethods': (options.shivMethods !== false),
2034
2035                'type': 'default',
2036
2037                'shivDocument': shivDocument,
2038
2039                createElement: createElement,
2040
2041                createDocumentFragment: createDocumentFragment
2042        };
2043
2044            window.html5 = html5;
2045
2046            shivDocument(document);
2047
2048    }(this, document));
2049
2050    Modernizr._version      = version;
2051
2052    Modernizr._prefixes     = prefixes;
2053    Modernizr._domPrefixes  = domPrefixes;
2054    Modernizr._cssomPrefixes  = cssomPrefixes;
2055
2056    Modernizr.mq            = testMediaQuery;
2057
2058    Modernizr.hasEvent      = isEventSupported;
2059
2060    Modernizr.testProp      = function(prop){
2061        return testProps([prop]);
2062    };
2063
2064    Modernizr.testAllProps  = testPropsAll;
2065
2066
2067    Modernizr.testStyles    = injectElementWithStyles;
2068    Modernizr.prefixed      = function(prop, obj, elem){
2069      if(!obj) {
2070        return testPropsAll(prop, 'pfx');
2071      } else {
2072            return testPropsAll(prop, obj, elem);
2073      }
2074    };
2075
2076
2077    docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
2078
2079                                                    (enableClasses ? ' js ' + classes.join(' ') : '');
2080
2081    return Modernizr;
2082
2083})(this, this.document);
2084/*yepnope1.5.4|WTFPL*/
2085(function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}})(this,document);
2086Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0));};
2087;;
2088/*!
2089 * scrollup v2.4.0
2090 * Url: http://markgoodyear.com/labs/scrollup/
2091 * Copyright (c) Mark Goodyear — @markgdyr — http://markgoodyear.com
2092 * License: MIT
2093 */
2094(function ($, window, document) {
2095    'use strict';
2096
2097    // Main function
2098    $.fn.scrollUp = function (options) {
2099
2100        // Ensure that only one scrollUp exists
2101        if (!$.data(document.body, 'scrollUp')) {
2102            $.data(document.body, 'scrollUp', true);
2103            $.fn.scrollUp.init(options);
2104        }
2105    };
2106
2107    // Init
2108    $.fn.scrollUp.init = function (options) {
2109
2110        // Define vars
2111        var o = $.fn.scrollUp.settings = $.extend({}, $.fn.scrollUp.defaults, options),
2112            triggerVisible = false,
2113            animIn, animOut, animSpeed, scrollDis, scrollEvent, scrollTarget, $self;
2114
2115        // Create element
2116        if (o.scrollTrigger) {
2117            $self = $(o.scrollTrigger);
2118        } else {
2119            $self = $('<a/>', {
2120                id: o.scrollName,
2121                href: '#top'
2122            });
2123        }
2124
2125        // Set scrollTitle if there is one
2126        if (o.scrollTitle) {
2127            $self.attr('title', o.scrollTitle);
2128        }
2129
2130        $self.appendTo('body');
2131
2132        // If not using an image display text
2133        if (!(o.scrollImg || o.scrollTrigger)) {
2134            $self.html(o.scrollText);
2135        }
2136
2137        // Minimum CSS to make the magic happen
2138        $self.css({
2139            display: 'none',
2140            position: 'fixed',
2141            zIndex: o.zIndex
2142        });
2143
2144        // Active point overlay
2145        if (o.activeOverlay) {
2146            $('<div/>', {
2147                id: o.scrollName + '-active'
2148            }).css({
2149                position: 'absolute',
2150                'top': o.scrollDistance + 'px',
2151                width: '100%',
2152                borderTop: '1px dotted' + o.activeOverlay,
2153                zIndex: o.zIndex
2154            }).appendTo('body');
2155        }
2156
2157        // Switch animation type
2158        switch (o.animation) {
2159            case 'fade':
2160                animIn = 'fadeIn';
2161                animOut = 'fadeOut';
2162                animSpeed = o.animationSpeed;
2163                break;
2164
2165            case 'slide':
2166                animIn = 'slideDown';
2167                animOut = 'slideUp';
2168                animSpeed = o.animationSpeed;
2169                break;
2170
2171            default:
2172                animIn = 'show';
2173                animOut = 'hide';
2174                animSpeed = 0;
2175        }
2176
2177        // If from top or bottom
2178        if (o.scrollFrom === 'top') {
2179            scrollDis = o.scrollDistance;
2180        } else {
2181            scrollDis = $(document).height() - $(window).height() - o.scrollDistance;
2182        }
2183
2184        // Scroll function
2185        scrollEvent = $(window).scroll(function () {
2186            if ($(window).scrollTop() > scrollDis) {
2187                if (!triggerVisible) {
2188                    $self[animIn](animSpeed);
2189                    triggerVisible = true;
2190                }
2191            } else {
2192                if (triggerVisible) {
2193                    $self[animOut](animSpeed);
2194                    triggerVisible = false;
2195                }
2196            }
2197        });
2198
2199        if (o.scrollTarget) {
2200            if (typeof o.scrollTarget === 'number') {
2201                scrollTarget = o.scrollTarget;
2202            } else if (typeof o.scrollTarget === 'string') {
2203                scrollTarget = Math.floor($(o.scrollTarget).offset().top);
2204            }
2205        } else {
2206            scrollTarget = 0;
2207        }
2208
2209        // To the top
2210        $self.click(function (e) {
2211            e.preventDefault();
2212
2213            $('html, body').animate({
2214                scrollTop: scrollTarget
2215            }, o.scrollSpeed, o.easingType);
2216        });
2217    };
2218
2219    // Defaults
2220    $.fn.scrollUp.defaults = {
2221        scrollName: 'scrollUp',      // Element ID
2222        scrollDistance: 300,         // Distance from top/bottom before showing element (px)
2223        scrollFrom: 'top',           // 'top' or 'bottom'
2224        scrollSpeed: 300,            // Speed back to top (ms)
2225        easingType: 'linear',        // Scroll to top easing (see http://easings.net/)
2226        animation: 'fade',           // Fade, slide, none
2227        animationSpeed: 200,         // Animation in speed (ms)
2228        scrollTrigger: false,        // Set a custom triggering element. Can be an HTML string or jQuery object
2229        scrollTarget: false,         // Set a custom target element for scrolling to. Can be element or number
2230        scrollText: 'Scroll to top', // Text for element, can contain HTML
2231        scrollTitle: false,          // Set a custom <a> title if required. Defaults to scrollText
2232        scrollImg: false,            // Set true to use image
2233        activeOverlay: false,        // Set CSS color to display scrollUp active point, e.g '#00FFFF'
2234        zIndex: 2147483647           // Z-Index for the overlay
2235    };
2236
2237    // Destroy scrollUp plugin and clean all modifications to the DOM
2238    $.fn.scrollUp.destroy = function (scrollEvent) {
2239        $.removeData(document.body, 'scrollUp');
2240        $('#' + $.fn.scrollUp.settings.scrollName).remove();
2241        $('#' + $.fn.scrollUp.settings.scrollName + '-active').remove();
2242
2243        // If 1.7 or above use the new .off()
2244        if ($.fn.jquery.split('.')[1] >= 7) {
2245            $(window).off('scroll', scrollEvent);
2246
2247        // Else use the old .unbind()
2248        } else {
2249            $(window).unbind('scroll', scrollEvent);
2250        }
2251    };
2252
2253    $.scrollUp = $.fn.scrollUp;
2254
2255})(jQuery, window, document);;
2256/*!
2257 * classie v1.0.1
2258 * class helper functions
2259 * from bonzo https://github.com/ded/bonzo
2260 * https://github.com/desandro/classie
2261 * MIT license
2262 *
2263 * classie.has( elem, 'my-class' ) -> true/false
2264 * classie.add( elem, 'my-new-class' )
2265 * classie.remove( elem, 'my-unwanted-class' )
2266 * classie.toggle( elem, 'my-class' )
2267 */
2268
2269/*jshint browser: true, strict: true, undef: true, unused: true */
2270/*global define: false, module: false */
2271
2272( function( window ) {
2273
2274'use strict';
2275
2276// class helper functions from bonzo https://github.com/ded/bonzo
2277
2278function classReg( className ) {
2279  return new RegExp("(^|\\s+)" + className + "(\\s+|$)");
2280}
2281
2282// classList support for class management
2283// altho to be fair, the api sucks because it won't accept multiple classes at once
2284var hasClass, addClass, removeClass;
2285
2286if ( 'classList' in document.documentElement ) {
2287  hasClass = function( elem, c ) {
2288    return elem.classList.contains( c );
2289  };
2290  addClass = function( elem, c ) {
2291    elem.classList.add( c );
2292  };
2293  removeClass = function( elem, c ) {
2294    elem.classList.remove( c );
2295  };
2296}
2297else {
2298  hasClass = function( elem, c ) {
2299    return classReg( c ).test( elem.className );
2300  };
2301  addClass = function( elem, c ) {
2302    if ( !hasClass( elem, c ) ) {
2303      elem.className = elem.className + ' ' + c;
2304    }
2305  };
2306  removeClass = function( elem, c ) {
2307    elem.className = elem.className.replace( classReg( c ), ' ' );
2308  };
2309}
2310
2311function toggleClass( elem, c ) {
2312  var fn = hasClass( elem, c ) ? removeClass : addClass;
2313  fn( elem, c );
2314}
2315
2316var classie = {
2317  // full names
2318  hasClass: hasClass,
2319  addClass: addClass,
2320  removeClass: removeClass,
2321  toggleClass: toggleClass,
2322  // short names
2323  has: hasClass,
2324  add: addClass,
2325  remove: removeClass,
2326  toggle: toggleClass
2327};
2328
2329// transport
2330if ( typeof define === 'function' && define.amd ) {
2331  // AMD
2332  define( classie );
2333} else if ( typeof exports === 'object' ) {
2334  // CommonJS
2335  module.exports = classie;
2336} else {
2337  // browser global
2338  window.classie = classie;
2339}
2340
2341})( window );;
2342/*
2343 * Fullscreen Overlay Styles & Effects
2344 * http://tympanus.net/codrops/?p=18459
2345 *
2346 * Copyright (c) 2013 http://www.codrops.com
2347 *
2348 * License: Integrate or build upon it for free in your personal or commercial projects.
2349 * http://tympanus.net/codrops/licensing/
2350 *
2351 */
2352(function( $ ) {
2353
2354	// Overlay Core
2355	var	transEndEventNames = {
2356			'WebkitTransition': 'webkitTransitionEnd',
2357			'MozTransition': 'transitionend',
2358			'OTransition': 'oTransitionEnd',
2359			'msTransition': 'MSTransitionEnd',
2360			'transition': 'transitionend'
2361		},
2362		transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
2363		support = { transitions : Modernizr.csstransitions };
2364
2365	function toggleOverlay( overlay ) {
2366		if( classie.has( overlay, 'open' ) ) {
2367			classie.remove( overlay, 'open' );
2368			classie.add( overlay, 'close' );
2369
2370			var onEndTransitionFn = function( ev ) {
2371				if( support.transitions ) {
2372					if( ev.propertyName !== 'visibility' ) return;
2373					this.removeEventListener( transEndEventName, onEndTransitionFn );
2374				}
2375				classie.remove( overlay, 'close' );
2376			};
2377
2378			if( support.transitions ) {
2379				overlay.addEventListener( transEndEventName, onEndTransitionFn );
2380			}
2381			else {
2382				onEndTransitionFn();
2383			}
2384		}
2385		else if( !classie.has( overlay, 'close' ) ) {
2386			classie.add( overlay, 'open' );
2387		}
2388	}
2389
2390	// Overlay Social
2391	$( '.toggle-social, .overlay-close-social' ).off( 'click' ).on( 'click', function( e ) {
2392		e.preventDefault();
2393		toggleOverlay( document.querySelector( '.overlay-social' ) );
2394	} );
2395
2396	// Overlay Search
2397	$( '.toggle-search, .overlay-close-search' ).off( 'click' ).on( 'click', function( e ) {
2398		e.preventDefault();
2399		toggleOverlay( document.querySelector( '.overlay-search' ) );
2400	} );
2401
2402})( jQuery );;
2403/*!
2404 PowerTip - v1.2.0 - 2013-04-03
2405 http://stevenbenner.github.com/jquery-powertip/
2406 Copyright (c) 2013 Steven Benner (http://stevenbenner.com/).
2407 Released under MIT license.
2408 https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt
2409*/
2410(function(factory) {
2411	if (typeof define === 'function' && define.amd) {
2412		// AMD. Register as an anonymous module.
2413		define(['jquery'], factory);
2414	} else {
2415		// Browser globals
2416		factory(jQuery);
2417	}
2418}(function($) {
2419
2420	// useful private variables
2421	var $document = $(document),
2422		$window = $(window),
2423		$body = $('body');
2424
2425	// constants
2426	var DATA_DISPLAYCONTROLLER = 'displayController',
2427		DATA_HASACTIVEHOVER = 'hasActiveHover',
2428		DATA_FORCEDOPEN = 'forcedOpen',
2429		DATA_HASMOUSEMOVE = 'hasMouseMove',
2430		DATA_MOUSEONTOTIP = 'mouseOnToPopup',
2431		DATA_ORIGINALTITLE = 'originalTitle',
2432		DATA_POWERTIP = 'powertip',
2433		DATA_POWERTIPJQ = 'powertipjq',
2434		DATA_POWERTIPTARGET = 'powertiptarget',
2435		RAD2DEG = 180 / Math.PI;
2436
2437	/**
2438	 * Session data
2439	 * Private properties global to all powerTip instances
2440	 */
2441	var session = {
2442		isTipOpen: false,
2443		isFixedTipOpen: false,
2444		isClosing: false,
2445		tipOpenImminent: false,
2446		activeHover: null,
2447		currentX: 0,
2448		currentY: 0,
2449		previousX: 0,
2450		previousY: 0,
2451		desyncTimeout: null,
2452		mouseTrackingActive: false,
2453		delayInProgress: false,
2454		windowWidth: 0,
2455		windowHeight: 0,
2456		scrollTop: 0,
2457		scrollLeft: 0
2458	};
2459
2460	/**
2461	 * Collision enumeration
2462	 * @enum {number}
2463	 */
2464	var Collision = {
2465		none: 0,
2466		top: 1,
2467		bottom: 2,
2468		left: 4,
2469		right: 8
2470	};
2471
2472	/**
2473	 * Display hover tooltips on the matched elements.
2474	 * @param {(Object|string)} opts The options object to use for the plugin, or
2475	 *     the name of a method to invoke on the first matched element.
2476	 * @param {*=} [arg] Argument for an invoked method (optional).
2477	 * @return {jQuery} jQuery object for the matched selectors.
2478	 */
2479	$.fn.powerTip = function(opts, arg) {
2480		// don't do any work if there were no matched elements
2481		if (!this.length) {
2482			return this;
2483		}
2484
2485		// handle api method calls on the plugin, e.g. powerTip('hide')
2486		if ($.type(opts) === 'string' && $.powerTip[opts]) {
2487			return $.powerTip[opts].call(this, this, arg);
2488		}
2489
2490		// extend options and instantiate TooltipController
2491		var options = $.extend({}, $.fn.powerTip.defaults, opts),
2492			tipController = new TooltipController(options);
2493
2494		// hook mouse and viewport dimension tracking
2495		initTracking();
2496
2497		// setup the elements
2498		this.each(function elementSetup() {
2499			var $this = $(this),
2500				dataPowertip = $this.data(DATA_POWERTIP),
2501				dataElem = $this.data(DATA_POWERTIPJQ),
2502				dataTarget = $this.data(DATA_POWERTIPTARGET),
2503				title;
2504
2505			// handle repeated powerTip calls on the same element by destroying the
2506			// original instance hooked to it and replacing it with this call
2507			if ($this.data(DATA_DISPLAYCONTROLLER)) {
2508				$.powerTip.destroy($this);
2509			}
2510
2511			// attempt to use title attribute text if there is no data-powertip,
2512			// data-powertipjq or data-powertiptarget. If we do use the title
2513			// attribute, delete the attribute so the browser will not show it
2514			title = $this.attr('title');
2515			if (!dataPowertip && !dataTarget && !dataElem && title) {
2516				$this.data(DATA_POWERTIP, title);
2517				$this.data(DATA_ORIGINALTITLE, title);
2518				$this.removeAttr('title');
2519			}
2520
2521			// create hover controllers for each element
2522			$this.data(
2523				DATA_DISPLAYCONTROLLER,
2524				new DisplayController($this, options, tipController)
2525			);
2526		});
2527
2528		// attach events to matched elements if the manual options is not enabled
2529		if (!options.manual) {
2530			this.on({
2531				// mouse events
2532				'mouseenter.powertip': function elementMouseEnter(event) {
2533					$.powerTip.show(this, event);
2534				},
2535				'mouseleave.powertip': function elementMouseLeave() {
2536					$.powerTip.hide(this);
2537				},
2538				// keyboard events
2539				'focus.powertip': function elementFocus() {
2540					$.powerTip.show(this);
2541				},
2542				'blur.powertip': function elementBlur() {
2543					$.powerTip.hide(this, true);
2544				},
2545				'keydown.powertip': function elementKeyDown(event) {
2546					// close tooltip when the escape key is pressed
2547					if (event.keyCode === 27) {
2548						$.powerTip.hide(this, true);
2549					}
2550				}
2551			});
2552		}
2553
2554		return this;
2555	};
2556
2557	/**
2558	 * Default options for the powerTip plugin.
2559	 */
2560	$.fn.powerTip.defaults = {
2561		fadeInTime: 200,
2562		fadeOutTime: 100,
2563		followMouse: false,
2564		popupId: 'powerTip',
2565		intentSensitivity: 7,
2566		intentPollInterval: 100,
2567		closeDelay: 100,
2568		placement: 'n',
2569		smartPlacement: false,
2570		offset: 10,
2571		mouseOnToPopup: false,
2572		manual: false
2573	};
2574
2575	/**
2576	 * Default smart placement priority lists.
2577	 * The first item in the array is the highest priority, the last is the lowest.
2578	 * The last item is also the default, which will be used if all previous options
2579	 * do not fit.
2580	 */
2581	$.fn.powerTip.smartPlacementLists = {
2582		n: ['n', 'ne', 'nw', 's'],
2583		e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'],
2584		s: ['s', 'se', 'sw', 'n'],
2585		w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'],
2586		nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
2587		ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
2588		sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
2589		se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
2590		'nw-alt': ['nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e'],
2591		'ne-alt': ['ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w'],
2592		'sw-alt': ['sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e'],
2593		'se-alt': ['se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w']
2594	};
2595
2596	/**
2597	 * Public API
2598	 */
2599	$.powerTip = {
2600		/**
2601		 * Attempts to show the tooltip for the specified element.
2602		 * @param {jQuery|Element} element The element to open the tooltip for.
2603		 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
2604		 *     tracking (optional).
2605		 */
2606		show: function apiShowTip(element, event) {
2607			if (event) {
2608				trackMouse(event);
2609				session.previousX = event.pageX;
2610				session.previousY = event.pageY;
2611				$(element).data(DATA_DISPLAYCONTROLLER).show();
2612			} else {
2613				$(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
2614			}
2615			return element;
2616		},
2617
2618		/**
2619		 * Repositions the tooltip on the element.
2620		 * @param {jQuery|Element} element The element the tooltip is shown for.
2621		 */
2622		reposition: function apiResetPosition(element) {
2623			$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
2624			return element;
2625		},
2626
2627		/**
2628		 * Attempts to close any open tooltips.
2629		 * @param {(jQuery|Element)=} element The element with the tooltip that
2630		 *     should be closed (optional).
2631		 * @param {boolean=} immediate Disable close delay (optional).
2632		 */
2633		hide: function apiCloseTip(element, immediate) {
2634			if (element) {
2635				$(element).first().data(DATA_DISPLAYCONTROLLER).hide(immediate);
2636			} else {
2637				if (session.activeHover) {
2638					session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
2639				}
2640			}
2641			return element;
2642		},
2643
2644		/**
2645		 * Destroy and roll back any powerTip() instance on the specified element.
2646		 * @param {jQuery|Element} element The element with the powerTip instance.
2647		 */
2648		destroy: function apiDestroy(element) {
2649			$(element).off('.powertip').each(function destroy() {
2650				var $this = $(this),
2651					dataAttributes = [
2652						DATA_ORIGINALTITLE,
2653						DATA_DISPLAYCONTROLLER,
2654						DATA_HASACTIVEHOVER,
2655						DATA_FORCEDOPEN
2656					];
2657
2658				if ($this.data(DATA_ORIGINALTITLE)) {
2659					$this.attr('title', $this.data(DATA_ORIGINALTITLE));
2660					dataAttributes.push(DATA_POWERTIP);
2661				}
2662
2663				$this.removeData(dataAttributes);
2664			});
2665			return element;
2666		}
2667	};
2668
2669	// API aliasing
2670	$.powerTip.showTip = $.powerTip.show;
2671	$.powerTip.closeTip = $.powerTip.hide;
2672
2673	/**
2674	 * Creates a new CSSCoordinates object.
2675	 * @private
2676	 * @constructor
2677	 */
2678	function CSSCoordinates() {
2679		var me = this;
2680
2681		// initialize object properties
2682		me.top = 'auto';
2683		me.left = 'auto';
2684		me.right = 'auto';
2685		me.bottom = 'auto';
2686
2687		/**
2688		 * Set a property to a value.
2689		 * @private
2690		 * @param {string} property The name of the property.
2691		 * @param {number} value The value of the property.
2692		 */
2693		me.set = function(property, value) {
2694			if ($.isNumeric(value)) {
2695				me[property] = Math.round(value);
2696			}
2697		};
2698	}
2699
2700	/**
2701	 * Creates a new tooltip display controller.
2702	 * @private
2703	 * @constructor
2704	 * @param {jQuery} element The element that this controller will handle.
2705	 * @param {Object}
2705 options Options object containing settings.
2706	 * @param {TooltipController} tipController The TooltipController object for
2707	 *     this instance.
2708	 */
2709	function DisplayController(element, options, tipController) {
2710		var hoverTimer = null;
2711
2712		/**
2713		 * Begins the process of showing a tooltip.
2714		 * @private
2715		 * @param {boolean=} immediate Skip intent testing (optional).
2716		 * @param {boolean=} forceOpen Ignore cursor position and force tooltip to
2717		 *     open (optional).
2718		 */
2719		function openTooltip(immediate, forceOpen) {
2720			cancelTimer();
2721			if (!element.data(DATA_HASACTIVEHOVER)) {
2722				if (!immediate) {
2723					session.tipOpenImminent = true;
2724					hoverTimer = setTimeout(
2725						function intentDelay() {
2726							hoverTimer = null;
2727							checkForIntent();
2728						},
2729						options.intentPollInterval
2730					);
2731				} else {
2732					if (forceOpen) {
2733						element.data(DATA_FORCEDOPEN, true);
2734					}
2735					tipController.showTip(element);
2736				}
2737			}
2738		}
2739
2740		/**
2741		 * Begins the process of closing a tooltip.
2742		 * @private
2743		 * @param {boolean=} disableDelay Disable close delay (optional).
2744		 */
2745		function closeTooltip(disableDelay) {
2746			cancelTimer();
2747			session.tipOpenImminent = false;
2748			if (element.data(DATA_HASACTIVEHOVER)) {
2749				element.data(DATA_FORCEDOPEN, false);
2750				if (!disableDelay) {
2751					session.delayInProgress = true;
2752					hoverTimer = setTimeout(
2753						function closeDelay() {
2754							hoverTimer = null;
2755							tipController.hideTip(element);
2756							session.delayInProgress = false;
2757						},
2758						options.closeDelay
2759					);
2760				} else {
2761					tipController.hideTip(element);
2762				}
2763			}
2764		}
2765
2766		/**
2767		 * Checks mouse position to make sure that the user intended to hover on the
2768		 * specified element before showing the tooltip.
2769		 * @private
2770		 */
2771		function checkForIntent() {
2772			// calculate mouse position difference
2773			var xDifference = Math.abs(session.previousX - session.currentX),
2774				yDifference = Math.abs(session.previousY - session.currentY),
2775				totalDifference = xDifference + yDifference;
2776
2777			// check if difference has passed the sensitivity threshold
2778			if (totalDifference < options.intentSensitivity) {
2779				tipController.showTip(element);
2780			} else {
2781				// try again
2782				session.previousX = session.currentX;
2783				session.previousY = session.currentY;
2784				openTooltip();
2785			}
2786		}
2787
2788		/**
2789		 * Cancels active hover timer.
2790		 * @private
2791		 */
2792		function cancelTimer() {
2793			hoverTimer = clearTimeout(hoverTimer);
2794			session.delayInProgress = false;
2795		}
2796
2797		/**
2798		 * Repositions the tooltip on this element.
2799		 * @private
2800		 */
2801		function repositionTooltip() {
2802			tipController.resetPosition(element);
2803		}
2804
2805		// expose the methods
2806		this.show = openTooltip;
2807		this.hide = closeTooltip;
2808		this.cancel = cancelTimer;
2809		this.resetPosition = repositionTooltip;
2810	}
2811
2812	/**
2813	 * Creates a new Placement Calculator.
2814	 * @private
2815	 * @constructor
2816	 */
2817	function PlacementCalculator() {
2818		/**
2819		 * Compute the CSS position to display a tooltip at the specified placement
2820		 * relative to the specified element.
2821		 * @private
2822		 * @param {jQuery} element The element that the tooltip should target.
2823		 * @param {string} placement The placement for the tooltip.
2824		 * @param {number} tipWidth Width of the tooltip element in pixels.
2825		 * @param {number} tipHeight Height of the tooltip element in pixels.
2826		 * @param {number} offset Distance to offset tooltips in pixels.
2827		 * @return {CSSCoordinates} A CSSCoordinates object with the position.
2828		 */
2829		function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) {
2830			var placementBase = placement.split('-')[0], // ignore 'alt' for corners
2831				coords = new CSSCoordinates(),
2832				position;
2833
2834			if (isSvgElement(element)) {
2835				position = getSvgPlacement(element, placementBase);
2836			} else {
2837				position = getHtmlPlacement(element, placementBase);
2838			}
2839
2840			// calculate the appropriate x and y position in the document
2841			switch (placement) {
2842			case 'n':
2843				coords.set('left', position.left - (tipWidth / 2));
2844				coords.set('bottom', session.windowHeight - position.top + offset);
2845				break;
2846			case 'e':
2847				coords.set('left', position.left + offset);
2848				coords.set('top', position.top - (tipHeight / 2));
2849				break;
2850			case 's':
2851				coords.set('left', position.left - (tipWidth / 2));
2852				coords.set('top', position.top + offset);
2853				break;
2854			case 'w':
2855				coords.set('top', position.top - (tipHeight / 2));
2856				coords.set('right', session.windowWidth - position.left + offset);
2857				break;
2858			case 'nw':
2859				coords.set('bottom', session.windowHeight - position.top + offset);
2860				coords.set('right', session.windowWidth - position.left - 20);
2861				break;
2862			case 'nw-alt':
2863				coords.set('left', position.left);
2864				coords.set('bottom', session.windowHeight - position.top + offset);
2865				break;
vendor: 4,428 bytes, lines 2866-3032
2866			case 'ne':
2867				coords.set('left', position.left - 20);
2868				coords.set('bottom', session.windowHeight - position.top + offset);
2869				break;
2870			case 'ne-alt':
2871				coords.set('bottom', session.windowHeight - position.top + offset);
2872				coords.set('right', session.windowWidth - position.left);
2873				break;
2874			case 'sw':
2875				coords.set('top', position.top + offset);
2876				coords.set('right', session.windowWidth - position.left - 20);
2877				break;
2878			case 'sw-alt':
2879				coords.set('left', position.left);
2880				coords.set('top', position.top + offset);
2881				break;
2882			case 'se':
2883				coords.set('left', position.left - 20);
2884				coords.set('top', position.top + offset);
2885				break;
2886			case 'se-alt':
2887				coords.set('top', position.top + offset);
2888				coords.set('right', session.windowWidth - position.left);
2889				break;
2890			}
2891
2892			return coords;
2893		}
2894
2895		/**
2896		 * Finds the tooltip attachment point in the document for a HTML DOM element
2897		 * for the specified placement.
2898		 * @private
2899		 * @param {jQuery} element The element that the tooltip should target.
2900		 * @param {string} placement The placement for the tooltip.
2901		 * @return {Object} An object with the top,left position values.
2902		 */
2903		function getHtmlPlacement(element, placement) {
2904			var objectOffset = element.offset(),
2905				objectWidth = element.outerWidth(),
2906				objectHeight = element.outerHeight(),
2907				left,
2908				top;
2909
2910			// calculate the appropriate x and y position in the document
2911			switch (placement) {
2912			case 'n':
2913				left = objectOffset.left + objectWidth / 2;
2914				top = objectOffset.top;
2915				break;
2916			case 'e':
2917				left = objectOffset.left + objectWidth;
2918				top = objectOffset.top + objectHeight / 2;
2919				break;
2920			case 's':
2921				left = objectOffset.left + objectWidth / 2;
2922				top = objectOffset.top + objectHeight;
2923				break;
2924			case 'w':
2925				left = objectOffset.left;
2926				top = objectOffset.top + objectHeight / 2;
2927				break;
2928			case 'nw':
2929				left = objectOffset.left;
2930				top = objectOffset.top;
2931				break;
2932			case 'ne':
2933				left = objectOffset.left + objectWidth;
2934				top = objectOffset.top;
2935				break;
2936			case 'sw':
2937				left = objectOffset.left;
2938				top = objectOffset.top + objectHeight;
2939				break;
2940			case 'se':
2941				left = objectOffset.left + objectWidth;
2942				top = objectOffset.top + objectHeight;
2943				break;
2944			}
2945
2946			return {
2947				top: top,
2948				left: left
2949			};
2950		}
2951
2952		/**
2953		 * Finds the tooltip attachment point in the document for a SVG element for
2954		 * the specified placement.
2955		 * @private
2956		 * @param {jQuery} element The element that the tooltip should target.
2957		 * @param {string} placement The placement for the tooltip.
2958		 * @return {Object} An object with the top,left position values.
2959		 */
2960		function getSvgPlacement(element, placement) {
2961			var svgElement = element.closest('svg')[0],
2962				domElement = element[0],
2963				point = svgElement.createSVGPoint(),
2964				boundingBox = domElement.getBBox(),
2965				matrix = domElement.getScreenCTM(),
2966				halfWidth = boundingBox.width / 2,
2967				halfHeight = boundingBox.height / 2,
2968				placements = [],
2969				placementKeys = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'],
2970				coords,
2971				rotation,
2972				steps,
2973				x;
2974
2975			function pushPlacement() {
2976				placements.push(point.matrixTransform(matrix));
2977			}
2978
2979			// get bounding box corners and midpoints
2980			point.x = boundingBox.x;
2981			point.y = boundingBox.y;
2982			pushPlacement();
2983			point.x += halfWidth;
2984			pushPlacement();
2985			point.x += halfWidth;
2986			pushPlacement();
2987			point.y += halfHeight;
2988			pushPlacement();
2989			point.y += halfHeight;
2990			pushPlacement();
2991			point.x -= halfWidth;
2992			pushPlacement();
2993			point.x -= halfWidth;
2994			pushPlacement();
2995			point.y -= halfHeight;
2996			pushPlacement();
2997
2998			// determine rotation
2999			if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) {
3000				rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG;
3001				steps = Math.ceil(((rotation % 360) - 22.5) / 45);
3002				if (steps < 1) {
3003					steps += 8;
3004				}
3005				while (steps--) {
3006					placementKeys.push(placementKeys.shift());
3007				}
3008			}
3009
3010			// find placement
3011			for (x = 0; x < placements.length; x++) {
3012				if (placementKeys[x] === placement) {
3013					coords = placements[x];
3014					break;
3015				}
3016			}
3017
3018			return {
3019				top: coords.y + session.scrollTop,
3020				left: coords.x + session.scrollLeft
3021			};
3022		}
3023
3024		// expose methods
3025		this.compute = computePlacementCoords;
3026	}
3027
3028	/**
3029	 * Creates a new tooltip controller.
3030	 * @private
3031	 * @constructor
3032	 * @param {Object}
3032 options Options object containing settings.
3033	 */
3034	function TooltipController(options) {
3035		var placementCalculator = new PlacementCalculator(),
3036			tipElement = $('#' + options.popupId);
3037
3038		// build and append tooltip div if it does not already exist
3039		if (tipElement.length === 0) {
3040			tipElement = $('<div/>', { id: options.popupId });
3041			// grab body element if it was not populated when the script loaded
3042			// note: this hack exists solely for jsfiddle support
3043			if ($body.length === 0) {
3044				$body = $('body');
3045			}
3046			$body.append(tipElement);
3047		}
3048
3049		// hook mousemove for cursor follow tooltips
3050		if (options.followMouse) {
3051			// only one positionTipOnCursor hook per tooltip element, please
3052			if (!tipElement.data(DATA_HASMOUSEMOVE)) {
3053				$document.on('mousemove', positionTipOnCursor);
3054				$window.on('scroll', positionTipOnCursor);
3055				tipElement.data(DATA_HASMOUSEMOVE, true);
3056			}
3057		}
3058
3059		// if we want to be able to mouse onto the tooltip then we need to attach
3060		// hover events to the tooltip that will cancel a close request on hover and
3061		// start a new close request on mouseleave
3062		if (options.mouseOnToPopup) {
3063			tipElement.on({
3064				mouseenter: function tipMouseEnter() {
3065					// we only let the mouse stay on the tooltip if it is set to let
3066					// users interact with it
3067					if (tipElement.data(DATA_MOUSEONTOTIP)) {
3068						// check activeHover in case the mouse cursor entered the
3069						// tooltip during the fadeOut and close cycle
3070						if (session.activeHover) {
3071							session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
3072						}
3073					}
3074				},
3075				mouseleave: function tipMouseLeave() {
3076					// check activeHover in case the mouse cursor entered the
3077					// tooltip during the fadeOut and close cycle
3078					if (session.activeHover) {
3079						session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
3080					}
3081				}
3082			});
3083		}
3084
3085		/**
3086		 * Gives the specified element the active-hover state and queues up the
3087		 * showTip function.
3088		 * @private
3089		 * @param {jQuery} element The element that the tooltip should target.
3090		 */
3091		function beginShowTip(element) {
3092			element.data(DATA_HASACTIVEHOVER, true);
3093			// show tooltip, asap
3094			tipElement.queue(function queueTipInit(next) {
3095				showTip(element);
3096				next();
3097			});
3098		}
3099
3100		/**
3101		 * Shows the tooltip, as soon as possible.
3102		 * @private
3103		 * @param {jQuery} element The element that the tooltip should target.
3104		 */
3105		function showTip(element) {
3106			var tipContent;
3107
3108			// it is possible, especially with keyboard navigation, to move on to
3109			// another element with a tooltip during the queue to get to this point
3110			// in the code. if that happens then we need to not proceed or we may
3111			// have the fadeout callback for the last tooltip execute immediately
3112			// after this code runs, causing bugs.
3113			if (!element.data(DATA_HASACTIVEHOVER)) {
3114				return;
3115			}
3116
3117			// if the tooltip is open and we got asked to open another one then the
3118			// old one is still in its fadeOut cycle, so wait and try again
3119			if (session.isTipOpen) {
3120				if (!session.isClosing) {
3121					hideTip(session.activeHover);
3122				}
3123				tipElement.delay(100).queue(function queueTipAgain(next) {
3124					showTip(element);
3125					next();
3126				});
3127				return;
3128			}
3129
3130			// trigger powerTipPreRender event
3131			element.trigger('powerTipPreRender');
3132
3133			// set tooltip content
3134			tipContent = getTooltipContent(element);
3135			if (tipContent) {
3136				tipElement.empty().append(tipContent);
3137			} else {
3138				// we have no content to display, give up
3139				return;
3140			}
3141
3142			// trigger powerTipRender event
3143			element.trigger('powerTipRender');
3144
3145			session.activeHover = element;
3146			session.isTipOpen = true;
3147
3148			tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);
3149
3150			// set tooltip position
3151			if (!options.followMouse) {
3152				positionTipOnElement(element);
3153				session.isFixedTipOpen = true;
3154			} else {
3155				positionTipOnCursor();
3156			}
3157
3158			// fadein
3159			tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
3160				// start desync polling
3161				if (!session.desyncTimeout) {
3162					session.desyncTimeout = setInterval(closeDesyncedTip, 500);
3163				}
3164
3165				// trigger powerTipOpen event
3166				element.trigger('powerTipOpen');
3167			});
3168		}
3169
3170		/**
3171		 * Hides the tooltip.
3172		 * @private
3173		 * @param {jQuery} element The element that the tooltip should target.
3174		 */
3175		function hideTip(element) {
3176			// reset session
3177			session.isClosing = true;
3178			session.activeHover = null;
3179			session.isTipOpen = false;
3180
3181			// stop desync polling
3182			session.desyncTimeout = clearInterval(session.desyncTimeout);
3183
3184			// reset element state
3185			element.data(DATA_HASACTIVEHOVER, false);
3186			element.data(DATA_FORCEDOPEN, false);
3187
3188			// fade out
3189			tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
3190				var coords = new CSSCoordinates();
3191
3192				// reset session and tooltip element
3193				session.isClosing = false;
3194				session.isFixedTipOpen = false;
3195				tipElement.removeClass();
3196
3197				// support mouse-follow and fixed position tips at the same time by
3198				// moving the tooltip to the last cursor location after it is hidden
3199				coords.set('top', session.currentY + options.offset);
3200				coords.set('left', session.currentX + options.offset);
3201				tipElement.css(coords);
3202
3203				// trigger powerTipClose event
3204				element.trigger('powerTipClose');
3205			});
3206		}
3207
3208		/**
3209		 * Moves the tooltip to the users mouse cursor.
3210		 * @private
3211		 */
3212		function positionTipOnCursor() {
3213			// to support having fixed tooltips on the same page as cursor tooltips,
3214			// where both instances are referencing the same tooltip element, we
3215			// need to keep track of the mouse position constantly, but we should
3216			// only set the tip location if a fixed tip is not currently open, a tip
3217			// open is imminent or active, and the tooltip element in question does
3218			// have a mouse-follow using it.
3219			if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
3220				// grab measurements
3221				var tipWidth = tipElement.outerWidth(),
3222					tipHeight = tipElement.outerHeight(),
3223					coords = new CSSCoordinates(),
3224					collisions,
3225					collisionCount;
3226
3227				// grab collisions
3228				coords.set('top', session.currentY + options.offset);
3229				coords.set('left', session.currentX + options.offset);
3230				collisions = getViewportCollisions(
3231					coords,
3232					tipWidth,
3233					tipHeight
3234				);
3235
3236				// handle tooltip view port collisions
3237				if (collisions !== Collision.none) {
3238					collisionCount = countFlags(collisions);
3239					if (collisionCount === 1) {
3240						// if there is only one collision (bottom or right) then
3241						// simply constrain the tooltip to the view port
3242						if (collisions === Collision.right) {
3243							coords.set('left', session.windowWidth - tipWidth);
3244						} else if (collisions === Collision.bottom) {
3245							coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
3246						}
3247					} else {
3248						// if the tooltip has more than one collision then it is
3249						// trapped in the corner and should be flipped to get it out
3250						// of the users way
3251						coords.set('left', session.currentX - tipWidth - options.offset);
3252						coords.set('top', session.currentY - tipHeight - options.offset);
3253					}
3254				}
3255
3256				// position the tooltip
3257				tipElement.css(coords);
3258			}
3259		}
3260
3261		/**
3262		 * Sets the tooltip to the correct position relative to the specified target
3263		 * element. Based on options settings.
3264		 * @private
3265		 * @param {jQuery} element The element that the tooltip should target.
3266		 */
3267		function positionTipOnElement(element) {
3268			var priorityList,
3269				finalPlacement;
3270
3271			if (options.smartPlacement) {
3272				priorityList = $.fn.powerTip.smartPlacementLists[options.placement];
3273
3274				// iterate over the priority list and use the first placement option
3275				// that does not collide with the view port. if they all collide
3276				// then the last placement in the list will be used.
3277				$.each(priorityList, function(idx, pos) {
3278					// place tooltip and find collisions
3279					var collisions = getViewportCollisions(
3280						placeTooltip(element, pos),
3281						tipElement.outerWidth(),
3282						tipElement.outerHeight()
3283					);
3284
3285					// update the final placement variable
3286					finalPlacement = pos;
3287
3288					// break if there were no collisions
3289					if (collisions === Collision.none) {
3290						return false;
3291					}
3292				});
3293			} else {
3294				// if we're not going to use the smart placement feature then just
3295				// compute the coordinates and do it
3296				placeTooltip(element, options.placement);
3297				finalPlacement = options.placement;
3298			}
3299
3300			// add placement as class for CSS arrows
3301			tipElement.addClass(finalPlacement);
3302		}
3303
3304		/**
3305		 * Sets the tooltip position to the appropriate values to show the tip at
3306		 * the specified placement. This function will iterate and test the tooltip
3307		 * to support elastic tooltips.
3308		 * @private
3309		 * @param {jQuery} element The element that the tooltip should target.
3310		 * @param {string} placement The placement for the tooltip.
3311		 * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
3312		 *     right position values.
3313		 */
3314		function placeTooltip(element, placement) {
3315			var iterationCount = 0,
3316				tipWidth,
3317				tipHeight,
3318				coords = new CSSCoordinates();
3319
3320			// set the tip to 0,0 to get the full expanded width
3321			coords.set('top', 0);
3322			coords.set('left', 0);
3323			tipElement.css(coords);
3324
3325			// to support elastic tooltips we need to check for a change in the
3326			// rendered dimensions after the tooltip has been positioned
3327			do {
3328				// grab the current tip dimensions
3329				tipWidth = tipElement.outerWidth();
3330				tipHeight = tipElement.outerHeight();
3331
3332				// get placement coordinates
3333				coords = placementCalculator.compute(
3334					element,
3335					placement,
3336					tipWidth,
3337					tipHeight,
3338					options.offset
3339				);
3340
3341				// place the tooltip
3342				tipElement.css(coords);
3343			} while (
3344				// sanity check: limit to 5 iterations, and...
3345				++iterationCount <= 5 &&
3346				// try again if the dimensions changed after placement
3347				(tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
3348			);
3349
3350			return coords;
3351		}
3352
3353		/**
3354		 * Checks for a tooltip desync and closes the tooltip if one occurs.
3355		 * @private
3356		 */
3357		function closeDesyncedTip() {
3358			var isDesynced = false;
3359			// It is possible for the mouse cursor to leave an element without
3360			// firing the mouseleave or blur event. This most commonly happens when
3361			// the element is disabled under mouse cursor. If this happens it will
3362			// result in a desynced tooltip because the tooltip was never asked to
3363			// close. So we should periodically check for a desync situation and
3364			// close the tip if such a situation arises.
3365			if (session.isTipOpen && !session.isClosing && !session.delayInProgress) {
3366				// user moused onto another tip or active hover is disabled
3367				if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
3368					isDesynced = true;
3369				} else {
3370					// hanging tip - have to test if mouse position is not over the
3371					// active hover and not over a tooltip set to let the user
3372					// interact with it.
3373					// for keyboard navigation: this only counts if the element does
3374					// not have focus.
3375					// for tooltips opened via the api: we need to check if it has
3376					// the forcedOpen flag.
3377					if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
3378						if (tipElement.data(DATA_MOUSEONTOTIP)) {
3379							if (!isMouseOver(tipElement)) {
3380								isDesynced = true;
3381							}
3382						} else {
3383							isDesynced = true;
3384						}
3385					}
3386				}
3387
3388				if (isDesynced) {
3389					// close the desynced tip
3390					hideTip(session.activeHover);
3391				}
3392			}
3393		}
3394
3395		// expose methods
3396		this.showTip = beginShowTip;
3397		this.hideTip = hideTip;
3398		this.resetPosition = positionTipOnElement;
3399	}
3400
3401	/**
3402	 * Determine whether a jQuery object is an SVG element
3403	 * @private
3404	 * @param {jQuery} element The element to check
3405	 * @return {boolean} Whether this is an SVG element
3406	 */
3407	function isSvgElement(element) {
3408		return window.SVGElement && element[0] instanceof SVGElement;
3409	}
3410
3411	/**
3412	 * Initializes the viewport dimension cache and hooks up the mouse position
3413	 * tracking and viewport dimension tracking events.
3414	 * Prevents attaching the events more than once.
3415	 * @private
3416	 */
3417	function initTracking() {
3418		if (!session.mouseTrackingActive) {
3419			session.mouseTrackingActive = true;
3420
3421			// grab the current viewport dimensions on load
3422			$(function getViewportDimensions() {
3423				session.scrollLeft = $window.scrollLeft();
3424				session.scrollTop = $window.scrollTop();
3425				session.windowWidth = $window.width();
3426				session.windowHeight = $window.height();
3427			});
3428
3429			// hook mouse move tracking
3430			$document.on('mousemove', trackMouse);
3431
3432			// hook viewport dimensions tracking
3433			$window.on({
3434				resize: function trackResize() {
3435					session.windowWidth = $window.width();
3436					session.windowHeight = $window.height();
3437				},
3438				scroll: function trackScroll() {
3439					var x = $window.scrollLeft(),
3440						y = $window.scrollTop();
3441					if (x !== session.scrollLeft) {
3442						session.currentX += x - session.scrollLeft;
3443						session.scrollLeft = x;
3444					}
3445					if (y !== session.scrollTop) {
3446						session.currentY += y - session.scrollTop;
3447						session.scrollTop = y;
3448					}
3449				}
3450			});
3451		}
3452	}
3453
3454	/**
3455	 * Saves the current mouse coordinates to the session object.
3456	 * @private
3457	 * @param {jQuery.Event} event The mousemove event for the document.
3458	 */
3459	function trackMouse(event) {
3460		session.currentX = event.pageX;
3461		session.currentY = event.pageY;
3462	}
3463
3464	/**
3465	 * Tests if the mouse is currently over the specified element.
3466	 * @private
3467	 * @param {jQuery} element The element to check for hover.
3468	 * @return {boolean}
3469	 */
3470	function isMouseOver(element) {
3471		// use getBoundingClientRect() because jQuery's width() and height()
3472		// methods do not work with SVG elements
3473		// compute width/height because those properties do not exist on the object
3474		// returned by getBoundingClientRect() in older versions of IE
3475		var elementPosition = element.offset(),
3476			elementBox = element[0].getBoundingClientRect(),
3477			elementWidth = elementBox.right - elementBox.left,
3478			elementHeight = elementBox.bottom - elementBox.top;
3479
3480		return session.currentX >= elementPosition.left &&
3481			session.currentX <= elementPosition.left + elementWidth &&
3482			session.currentY >= elementPosition.top &&
3483			session.currentY <= elementPosition.top + elementHeight;
3484	}
3485
3486	/**
3487	 * Fetches the tooltip content from the specified element's data attributes.
3488	 * @private
3489	 * @param {jQuery} element The element to get the tooltip content for.
3490	 * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
3491	 *     undefined if there was no tooltip content for the element.
3492	 */
3493	function getTooltipContent(element) {
3494		var tipText = element.data(DATA_POWERTIP),
3495			tipObject = element.data(DATA_POWERTIPJQ),
3496			tipTarget = element.data(DATA_POWERTIPTARGET),
3497			targetElement,
3498			content;
3499
3500		if (tipText) {
3501			if ($.isFunction(tipText)) {
3502				tipText = tipText.call(element[0]);
3503			}
3504			content = tipText;
3505		} else if (tipObject) {
3506			if ($.isFunction(tipObject)) {
3507				tipObject = tipObject.call(element[0]);
3508			}
3509			if (tipObject.length > 0) {
3510				content = tipObject.clone(true, true);
3511			}
3512		} else if (tipTarget) {
3513			targetElement = $('#' + tipTarget);
3514			if (targetElement.length > 0) {
3515				content = targetElement.html();
3516			}
3517		}
3518
3519		return content;
3520	}
3521
3522	/**
3523	 * Finds any viewport collisions that an element (the tooltip) would have if it
3524	 * were absolutely positioned at the specified coordinates.
3525	 * @private
3526	 * @param {CSSCoordinates} coords Coordinates for the element.
3527	 * @param {number} elementWidth Width of the element in pixels.
3528	 * @param {number} elementHeight Height of the element in pixels.
3529	 * @return {number} Value with the collision flags.
3530	 */
3531	function getViewportCollisions(coords, elementWidth, elementHeight) {
3532		var viewportTop = session.scrollTop,
3533			viewportLeft =  session.scrollLeft,
3534			viewportBottom = viewportTop + session.windowHeight,
3535			viewportRight = viewportLeft + session.windowWidth,
3536			collisions = Collision.none;
3537
3538		if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
3539			collisions |= Collision.top;
3540		}
3541		if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
3542			collisions |= Collision.bottom;
3543		}
3544		if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
3545			collisions |= Collision.left;
3546		}
3547		if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
3548			collisions |= Collision.right;
3549		}
3550
3551		return collisions;
3552	}
3553
3554	/**
3555	 * Counts the number of bits set on a flags value.
3556	 * @param {number} value The flags value.
3557	 * @return {number} The number of bits that have been set.
3558	 */
3559	function countFlags(value) {
3560		var count = 0;
3561		while (value) {
3562			value &= value - 1;
3563			count++;
3564		}
3565		return count;
3566	}
3567
3568}));
3569;
3570/*
3571 * Custom v1.0
3572 * DesignOrbital.com
3573 *
3574 * Copyright (c) 2013-2014 DesignOrbital.com
3575 *
3576 * License: GNU General Public License v2 or later
3577 * http://www.gnu.org/licenses/gpl-2.0.html
3578 *
3579 */
3580
3581( function( $ ) {
3582
3583	var candela = {
3584
3585		// Menu
3586		menuInit: function() {
3587
3588			/*
3589			 * Add dropdown toggle that display child menu items.
3590			 */
3591			$( '.site-primary-menu .page_item_has_children > a, .site-primary-menu .menu-item-has-children > a, .sidebar .widget_pages .page_item_has_children > a, .sidebar .widget_nav_menu .menu-item-has-children > a' ).append( '<button class="dropdown-toggle fa" aria-expanded="false"/>' );
3592		
3593			$( '.dropdown-toggle' ).click( function( e ) {
3594				e.preventDefault();
3595				$( this ).toggleClass( 'toggle-on' );
3596				$( this ).parent().next( '.children, .sub-menu' ).toggleClass( 'toggle-on' );
3597				$( this ).attr( 'aria-expanded', $( this ).attr( 'aria-expanded' ) == 'false' ? 'true' : 'false');
3598			} );
3599			
3600		},
3601		
3602		// Open Slide Panel - Menu Or Sidebar
3603		slidePanelInit: function() {			
3604			
3605			// Elements
3606			var nav = $( '.main-navigation' );
3607			var sidebar = $( '.sidebar-area' );
3608			var overlaySlide = $( '.overlay-slide' );
3609			
3610			// Main Navigation Slide
3611			$( '.toggle-menu' ).off( 'click' ).on( 'click', function( e ) {
3612				
3613				// Prevent Default
3614				e.preventDefault();
3615				e.stopPropagation();
3616				
3617				// ToggleClass
3618				nav.toggleClass( 'show' );
3619				overlaySlide.toggleClass( 'open' );
3620				
3621			} );
3622			
3623			// Sidebar Slide
3624			$( '.toggle-sidebar' ).off( 'click' ).on( 'click', function( e ) {
3625				
3626				// Prevent Default
3627				e.preventDefault();
3628				e.stopPropagation();
3629				
3630				// ToggleClass
3631				sidebar.toggleClass( 'show' );
3632				overlaySlide.toggleClass( 'open' );
3633				
3634			} );
3635			
3636			// Overlay Slide Close
3637			overlaySlide.off( 'click' ).on( 'click', function( e ) {				
3638				candela.slidePanelCloseInit();				
3639			} );
3640			
3641		},
3642		
3643		// Close Slide Panel - Menu Or Sidebar
3644		slidePanelCloseInit: function() {
3645			
3646			// Elements
3647			var nav = $( '.main-navigation' );
3648			var sidebar = $( '.sidebar-area' );
3649			var overlaySlide = $( '.overlay-slide' );
3650			
3651			// Slide Panel Close Logic
3652			if( overlaySlide.hasClass( 'open' ) ) {
3653						
3654				// For Menu
3655				if( nav.hasClass( 'show' ) ) {
3656					nav.toggleClass( 'show' );
3657				}
3658				
3659				// For Sidebar
3660				if( sidebar.hasClass( 'show' ) ) {
3661					sidebar.toggleClass( 'show' );
3662				}
3663				
3664				// Toggle Overlay Slide
3665				overlaySlide.toggleClass( 'open' );
3666					
3667			}
3668			
3669		},
3670		
3671		// Social Menu Widget
3672		socialWidgetInit: function() {
3673			
3674			// 'fa' class for Social Menu widget
3675			$( '.widget_nav_menu .menu-social-container' ).addClass( 'fa' );
3676			
3677			// Wrap Social Menu HTML
3678			$( '.widget_nav_menu .menu-social-container > ul > li > a' ).wrapInner( '<span class="screen-reader-text"></span>' );	
3679		
3680		},
3681		
3682		// Footer Widget Trigger
3683		widgetTriggerInit: function() {
3684		
3685			$( '.widgets-trigger' ).off( 'click' ).on( 'click', function( e ) {
3686			
3687				// Prevent Default
3688				e.preventDefault();
3689				
3690				// Toggle Class
3691				$( this ).toggleClass( 'active' );
3692				if( $( this ).hasClass( 'active' ) ) {
3693					
3694					$( '.site-footer-widgets-wrapper' ).slideDown( 250 );
3695					$( '.widgets-trigger .fa' ).removeClass( 'fa-plus' ).addClass( 'fa-minus' );
3696					
3697					// Scroll Top
3698					var timer;
3699					clearTimeout( timer );
3700					timer = setTimeout( function() {
3701						
3702						var offset = ( $( '.site-footer-widgets' ).offset().top ) - 50;						
3703						$('html, body').animate({					        
3704					        scrollTop: offset
3705					    }, 500);
3706					
3707					}, 250 );
3708				
3709				} else {
3710					
3711					$( '.site-footer-widgets-wrapper' ).slideUp( 250 );
3712					$( '.widgets-trigger .fa' ).removeClass( 'fa-minus' ).addClass( 'fa-plus' );
3713				
3714				}
3715				
3716			});
3717			
3718		},	
3719		
3720		// CSS3 Animation
3721		animationInit: function() {
3722
3723			// On Hover Effect
3724			$( '.img-postlist, .img-featured, .img-portfolio-featured' ).hover(
3725				function() {
3726					$( this ).addClass( 'opacity-in' );
3727				}, function() {
3728					$( this ).removeClass( 'opacity-in' );
3729				}
3730			);
3731
3732		},		
3733
3734	}
3735
3736	// Document Ready
3737	$( document ).ready( function() {
3738		
3739		// Menu
3740		candela.menuInit();
3741		
3742		// Sliding Panels for Menu and Sidebar
3743		candela.slidePanelInit();
3744		
3745		// Social Menu Widget
3746		candela.socialWidgetInit();
3747		
3748		// Footer Widget Trigger
3749		candela.widgetTriggerInit();		
3750		
3751		// CSS3 Animation
3752		candela.animationInit();
3753
3754		// Powertip
3755		$( '.no-touch .site-bar-controls-tooltip' ).powerTip( { 
3756			placement: 'n'
3757		} );
3758		
3759		// Scroll Up
3760		$.scrollUp({
3761            scrollDistance: 500,
3762            animation: 'slide',
3763            scrollText: '<span><i class="fa fa-chevron-up"></i></span>'
3764        });		
3765
3766	} );
3767	
3768	// Document Keyup
3769	$( document ).keyup( function( e ) {
3770	    
3771	    // Make the escape key to close the slide panel
3772	    if ( e.keyCode == 27 ) {	       
3773			candela.slidePanelCloseInit();	       
3774	    } 
3775	
3776	} );
3777
3778} )( jQuery );;
3779!function(){var e=document.currentScript;function t(t){var n=document.createElement("script"),o=e||document.getElementsByTagName("script")[0];n.setAttribute("async",!0),n.setAttribute("src",t),o.parentNode.insertBefore(n,o)}function n(e,t){return Element.prototype.matches?e.matches(t):Element.prototype.msMatchesSelector?e.msMatchesSelector(t):void 0}function o(e,t){if(e.closest)return e.closest(t);var o=e;do{if(n(o,t))return o;o=o.parentElement||o.parentNode}while(null!==o&&1===o.nodeType);return null}function i(e,t){for(var n=0;n<e.length;n++)t(e[n],n,e)}var r,s=".sharing-hidden .inner",a="data-sharing-more-button-id";function c(e){this.button=e,this.pane=o(e,"div").querySelector(s),this.openedBy=null,this.recentlyOpenedByHover=!1,c.instances.push(this),this.pane.setAttribute(a,c.instances.length-1),this.attachHandlers()}if(c.instances=[],c.hoverOpenDelay=200,c.recentOpenDelay=400,c.hoverCloseDelay=300,c.instantiateOrReuse=function(e){var t=o(e,"div").querySelector(s),n=t&&t.getAttribute(a),i=c.instances[n];return i||new c(e)},c.getButtonInstanceFromPane=function(e){var t=e&&e.getAttribute(a);return c.instances[t]},c.closeAll=function(){for(var e=0;e<c.instances.length;e++)c.instances[e].close()},c.prototype.open=function(){var e,t,n=[0,0];function o(e){var t=e.getBoundingClientRect();return[t.left+(window.scrollX||window.pageXOffset||0),t.top+(window.scrollY||window.pageYOffset||0)]}function i(e,t){return parseInt(getComputedStyle(e).getPropertyValue(t)||0)}for(e=o(this.button),t=this.button.offsetParent||document.documentElement;t&&(t===document.body||t===document.documentElement)&&"static"===getComputedStyle(t).getPropertyValue("position");)t=t.parentNode;t&&t!==this.button&&1===t.nodeType&&(n=[(n=o(t))[0]+i(t,"border-left-width"),n[1]+i(t,"border-top-width")]);var r,s=e[0]-n[0]-i(this.button,"margin-left"),a=e[1]-n[1]-i(this.button,"margin-top");this.pane.style.left=s+"px",this.pane.style.top=a+this.button.offsetHeight+3+"px",(r=this.pane)&&r.style.removeProperty("display")},c.prototype.close=function(){var e;(e=this.pane)&&(e.style.display="none"),this.openedBy=null},c.prototype.toggle=function(){var e;(e=this.pane)&&"none"!==e.style.display?this.close():this.open()},c.prototype.nonHoverOpen=function(){clearTimeout(this.openTimer),clearTimeout(this.closeTimer),this.recentlyOpenedByHover?(this.recentlyOpenedByHover=!1,clearTimeout(this.hoverOpenTimer),this.open()):this.toggle()},c.prototype.resetCloseTimer=function(){clearTimeout(this.closeTimer),this.closeTimer=setTimeout(this.close.bind(this),c.hoverCloseDelay)},c.prototype.attachHandlers=function(){this.buttonClick=function(e){e.preventDefault(),e.stopPropagation(),this.openedBy="click",this.nonHoverOpen()}.bind(this),this.buttonKeydown=function(e){13!==e.keyCode&&32!==e.keyCode||(e.preventDefault(),e.stopPropagation(),this.openedBy="keydown",this.nonHoverOpen())}.bind(this),this.buttonEnter=function(){this.openedBy||(this.openTimer=setTimeout(function(){this.open(),this.openedBy="hover",this.recentlyOpenedByHover=!0,this.hoverOpenTimer=setTimeout(function(){this.recentlyOpenedByHover=!1}.bind(this),c.recentOpenDelay)}.bind(this),c.hoverOpenDelay)),clearTimeout(this.closeTimer)}.bind(this),this.buttonLeave=function(){"hover"===this.openedBy&&this.resetCloseTimer(),clearTimeout(this.openTimer)}.bind(this),this.paneEnter=function(){clearTimeout(this.closeTimer)}.bind(this),this.paneLeave=function(){"hover"===this.openedBy&&this.resetCloseTimer()}.bind(this),this.documentClick=function(){this.close()}.bind(this),this.button.addEventListener("click",this.buttonClick),this.button.addEventListener("keydown",this.buttonKeydown),document.addEventListener("click",this.documentClick),void 0===document.ontouchstart&&(this.button.addEventListener("mouseenter",this.buttonEnter),this.button.addEventListener("mouseleave",this.buttonLeave),this.pane.addEventListener("mouseenter",this.paneEnter),this.pane.addEventListener("mouseleave",this.paneLeave))},window.sharing_js_options&&window.sharing_js_options.counts){var u={done_urls:[],get_counts:function(){var e,n,o,i,r;if("undefined"!=typeof WPCOM_sharing_counts)for(e in WPCOM_sharing_counts)if(o=WPCOM_sharing_counts[e],void 0===u.done_urls[o]){for(i in n={pinterest:[window.location.protocol+"//api.pinterest.com/v1/urls/count.json?callback=WPCOMSharing.update_pinterest_count&url="+encodeURIComponent(e)]})if(document.querySelector("a[data-shared=sharing-"+i+"-"+o+"]")){for(;r=n[i].pop();)t(r);window.sharing_js_options.is_stats_active&&u.bump_sharing_count_stat(i)}u.done_urls[o]=!0}},update_pinterest_count:function(e){void 0!==e.count&&1*e.count>0&&u.inject_share_count("sharing-pinterest-"+WPCOM_sharing_counts[e.url],e.count)},inject_share_count:function(e,t){i(document.querySelectorAll("a[data-shared="+e+"] > span"),function(e){var n,o=e.querySelector(".share-count");(n=o)&&n.parentNode&&n.parentNode.removeChild(n);var i=document.createElement("span");
3779i.className="share-count",i.textContent=u.format_count(t),e.appendChild(i)})},format_count:function(e){return e<1e3?e:e>=1e3&&e<1e4?String(e).substring(0,1)+"K+":"10K+"},bump_sharing_count_stat:function(e){(new Image).src=document.location.protocol+"//pixel.wp.com/g.gif?v=wpcom-no-pv&x_sharing-count-request="+e+"&r="+Math.random()}};window.WPCOMSharing=u}function d(e,t){e.setAttribute("jetpack-share-click-count",t)}function l(e){var t=e.getAttribute("jetpack-share-click-count");return null===t?0:parseInt(t,10)}function h(e,t){var n,o=new XMLHttpRequest;o.open("POST",e,!0),o.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"),o.setRequestHeader("x-requested-with","XMLHttpRequest"),o.send((n=t,(encodeURIComponent("email-share-nonce")+"="+encodeURIComponent(n)).replace(/%20/g,"+")))}function p(){m()}function m(){window.WPCOMSharing&&window.WPCOMSharing.get_counts(),i(document.querySelectorAll(".sharedaddy a"),function(e){var t=e.getAttribute("href");t&&-1!==t.indexOf("share=")&&-1===t.indexOf("&nb=1")&&e.setAttribute("href",t+"&nb=1")}),i(document.querySelectorAll(".sharedaddy a.sharing-anchor"),function(e){c.instantiateOrReuse(e)}),void 0!==document.ontouchstart&&document.body.classList.add("jp-sharing-input-touch"),i(document.querySelectorAll(".sharedaddy ul"),function(e){"true"!==e.getAttribute("data-sharing-events-added")&&(e.setAttribute("data-sharing-events-added","true"),i(e.querySelectorAll("a.share-print"),function(e){e.addEventListener("click",function(t){t.preventDefault(),t.stopPropagation();var n=e.getAttribute("href")||"",i=function(){if(-1===n.indexOf("#print")){var e=(new Date).getTime();t=e,o=n,(i=document.createElement("iframe")).setAttribute("style","position:fixed; top:100; left:100; height:1px; width:1px; border:none;"),i.setAttribute("id","printFrame-"+t),i.setAttribute("name",i.getAttribute("id")),i.setAttribute("src",o),i.setAttribute("onload",'frames["printFrame-'+t+'"].focus();frames["printFrame-'+t+'"].print();'),document.body.appendChild(i)}else window.print();var t,o,i},r=o(e,s);if(r){var a=c.getButtonInstanceFromPane(r);a&&(a.close(),i())}else i()})}),i(e.querySelectorAll("a.share-press-this"),function(e){var t="wp-press-this-"+Math.random().toString(36).slice(2);e.addEventListener("click",function(n){n.preventDefault(),n.stopPropagation();var o="";if(window.getSelection?o=window.getSelection():document.getSelection?o=document.getSelection():document.selection&&(o=document.selection.createRange().text),o){var i=e.getAttribute("href");e.setAttribute("href",i+"&sel="+encodeURI(o))}window.open(e.getAttribute("href"),t,"toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=570")||(document.location.href=e.getAttribute("href"))})}),i(e.querySelectorAll("a.share-email"),function(t){d(t,0);var n,o,r=t.getAttribute("data-email-share-nonce"),s=t.getAttribute("data-email-share-track-url");r&&s&&(n=s,o=window.location.protocol+"//"+window.location.hostname+"/",0===String(n).indexOf(o))&&t.addEventListener("click",function(){var n;d(n=t,l(n)+1),l(t)>2&&function(e,t){var n=t.parentElement;if(n.classList.contains("sd-content")){i(n.querySelectorAll(".share-email-error"),function(e){e.parentElement.removeChild(e)});var o=document.createElement("div");o.className="share-email-error";var r=document.createElement("h6");r.className="share-email-error-title",r.innerText=e.getAttribute("data-email-share-error-title"),o.appendChild(r);var s=document.createElement("p");s.className="share-email-error-text",s.innerText=e.getAttribute("data-email-share-error-text"),o.appendChild(s),n.appendChild(o)}}(t,e),h(s,r)})}))}),i(document.querySelectorAll("li.share-email, li.share-custom a.sharing-anchor"),function(e){e.classList.add("share-service-visible")})}"loading"!==document.readyState?p():document.addEventListener("DOMContentLoaded",p),document.body.addEventListener("is.post-load",m);var f={};!0!==window.WPCOM_sharing_popups_bound&&(document.body.addEventListener("click",function(e){var t=e.target;if(t&&1===t.nodeType){var n=function(e){if("A"===e.nodeName)return e;var t=e.parentNode;return t&&"A"===t.nodeName?t:null}(t);if(n){var o=function(e){var t=window.WPCOM_sharing_popups;if(!t)return null;for(var n="share-",o=(e.getAttribute("class")||"").split(/\s+/),i=0;i<o.length;i++)if(0===o[i].indexOf(n)){var r=o[i].slice(6);if("string"==typeof t[r])return r}return null}(n);o&&(e.preventDefault(),r&&r.close(),r=window.open(n.getAttribute("href"),function(e){return"string"!=typeof f[e]&&(f[e]="wpcom"+e+"-"+Math.random().toString(36).slice(2)),f[e]}(o),window.WPCOM_sharing_popups[o]))}}}
3779),window.WPCOM_sharing_popups_bound=!0)}();;

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.