PageSourceSearch

https://britlitblog.com/script/ZghxzrJKuXWUIqI/kJulVb-VP4jbzUTf1u8…1vcRkUoCO2-IzUJl3t3bS9sej8-vh0NrvgDvkB8T.js

js britlitblog.com collected 2026-09-27 07:09:55 UTC 231,451 bytes, 6,700 lines download raw bytes

1/* Detect-zoom
2 * -----------
3 * Cross Browser Zoom and Pixel Ratio Detector
4 * Version 1.0.4 | Apr 1 2013
5 * dual-licensed under the WTFPL and MIT license
6 * Maintained by https://github/tombigel
7 * Original developer https://github.com/yonran
8 */
9
10//AMD and CommonJS initialization copied from https://github.com/zohararad/audio5js
11(function (root, ns, factory) {
12    "use strict";
13
14    if (typeof (module) !== 'undefined' && module.exports) { // CommonJS
15        module.exports = factory(ns, root);
16    } else if (typeof (define) === 'function' && define.amd) { // AMD
17        define("factory", function () {
18            return factory(ns, root);
19        });
20    } else {
21        root[ns] = factory(ns, root);
22    }
23
24}(window, 'detectZoom', function () {
25
26    /**
27     * Use devicePixelRatio if supported by the browser
28     * @return {Number}
29     * @private
30     */
31    var devicePixelRatio = function () {
32        return window.devicePixelRatio || 1;
33    };
34
35    /**
36     * Fallback function to set default values
37     * @return {Object}
38     * @private
39     */
40    var fallback = function () {
41        return {
42            zoom: 1,
43            devicePxPerCssPx: 1
44        };
45    };
46    /**
47     * IE 8 and 9: no trick needed!
48     * TODO: Test on IE10 and Windows 8 RT
49     * @return {Object}
50     * @private
51     **/
52    var ie8 = function () {
53        var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100) / 100;
54        return {
55            zoom: zoom,
56            devicePxPerCssPx: zoom * devicePixelRatio()
57        };
58    };
59
60    /**
61     * For IE10 we need to change our technique again...
62     * thanks https://github.com/stefanvanburen
63     * @return {Object}
64     * @private
65     */
66    var ie10 = function () {
67        var zoom = Math.round((document.documentElement.offsetHeight / window.innerHeight) * 100) / 100;
68        return {
69            zoom: zoom,
70            devicePxPerCssPx: zoom * devicePixelRatio()
71        };
72    };
73
74    /**
75     * Mobile WebKit
76     * the trick: window.innerWIdth is in CSS pixels, while
77     * screen.width and screen.height are in system pixels.
78     * And there are no scrollbars to mess up the measurement.
79     * @return {Object}
80     * @private
81     */
82    var webkitMobile = function () {
83        var deviceWidth = (Math.abs(window.orientation) == 90) ? screen.height : screen.width;
84        var zoom = deviceWidth / window.innerWidth;
85        return {
86            zoom: zoom,
87            devicePxPerCssPx: zoom * devicePixelRatio()
88        };
89    };
90
91    /**
92     * Desktop Webkit
93     * the trick: an element's clientHeight is in CSS pixels, while you can
94     * set its line-height in system pixels using font-size and
95     * -webkit-text-size-adjust:none.
96     * device-pixel-ratio: https://www.webkit.org/blog/55/high-dpi-web-sites/
97     *
98     * Previous trick (used before https://trac.webkit.org/changeset/100847):
99     * documentElement.scrollWidth is in CSS pixels, while
100     * document.width was in system pixels. Note that this is the
101     * layout width of the document, which is slightly different from viewport
102     * because document width does not include scrollbars and might be wider
103     * due to big elements.
104     * @return {Object}
105     * @private
106     */
107    var webkit = function () {
108        var important = function (str) {
109            return str.replace(/;/g, " !important;");
110        };
111
112        var div = document.createElement('div');
113        div.innerHTML = "1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>0";
114        div.setAttribute('style', important('font: 100px/1em sans-serif; -webkit-text-size-adjust: none; text-size-adjust: none; height: auto; width: 1em; padding: 0; overflow: visible;'));
115
116        // The container exists so that the div will be laid out in its own flow
117        // while not impacting the layout, viewport size, or display of the
118        // webpage as a whole.
119        // Add !important and relevant CSS rule resets
120        // so that other rules cannot affect the results.
121        var container = document.createElement('div');
122        container.setAttribute('style', important('width:0; height:0; overflow:hidden; visibility:hidden; position: absolute;'));
123        container.appendChild(div);
124
125        document.body.appendChild(container);
126        var zoom = 1000 / div.clientHeight;
127        zoom = Math.round(zoom * 100) / 100;
128        document.body.removeChild(container);
129
130        return{
131            zoom: zoom,
132            devicePxPerCssPx: zoom * devicePixelRatio()
133        };
134    };
135
136    /**
137     * no real trick; device-pixel-ratio is the ratio of device dpi / css dpi.
138     * (Note that this is a different interpretation than Webkit's device
139     * pixel ratio, which is the ratio device dpi / system dpi).
140     *
141     * Also, for Mozilla, there is no difference between the zoom factor and the device ratio.
142     *
143     * @return {Object}
144     * @private
145     */
146    var firefox4 = function () {
147        var zoom = mediaQueryBinarySearch('min--moz-device-pixel-ratio', '', 0, 10, 20, 0.0001);
148        zoom = Math.round(zoom * 100) / 100;
149        return {
150            zoom: zoom,
151            devicePxPerCssPx: zoom
152        };
153    };
154
155    /**
156     * Firefox 18.x
157     * Mozilla added support for devicePixelRatio to Firefox 18,
158     * but it is affected by the zoom level, so, like in older
159     * Firefox we can't tell if we are in zoom mode or in a device
160     * with a different pixel ratio
161     * @return {Object}
162     * @private
163     */
164    var firefox18 = function () {
165        return {
166            zoom: firefox4().zoom,
167            devicePxPerCssPx: devicePixelRatio()
168        };
169    };
170
171    /**
172     * works starting Opera 11.11
173     * the trick: outerWidth is the viewport width including scrollbars in
174     * system px, while innerWidth is the viewport width including scrollbars
175     * in CSS px
176     * @return {Object}
177     * @private
178     */
179    var opera11 = function () {
180        var zoom = window.top.outerWidth / window.top.innerWidth;
181        zoom = Math.round(zoom * 100) / 100;
182        return {
183            zoom: zoom,
184            devicePxPerCssPx: zoom * devicePixelRatio()
185        };
186    };
187
188    /**
189     * Use a binary search through media queries to find zoom level in Firefox
190     * @param property
191     * @param unit
192     * @param a
193     * @param b
194     * @param maxIter
195     * @param epsilon
196     * @return {Number}
197     */
198    var mediaQueryBinarySearch = function (property, unit, a, b, maxIter, epsilon) {
199        var matchMedia;
200        var head, style, div;
201        if (window.matchMedia) {
202            matchMedia = window.matchMedia;
203        } else {
204            head = document.getElementsByTagName('head')[0];
205            style = document.createElement('style');
206            head.appendChild(style);
207
208            div = document.createElement('div');
209            div.className = 'mediaQueryBinarySearch';
210            div.style.display = 'none';
211            document.body.appendChild(div);
212
213            matchMedia = function (query) {
214                style.sheet.insertRule('@media ' + query + '{.mediaQueryBinarySearch ' + '{text-decoration: underline} }', 0);
215                var matched = getComputedStyle(div, null).textDecoration == 'underline';
216                style.sheet.deleteRule(0);
217                return {matches: matched};
218            };
219        }
220        var ratio = binarySearch(a, b, maxIter);
221        if (div) {
222            head.removeChild(style);
223            document.body.removeChild(div);
224        }
225        return ratio;
226
227        function binarySearch(a, b, maxIter) {
228            var mid = (a + b) / 2;
229            if (maxIter <= 0 || b - a < epsilon) {
230                return mid;
231            }
232            var query = "(" + property + ":" + mid + unit + ")";
233            if (matchMedia(query).matches) {
234                return binarySearch(mid, b, maxIter - 1);
235            } else {
236                return binarySearch(a, mid, maxIter - 1);
237            }
238        }
239    };
240
241    /**
242     * Generate detection function
243     * @private
244     */
245    var detectFunction = (function () {
246        var func = fallback;
247        //IE8+
248        if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) {
249            func = ie8;
250        }
251        // IE10+ / Touch
252        else if (window.navigator.msMaxTouchPoints) {
253            func = ie10;
254        }
255        //Mobile Webkit
256        else if ('orientation' in window && typeof document.body.style.webkitMarquee === 'string') {
257            func = webkitMobile;
258        }
259        //WebKit
260        else if (typeof document.body.style.webkitMarquee === 'string') {
261            func = webkit;
262        }
263        //Opera
264        else if (navigator.userAgent.indexOf('Opera') >= 0) {
265            func = opera11;
266        }
267        //Last one is Firefox
268        //FF 18.x
269        else if (window.devicePixelRatio) {
270            func = firefox18;
271        }
272        //FF 4.0 - 17.x
273        else if (firefox4().zoom > 0.001) {
274            func = firefox4;
275        }
276
277        return func;
278    }());
279
280
281    return ({
282
283        /**
284         * Ratios.zoom shorthand
285         * @return {Number} Zoom level
286         */
287        zoom: function () {
288            return detectFunction().zoom;
289        },
290
291        /**
292         * Ratios.devicePxPerCssPx shorthand
293         * @return {Number} devicePxPerCssPx level
294         */
295        device: function () {
296            return detectFunction().devicePxPerCssPx;
297        }
298    });
299}));
300
301var wpcom_img_zoomer = {
302        clientHintSupport: {
303                gravatar: false,
304                files: false,
305                photon: false,
306                mshots: false,
307                staticAssets: false,
308                latex: false,
309                imgpress: false,
310        },
311	useHints: false,
312	zoomed: false,
313	timer: null,
314	interval: 1000, // zoom polling interval in millisecond
315
316	// Should we apply width/height attributes to control the image size?
317	imgNeedsSizeAtts: function( img ) {
318		// Do not overwrite existing width/height attributes.
319		if ( img.getAttribute('width') !== null || img.getAttribute('height') !== null )
320			return false;
321		// Do not apply the attributes if the image is already constrained by a parent element.
322		if ( img.width < img.naturalWidth || img.height < img.naturalHeight )
323			return false;
324		return true;
325	},
326
327        hintsFor: function( service ) {
328                if ( this.useHints === false ) {
329                        return false;
330                }
331                if ( this.hints() === false ) {
332                        return false;
333                }
334                if ( typeof this.clientHintSupport[service] === "undefined" ) {
335                        return false;
336                }
337                if ( this.clientHintSupport[service] === true ) {
338                        return true;
339                }
340                return false;
341        },
342
343	hints: function() {
344		try {
345			var chrome = window.navigator.userAgent.match(/\sChrome\/([0-9]+)\.[.0-9]+\s/)
346			if (chrome !== null) {
347				var version = parseInt(chrome[1], 10)
348				if (isNaN(version) === false && version >= 46) {
349					return true
350				}
351			}
352		} catch (e) {
353			return false
354		}
355		return false
356	},
357
358	init: function() {
359		var t = this;
360		try{
361			t.zoomImages();
362			t.timer = setInterval( function() { t.zoomImages(); }, t.interval );
363		}
364		catch(e){
365		}
366	},
367
368	stop: function() {
369		if ( this.timer )
370			clearInterval( this.timer );
371	},
372
373	getScale: function() {
374		var scale = detectZoom.device();
375		// Round up to 1.5 or the next integer below the cap.
376		if      ( scale <= 1.0 ) scale = 1.0;
377		else if ( scale <= 1.5 ) scale = 1.5;
378		else if ( scale <= 2.0 ) scale = 2.0;
379		else if ( scale <= 3.0 ) scale = 3.0;
380		else if ( scale <= 4.0 ) scale = 4.0;
381		else                     scale = 5.0;
382		return scale;
383	},
384
385	shouldZoom: function( scale ) {
386		var t = this;
387		// Do not operate on hidden frames.
388		if ( "innerWidth" in window && !window.innerWidth )
389			return false;
390		// Don't do anything until scale > 1
391		if ( scale == 1.0 && t.zoomed == false )
392			return false;
393		return true;
394	},
395
396	zoomImages: function() {
397		var t = this;
398		var scale = t.getScale();
399		if ( ! t.shouldZoom( scale ) ){
400			return;
401		}
402		t.zoomed = true;
403		// Loop through all the <img> elements on the page.
404		var imgs = document.getElementsByTagName("img");
405
406		for ( var i = 0; i < imgs.length; i++ ) {
407			// Wait for original images to load
408			if ( "complete" in imgs[i] && ! imgs[i].complete )
409				continue;
410
411			// Skip images that have srcset attributes.
412			if ( imgs[i].hasAttribute('srcset') ) {
413				continue;
414			}
415
416			// Skip images that don't need processing.
417			var imgScale = imgs[i].getAttribute("scale");
418			if ( imgScale == scale || imgScale == "0" )
419				continue;
420
421			// Skip images that have already failed at this scale
422			var scaleFail = imgs[i].getAttribute("scale-fail");
423			if ( scaleFail && scaleFail <= scale )
424				continue;
425
426			// Skip images that have no dimensions yet.
427			if ( ! ( imgs[i].width && imgs[i].height ) )
428				continue;
429
430			// Skip images from Lazy Load plugins
431			if ( ! imgScale && imgs[i].getAttribute("data-lazy-src") && (imgs[i].getAttribute("data-lazy-src") !== imgs[i].getAttribute("src")))
432				continue;
433
434			if ( t.scaleImage( imgs[i], scale ) ) {
435				// Mark the img as having been processed at this scale.
436				imgs[i].setAttribute("scale", scale);
437			}
438			else {
439				// Set the flag to skip this image.
440				imgs[i].setAttribute("scale", "0");
441			}
442		}
443	},
444
445	scaleImage: function( img, scale ) {
446		var t = this;
447		var newSrc = img.src;
448
449                var isFiles = false;
450                var isLatex = false;
451                var isPhoton = false;
452
453		// Skip slideshow images
454		if ( img.parentNode.className.match(/slideshow-slide/) )
455			return false;
456
457		// Scale gravatars that have ?s= or ?size=
458		if ( img.src.match( /^https?:\/\/([^\/]*\.)?gravatar\.com\/.+[?&](s|size)=/ ) ) {
459                        if ( this.hintsFor( "gravatar" ) === true ) {
460                                return false;
461                        }
462			newSrc = img.src.replace( /([?&](s|size)=)(\d+)/, function( $0, $1, $2, $3 ) {
463				// Stash the original size
464				var originalAtt = "originals",
465				originalSize = img.getAttribute(originalAtt);
466				if ( originalSize === null ) {
467					originalSize = $3;
468					img.setAttribute(originalAtt, originalSize);
469					if ( t.imgNeedsSizeAtts( img ) ) {
470						// Fix width and height attributes to rendered dimensions.
471						img.width = img.width;
472						img.height = img.height;
473					}
474				}
475				// Get the width/height of the image in CSS pixels
476				var size = img.clientWidth;
477				// Convert CSS pixels to device pixels
478				var targetSize = Math.ceil(img.clientWidth * scale);
479				// Don't go smaller than the original size
480				targetSize = Math.max( targetSize, originalSize );
481				// Don't go larger than the service supports
482				targetSize = Math.min( targetSize, 512 );
483				return $1 + targetSize;
484			});
485		}
486
487		// Scale mshots that have width
488		else if ( img.src.match(/^https?:\/\/([^\/]+\.)*(wordpress|wp)\.com\/mshots\/.+[?&]w=\d+/) ) {
489                        if ( this.hintsFor( "mshots" ) === true ) {
490                                return false;
491                        }
492			newSrc = img.src.replace( /([?&]w=)(\d+)/, function($0, $1, $2) {
493				// Stash the original size
494				var originalAtt = 'originalw', originalSize = img.getAttribute(originalAtt);
495				if ( originalSize === null ) {
496					originalSize = $2;
497					img.setAttribute(originalAtt, originalSize);
498					if ( t.imgNeedsSizeAtts( img ) ) {
499						// Fix width and height attributes to rendered dimensions.
500						img.width = img.width;
501						img.height = img.height;
502					}
503				}
504				// Get the width of the image in CSS pixels
505				var size = img.clientWidth;
506				// Convert CSS pixels to device pixels
507				var targetSize = Math.ceil(size * scale);
508				// Don't go smaller than the original size
509				targetSize = Math.max( targetSize, originalSize );
510				// Don't go bigger unless the current one is actually lacking
511				if ( scale > img.getAttribute("scale") && targetSize <= img.naturalWidth )
512					targetSize = $2;
513				if ( $2 != targetSize )
514					return $1 + targetSize;
515				return $0;
516			});
517
518			// Update height attribute to match width
519			newSrc = newSrc.replace( /([?&]h=)(\d+)/, function($0, $1, $2) {
520				if ( newSrc == img.src ) {
521					return $0;
522				}
523				// Stash the original size
524				var originalAtt = 'originalh', originalSize = img.getAttribute(originalAtt);
525				if ( originalSize === null ) {
526					originalSize = $2;
527					img.setAttribute(originalAtt, originalSize);
528				}
529				// Get the height of the image in CSS pixels
530				var size = img.clientHeight;
531				// Convert CSS pixels to device pixels
532				var targetSize = Math.ceil(size * scale);
533				// Don't go smaller than the original size
534				targetSize = Math.max( targetSize, originalSize );
535				// Don't go bigger unless the current one is actually lacking
536				if ( scale > img.getAttribute("scale") && targetSize <= img.naturalHeight )
537					targetSize = $2;
538				if ( $2 != targetSize )
539					return $1 + targetSize;
540				return $0;
541			});
542		}
543
544		// Scale simple imgpress queries (s0.wp.com) that only specify w/h/fit
545		else if ( img.src.match(/^https?:\/\/([^\/.]+\.)*(wp|wordpress)\.com\/imgpress\?(.+)/) ) {
546                        if ( this.hintsFor( "imgpress" ) === true ) {
547                                return false; 
548                        }
549			var imgpressSafeFunctions = ["zoom", "url", "h", "w", "fit", "filter", "brightness", "contrast", "colorize", "smooth", "unsharpmask"];
550			// Search the query string for unsupported functions.
551			var qs = RegExp.$3.split('&');
552			for ( var q in qs ) {
553				q = qs[q].split('=')[0];
554				if ( imgpressSafeFunctions.indexOf(q) == -1 ) {
555					return false;
556				}
557			}
558			// Fix width and height attributes to rendered dimensions.
559			img.width = img.width;
560			img.height = img.height;
561			// Compute new src
562			if ( scale == 1 )
563				newSrc = img.src.replace(/\?(zoom=[^&]+&)?/, '?');
564			else
565				newSrc = img.src.replace(/\?(zoom=[^&]+&)?/, '?zoom=' + scale + '&');
566		}
567
568		// Scale files.wordpress.com, LaTeX, or Photon images (i#.wp.com)
569		else if (
570			( isFiles = img.src.match(/^https?:\/\/([^\/]+)\.files\.wordpress\.com\/.+[?&][wh]=/) ) ||
571			( isLatex = img.src.match(/^https?:\/\/([^\/.]+\.)*(wp|wordpress)\.com\/latex\.php\?(latex|zoom)=(.+)/) ) ||
572			( isPhoton = img.src.match(/^https?:\/\/i[\d]{1}\.wp\.com\/(.+)/) )
573		) {
574                        if ( false !== isFiles && this.hintsFor( "files" ) === true ) {
575                                return false
576                        }
577                        if ( false !== isLatex && this.hintsFor( "latex" ) === true ) {
578                                return false
579                        }
580                        if ( false !== isPhoton && this.hintsFor( "photon" ) === true ) {
581                                return false
582                        }
583			// Fix width and height attributes to rendered dimensions.
584			img.width = img.width;
585			img.height = img.height;
586			// Compute new src
587			if ( scale == 1 ) {
588				newSrc = img.src.replace(/\?(zoom=[^&]+&)?/, '?');
589			} else {
590				newSrc = img.src;
591
592				var url_var = newSrc.match( /([?&]w=)(\d+)/ );
593				if ( url_var !== null && url_var[2] ) {
594					newSrc = newSrc.replace( url_var[0], url_var[1] + img.width );
595				}
596
597				url_var = newSrc.match( /([?&]h=)(\d+)/ );
598				if ( url_var !== null && url_var[2] ) {
599					newSrc = newSrc.replace( url_var[0], url_var[1] + img.height );
600				}
601
602				var zoom_arg = '&zoom=2';
603				if ( !newSrc.match( /\?/ ) ) {
604					zoom_arg = '?zoom=2';
605				}
606				img.setAttribute( 'srcset', newSrc + zoom_arg + ' ' + scale + 'x' );
607			}
608		}
609
610		// Scale static assets that have a name matching *-1x.png or *@1x.png
611		else if ( img.src.match(/^https?:\/\/[^\/]+\/.*[-@]([12])x\.(gif|jpeg|jpg|png)(\?|$)/) ) {
612                        if ( this.hintsFor( "staticAssets" ) === true ) {
613                                return false; 
614                        }
615			// Fix width and height attributes to rendered dimensions.
616			img.width = img.width;
617			img.height = img.height;
618			var currentSize = RegExp.$1, newSize = currentSize;
619			if ( scale <= 1 )
620				newSize = 1;
621			else
622				newSize = 2;
623			if ( currentSize != newSize )
624				newSrc = img.src.replace(/([-@])[12]x\.(gif|jpeg|jpg|png)(\?|$)/, '$1'+newSize+'x.$2$3');
625		}
626
627		else {
628			return false;
629		}
630
631		// Don't set img.src unless it has changed. This avoids unnecessary reloads.
632		if ( newSrc != img.src ) {
633			// Store the original img.src
634			var prevSrc, origSrc = img.getAttribute("src-orig");
635			if ( !origSrc ) {
636				origSrc = img.src;
637				img.setAttribute("src-orig", origSrc);
638			}
639			// In case of error, revert img.src
640			prevSrc = img.src;
641			img.onerror = function(){
642				img.src = prevSrc;
643				if ( img.getAttribute("scale-fail") < scale )
644					img.setAttribute("scale-fail", scale);
645				img.onerror = null;
646			};
647			// Finally load the new image
648			img.src = newSrc;
649		}
650
651		return true;
652	}
653};
654
655wpcom_img_zoomer.init();
656;
657/* global pm, wpcom_reblog */
658
659var jetpackLikesWidgetQueue = [];
660var jetpackLikesWidgetBatch = [];
661var jetpackLikesMasterReady = false;
662
663function JetpackLikespostMessage( message, target ) {
664	if ( 'string' === typeof message ){
665		try {
666			message = JSON.parse( message );
667		} catch(e) {
668			return;
669		}
670	}
671
672	pm( {
673		target: target,
674		type: 'likesMessage',
675		data: message,
676		origin: '*'
677	} );
678}
679
680function JetpackLikesBatchHandler() {
681	var requests = [];
682	jQuery( 'div.jetpack-likes-widget-unloaded' ).each( function() {
683		if ( jetpackLikesWidgetBatch.indexOf( this.id ) > -1 ) {
684			return;
685		}
686		jetpackLikesWidgetBatch.push( this.id );
687		var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/,
688			match = regex.exec( this.id ),
689			info;
690
691		if ( ! match || match.length !== 5 ) {
692			return;
693		}
694
695		info = {
696			blog_id: match[2],
697			width:   this.width
698		};
699
700		if ( 'post' === match[1] ) {
701			info.post_id = match[3];
702		} else if ( 'comment' === match[1] ) {
703			info.comment_id = match[3];
704		}
705
706		info.obj_id = match[4];
707
708		requests.push( info );
709	});
710
711	if ( requests.length > 0 ) {
712		JetpackLikespostMessage( { event: 'initialBatch', requests: requests }, window.frames['likes-master'] );
713	}
714}
715
716function JetpackLikesMessageListener( event, message ) {
717	var allowedOrigin, $container, $list, offset, rowLength, height, scrollbarWidth;
718
719	if ( 'undefined' === typeof event.event ) {
720		return;
721	}
722
723	// We only allow messages from one origin
724	allowedOrigin = window.location.protocol + 'https://widgets.wp.com';
725	if ( allowedOrigin !== message.origin ) {
726		return;
727	}
728
729	if ( 'masterReady' === event.event ) {
730		jQuery( document ).ready( function() {
731			jetpackLikesMasterReady = true;
732
733			var stylesData = {
734					event: 'injectStyles'
735				},
736				$sdTextColor = jQuery( '.sd-text-color' ),
737				$sdLinkColor = jQuery( '.sd-link-color' );
738
739			if ( jQuery( 'iframe.admin-bar-likes-widget' ).length > 0 ) {
740				JetpackLikespostMessage( { event: 'adminBarEnabled' }, window.frames[ 'likes-master' ] );
741
742				stylesData.adminBarStyles = {
743					background: jQuery( '#wpadminbar .quicklinks li#wp-admin-bar-wpl-like > a' ).css( 'background' ),
744					isRtl: ( 'rtl' === jQuery( '#wpadminbar' ).css( 'direction' ) )
745				};
746			}
747
748			// enable reblogs if we're on a single post page
749			if ( jQuery( 'body' ).hasClass( 'single' ) ) {
750				JetpackLikespostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] );
751			}
752
753			if ( ! window.addEventListener ) {
754				jQuery( '#wp-admin-bar-admin-bar-likes-widget' ).hide();
755			}
756
757			stylesData.textStyles = {
758				color:          $sdTextColor.css( 'color' ),
759				fontFamily:     $sdTextColor.css( 'font-family' ),
760				fontSize:       $sdTextColor.css( 'font-size' ),
761				direction:      $sdTextColor.css( 'direction' ),
762				fontWeight:     $sdTextColor.css( 'font-weight' ),
763				fontStyle:      $sdTextColor.css( 'font-style' ),
764				textDecoration: $sdTextColor.css('text-decoration')
765			};
766
767			stylesData.linkStyles = {
768				color:          $sdLinkColor.css('color'),
769				fontFamily:     $sdLinkColor.css('font-family'),
770				fontSize:       $sdLinkColor.css('font-size'),
771				textDecoration: $sdLinkColor.css('text-decoration'),
772				fontWeight:     $sdLinkColor.css( 'font-weight' ),
773				fontStyle:      $sdLinkColor.css( 'font-style' )
774			};
775
776			JetpackLikespostMessage( stylesData, window.frames[ 'likes-master' ] );
777
778			JetpackLikesBatchHandler();
779
780			jQuery( document ).on( 'inview', 'div.jetpack-likes-widget-unloaded', function() {
781				jetpackLikesWidgetQueue.push( this.id );
782			});
783		});
784	}
785
786	if ( 'showLikeWidget' === event.event ) {
787		jQuery( '#' + event.id + ' .post-likes-widget-placeholder'  ).fadeOut( 'fast', function() {
788			jQuery( '#' + event.id + ' .post-likes-widget' ).fadeIn( 'fast', function() {
789				JetpackLikespostMessage( { event: 'likeWidgetDisplayed', blog_id: event.blog_id, post_id: event.post_id, obj_id: event.obj_id }, window.frames['likes-master'] );
790			});
791		});
792	}
793
794	if ( 'clickReblogFlair' === event.event ) {
795		wpcom_reblog.toggle_reblog_box_flair( event.obj_id );
796	}
797
798	if ( 'showOtherGravatars' === event.event ) {
799		$container = jQuery( '#likes-other-gravatars' );
800		$list = $container.find( 'ul' );
801
802		$container.hide();
803		$list.html( '' );
804
805		$container.find( '.likes-text span' ).text( event.total );
806
807		jQuery.each( event.likers, function( i, liker ) {
808			var element = jQuery( '<li><a><img /></a></li>' );
809			element.addClass( liker.css_class );
810
811			element.find( 'a' ).
812				attr({
813					href: liker.profile_URL,
814					rel: 'nofollow',
815					target: '_parent'
816				}).
817				addClass( 'wpl-liker' );
818
819			element.find( 'img' ).
820				attr({
821					src: liker.avatar_URL,
822					alt: liker.name
823				}).
824				css({
825					width: '30px',
826					height: '30px',
827					paddingRight: '3px'
828				});
829
830			$list.append( element );
831		} );
832
833		offset = jQuery( '[name=\'' + event.parent + '\']' ).offset();
834
835		$container.css( 'left', offset.left + event.position.left - 10 + 'px' );
836		$container.css( 'top', offset.top + event.position.top - 33 + 'px' );
837
838		rowLength = Math.floor( event.width / 37 );
839		height = ( Math.ceil( event.likers.length / rowLength ) * 37 ) + 13;
840		if ( height > 204 ) {
841			height = 204;
842		}
843
844		$container.css( 'height', height + 'px' );
845		$container.css( 'width', rowLength * 37 - 7 + 'px' );
846
847		$list.css( 'width', rowLength * 37 + 'px' );
848
849		$container.fadeIn( 'slow' );
850
851		scrollbarWidth = $list[0].offsetWidth - $list[0].clientWidth;
852		if ( scrollbarWidth > 0 ) {
853			$container.width( $container.width() + scrollbarWidth );
854			$list.width( $list.width() + scrollbarWidth );
855		}
856	}
857}
858
859pm.bind( 'likesMessage', JetpackLikesMessageListener );
860
861jQuery( document ).click( function( e ) {
862	var $container = jQuery( '#likes-other-gravatars' );
863
864	if ( $container.has( e.target ).length === 0 ) {
865		$container.fadeOut( 'slow' );
866	}
867});
868
869function JetpackLikesWidgetQueueHandler() {
870	var $wrapper, wrapperID, found;
871	if ( ! jetpackLikesMasterReady ) {
872		setTimeout( JetpackLikesWidgetQueueHandler, 500 );
873		return;
874	}
875
876	if ( jetpackLikesWidgetQueue.length > 0 ) {
877		// We may have a widget that needs creating now
878		found = false;
879		while( jetpackLikesWidgetQueue.length > 0 ) {
880			// Grab the first member of the queue that isn't already loading.
881			wrapperID = jetpackLikesWidgetQueue.splice( 0, 1 )[0];
882			if ( jQuery( '#' + wrapperID ).hasClass( 'jetpack-likes-widget-unloaded' ) ) {
883				found = true;
884				break;
885			}
886		}
887		if ( ! found ) {
888			setTimeout( JetpackLikesWidgetQueueHandler, 500 );
889			return;
890		}
891	} else if ( jQuery( 'div.jetpack-likes-widget-unloaded' ).length > 0 ) {
892		// Grab any unloaded widgets for a batch request
893		JetpackLikesBatchHandler();
894
895		// Get the next unloaded widget
896		wrapperID = jQuery( 'div.jetpack-likes-widget-unloaded' ).first()[0].id;
897		if ( ! wrapperID ) {
898			// Everything is currently loaded
899			setTimeout( JetpackLikesWidgetQueueHandler, 500 );
900			return;
901		}
902	}
903
904	if ( 'undefined' === typeof wrapperID ) {
905		setTimeout( JetpackLikesWidgetQueueHandler, 500 );
906		return;
907	}
908
909	$wrapper = jQuery( '#' + wrapperID );
910	$wrapper.find( 'iframe' ).remove();
911
912	if ( $wrapper.hasClass( 'slim-likes-widget' ) ) {
913		$wrapper.find( '.post-likes-widget-placeholder' ).after( '<iframe class="post-likes-widget jetpack-likes-widget" name="' + $wrapper.data( 'name' ) + '" height="22px" width="68px" frameBorder="0" scrolling="no" src="' + $wrapper.data( 'src' ) + '"></iframe>' );
914	} else {
915		$wrapper.find( '.post-likes-widget-placeholder' ).after( '<iframe class="post-likes-widget jetpack-likes-widget" name="' + $wrapper.data( 'name' ) + '" height="55px" width="100%" frameBorder="0" src="' + $wrapper.data( 'src' ) + '"></iframe>' );
916	}
917
918	$wrapper.removeClass( 'jetpack-likes-widget-unloaded' ).addClass( 'jetpack-likes-widget-loading' );
919
920	$wrapper.find( 'iframe' ).load( function( e ) {
921		var $iframe = jQuery( e.target );
922		$wrapper.removeClass( 'jetpack-likes-widget-loading' ).addClass( 'jetpack-likes-widget-loaded' );
923
924		JetpackLikespostMessage( { event: 'loadLikeWidget', name: $iframe.attr( 'name' ), width: $iframe.width(), domain: window.location.hostname }, window.frames[ 'likes-master' ] );
925
926		if ( $wrapper.hasClass( 'slim-likes-widget' ) ) {
927			$wrapper.find( 'iframe' ).Jetpack( 'resizeable' );
928		}
929	});
930	setTimeout( JetpackLikesWidgetQueueHandler, 250 );
931}
932JetpackLikesWidgetQueueHandler();
933;
934/*
935 * Swipe 2.0
936 *
937 * Brad Birdsall
938 * Copyright 2013, MIT License
939 *
940*/
941
942function Swipe(container, options) {
943
944  "use strict";
945
946  // utilities
947  var noop = function() {}; // simple no operation function
948  var offloadFn = function(fn) { setTimeout(fn || noop, 0) }; // offload a functions execution
949  
950  // check browser capabilities
951  var browser = {
952    addEventListener: !!window.addEventListener,
953    touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
954    transitions: (function(temp) {
955      var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
956      for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
957      return false;
958    })(document.createElement('swipe'))
959  };
960
961  // quit if no root element
962  if (!container) return;
963  var element = container.children[0];
964  var slides, slidePos, width, length;
965  options = options || {};
966  var index = parseInt(options.startSlide, 10) || 0;
967  var speed = options.speed || 300;
968  options.continuous = options.continuous !== undefined ? options.continuous : true;
969
970  function setup() {
971
972    // cache slides
973    slides = element.children;
974    length = slides.length;
975
976    // set continuous to false if only one slide
977    if (slides.length < 2) options.continuous = false;
978
979    //special case if two slides
980    if (browser.transitions && options.continuous && slides.length < 3) {
981      element.appendChild(slides[0].cloneNode(true));
982      element.appendChild(element.children[1].cloneNode(true));
983      slides = element.children;
984    }
985
986    // create an array to store current positions of each slide
987    slidePos = new Array(slides.length);
988
989    // determine width of each slide
990    width = container.getBoundingClientRect().width || container.offsetWidth;
991
992    element.style.width = (slides.length * width) + 'px';
993
994    // stack elements
995    var pos = slides.length;
996    while(pos--) {
997
998      var slide = slides[pos];
999
1000      slide.style.width = width + 'px';
1001      slide.setAttribute('data-index', pos);
1002
1003      if (browser.transitions) {
1004        slide.style.left = (pos * -width) + 'px';
1005        move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
1006      }
1007
1008    }
1009
1010    // reposition elements before and after index
1011    if (options.continuous && browser.transitions) {
1012      move(circle(index-1), -width, 0);
1013      move(circle(index+1), width, 0);
1014    }
1015
1016    if (!browser.transitions) element.style.left = (index * -width) + 'px';
1017
1018    container.style.visibility = 'visible';
1019
1020  }
1021
1022  function prev() {
1023
1024    if (options.continuous) slide(index-1);
1025    else if (index) slide(index-1);
1026
1027  }
1028
1029  function next() {
1030
1031    if (options.continuous) slide(index+1);
1032    else if (index < slides.length - 1) slide(index+1);
1033
1034  }
1035
1036  function circle(index) {
1037
1038    // a simple positive modulo using slides.length
1039    return (slides.length + (index % slides.length)) % slides.length;
1040
1041  }
1042
1043  function slide(to, slideSpeed) {
1044
1045    // do nothing if already on requested slide
1046    if (index == to) return;
1047    
1048    if (browser.transitions) {
1049
1050      var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward
1051
1052      // get the actual position of the slide
1053      if (options.continuous) {
1054        var natural_direction = direction;
1055        direction = -slidePos[circle(to)] / width;
1056
1057        // if going forward but to < index, use to = slides.length + to
1058        // if going backward but to > index, use to = -slides.length + to
1059        if (direction !== natural_direction) to =  -direction * slides.length + to;
1060
1061      }
1062
1063      var diff = Math.abs(index-to) - 1;
1064
1065      // move all the slides between index and to in the right direction
1066      while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
1067            
1068      to = circle(to);
1069
1070      move(index, width * direction, slideSpeed || speed);
1071      move(to, 0, slideSpeed || speed);
1072
1073      if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
1074      
1075    } else {     
1076      
1077      to = circle(to);
1078      animate(index * -width, to * -width, slideSpeed || speed);
1079      //no fallback for a circular continuous if the browser does not accept transitions
1080    }
1081
1082    index = to;
1083    offloadFn(options.callback && options.callback(index, slides[index]));
1084  }
1085
1086  function move(index, dist, speed) {
1087
1088    translate(index, dist, speed);
1089    slidePos[index] = dist;
1090
1091  }
1092
1093  function translate(index, dist, speed) {
1094
1095    var slide = slides[index];
1096    var style = slide && slide.style;
1097
1098    if (!style) return;
1099
1100    style.webkitTransitionDuration = 
1101    style.MozTransitionDuration = 
1102    style.msTransitionDuration = 
1103    style.OTransitionDuration = 
1104    style.transitionDuration = speed + 'ms';
1105
1106    style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
1107    style.msTransform = 
1108    style.MozTransform = 
1109    style.OTransform = 'translateX(' + dist + 'px)';
1110
1111  }
1112
1113  function animate(from, to, speed) {
1114
1115    // if not an animation, just reposition
1116    if (!speed) {
1117
1118      element.style.left = to + 'px';
1119      return;
1120
1121    }
1122    
1123    var start = +new Date;
1124    
1125    var timer = setInterval(function() {
1126
1127      var timeElap = +new Date - start;
1128      
1129      if (timeElap > speed) {
1130
1131        element.style.left = to + 'px';
1132
1133        if (delay) begin();
1134
1135        options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
1136
1137        clearInterval(timer);
1138        return;
1139
1140      }
1141
1142      element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
1143
1144    }, 4);
1145
1146  }
1147
1148  // setup auto slideshow
1149  var delay = options.auto || 0;
1150  var interval;
1151
1152  function begin() {
1153
1154    interval = setTimeout(next, delay);
1155
1156  }
1157
1158  function stop() {
1159
1160    delay = 0;
1161    clearTimeout(interval);
1162
1163  }
1164
1165
1166  // setup initial vars
1167  var start = {};
1168  var delta = {};
1169  var isScrolling;      
1170
1171  // setup event capturing
1172  var events = {
1173
1174    handleEvent: function(event) {
1175
1176      switch (event.type) {
1177        case 'touchstart': this.start(event); break;
1178        case 'touchmove': this.move(event); break;
1179        case 'touchend': offloadFn(this.end(event)); break;
1180        case 'webkitTransitionEnd':
1181        case 'msTransitionEnd':
1182        case 'oTransitionEnd':
1183        case 'otransitionend':
1184        case 'transitionend': offloadFn(this.transitionEnd(event)); break;
1185        case 'resize': offloadFn(setup.call()); break;
1186      }
1187
1188      if (options.stopPropagation) event.stopPropagation();
1189
1190    },
1191    start: function(event) {
1192
1193      var touches = event.touches[0];
1194
1195      // measure start values
1196      start = {
1197
1198        // get initial touch coords
1199        x: touches.pageX,
1200        y: touches.pageY,
1201
1202        // store time to determine touch duration
1203        time: +new Date
1204
1205      };
1206      
1207      // used for testing first move event
1208      isScrolling = undefined;
1209
1210      // reset delta and end measurements
1211      delta = {};
1212
1213      // attach touchmove and touchend listeners
1214      element.addEventListener('touchmove', this, false);
1215      element.addEventListener('touchend', this, false);
1216
1217    },
1218    move: function(event) {
1219
1220      // ensure swiping with one touch and not pinching
1221      if ( event.touches.length > 1 || event.scale && event.scale !== 1) return
1222
1223      if (options.disableScroll) event.preventDefault();
1224
1225      var touches = event.touches[0];
1226
1227      // measure change in x and y
1228      delta = {
1229        x: touches.pageX - start.x,
1230        y: touches.pageY - start.y
1231      }
1232
1233      // determine if scrolling test has run - one time test
1234      if ( typeof isScrolling == 'undefined') {
1235        isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
1236      }
1237
1238      // if user is not trying to scroll vertically
1239      if (!isScrolling) {
1240
1241        // prevent native scrolling 
1242        event.preventDefault();
1243
1244        // stop slideshow
1245        stop();
1246
1247        // increase resistance if first or last slide
1248        if (options.continuous) { // we don't add resistance at the end
1249
1250          translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);
1251          translate(index, delta.x + slidePos[index], 0);
1252          translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);
1253
1254        } else {
1255
1256          delta.x = 
1257            delta.x / 
1258              ( (!index && delta.x > 0               // if first slide and sliding left
1259                || index == slides.length - 1        // or if last slide and sliding right
1260                && delta.x < 0                       // and if sliding at all
1261              ) ?                      
1262              ( Math.abs(delta.x) / width + 1 )      // determine resistance level
1263              : 1 );                                 // no resistance if false
1264          
1265          // translate 1:1
1266          translate(index-1, delta.x + slidePos[index-1], 0);
1267          translate(index, delta.x + slidePos[index], 0);
1268          translate(index+1, delta.x + slidePos[index+1], 0);
1269        }
1270
1271      }
1272
1273    },
1274    end: function(event) {
1275
1276      // measure duration
1277      var duration = +new Date - start.time;
1278
1279      // determine if slide attempt triggers next/prev slide
1280      var isValidSlide = 
1281            Number(duration) < 250               // if slide duration is less than 250ms
1282            && Math.abs(delta.x) > 20            // and if slide amt is greater than 20px
1283            || Math.abs(delta.x) > width/2;      // or if slide amt is greater than half the width
1284
1285      // determine if slide attempt is past start and end
1286      var isPastBounds = 
1287            !index && delta.x > 0                            // if first slide and slide amt is greater than 0
1288            || index == slides.length - 1 && delta.x < 0;    // or if last slide and slide amt is less than 0
1289
1290      if (options.continuous) isPastBounds = false;
1291      
1292      // determine direction of swipe (true:right, false:left)
1293      var direction = delta.x < 0;
1294
1295      // if not scrolling vertically
1296      if (!isScrolling) {
1297
1298        if (isValidSlide && !isPastBounds) {
1299
1300          if (direction) {
1301
1302            if (options.continuous) { // we need to get the next in this direction in place
1303
1304              move(circle(index-1), -width, 0);
1305              move(circle(index+2), width, 0);
1306
1307            } else {
1308              move(index-1, -width, 0);
1309            }
1310
1311            move(index, slidePos[index]-width, speed);
1312            move(circle(index+1), slidePos[circle(index+1)]-width, speed);
1313            index = circle(index+1);  
1314                      
1315          } else {
1316            if (options.continuous) { // we need to get the next in this direction in place
1317
1318              move(circle(index+1), width, 0);
1319              move(circle(index-2), -width, 0);
1320
1321            } else {
1322              move(index+1, width, 0);
1323            }
1324
1325            move(index, slidePos[index]+width, speed);
1326            move(circle(index-1), slidePos[circle(index-1)]+width, speed);
1327            index = circle(index-1);
1328
1329          }
1330
1331          options.callback && options.callback(index, slides[index]);
1332
1333        } else {
1334
1335          if (options.continuous) {
1336
1337            move(circle(index-1), -width, speed);
1338            move(index, 0, speed);
1339            move(circle(index+1), width, speed);
1340
1341          } else {
1342
1343            move(index-1, -width, speed);
1344            move(index, 0, speed);
1345            move(index+1, width, speed);
1346          }
1347
1348        }
1349
1350      }
1351
1352      // kill touchmove and touchend event listeners until touchstart called again
1353      element.removeEventListener('touchmove', events, false)
1354      element.removeEventListener('touchend', events, false)
1355
1356    },
1357    transitionEnd: function(event) {
1358
1359      if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
1360        
1361        if (delay) begin();
1362
1363        options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
1364
1365      }
1366
1367    }
1368
1369  }
1370
1371  // trigger setup
1372  setup();
1373
1374  // start auto slideshow if applicable
1375  if (delay) begin();
1376
1377
1378  // add event listeners
1379  if (browser.addEventListener) {
1380    
1381    // set touchstart event on element    
1382    if (browser.touch) element.addEventListener('touchstart', events, false);
1383
1384    if (browser.transitions) {
1385      element.addEventListener('webkitTransitionEnd', events, false);
1386      element.addEventListener('msTransitionEnd', events, false);
1387      element.addEventListener('oTransitionEnd', events, false);
1388      element.addEventListener('otransitionend', events, false);
1389      element.addEventListener('transitionend', events, false);
1390    }
1391
1392    // set resize event on window
1393    window.addEventListener('resize', events, false);
1394
1395  } else {
1396
1397    window.onresize = function () { setup() }; // to play nice with old IE
1398
1399  }
1400
1401  // expose the Swipe API
1402  return {
1403    setup: function() {
1404
1405      setup();
1406
1407    },
1408    slide: function(to, speed) {
1409      
1410      // cancel slideshow
1411      stop();
1412      
1413      slide(to, speed);
1414
1415    },
1416    prev: function() {
1417
1418      // cancel slideshow
1419      stop();
1420
1421      prev();
1422
1423    },
1424    next: function() {
1425
1426      // cancel slideshow
1427      stop();
1428
1429      next();
1430
1431    },
1432    getPos: function() {
1433
1434      // return current index position
1435      return index;
1436
1437    },
1438    getNumSlides: function() {
1439      
1440      // return total number of slides
1441      return length;
1442    },
1443    kill: function() {
1444
1445      // cancel slideshow
1446      stop();
1447
1448      // reset element
1449      element.style.width = 'auto';
1450      element.style.left = 0;
1451
1452      // reset slides
1453      var pos = slides.length;
1454      while(pos--) {
1455
1456        var slide = slides[pos];
1457        slide.style.width = '100%';
1458        slide.style.left = 0;
1459
1460        if (browser.transitions) translate(pos, 0, 0);
1461
1462      }
1463
1464      // removed event listeners
1465      if (browser.addEventListener) {
1466
1467        // remove current event listeners
1468        element.removeEventListener('touchstart', events, false);
1469        element.removeEventListener('webkitTransitionEnd', events, false);
1470        element.removeEventListener('msTransitionEnd', events, false);
1471        element.removeEventListener('oTransitionEnd', events, false);
1472        element.removeEventListener('otransitionend', events, false);
1473        element.removeEventListener('transitionend', events, false);
1474        window.removeEventListener('resize', events, false);
1475
1476      }
1477      else {
1478
1479        window.onresize = null;
1480
1481      }
1482
1483    }
1484  }
1485
1486}
1487
1488
1489if ( window.jQuery || window.Zepto ) {
1490  (function($) {
1491    $.fn.Swipe = function(params) {
1492      return this.each(function() {
1493        $(this).data('Swipe', new Swipe($(this)[0], params));
1494      });
1495    }
1496  })( window.jQuery || window.Zepto )
1497}
1498;
1499/**
1500 * Comment Likes - JavaScript
1501 *
1502 * This handles liking and unliking comments, as well as viewing who has
1503 * liked a particular comment.
1504 *
1505 * @dependency  jQuery
1506 * @dependency  Swipe
1507 *
1508 * @package     Comment_Likes
1509 * @subpackage  JavaScript
1510 */
1511jQuery( function() {
1512	var $ = jQuery;
1513	var extWin;
1514	var extWinCheck;
1515	var commentLikeEvent;
1516
1517	// The O2 theme re-injects this script into the DOM when somebody
1518	// creates a new thread and the page is already open, but we don't
1519	// want to run this script a second time.
1520	if ( window.comment_likes_loaded ) return;
1521	window.comment_likes_loaded = true;
1522
1523	// Client-side cache of who liked a particular comment to avoid
1524	// having to hit the server multiple times for the same data.
1525	var comment_like_cache = {};
1526
1527	/**
1528	 * Parse the comment ID from a comment like link.
1529	 */
1530	var get_comment_id = function( $link ) {
1531		var comment_id = $link.attr( 'href' ).split( 'like_comment=' );
1532		return comment_id[1].split( '&_wpnonce=' )[0];
1533	};
1534
1535	/**
1536	 * Handle an ajax action on the comment like link.
1537	 */
1538	var handle_link_action = function( $link, action, comment_id, callback ) {
1539		var nonce = $link.attr( 'href' ).split( '_wpnonce=' )[1];
1540
1541		$.post( '/wp-admin/admin-ajax.php', {
1542			'action': action,
1543			'_wpnonce': nonce,
1544			'like_comment': comment_id,
1545			'blog_id': Number( $link.data( 'blog' ) )
1546		}, callback, 'json' );
1547	};
1548
1549	// Overlay used for displaying comment like info.
1550	var overlay = {
1551
1552		// Overlay element.
1553		$el: $( '<div/>' )
1554			.appendTo( 'body' )
1555			.addClass( 'comment-likes-overlay' )
1556			.hide()
1557			.mouseenter( function() {
1558				// Don't hide the overlay if the user is mousing over it.
1559				overlay.cancel_hide();
1560			} ).mouseleave( function() {
1561				overlay.request_hide();
1562			} ),
1563
1564		// Inner contents of overlay.
1565		$inner: null,
1566
1567		// Instance of the Swipe library.
1568		swipe: null,
1569
1570		// Initialise the overlay for use, removing any old content.
1571		clear: function() {
1572			// Unload any previous instance of Swipe (to avoid leaking a global
1573			// event handler). This is done before clearing the contents of the
1574			// overlay because Swipe expects the slides to still be present.
1575			if ( this.swipe ) {
1576				this.swipe.kill();
1577				this.swipe = null;
1578			}
1579			this.$el.html( '' );
1580			this.$inner = $( '<div/>' ).addClass( 'inner' ).appendTo( this.$el );
1581		},
1582
1583		/**
1584		 * Construct a list (<ul>) of user (gravatar, name) details.
1585		 *
1586		 * @param  data     liker data returned from the server
1587		 * @param  klass    CSS class to apply to the <ul> element
1588		 * @param  start    index of user to start at
1589		 * @param  length   number of users to include in the list
1590		 *
1591		 * @return          HTML for the list
1592		 */
1593		get_user_bits: function( data, klass, start, length ) {
1594			start = start || 0;
1595			var last = start + ( length || data.length );
1596			last = ( last > data.length ) ? data.length : last;
1597			var html = '<div class="liker-list"><ul class="' + ( klass || '' ) + '">';
1598			for ( var i = start; i < last; ++i ) {
1599				var user = data[ i ];
1600				html += '<li>';
1601				html += '<a rel="nofollow" title="' + user.display_name_esc + '" href="' + user.profile_url_esc + '"><img src="' + user.avatar_url_esc + '" alt="' + user.display_name_esc + '"  /> <span class="user-name">' + user.display_name_esc + '</span></a>';
1602				html += '</li>';
1603			}
1604			html += '</ul></div>';
1605			return html;
1606		},
1607
1608		/**
1609		 * Render the display of who has liked this comment. The type of
1610		 * display depends on how many people have liked the comment.
1611		 * If more than 10 people have liked the comment, this function
1612		 * renders navigation controls and sets up the Swipe library for
1613		 * changing between pages.
1614		 *
1615		 * @param link  the element over which the user is hovering
1616		 * @param data  the results retrieved from the server
1617		 */
1618		show_likes: function( $link, data ) {
1619			this.clear();
1620
1621			$link.data( 'likeCount', data.length );
1622			if ( 0 === data.length ) {
1623				// No likers after all.
1624				return this.$el.hide();
1625			}
1626
1627			this.$inner.css( 'padding', 12 );
1628
1629			if ( data.length < 6 ) {
1630				// Only one column needed.
1631				this.$inner.css( 'max-width', 200 );
1632				this.$inner.html( this.get_user_bits( data, 'single' ) );
1633
1634			} else if ( data.length < 11 ) {
1635				// Two columns, but only one page.
1636				this.$inner.html( this.get_user_bits( data, 'double' ) );
1637
1638			} else {
1639				// Multiple pages.
1640				this.render_likes_with_pagination( data );
1641			}
1642
1643			// Move the overlay into the correct position and then show it.
1644			this.set_position( $link );
1645		},
1646
1647		/**
1648		 * Render multiple pages of likes with pagination controls.
1649		 * This function is intended to be called by `show_likes` above.
1650		 *
1651		 * @param data  the results retrieved from the server
1652		 */
1653		render_likes_with_pagination: function( data ) {
1654			var page_count = Math.ceil( data.length / 10 );
1655			// Swipe requires two nested containers.
1656			var $swipe = $( '<div/>' ).addClass( 'swipe' ).appendTo( this.$inner );
1657			var $div = $( '<div/>' ).addClass( 'swipe-wrap' ).appendTo( $swipe );
1658			for ( var i = 0; i < page_count; ++i ) {
1659				$( this.get_user_bits( data, 'double', i * 10, 10 ) ).appendTo( $div );
1660			}
1661
1662			/** Navigation controls.
1663			 *  This is based on the Newdash controls found in
1664			 *    reader/recommendations-templates.php
1665			 */
1666			var nav_html = '<nav class="slider-nav"><a href="#" class="prev"><span class="noticon noticon-previous" title="Previous" alt="<"></span></a><span class="position">';
1667			for ( var i = 0; i < page_count; ++i ) {
1668				nav_html += '<em data-page="' + i + '" class="' + ( ( i === 0 ) ? 'on' : '' ) + '">&bull;</em>';
1669			}
1670			nav_html += '</span><a href="#" class="next"><span class="noticon noticon-next" title="Next" alt=">"></span></a></nav>';
1671			var $nav = $( nav_html ).appendTo( this.$inner );
1672
1673			/** Set up Swipe. **/
1674
1675			// Swipe cannot be set up successfully unless its container
1676			// is visible, so we show it now.
1677			this.$el.show();
1678
1679			var swipe = this.swipe = new Swipe( $swipe[0], {
1680				callback: function( pos ) {
1681					// Update the pagination indicators.
1682					//
1683					// If there are exactly two pages, Swipe has a weird
1684					// special case where it duplicates both pages and
1685					// can return index 2 and 3 even though those aren't
1686					// real pages (see swipe.js, line 47). To deal with
1687					// this, we use the expression `pos % page_count`.
1688					pos = pos % page_count;
1689					$nav.find( 'em' ).each( function() {
1690						var page = Number( $( this ).data( 'page' ) );
1691						$( this ).attr( 'class', ( pos === page ) ? 'on' : '' );
1692					} );
1693				}
1694			} );
1695			$nav.find( 'em' ).on( 'click', function( $e ) {
1696				// Go to the page corresponding to the indicator clicked.
1697				swipe.slide( Number( $( this ).data( 'page' ) ) );
1698				$e.preventDefault();
1699			} );
1700			// Previous and next buttons.
1701			$nav.find( '.prev' ).on( 'click', function( $e ) {
1702				swipe.prev();
1703				$e.preventDefault();
1704			} );
1705			$nav.find( '.next' ).on( 'click', function( $e ) {
1706				swipe.next();
1707				$e.preventDefault();
1708			} );
1709		},
1710
1711		/**
1712		 * Open the overlay and show a loading message.
1713		 */
1714		show_loading_message: function( $link ) {
1715			this.clear();
1716			this.$inner.text( comment_like_text.loading );
1717			this.set_position( $link );
1718		},
1719
1720		/**
1721		 * Position the overlay near the current comment.
1722		 *
1723		 * @param $link  element near which to position the overlay
1724		 */
1725		set_position: function( $link ) {
1726			// Prepare a down arrow icon for the bottom of the overlay.
1727			var $icon = $( '<span/>' )
1728				.appendTo( this.$el )
1729				.addClass( 'icon noticon noticon-downarrow' )
1730				.css( 'text-shadow', '0px 1px 1px rgb(223, 223, 223)' );
1731
1732			var offset = $link.offset();
1733			var left = offset.left - ( this.$el.width() - $link.width() ) / 2;
1734			left = left < 5 ? 5 : left;
1735			var top = offset.top - this.$el.height() + 5;
1736
1737			// Check if the overlay would appear off the screen.
1738			if ( top < ( $( window ).scrollTop() + ( $( '#wpadminbar' ).height() || 0 ) ) ) {
1739				// We'll display the overlay beneath the link instead.
1740				top = offset.top + $link.height();
1741				// Instead of using the down arrow icon, use an up arrow.
1742				$icon.remove().prependTo( this.$el )
1743					.removeClass( 'noticon-downarrow')
1744					.addClass( 'noticon-uparrow' )
1745					.css( {
1746						'text-shadow': '0px -1px 1px rgb(223, 223, 223)',
1747						'vertical-align': 'bottom'
1748					} );
1749			}
1750
1751			this.$el.css( { 'left': left, 'top': top } ).show();
1752
1753			$icon.css( {
1754				// The height of the arrow icon differs slightly between browsers,
1755				// so we compute the margin here to make sure it isn't disjointed
1756				// from the overlay.
1757				'margin-top': $icon[0].scrollHeight - 26,
1758				'margin-bottom': 20 - $icon[0].scrollHeight,
1759
1760				// Position the arrow to be horizontally centred on the link.
1761				'padding-left': offset.left - left + ( $link.width() - $icon[0].scrollWidth ) / 2
1762			} );
1763		},
1764
1765		/**
1766		 * Return whether the overlay is visible.
1767		 */
1768		is_visible: function() {
1769			return ( 'none' !== this.$el.css( 'display' ) );
1770		},
1771
1772		// Timeout used for hiding the overlay.
1773		hide_timeout: null,
1774
1775		/**
1776		 * Request that the overlay be hidden after a short delay.
1777		 */
1778		request_hide: function() {
1779			if ( null !== this.hide_timeout ) {
1780				return;
1781			}
1782			var self = this;
1783			this.hide_timeout = setTimeout( function() {
1784				self.$el.hide();
1785				self.clear();
1786			}, 300 );
1787		},
1788
1789		/**
1790		 * Cancel a request to hide the overlay.
1791		 */
1792		cancel_hide: function() {
1793			if ( null !== this.hide_timeout ) {
1794				clearTimeout( this.hide_timeout );
1795				this.hide_timeout = null;
1796			}
1797		}
1798	};
1799
1800	// The most recent comment for which the user has requested to see
1801	// who liked it.
1802	var relevant_comment;
1803
1804	// Precache after this timeout.
1805	var precache_timeout = null;
1806
1807	/**
1808	 * Fetch the like data for a particular comment.
1809	 */
1810	var fetch_like_data = function( $link, comment_id ) {
1811		comment_like_cache[ comment_id ] = null;
1812
1813		var $star = $link.parent().parent().find( 'a.comment-like-link' );
1814		handle_link_action( $star, 'view_comment_likes', comment_id, function( data ) {
1815			// Populate the cache.
1816			comment_like_cache[ comment_id ] = data;
1817
1818			// Only show the overlay if the user is interested.
1819			if ( overlay.is_visible() && ( relevant_comment === comment_id ) ) {
1820				overlay.show_likes( $link, data );
1821			}
1822		} );
1823	};
1824
1825	function readCookie( c ) {
1826		var nameEQ = c + '=',
1827			cookieStrings = document.cookie.split( ';' ),
1828			i, cookieString, num, chunk, pairs, pair, cookie_data;
1829		for ( i = 0; i < cookieStrings.length; i++ ) {
1830			cookieString = cookieStrings[ i ];
1831			while ( cookieString.charAt( 0 ) === ' ' ) {
1832				cookieString = cookieString.substring( 1, cookieString.length );
1833			}
1834			if ( cookieString.indexOf( nameEQ ) === 0 ) {
1835				chunk = cookieString.substring( nameEQ.length, cookieString.length );
1836				pairs = chunk.split( '&' );
1837				cookie_data = {};
1838				for ( num = pairs.length - 1; num >= 0; num-- ) {
1839					pair = pairs[ num ].split( '=' );
1840					cookie_data[ pair[0] ] = decodeURIComponent( pair[1] );
1841				}
1842				return cookie_data;
1843			}
1844		}
1845		return null;
1846	}
1847
1848	function getServiceData() {
1849		var data = readCookie( 'wpc_wpc' );
1850		if ( null === data || 'undefined' === typeof data.access_token || ! data.access_token ) {
1851			return false;
1852		}
1853		return data;
1854	}
1855
1856	function readMessage( event ) {
1857		if ( 'undefined' == typeof event.event ) {
1858			return;
1859		}
1860
1861		if ( 'login' == event.event && event.success ) {
1862			extWinCheck = setInterval( function() {
1863				if ( ! extWin || extWin.closed ) {
1864					clearInterval( extWinCheck );
1865					if ( getServiceData() ) {
1866
1867						// Load page in an iframe to get the current comment nonce
1868						var nonceIframe = document.createElement( 'iframe' );
1869						nonceIframe.id = 'wp-login-comment-nonce-iframe';
1870						nonceIframe.style.display = 'none';
1871						nonceIframe.src = commentLikeEvent + '';
1872						document.body.appendChild( nonceIframe );
1873
1874						var commentLikeId = ( commentLikeEvent + '' ).split( 'like_comment=' )[1].split( '&_wpnonce=' )[0];
1875						var c = false;
1876
1877						// Set a 5 second timeout to redirect to the comment page without doing the Like as a fallback
1878						var commentLikeTimeout = setTimeout( function() {
1879							window.location = commentLikeEvent;
1880						}, 5000 );
1881
1882						// Check for a new nonced redirect and use that if available before timing out
1883						var commentLikeCheck = setInterval( function() {
1884							c = $( '#wp-login-comment-nonce-iframe' ).contents().find( '#comment-like-' + commentLikeId + ' .comment-like-link' );
1885							if ( 'undefined' !== typeof c && 'undefined' !== typeof c[0] && 'undefined' !== typeof c[0].href ) {
1886								clearTimeout( commentLikeTimeout );
1887								clearInterval( commentLikeCheck );
1888								window.location = c[0].href;
1889							}
1890						}, 100 );
1891					}
1892				}
1893			}, 100 );
1894
1895			if ( extWin ) {
1896				if ( ! extWin.closed ) {
1897					extWin.close();
1898				}
1899				extWin = false;
1900			}
1901
1902			$( '#wp-login-polling-iframe' ).remove();
1903		}
1904	}
1905
1906	if ( 'undefined' != typeof window.pm ) {
1907		pm.bind( 'loginMessage', function( e ) { readMessage( e ); } );
1908	}
1909
1910	$( 'body' ).on( 'click', 'a.comment-like-link', function( $e ) {
1911		if ( $( $e.target ).hasClass( 'needs-login' ) ) {
1912			$e.preventDefault();
1913			commentLikeEvent = $e.target;
1914			if ( extWin ) {
1915				if ( ! extWin.closed ) {
1916					extWin.close();
1917				}
1918				extWin = false;
1919			}
1920
1921			$( '#wp-login-polling-iframe' ).remove();
1922
1923			var url = 'https://wordpress.com/public.api/connect/?action=request&service=wordpress';
1924			extWin = window.open( url, 'likeconn', 'status=0,toolbar=0,location=1,menubar=0,directories=0,resizable=1,scrollbars=1,height=560,width=500' );
1925
1926			// Append cookie polling login iframe to this window to wait for user to finish logging in (or cancel)
1927			var loginIframe = $( "<iframe id='wp-login-polling-iframe'></iframe>" );
1928			loginIframe.attr( "src", "https://wordpress.com/public.api/connect/?iframe=true" );
1929			loginIframe.css( "display", "none" );
1930			$( document.body ).append( loginIframe );
1931
1932			return false;
1933		}
1934
1935		// Record that the user likes or does not like this comment.
1936		var $star = $( this );
1937		var comment_id = get_comment_id( $star );
1938		$star.addClass( 'loading' );
1939		// Determine whether to like or unlike based on whether the comment is
1940		// currently liked.
1941		var action = ( $( 'p#comment-like-' + comment_id ).data( 'liked' ) === 'comment-liked' ) ? 'unlike_comment' : 'like_comment';
1942		handle_link_action( $star, action, comment_id, function( data ) {
1943			// Invalidate the like cache for this comment.
1944			delete comment_like_cache[ comment_id ];
1945
1946			$( '#comment-like-count-' + data.context ).html( data.display );
1947
1948			if ( 'like_comment' === action ) {
1949				$( 'p#comment-like-' + data.context ).removeClass( 'comment-not-liked' )
1950					.addClass( 'comment-liked' )
1951					.data( 'liked', 'comment-liked' );
1952			} else {
1953				$( 'p#comment-like-' + data.context ).removeClass( 'comment-liked' )
1954					.addClass( 'comment-not-liked' )
1955					.data( 'liked', 'comment-not-liked' );
1956			}
1957
1958			// Prefetch new data for this comment (if there are likers left).
1959			var $link = $star.parent().find( 'a.view-likers' );
1960			if ( 0 !== $link.length ) {
1961				fetch_like_data( $link, comment_id );
1962			}
1963
1964			$star.removeClass( 'loading' );
1965		} );
1966		$e.preventDefault();
1967		$e.stopPropagation();
1968
1969	} ).on( 'click', 'p.comment-not-liked', function() {
1970		// When a comment hasn't been liked, make the text clickable, too
1971		$( this ).find( 'a.comment-like-link' ).click();
1972
1973	} ).on( 'mouseenter', 'p.comment-likes a.view-likers', function() {
1974		// Show the user a list of who has liked this comment.
1975
1976		var $link = $( this );
1977		if ( 0 === Number( $link.data( 'likeCount' ) || 0 ) ) {
1978			// No one has liked this comment.
1979			return;
1980		}
1981
1982		// Don't hide the overlay.
1983		overlay.cancel_hide();
1984
1985		// Get the comment ID.
1986		var $star = $link.parent().parent().find( 'a.comment-like-link' );
1987		var comment_id = relevant_comment = get_comment_id( $star );
1988
1989		// Check if the list of likes for this comment is already in
1990		// the cache.
1991		if ( comment_id in comment_like_cache ) {
1992			var entry = comment_like_cache[ comment_id ];
1993			// Only display the likes if the ajax request is
1994			// actually done.
1995			if ( null !== entry ) {
1996				overlay.show_likes( $link, entry );
1997			} else {
1998				// Make sure the overlay is visible (in case
1999				// the user moved the mouse away while loading
2000				// but then came back before it finished
2001				// loading).
2002				overlay.show_loading_message( $link );
2003			}
2004			return;
2005		}
2006
2007		// Position the "Loading..." overlay.
2008		overlay.show_loading_message( $link );
2009
2010		// Fetch the data.
2011		fetch_like_data( $link, comment_id );
2012
2013	} ).on( 'mouseleave', 'p.comment-likes a.view-likers', function() {
2014		// User has moved cursor away - hide the overlay.
2015		overlay.request_hide();
2016
2017	} ).on( 'click', 'p.comment-likes a.view-likers', function( $e ) {
2018		// Don't do anything when clicking on the text.
2019		$e.preventDefault();
2020
2021	} ).on( 'mouseenter', '.comment:has(a.comment-like-link)', function() {
2022		// User is moving over a comment - precache the comment like data.
2023		if ( null !== precache_timeout ) {
2024			clearTimeout( precache_timeout );
2025			precache_timeout = null;
2026		}
2027
2028		var $star = $( this ).find( 'a.comment-like-link' );
2029		var $link = $star.parent().find( 'a.view-likers' );
2030		if ( 0 === Number( $link.data( 'likeCount' ) || 0 ) ) {
2031			// No likes.
2032			return;
2033		}
2034		var comment_id = get_comment_id( $star );
2035		if ( comment_id in comment_like_cache ) {
2036			// Already in cache.
2037			return;
2038		}
2039
2040		precache_timeout = setTimeout( function() {
2041			precache_timeout = null;
2042			if ( comment_id in comment_like_cache ) {
2043				// Was cached in the interim.
2044				return;
2045			}
2046			fetch_like_data( $link, comment_id );
2047		}, 1000 );
2048	} );
2049} );
2050;
2051/**
2052 * File skip-link-focus-fix.js.
2053 *
2054 * Helps with accessibility for keyboard only users.
2055 *
2056 * Learn more: https://git.io/vWdr2
2057 */
2058( function() {
2059	var isWebkit = navigator.userAgent.toLowerCase().indexOf( 'webkit' ) > -1,
2060	    isOpera  = navigator.userAgent.toLowerCase().indexOf( 'opera' )  > -1,
2061	    isIe     = navigator.userAgent.toLowerCase().indexOf( 'msie' )   > -1;
2062
2063	if ( ( isWebkit || isOpera || isIe ) && document.getElementById && window.addEventListener ) {
2064		window.addEventListener( 'hashchange', function() {
2065			var id = location.hash.substring( 1 ),
2066				element;
2067
2068			if ( ! ( /^[A-z0-9_-]+$/.test( id ) ) ) {
2069				return;
2070			}
2071
2072			element = document.getElementById( id );
2073
2074			if ( element ) {
2075				if ( ! ( /^(?:a|select|input|button|textarea)$/i.test( element.tagName ) ) ) {
2076					element.tabIndex = -1;
2077				}
2078
2079				element.focus();
2080			}
2081		}, false );
2082	}
2083})();;
2084/*
2085 * jQuery FlexSlider v2.4.0
2086 * Copyright 2012 WooThemes
2087 * Contributing Author: Tyler Smith
2088 */
2089;
2090(function ($) {
2091
2092  //FlexSlider: Object Instance
2093  $.flexslider = function(el, options) {
2094    var slider = $(el);
2095
2096    // making variables public
2097    slider.vars = $.extend({}, $.flexslider.defaults, options);
2098
2099    var namespace = slider.vars.namespace,
2100        msGesture = window.navigator && window.navigator.msPointerEnabled && window.MSGesture,
2101        touch = (( "ontouchstart" in window ) || msGesture || window.DocumentTouch && document instanceof DocumentTouch) && slider.vars.touch,
2102        // depricating this idea, as devices are being released with both of these events
2103        //eventType = (touch) ? "touchend" : "click",
2104        eventType = "click touchend MSPointerUp keyup",
2105        watchedEvent = "",
2106        watchedEventClearTimer,
2107        vertical = slider.vars.direction === "vertical",
2108        reverse = slider.vars.reverse,
2109        carousel = (slider.vars.itemWidth > 0),
2110        fade = slider.vars.animation === "fade",
2111        asNav = slider.vars.asNavFor !== "",
2112        methods = {},
2113        focused = true;
2114
2115    // Store a reference to the slider object
2116    $.data(el, "flexslider", slider);
2117
2118    // Private slider methods
2119    methods = {
2120      init: function() {
2121        slider.animating = false;
2122        // Get current slide and make sure it is a number
2123        slider.currentSlide = parseInt( ( slider.vars.startAt ? slider.vars.startAt : 0), 10 );
2124        if ( isNaN( slider.currentSlide ) ) { slider.currentSlide = 0; }
2125        slider.animatingTo = slider.currentSlide;
2126        slider.atEnd = (slider.currentSlide === 0 || slider.currentSlide === slider.last);
2127        slider.containerSelector = slider.vars.selector.substr(0,slider.vars.selector.search(' '));
2128        slider.slides = $(slider.vars.selector, slider);
2129        slider.container = $(slider.containerSelector, slider);
2130        slider.count = slider.slides.length;
2131        // SYNC:
2132        slider.syncExists = $(slider.vars.sync).length > 0;
2133        // SLIDE:
2134        if (slider.vars.animation === "slide") { slider.vars.animation = "swing"; }
2135        slider.prop = (vertical) ? "top" : "marginLeft";
2136        slider.args = {};
2137        // SLIDESHOW:
2138        slider.manualPause = false;
vendor: 44,973 bytes, lines 2139-3184
2139        slider.stopped = false;
2140        //PAUSE WHEN INVISIBLE
2141        slider.started = false;
2142        slider.startTimeout = null;
2143        // TOUCH/USECSS:
2144        slider.transitions = !slider.vars.video && !fade && slider.vars.useCSS && (function() {
2145          var obj = document.createElement('div'),
2146              props = ['perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective'];
2147          for (var i in props) {
2148            if ( obj.style[ props[i] ] !== undefined ) {
2149              slider.pfx = props[i].replace('Perspective','').toLowerCase();
2150              slider.prop = "-" + slider.pfx + "-transform";
2151              return true;
2152            }
2153          }
2154          return false;
2155        }());
2156        slider.ensureAnimationEnd = '';
2157        // CONTROLSCONTAINER:
2158        if (slider.vars.controlsContainer !== "") slider.controlsContainer = $(slider.vars.controlsContainer).length > 0 && $(slider.vars.controlsContainer);
2159        // MANUAL:
2160        if (slider.vars.manualControls !== "") slider.manualControls = $(slider.vars.manualControls).length > 0 && $(slider.vars.manualControls);
2161
2162        // RANDOMIZE:
2163        if (slider.vars.randomize) {
2164          slider.slides.sort(function() { return (Math.round(Math.random())-0.5); });
2165          slider.container.empty().append(slider.slides);
2166        }
2167
2168        slider.doMath();
2169
2170        // INIT
2171        slider.setup("init");
2172
2173        // CONTROLNAV:
2174        if (slider.vars.controlNav) { methods.controlNav.setup(); }
2175
2176        // DIRECTIONNAV:
2177        if (slider.vars.directionNav) { methods.directionNav.setup(); }
2178
2179        // KEYBOARD:
2180        if (slider.vars.keyboard && ($(slider.containerSelector).length === 1 || slider.vars.multipleKeyboard)) {
2181          $(document).bind('keyup', function(event) {
2182            var keycode = event.keyCode;
2183            if (!slider.animating && (keycode === 39 || keycode === 37)) {
2184              var target = (keycode === 39) ? slider.getTarget('next') :
2185                           (keycode === 37) ? slider.getTarget('prev') : false;
2186              slider.flexAnimate(target, slider.vars.pauseOnAction);
2187            }
2188          });
2189        }
2190        // MOUSEWHEEL:
2191        if (slider.vars.mousewheel) {
2192          slider.bind('mousewheel', function(event, delta, deltaX, deltaY) {
2193            event.preventDefault();
2194            var target = (delta < 0) ? slider.getTarget('next') : slider.getTarget('prev');
2195            slider.flexAnimate(target, slider.vars.pauseOnAction);
2196          });
2197        }
2198
2199        // PAUSEPLAY
2200        if (slider.vars.pausePlay) { methods.pausePlay.setup(); }
2201
2202        //PAUSE WHEN INVISIBLE
2203        if (slider.vars.slideshow && slider.vars.pauseInvisible) { methods.pauseInvisible.init(); }
2204
2205        // SLIDSESHOW
2206        if (slider.vars.slideshow) {
2207          if (slider.vars.pauseOnHover) {
2208            slider.hover(function() {
2209              if (!slider.manualPlay && !slider.manualPause) { slider.pause(); }
2210            }, function() {
2211              if (!slider.manualPause && !slider.manualPlay && !slider.stopped) { slider.play(); }
2212            });
2213          }
2214          // initialize animation
2215          //If we're visible, or we don't use PageVisibility API
2216          if(!slider.vars.pauseInvisible || !methods.pauseInvisible.isHidden()) {
2217            (slider.vars.initDelay > 0) ? slider.startTimeout = setTimeout(slider.play, slider.vars.initDelay) : slider.play();
2218          }
2219        }
2220
2221        // ASNAV:
2222        if (asNav) { methods.asNav.setup(); }
2223
2224        // TOUCH
2225        if (touch && slider.vars.touch) { methods.touch(); }
2226
2227        // FADE&&SMOOTHHEIGHT || SLIDE:
2228        if (!fade || (fade && slider.vars.smoothHeight)) { $(window).bind("resize orientationchange focus", methods.resize); }
2229
2230        slider.find("img").attr("draggable", "false");
2231
2232        // API: start() Callback
2233        setTimeout(function(){
2234          slider.vars.start(slider);
2235        }, 200);
2236      },
2237      asNav: {
2238        setup: function() {
2239          slider.asNav = true;
2240          slider.animatingTo = Math.floor(slider.currentSlide/slider.move);
2241          slider.currentItem = slider.currentSlide;
2242          slider.slides.removeClass(namespace + "active-slide").eq(slider.currentItem).addClass(namespace + "active-slide");
2243          if(!msGesture){
2244              slider.slides.on(eventType, function(e){
2245                e.preventDefault();
2246                var $slide = $(this),
2247                    target = $slide.index();
2248                var posFromLeft = $slide.offset().left - $(slider).scrollLeft(); // Find position of slide relative to left of slider container
2249                if( posFromLeft <= 0 && $slide.hasClass( namespace + 'active-slide' ) ) {
2250                  slider.flexAnimate(slider.getTarget("prev"), true);
2251                } else if (!$(slider.vars.asNavFor).data('flexslider').animating && !$slide.hasClass(namespace + "active-slide")) {
2252                  slider.direction = (slider.currentItem < target) ? "next" : "prev";
2253                  slider.flexAnimate(target, slider.vars.pauseOnAction, false, true, true);
2254                }
2255              });
2256          }else{
2257              el._slider = slider;
2258              slider.slides.each(function (){
2259                  var that = this;
2260                  that._gesture = new MSGesture();
2261                  that._gesture.target = that;
2262                  that.addEventListener("MSPointerDown", function (e){
2263                      e.preventDefault();
2264                      if(e.currentTarget._gesture) {
2265                        e.currentTarget._gesture.addPointer(e.pointerId);
2266                      }
2267                  }, false);
2268                  that.addEventListener("MSGestureTap", function (e){
2269                      e.preventDefault();
2270                      var $slide = $(this),
2271                          target = $slide.index();
2272                      if (!$(slider.vars.asNavFor).data('flexslider').animating && !$slide.hasClass('active')) {
2273                          slider.direction = (slider.currentItem < target) ? "next" : "prev";
2274                          slider.flexAnimate(target, slider.vars.pauseOnAction, false, true, true);
2275                      }
2276                  });
2277              });
2278          }
2279        }
2280      },
2281      controlNav: {
2282        setup: function() {
2283          if (!slider.manualControls) {
2284            methods.controlNav.setupPaging();
2285          } else { // MANUALCONTROLS:
2286            methods.controlNav.setupManual();
2287          }
2288        },
2289        setupPaging: function() {
2290          var type = (slider.vars.controlNav === "thumbnails") ? 'control-thumbs' : 'control-paging',
2291              j = 1,
2292              item,
2293              slide;
2294
2295          slider.controlNavScaffold = $('<ol class="'+ namespace + 'control-nav ' + namespace + type + '"></ol>');
2296
2297          if (slider.pagingCount > 1) {
2298            for (var i = 0; i < slider.pagingCount; i++) {
2299              slide = slider.slides.eq(i);
2300
2301              if ( undefined === slide.attr( 'data-thumb-alt' ) ) { 
2302                slide.attr( 'data-thumb-alt', '' ); 
2303              }
2304              
2305              item = $( '<a></a>' ).attr( 'href', '#' ).text( j );
2306              if ( slider.vars.controlNav === "thumbnails" ) {
2307                item = $( '<img/>' ).attr( 'src', slide.attr( 'data-thumb' ) );
2308              }
2309              
2310              if ( '' !== slide.attr( 'data-thumb-alt' ) ) {
2311                item.attr( 'alt', slide.attr( 'data-thumb-alt' ) );
2312              }
2313
2314              if ( 'thumbnails' === slider.vars.controlNav && true === slider.vars.thumbCaptions ) {
2315                var captn = slide.attr( 'data-thumbcaption' );
2316                if ( '' !== captn && undefined !== captn ) { 
2317                  var caption = $('<span></span>' ).addClass( namespace + 'caption' ).text( captn );
2318                  item.append( caption );
2319                }
2320              }
2321              
2322              var liElement = $( '<li>' );
2323              item.appendTo( liElement );
2324              liElement.append( '</li>' );
2325
2326              slider.controlNavScaffold.append(liElement);
2327              j++;
2328            }
2329          }
2330
2331          // CONTROLSCONTAINER:
2332          (slider.controlsContainer) ? $(slider.controlsContainer).append(slider.controlNavScaffold) : slider.append(slider.controlNavScaffold);
2333          methods.controlNav.set();
2334
2335          methods.controlNav.active();
2336
2337          slider.controlNavScaffold.delegate('a, img', eventType, function(event) {
2338            event.preventDefault();
2339
2340            if (watchedEvent === "" || watchedEvent === event.type) {
2341              var $this = $(this),
2342                  target = slider.controlNav.index($this);
2343
2344              if (!$this.hasClass(namespace + 'active')) {
2345                slider.direction = (target > slider.currentSlide) ? "next" : "prev";
2346                slider.flexAnimate(target, slider.vars.pauseOnAction);
2347              }
2348            }
2349
2350            // setup flags to prevent event duplication
2351            if (watchedEvent === "") {
2352              watchedEvent = event.type;
2353            }
2354            methods.setToClearWatchedEvent();
2355
2356          });
2357        },
2358        setupManual: function() {
2359          slider.controlNav = slider.manualControls;
2360          methods.controlNav.active();
2361
2362          slider.controlNav.bind(eventType, function(event) {
2363            event.preventDefault();
2364
2365            if (watchedEvent === "" || watchedEvent === event.type) {
2366              var $this = $(this),
2367                  target = slider.controlNav.index($this);
2368
2369              if (!$this.hasClass(namespace + 'active')) {
2370                (target > slider.currentSlide) ? slider.direction = "next" : slider.direction = "prev";
2371                slider.flexAnimate(target, slider.vars.pauseOnAction);
2372              }
2373            }
2374
2375            // setup flags to prevent event duplication
2376            if (watchedEvent === "") {
2377              watchedEvent = event.type;
2378            }
2379            methods.setToClearWatchedEvent();
2380          });
2381        },
2382        set: function() {
2383          var selector = (slider.vars.controlNav === "thumbnails") ? 'img' : 'a';
2384          slider.controlNav = $('.' + namespace + 'control-nav li ' + selector, (slider.controlsContainer) ? slider.controlsContainer : slider);
2385        },
2386        active: function() {
2387          slider.controlNav.removeClass(namespace + "active").eq(slider.animatingTo).addClass(namespace + "active");
2388        },
2389        update: function(action, pos) {
2390          if (slider.pagingCount > 1 && action === "add") {
2391            slider.controlNavScaffold.append($('<li><a>' + slider.count + '</a></li>'));
2392          } else if (slider.pagingCount === 1) {
2393            slider.controlNavScaffold.find('li').remove();
2394          } else {
2395            slider.controlNav.eq(pos).closest('li').remove();
2396          }
2397          methods.controlNav.set();
2398          (slider.pagingCount > 1 && slider.pagingCount !== slider.controlNav.length) ? slider.update(pos, action) : methods.controlNav.active();
2399        }
2400      },
2401      directionNav: {
2402        setup: function() {
2403          var directionNavScaffold = $('<ul class="' + namespace + 'direction-nav"><li class="' + namespace + 'nav-prev"><a class="' + namespace + 'prev" href="#">' + slider.vars.prevText + '</a></li><li class="' + namespace + 'nav-next"><a class="' + namespace + 'next" href="#">' + slider.vars.nextText + '</a></li></ul>');
2404
2405          // CONTROLSCONTAINER:
2406          if (slider.controlsContainer) {
2407            $(slider.controlsContainer).append(directionNavScaffold);
2408            slider.directionNav = $('.' + namespace + 'direction-nav li a', slider.controlsContainer);
2409          } else {
2410            slider.append(directionNavScaffold);
2411            slider.directionNav = $('.' + namespace + 'direction-nav li a', slider);
2412          }
2413
2414          methods.directionNav.update();
2415
2416          slider.directionNav.bind(eventType, function(event) {
2417            event.preventDefault();
2418            var target;
2419
2420            if (watchedEvent === "" || watchedEvent === event.type) {
2421              target = ($(this).hasClass(namespace + 'next')) ? slider.getTarget('next') : slider.getTarget('prev');
2422              slider.flexAnimate(target, slider.vars.pauseOnAction);
2423            }
2424
2425            // setup flags to prevent event duplication
2426            if (watchedEvent === "") {
2427              watchedEvent = event.type;
2428            }
2429            methods.setToClearWatchedEvent();
2430          });
2431        },
2432        update: function() {
2433          var disabledClass = namespace + 'disabled';
2434          if (slider.pagingCount === 1) {
2435            slider.directionNav.addClass(disabledClass).attr('tabindex', '-1');
2436          } else if (!slider.vars.animationLoop) {
2437            if (slider.animatingTo === 0) {
2438              slider.directionNav.removeClass(disabledClass).filter('.' + namespace + "prev").addClass(disabledClass).attr('tabindex', '-1');
2439            } else if (slider.animatingTo === slider.last) {
2440              slider.directionNav.removeClass(disabledClass).filter('.' + namespace + "next").addClass(disabledClass).attr('tabindex', '-1');
2441            } else {
2442              slider.directionNav.removeClass(disabledClass).removeAttr('tabindex');
2443            }
2444          } else {
2445            slider.directionNav.removeClass(disabledClass).removeAttr('tabindex');
2446          }
2447        }
2448      },
2449      pausePlay: {
2450        setup: function() {
2451          var pausePlayScaffold = $('<div class="' + namespace + 'pauseplay"><a></a></div>');
2452
2453          // CONTROLSCONTAINER:
2454          if (slider.controlsContainer) {
2455            slider.controlsContainer.append(pausePlayScaffold);
2456            slider.pausePlay = $('.' + namespace + 'pauseplay a', slider.controlsContainer);
2457          } else {
2458            slider.append(pausePlayScaffold);
2459            slider.pausePlay = $('.' + namespace + 'pauseplay a', slider);
2460          }
2461
2462          methods.pausePlay.update((slider.vars.slideshow) ? namespace + 'pause' : namespace + 'play');
2463
2464          slider.pausePlay.bind(eventType, function(event) {
2465            event.preventDefault();
2466
2467            if (watchedEvent === "" || watchedEvent === event.type) {
2468              if ($(this).hasClass(namespace + 'pause')) {
2469                slider.manualPause = true;
2470                slider.manualPlay = false;
2471                slider.pause();
2472              } else {
2473                slider.manualPause = false;
2474                slider.manualPlay = true;
2475                slider.play();
2476              }
2477            }
2478
2479            // setup flags to prevent event duplication
2480            if (watchedEvent === "") {
2481              watchedEvent = event.type;
2482            }
2483            methods.setToClearWatchedEvent();
2484          });
2485        },
2486        update: function(state) {
2487          (state === "play") ? slider.pausePlay.removeClass(namespace + 'pause').addClass(namespace + 'play').html(slider.vars.playText) : slider.pausePlay.removeClass(namespace + 'play').addClass(namespace + 'pause').html(slider.vars.pauseText);
2488        }
2489      },
2490      touch: function() {
2491        var startX,
2492          startY,
2493          offset,
2494          cwidth,
2495          dx,
2496          startT,
2497          scrolling = false,
2498          localX = 0,
2499          localY = 0,
2500          accDx = 0;
2501
2502        if(!msGesture){
2503            el.addEventListener('touchstart', onTouchStart, false);
2504
2505            function onTouchStart(e) {
2506              if (slider.animating) {
2507                e.preventDefault();
2508              } else if ( ( window.navigator.msPointerEnabled ) || e.touches.length === 1 ) {
2509                slider.pause();
2510                // CAROUSEL:
2511                cwidth = (vertical) ? slider.h : slider. w;
2512                startT = Number(new Date());
2513                // CAROUSEL:
2514
2515                // Local vars for X and Y points.
2516                localX = e.touches[0].pageX;
2517                localY = e.touches[0].pageY;
2518
2519                offset = (carousel && reverse && slider.animatingTo === slider.last) ? 0 :
2520                         (carousel && reverse) ? slider.limit - (((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo) :
2521                         (carousel && slider.currentSlide === slider.last) ? slider.limit :
2522                         (carousel) ? ((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.currentSlide :
2523                         (reverse) ? (slider.last - slider.currentSlide + slider.cloneOffset) * cwidth : (slider.currentSlide + slider.cloneOffset) * cwidth;
2524                startX = (vertical) ? localY : localX;
2525                startY = (vertical) ? localX : localY;
2526
2527                el.addEventListener('touchmove', onTouchMove, false);
2528                el.addEventListener('touchend', onTouchEnd, false);
2529              }
2530            }
2531
2532            function onTouchMove(e) {
2533              // Local vars for X and Y points.
2534
2535              localX = e.touches[0].pageX;
2536              localY = e.touches[0].pageY;
2537
2538              dx = (vertical) ? startX - localY : startX - localX;
2539              scrolling = (vertical) ? (Math.abs(dx) < Math.abs(localX - startY)) : (Math.abs(dx) < Math.abs(localY - startY));
2540
2541              var fxms = 500;
2542
2543              if ( ! scrolling || Number( new Date() ) - startT > fxms ) {
2544                e.preventDefault();
2545                if (!fade && slider.transitions) {
2546                  if (!slider.vars.animationLoop) {
2547                    dx = dx/((slider.currentSlide === 0 && dx < 0 || slider.currentSlide === slider.last && dx > 0) ? (Math.abs(dx)/cwidth+2) : 1);
2548                  }
2549                  slider.setProps(offset + dx, "setTouch");
2550                }
2551              }
2552            }
2553
2554            function onTouchEnd(e) {
2555              // finish the touch by undoing the touch session
2556              el.removeEventListener('touchmove', onTouchMove, false);
2557
2558              if (slider.animatingTo === slider.currentSlide && !scrolling && !(dx === null)) {
2559                var updateDx = (reverse) ? -dx : dx,
2560                    target = (updateDx > 0) ? slider.getTarget('next') : slider.getTarget('prev');
2561
2562                if (slider.canAdvance(target) && (Number(new Date()) - startT < 550 && Math.abs(updateDx) > 50 || Math.abs(updateDx) > cwidth/2)) {
2563                  slider.flexAnimate(target, slider.vars.pauseOnAction);
2564                } else {
2565                  if (!fade) { slider.flexAnimate(slider.currentSlide, slider.vars.pauseOnAction, true); }
2566                }
2567              }
2568              el.removeEventListener('touchend', onTouchEnd, false);
2569
2570              startX = null;
2571              startY = null;
2572              dx = null;
2573              offset = null;
2574            }
2575        }else{
2576            el.style.msTouchAction = "none";
2577            el._gesture = new MSGesture();
2578            el._gesture.target = el;
2579            el.addEventListener("MSPointerDown", onMSPointerDown, false);
2580            el._slider = slider;
2581            el.addEventListener("MSGestureChange", onMSGestureChange, false);
2582            el.addEventListener("MSGestureEnd", onMSGestureEnd, false);
2583
2584            function onMSPointerDown(e){
2585                e.stopPropagation();
2586                if (slider.animating) {
2587                    e.preventDefault();
2588                }else{
2589                    slider.pause();
2590                    el._gesture.addPointer(e.pointerId);
2591                    accDx = 0;
2592                    cwidth = (vertical) ? slider.h : slider. w;
2593                    startT = Number(new Date());
2594                    // CAROUSEL:
2595
2596                    offset = (carousel && reverse && slider.animatingTo === slider.last) ? 0 :
2597                        (carousel && reverse) ? slider.limit - (((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo) :
2598                            (carousel && slider.currentSlide === slider.last) ? slider.limit :
2599                                (carousel) ? ((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.currentSlide :
2600                                    (reverse) ? (slider.last - slider.currentSlide + slider.cloneOffset) * cwidth : (slider.currentSlide + slider.cloneOffset) * cwidth;
2601                }
2602            }
2603
2604            function onMSGestureChange(e) {
2605                e.stopPropagation();
2606                var slider = e.target._slider;
2607                if(!slider){
2608                    return;
2609                }
2610                var transX = -e.translationX,
2611                    transY = -e.translationY;
2612
2613                //Accumulate translations.
2614                accDx = accDx + ((vertical) ? transY : transX);
2615                dx = accDx;
2616                scrolling = (vertical) ? (Math.abs(accDx) < Math.abs(-transX)) : (Math.abs(accDx) < Math.abs(-transY));
2617
2618                if(e.detail === e.MSGESTURE_FLAG_INERTIA){
2619                    setImmediate(function (){
2620                        el._gesture.stop();
2621                    });
2622
2623                    return;
2624                }
2625
2626                if (!scrolling || Number(new Date()) - startT > 500) {
2627                    e.preventDefault();
2628                    if (!fade && slider.transitions) {
2629                        if (!slider.vars.animationLoop) {
2630                            dx = accDx / ((slider.currentSlide === 0 && accDx < 0 || slider.currentSlide === slider.last && accDx > 0) ? (Math.abs(accDx) / cwidth + 2) : 1);
2631                        }
2632                        slider.setProps(offset + dx, "setTouch");
2633                    }
2634                }
2635            }
2636
2637            function onMSGestureEnd(e) {
2638                e.stopPropagation();
2639                var slider = e.target._slider;
2640                if(!slider){
2641                    return;
2642                }
2643                if (slider.animatingTo === slider.currentSlide && !scrolling && !(dx === null)) {
2644                    var updateDx = (reverse) ? -dx : dx,
2645                        target = (updateDx > 0) ? slider.getTarget('next') : slider.getTarget('prev');
2646
2647                    if (slider.canAdvance(target) && (Number(new Date()) - startT < 550 && Math.abs(updateDx) > 50 || Math.abs(updateDx) > cwidth/2)) {
2648                        slider.flexAnimate(target, slider.vars.pauseOnAction);
2649                    } else {
2650                        if (!fade) { slider.flexAnimate(slider.currentSlide, slider.vars.pauseOnAction, true); }
2651                    }
2652                }
2653
2654                startX = null;
2655                startY = null;
2656                dx = null;
2657                offset = null;
2658                accDx = 0;
2659            }
2660        }
2661      },
2662      resize: function() {
2663        if (!slider.animating && slider.is(':visible')) {
2664          if (!carousel) { slider.doMath(); }
2665
2666          if (fade) {
2667            // SMOOTH HEIGHT:
2668            methods.smoothHeight();
2669          } else if (carousel) { //CAROUSEL:
2670            slider.slides.width(slider.computedW);
2671            slider.update(slider.pagingCount);
2672            slider.setProps();
2673          }
2674          else if (vertical) { //VERTICAL:
2675            slider.viewport.height(slider.h);
2676            slider.setProps(slider.h, "setTotal");
2677          } else {
2678            // SMOOTH HEIGHT:
2679            if (slider.vars.smoothHeight) { methods.smoothHeight(); }
2680            slider.newSlides.width(slider.computedW);
2681            slider.setProps(slider.computedW, "setTotal");
2682          }
2683        }
2684      },
2685      smoothHeight: function(dur) {
2686        if (!vertical || fade) {
2687          var $obj = (fade) ? slider : slider.viewport;
2688          (dur) ? $obj.animate({"height": slider.slides.eq(slider.animatingTo).height()}, dur) : $obj.height(slider.slides.eq(slider.animatingTo).height());
2689        }
2690      },
2691      sync: function(action) {
2692        var $obj = $(slider.vars.sync).data("flexslider"),
2693            target = slider.animatingTo;
2694
2695        switch (action) {
2696          case "animate": $obj.flexAnimate(target, slider.vars.pauseOnAction, false, true); break;
2697          case "play": if (!$obj.playing && !$obj.asNav) { $obj.play(); } break;
2698          case "pause": $obj.pause(); break;
2699        }
2700      },
2701      uniqueID: function($clone) {
2702        // Append _clone to current level and children elements with id attributes
2703        $clone.filter( '[id]' ).add($clone.find( '[id]' )).each(function() {
2704          var $this = $(this);
2705          $this.attr( 'id', $this.attr( 'id' ) + '_clone' );
2706        });
2707        return $clone;
2708      },
2709      pauseInvisible: {
2710        visProp: null,
2711        init: function() {
2712          var visProp = methods.pauseInvisible.getHiddenProp();
2713          if (visProp) {
2714            var evtname = visProp.replace(/[H|h]idden/,'') + 'visibilitychange';
2715            document.addEventListener(evtname, function() {
2716              if (methods.pauseInvisible.isHidden()) {
2717                if(slider.startTimeout) {
2718                  clearTimeout(slider.startTimeout); //If clock is ticking, stop timer and prevent from starting while invisible
2719                } else { 
2720                  slider.pause(); //Or just pause
2721                }
2722              }
2723              else {
2724                if(slider.started) {
2725                  slider.play(); //Initiated before, just play
2726                } else { 
2727                  if (slider.vars.initDelay > 0) { 
2728                    setTimeout(slider.play, slider.vars.initDelay);
2729                  } else {
2730                    slider.play(); //Didn't init before: simply init or wait for it
2731                  } 
2732                }
2733              }
2734            });
2735          }
2736        },
2737        isHidden: function() {
2738          var prop = methods.pauseInvisible.getHiddenProp();
2739          if (!prop) {
2740            return false;
2741          }
2742          return document[prop];
2743        },
2744        getHiddenProp: function() {
2745          var prefixes = ['webkit','moz','ms','o'];
2746          // if 'hidden' is natively supported just return it
2747          if ('hidden' in document) {
2748            return 'hidden';
2749          }
2750          // otherwise loop over all the known prefixes until we find one
2751          for ( var i = 0; i < prefixes.length; i++ ) {
2752              if ((prefixes[i] + 'Hidden') in document) {
2753                return prefixes[i] + 'Hidden';
2754              }
2755          }
2756          // otherwise it's not supported
2757          return null;
2758        }
2759      },
2760      setToClearWatchedEvent: function() {
2761        clearTimeout(watchedEventClearTimer);
2762        watchedEventClearTimer = setTimeout(function() {
2763          watchedEvent = "";
2764        }, 3000);
2765      }
2766    };
2767
2768    // public methods
2769    slider.flexAnimate = function(target, pause, override, withSync, fromNav) {
2770      if (!slider.vars.animationLoop && target !== slider.currentSlide) {
2771        slider.direction = (target > slider.currentSlide) ? "next" : "prev";
2772      }
2773
2774      if (asNav && slider.pagingCount === 1) slider.direction = (slider.currentItem < target) ? "next" : "prev";
2775
2776      if (!slider.animating && (slider.canAdvance(target, fromNav) || override) && slider.is(":visible")) {
2777        if (asNav && withSync) {
2778          var master = $(slider.vars.asNavFor).data('flexslider');
2779          slider.atEnd = target === 0 || target === slider.count - 1;
2780          master.flexAnimate(target, true, false, true, fromNav);
2781          slider.direction = (slider.currentItem < target) ? "next" : "prev";
2782          master.direction = slider.direction;
2783
2784          if (Math.ceil((target + 1)/slider.visible) - 1 !== slider.currentSlide && target !== 0) {
2785            slider.currentItem = target;
2786            slider.slides.removeClass(namespace + "active-slide").eq(target).addClass(namespace + "active-slide");
2787            target = Math.floor(target/slider.visible);
2788          } else {
2789            slider.currentItem = target;
2790            slider.slides.removeClass(namespace + "active-slide").eq(target).addClass(namespace + "active-slide");
2791            return false;
2792          }
2793        }
2794
2795        slider.animating = true;
2796        slider.animatingTo = target;
2797
2798        // SLIDESHOW:
2799        if (pause) { slider.pause(); }
2800
2801        // API: before() animation Callback
2802        slider.vars.before(slider);
2803
2804        // SYNC:
2805        if (slider.syncExists && !fromNav) { methods.sync("animate"); }
2806
2807        // CONTROLNAV
2808        if (slider.vars.controlNav) { methods.controlNav.active(); }
2809
2810        // !CAROUSEL:
2811        // CANDIDATE: slide active class (for add/remove slide)
2812        if (!carousel) { slider.slides.removeClass(namespace + 'active-slide').eq(target).addClass(namespace + 'active-slide'); }
2813
2814        // INFINITE LOOP:
2815        // CANDIDATE: atEnd
2816        slider.atEnd = target === 0 || target === slider.last;
2817
2818        // DIRECTIONNAV:
2819        if (slider.vars.directionNav) { methods.directionNav.update(); }
2820
2821        if (target === slider.last) {
2822          // API: end() of cycle Callback
2823          slider.vars.end(slider);
2824          // SLIDESHOW && !INFINITE LOOP:
2825          if (!slider.vars.animationLoop) { slider.pause(); }
2826        }
2827
2828        // SLIDE:
2829        if (!fade) {
2830          var dimension = (vertical) ? slider.slides.filter(':first').height() : slider.computedW,
2831              margin, slideString, calcNext;
2832
2833          // INFINITE LOOP / REVERSE:
2834          if (carousel) {
2835            //margin = (slider.vars.itemWidth > slider.w) ? slider.vars.itemMargin * 2 : slider.vars.itemMargin;
2836            margin = slider.vars.itemMargin;
2837            calcNext = ((slider.itemW + margin) * slider.move) * slider.animatingTo;
2838            slideString = (calcNext > slider.limit && slider.visible !== 1) ? slider.limit : calcNext;
2839          } else if (slider.currentSlide === 0 && target === slider.count - 1 && slider.vars.animationLoop && slider.direction !== "next") {
2840            slideString = (reverse) ? (slider.count + slider.cloneOffset) * dimension : 0;
2841          } else if (slider.currentSlide === slider.last && target === 0 && slider.vars.animationLoop && slider.direction !== "prev") {
2842            slideString = (reverse) ? 0 : (slider.count + 1) * dimension;
2843          } else {
2844            slideString = (reverse) ? ((slider.count - 1) - target + slider.cloneOffset) * dimension : (target + slider.cloneOffset) * dimension;
2845          }
2846          slider.setProps(slideString, "", slider.vars.animationSpeed);
2847          if (slider.transitions) {
2848            if (!slider.vars.animationLoop || !slider.atEnd) {
2849              slider.animating = false;
2850              slider.currentSlide = slider.animatingTo;
2851            }
2852            
2853            // Unbind previous transitionEnd events and re-bind new transitionEnd event
2854            slider.container.unbind("webkitTransitionEnd transitionend");
2855            slider.container.bind("webkitTransitionEnd transitionend", function() {
2856              clearTimeout(slider.ensureAnimationEnd);
2857              slider.wrapup(dimension);
2858            });
2859
2860            // Insurance for the ever-so-fickle transitionEnd event
2861            clearTimeout(slider.ensureAnimationEnd);
2862            slider.ensureAnimationEnd = setTimeout(function() {
2863              slider.wrapup(dimension);
2864            }, slider.vars.animationSpeed + 100);
2865
2866          } else {
2867            slider.container.animate(slider.args, slider.vars.animationSpeed, slider.vars.easing, function(){
2868              slider.wrapup(dimension);
2869            });
2870          }
2871        } else { // FADE:
2872          if (!touch) {
2873            //slider.slides.eq(slider.currentSlide).fadeOut(slider.vars.animationSpeed, slider.vars.easing);
2874            //slider.slides.eq(target).fadeIn(slider.vars.animationSpeed, slider.vars.easing, slider.wrapup);
2875
2876            slider.slides.eq(slider.currentSlide).css({"zIndex": 1}).animate({"opacity": 0}, slider.vars.animationSpeed, slider.vars.easing);
2877            slider.slides.eq(target).css({"zIndex": 2}).animate({"opacity": 1}, slider.vars.animationSpeed, slider.vars.easing, slider.wrapup);
2878
2879          } else {
2880            slider.slides.eq(slider.currentSlide).css({ "opacity": 0, "zIndex": 1 });
2881            slider.slides.eq(target).css({ "opacity": 1, "zIndex": 2 });
2882            slider.wrapup(dimension);
2883          }
2884        }
2885        // SMOOTH HEIGHT:
2886        if (slider.vars.smoothHeight) { methods.smoothHeight(slider.vars.animationSpeed); }
2887      }
2888    };
2889    slider.wrapup = function(dimension) {
2890      // SLIDE:
2891      if (!fade && !carousel) {
2892        if (slider.currentSlide === 0 && slider.animatingTo === slider.last && slider.vars.animationLoop) {
2893          slider.setProps(dimension, "jumpEnd");
2894        } else if (slider.currentSlide === slider.last && slider.animatingTo === 0 && slider.vars.animationLoop) {
2895          slider.setProps(dimension, "jumpStart");
2896        }
2897      }
2898      slider.animating = false;
2899      slider.currentSlide = slider.animatingTo;
2900      // API: after() animation Callback
2901      slider.vars.after(slider);
2902    };
2903
2904    // SLIDESHOW:
2905    slider.animateSlides = function() {
2906      if (!slider.animating && focused ) { slider.flexAnimate(slider.getTarget("next")); }
2907    };
2908    // SLIDESHOW:
2909    slider.pause = function() {
2910      clearInterval(slider.animatedSlides);
2911      slider.animatedSlides = null;
2912      slider.playing = false;
2913      // PAUSEPLAY:
2914      if (slider.vars.pausePlay) { methods.pausePlay.update("play"); }
2915      // SYNC:
2916      if (slider.syncExists) { methods.sync("pause"); }
2917    };
2918    // SLIDESHOW:
2919    slider.play = function() {
2920      if (slider.playing) { clearInterval(slider.animatedSlides); }
2921      slider.animatedSlides = slider.animatedSlides || setInterval(slider.animateSlides, slider.vars.slideshowSpeed);
2922      slider.started = slider.playing = true;
2923      // PAUSEPLAY:
2924      if (slider.vars.pausePlay) { methods.pausePlay.update("pause"); }
2925      // SYNC:
2926      if (slider.syncExists) { methods.sync("play"); }
2927    };
2928    // STOP:
2929    slider.stop = function () {
2930      slider.pause();
2931      slider.stopped = true;
2932    };
2933    slider.canAdvance = function(target, fromNav) {
2934      // ASNAV:
2935      var last = (asNav) ? slider.pagingCount - 1 : slider.last;
2936      return (fromNav) ? true :
2937             (asNav && slider.currentItem === slider.count - 1 && target === 0 && slider.direction === "prev") ? true :
2938             (asNav && slider.currentItem === 0 && target === slider.pagingCount - 1 && slider.direction !== "next") ? false :
2939             (target === slider.currentSlide && !asNav) ? false :
2940             (slider.vars.animationLoop) ? true :
2941             (slider.atEnd && slider.currentSlide === 0 && target === last && slider.direction !== "next") ? false :
2942             (slider.atEnd && slider.currentSlide === last && target === 0 && slider.direction === "next") ? false :
2943             true;
2944    };
2945    slider.getTarget = function(dir) {
2946      slider.direction = dir;
2947      if (dir === "next") {
2948        return (slider.currentSlide === slider.last) ? 0 : slider.currentSlide + 1;
2949      } else {
2950        return (slider.currentSlide === 0) ? slider.last : slider.currentSlide - 1;
2951      }
2952    };
2953
2954    // SLIDE:
2955    slider.setProps = function(pos, special, dur) {
2956      var target = (function() {
2957        var posCheck = (pos) ? pos : ((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo,
2958            posCalc = (function() {
2959              if (carousel) {
2960                return (special === "setTouch") ? pos :
2961                       (reverse && slider.animatingTo === slider.last) ? 0 :
2962                       (reverse) ? slider.limit - (((slider.itemW + slider.vars.itemMargin) * slider.move) * slider.animatingTo) :
2963                       (slider.animatingTo === slider.last) ? slider.limit : posCheck;
2964              } else {
2965                switch (special) {
2966                  case "setTotal": return (reverse) ? ((slider.count - 1) - slider.currentSlide + slider.cloneOffset) * pos : (slider.currentSlide + slider.cloneOffset) * pos;
2967                  case "setTouch": return (reverse) ? pos : pos;
2968                  case "jumpEnd": return (reverse) ? pos : slider.count * pos;
2969                  case "jumpStart": return (reverse) ? slider.count * pos : pos;
2970                  default: return pos;
2971                }
2972              }
2973            }());
2974
2975            return (posCalc * -1) + "px";
2976          }());
2977
2978      if (slider.transitions) {
2979        target = (vertical) ? "translate3d(0," + target + ",0)" : "translate3d(" + target + ",0,0)";
2980        dur = (dur !== undefined) ? (dur/1000) + "s" : "0s";
2981        slider.container.css("-" + slider.pfx + "-transition-duration", dur);
2982         slider.container.css("transition-duration", dur);
2983      }
2984
2985      slider.args[slider.prop] = target;
2986      if (slider.transitions || dur === undefined) { slider.container.css(slider.args); }
2987
2988      slider.container.css('transform',target);
2989    };
2990
2991    slider.setup = function(type) {
2992      // SLIDE:
2993      if (!fade) {
2994        var sliderOffset, arr;
2995
2996        if (type === "init") {
2997          slider.viewport = $('<div class="' + namespace + 'viewport"></div>').css({"overflow": "hidden", "position": "relative"}).appendTo(slider).append(slider.container);
2998          // INFINITE LOOP:
2999          slider.cloneCount = 0;
3000          slider.cloneOffset = 0;
3001          // REVERSE:
3002          if (reverse) {
3003            arr = $.makeArray(slider.slides).reverse();
3004            slider.slides = $(arr);
3005            slider.container.empty().append(slider.slides);
3006          }
3007        }
3008        // INFINITE LOOP && !CAROUSEL:
3009        if (slider.vars.animationLoop && !carousel) {
3010          slider.cloneCount = 2;
3011          slider.cloneOffset = 1;
3012          // clear out old clones
3013          if (type !== "init") { slider.container.find('.clone').remove(); }
3014          slider.container.append(methods.uniqueID(slider.slides.first().clone().addClass('clone')).attr('aria-hidden', 'true'))
3015                          .prepend(methods.uniqueID(slider.slides.last().clone().addClass('clone')).attr('aria-hidden', 'true'));
3016        }
3017        slider.newSlides = $(slider.vars.selector, slider);
3018
3019        sliderOffset = (reverse) ? slider.count - 1 - slider.currentSlide + slider.cloneOffset : slider.currentSlide + slider.cloneOffset;
3020        // VERTICAL:
3021        if (vertical && !carousel) {
3022          slider.container.height((slider.count + slider.cloneCount) * 200 + "%").css("position", "absolute").width("100%");
3023          setTimeout(function(){
3024            slider.newSlides.css({"display": "block"});
3025            slider.doMath();
3026            slider.viewport.height(slider.h);
3027            slider.setProps(sliderOffset * slider.h, "init");
3028          }, (type === "init") ? 100 : 0);
3029        } else {
3030          slider.container.width((slider.count + slider.cloneCount) * 200 + "%");
3031          slider.setProps(sliderOffset * slider.computedW, "init");
3032          setTimeout(function(){
3033            slider.doMath();
3034            slider.newSlides.css({"width": slider.computedW, "float": "left", "display": "block"});
3035            // SMOOTH HEIGHT:
3036            if (slider.vars.smoothHeight) { methods.smoothHeight(); }
3037          }, (type === "init") ? 100 : 0);
3038        }
3039      } else { // FADE:
3040        slider.slides.css({"width": "100%", "float": "left", "marginRight": "-100%", "position": "relative"});
3041        if (type === "init") {
3042          if (!touch) {
3043            //slider.slides.eq(slider.currentSlide).fadeIn(slider.vars.animationSpeed, slider.vars.easing);
3044            if (slider.vars.fadeFirstSlide == false) {
3045              slider.slides.css({ "opacity": 0, "display": "block", "zIndex": 1 }).eq(slider.currentSlide).css({"zIndex": 2}).css({"opacity": 1});
3046            } else {
3047              slider.slides.css({ "opacity": 0, "display": "block", "zIndex": 1 }).eq(slider.currentSlide).css({"zIndex": 2}).animate({"opacity": 1},slider.vars.animationSpeed,slider.vars.easing);
3048            }
3049          } else {
3050            slider.slides.css({ "opacity": 0, "display": "block", "webkitTransition": "opacity " + slider.vars.animationSpeed / 1000 + "s ease", "zIndex": 1 }).eq(slider.currentSlide).css({ "opacity": 1, "zIndex": 2});
3051          }
3052        }
3053        // SMOOTH HEIGHT:
3054        if (slider.vars.smoothHeight) { methods.smoothHeight(); }
3055      }
3056      // !CAROUSEL:
3057      // CANDIDATE: active slide
3058      if (!carousel) { slider.slides.removeClass(namespace + "active-slide").eq(slider.currentSlide).addClass(namespace + "active-slide"); }
3059
3060      //FlexSlider: init() Callback
3061      slider.vars.init(slider);
3062    };
3063
3064    slider.doMath = function() {
3065      var slide = slider.slides.first(),
3066          slideMargin = slider.vars.itemMargin,
3067          minItems = slider.vars.minItems,
3068          maxItems = slider.vars.maxItems;
3069
3070      slider.w = (slider.viewport===undefined) ? slider.width() : slider.viewport.width();
3071      slider.h = slide.height();
3072      slider.boxPadding = slide.outerWidth() - slide.width();
3073
3074      // CAROUSEL:
3075      if (carousel) {
3076        slider.itemT = slider.vars.itemWidth + slideMargin;
3077        slider.minW = (minItems) ? minItems * slider.itemT : slider.w;
3078        slider.maxW = (maxItems) ? (maxItems * slider.itemT) - slideMargin : slider.w;
3079        slider.itemW = (slider.minW > slider.w) ? (slider.w - (slideMargin * (minItems - 1)))/minItems :
3080                       (slider.maxW < slider.w) ? (slider.w - (slideMargin * (maxItems - 1)))/maxItems :
3081                       (slider.vars.itemWidth > slider.w) ? slider.w : slider.vars.itemWidth;
3082
3083        slider.visible = Math.floor(slider.w/(slider.itemW));
3084        slider.move = (slider.vars.move > 0 && slider.vars.move < slider.visible ) ? slider.vars.move : slider.visible;
3085        slider.pagingCount = Math.ceil(((slider.count - slider.visible)/slider.move) + 1);
3086        slider.last =  slider.pagingCount - 1;
3087        slider.limit = (slider.pagingCount === 1) ? 0 :
3088                       (slider.vars.itemWidth > slider.w) ? (slider.itemW * (slider.count - 1)) + (slideMargin * (slider.count - 1)) : ((slider.itemW + slideMargin) * slider.count) - slider.w - slideMargin;
3089      } else {
3090        slider.itemW = slider.w;
3091        slider.pagingCount = slider.count;
3092        slider.last = slider.count - 1;
3093      }
3094      slider.computedW = slider.itemW - slider.boxPadding;
3095    };
3096
3097    slider.update = function(pos, action) {
3098      slider.doMath();
3099
3100      // update currentSlide and slider.animatingTo if necessary
3101      if (!carousel) {
3102        if (pos < slider.currentSlide) {
3103          slider.currentSlide += 1;
3104        } else if (pos <= slider.currentSlide && pos !== 0) {
3105          slider.currentSlide -= 1;
3106        }
3107        slider.animatingTo = slider.currentSlide;
3108      }
3109
3110      // update controlNav
3111      if (slider.vars.controlNav && !slider.manualControls) {
3112        if ((action === "add" && !carousel) || slider.pagingCount > slider.controlNav.length) {
3113          methods.controlNav.update("add");
3114        } else if ((action === "remove" && !carousel) || slider.pagingCount < slider.controlNav.length) {
3115          if (carousel && slider.currentSlide > slider.last) {
3116            slider.currentSlide -= 1;
3117            slider.animatingTo -= 1;
3118          }
3119          methods.controlNav.update("remove", slider.last);
3120        }
3121      }
3122      // update directionNav
3123      if (slider.vars.directionNav) { methods.directionNav.update(); }
3124
3125    };
3126
3127    slider.addSlide = function(obj, pos) {
3128      var $obj = $(obj);
3129
3130      slider.count += 1;
3131      slider.last = slider.count - 1;
3132
3133      // append new slide
3134      if (vertical && reverse) {
3135        (pos !== undefined) ? slider.slides.eq(slider.count - pos).after($obj) : slider.container.prepend($obj);
3136      } else {
3137        (pos !== undefined) ? slider.slides.eq(pos).before($obj) : slider.container.append($obj);
3138      }
3139
3140      // update currentSlide, animatingTo, controlNav, and directionNav
3141      slider.update(pos, "add");
3142
3143      // update slider.slides
3144      slider.slides = $(slider.vars.selector + ':not(.clone)', slider);
3145      // re-setup the slider to accomdate new slide
3146      slider.setup();
3147
3148      //FlexSlider: added() Callback
3149      slider.vars.added(slider);
3150    };
3151    slider.removeSlide = function(obj) {
3152      var pos = (isNaN(obj)) ? slider.slides.index($(obj)) : obj;
3153
3154      // update count
3155      slider.count -= 1;
3156      slider.last = slider.count - 1;
3157
3158      // remove slide
3159      if (isNaN(obj)) {
3160        $(obj, slider.slides).remove();
3161      } else {
3162        (vertical && reverse) ? slider.slides.eq(slider.last).remove() : slider.slides.eq(obj).remove();
3163      }
3164
3165      // update currentSlide, animatingTo, controlNav, and directionNav
3166      slider.doMath();
3167      slider.update(pos, "remove");
3168
3169      // update slider.slides
3170      slider.slides = $(slider.vars.selector + ':not(.clone)', slider);
3171      // re-setup the slider to accomdate new slide
3172      slider.setup();
3173
3174      // FlexSlider: removed() Callback
3175      slider.vars.removed(slider);
3176    };
3177
3178    //FlexSlider: Initialize
3179    methods.init();
3180  };
3181
3182  // Ensure the slider isn't focussed if the window loses focus.
3183  $( window ).blur( function ( e ) {
3184    focused = false;
3185  }).focus( function ( e ) {
3186    focused = true;
3187  });
3188
3189  //FlexSlider: Default Settings
3190  $.flexslider.defaults = {
3191    namespace: "flex-",             //{NEW} String: Prefix string attached to the class of every element generated by the plugin
3192    selector: ".slides > li",       //{NEW} Selector: Must match a simple pattern. '{container} > {slide}' -- Ignore pattern at your own peril
3193    animation: "fade",              //String: Select your animation type, "fade" or "slide"
3194    easing: "swing",                //{NEW} String: Determines the easing method used in jQuery transitions. jQuery easing plugin is supported!
3195    direction: "horizontal",        //String: Select the sliding direction, "horizontal" or "vertical"
3196    reverse: false,                 //{NEW} Boolean: Reverse the animation direction
3197    animationLoop: true,            //Boolean: Should the animation loop? If false, directionNav will received "disable" classes at either end
3198    smoothHeight: false,            //{NEW} Boolean: Allow height of the slider to animate smoothly in horizontal mode
3199    startAt: 0,                     //Integer: The slide that the slider should start on. Array notation (0 = first slide)
3200    slideshow: true,                //Boolean: Animate slider automatically
3201    slideshowSpeed: 7000,           //Integer: Set the speed of the slideshow cycling, in milliseconds
3202    animationSpeed: 600,            //Integer: Set the speed of animations, in milliseconds
3203    initDelay: 0,                   //{NEW} Integer: Set an initialization delay, in milliseconds
3204    randomize: false,               //Boolean: Randomize slide order
3205    fadeFirstSlide: true,           //Boolean: Fade in the first slide when animation type is "fade"
3206    thumbCaptions: false,           //Boolean: Whether or not to put captions on thumbnails when using the "thumbnails" controlNav.
3207
3208    // Usability features
3209    pauseOnAction: true,            //Boolean: Pause the slideshow when interacting with control elements, highly recommended.
3210    pauseOnHover: false,            //Boolean: Pause the slideshow when hovering over slider, then resume when no longer hovering
3211    pauseInvisible: true,   		//{NEW} Boolean: Pause the slideshow when tab is invisible, resume when visible. Provides better UX, lower CPU usage.
3212    useCSS: true,                   //{NEW} Boolean: Slider will use CSS3 transitions if available
3213    touch: true,                    //{NEW} Boolean: Allow touch swipe navigation of the slider on touch-enabled devices
3214    video: false,                   //{NEW} Boolean: If using video in the slider, will prevent CSS3 3D Transforms to avoid graphical glitches
3215
3216    // Primary Controls
3217    controlNav: true,               //Boolean: Create navigation for paging control of each slide? Note: Leave true for manualControls usage
3218    directionNav: true,             //Boolean: Create navigation for previous/next navigation? (true/false)
3219    prevText: "Previous",           //String: Set the text for the "previous" directionNav item
3220    nextText: "Next",               //String: Set the text for the "next" directionNav item
3221
3222    // Secondary Navigation
3223    keyboard: true,                 //Boolean: Allow slider navigating via keyboard left/right keys
3224    multipleKeyboard: false,        //{NEW} Boolean: Allow keyboard navigation to affect multiple sliders. Default behavior cuts out keyboard navigation with more than one slider present.
3225    mousewheel: false,              //{UPDATED} Boolean: Requires jquery.mousewheel.js (https://github.com/brandonaaron/jquery-mousewheel) - Allows slider navigating via mousewheel
3226    pausePlay: false,               //Boolean: Create pause/play dynamic element
3227    pauseText: "Pause",             //String: Set the text for the "pause" pausePlay item
3228    playText: "Play",               //String: Set the text for the "play" pausePlay item
3229
3230    // Special properties
3231    controlsContainer: "",          //{UPDATED} jQuery Object/Selector: Declare which container the navigation elements should be appended too. Default container is the FlexSlider element. Example use would be $(".flexslider-container"). Property is ignored if given element is not found.
3232    manualControls: "",             //{UPDATED} jQuery Object/Selector: Declare custom control navigation. Examples would be $(".flex-control-nav li") or "#tabs-nav li img", etc. The number of elements in your controlNav should match the number of slides/tabs.
3233    sync: "",                       //{NEW} Selector: Mirror the actions performed on this slider with another slider. Use with care.
3234    asNavFor: "",                   //{NEW} Selector: Internal property exposed for turning the slider into a thumbnail navigation for another slider
3235
3236    // Carousel Options
3237    itemWidth: 0,                   //{NEW} Integer: Box-model width of individual carousel items, including horizontal borders and padding.
3238    itemMargin: 0,                  //{NEW} Integer: Margin between carousel items.
3239    minItems: 1,                    //{NEW} Integer: Minimum number of carousel items that should be visible. Items will resize fluidly when below this.
3240    maxItems: 0,                    //{NEW} Integer: Maxmimum number of carousel items that should be visible. Items will resize fluidly when above this limit.
3241    move: 0,                        //{NEW} Integer: Number of carousel items that should move on animation. If 0, slider will move all visible items.
3242    allowOneSlide: true,           //{NEW} Boolean: Whether or not to allow a slider comprised of a single slide
3243
3244    // Callback API
3245    start: function(){},            //Callback: function(slider) - Fires when the slider loads the first slide
3246    before: function(){},           //Callback: function(slider) - Fires asynchronously with each slider animation
3247    after: function(){},            //Callback: function(slider) - Fires after each slider animation completes
3248    end: function(){},              //Callback: function(slider) - Fires when the slider reaches the last slide (asynchronous)
3249    added: function(){},            //{NEW} Callback: function(slider) - Fires after a slide is added
3250    removed: function(){},           //{NEW} Callback: function(slider) - Fires after a slide is removed
3251    init: function() {}             //{NEW}
3251 Callback: function(slider) - Fires after the slider is initially setup
3252  };
3253
3254  //FlexSlider: Plugin Function
3255  $.fn.flexslider = function(options) {
3256    if (options === undefined) { options = {}; }
3257
3258    if (typeof options === "object") {
3259      return this.each(function() {
3260        var $this = $(this),
3261            selector = (options.selector) ? options.selector : ".slides > li",
3262            $slides = $this.find(selector);
3263
3264      if ( ( $slides.length === 1 && options.allowOneSlide === true ) || $slides.length === 0 ) {
3265          $slides.fadeIn(400);
3266          if (options.start) { options.start($this); }
3267        } else if ($this.data('flexslider') === undefined) {
3268          new $.flexslider(this, options);
3269        }
3270      });
3271    } else {
3272      // Helper strings to quickly perform functions on the slider
3273      var $slider = $(this).data('flexslider');
3274      switch (options) {
3275        case "play": $slider.play(); break;
3276        case "pause": $slider.pause(); break;
3277        case "stop": $slider.stop(); break;
3278        case "next": $slider.flexAnimate($slider.getTarget("next"), true); break;
3279        case "prev":
3280        case "previous": $slider.flexAnimate($slider.getTarget("prev"), true); break;
3281        default: if (typeof options === "number") { $slider.flexAnimate(options, true); }
3282      }
3283    }
3284  };
3285})(jQuery);
3286;
3287/*
3288	By Osvaldas Valutis, www.osvaldas.info
3289	Available for use under the MIT License
3290*/
3291
3292
3293
3294;(function( $, window, document, undefined )
3295{
3296	$.fn.doubleTapToGo = function( params )
3297	{
3298		if( !( 'ontouchstart' in window ) &&
3299			!navigator.msMaxTouchPoints &&
3300			!navigator.userAgent.toLowerCase().match( /windows phone os 7/i ) ) return false;
3301
3302		this.each( function()
3303		{
3304			var curItem = false;
3305
3306			$( this ).on( 'click', function( e )
3307			{
3308				var item = $( this );
3309				if( item[ 0 ] != curItem[ 0 ] )
3310				{
3311					e.preventDefault();
3312					curItem = item;
3313				}
3314			});
3315
3316			$( document ).on( 'click touchstart MSPointerDown', function( e )
3317			{
3318				var resetItem = true,
3319					parents	= $( e.target ).parents();
3320
3321				for( var i = 0; i < parents.length; i++ )
3322					if( parents[ i ] == curItem[ 0 ] )
3323						resetItem = false;
3324
3325				if( resetItem )
3326					curItem = false;
3327			});
3328		});
3329		return this;
3330	};
3331})( jQuery, window, document );;
3332/**
3333 * global.js
3334 *
3335 * Handles various UI actions
3336 * - Mobile menu
3337 * - Search form show/hide
3338 * - Touch device dropdowns (requires: doubletaptogo.js)
3339 * - Flexslider (requires: flexslider.js)
3340 * - Scroll to top
3341 * - Smooth page load
3342 *
3343 * @package Rowling
3344 */
3345( function( $ ) {
3346
3347/**
3348 * On ready
3349 */
3350	$( document ).ready( function() {
3351		var windowWidth = $( window ).width();
3352
3353
3354		// Toggle mobile menu
3355		$( ".nav-toggle" ).on( "click", function() {
3356			$( this ).toggleClass( "active" );
3357			$( ".header-bottom-menu" ).slideToggle();
3358			if ( $( ".search-toggle" ).hasClass( "active" ) ) {
3359				$( ".search-toggle" ).removeClass( "active" );
3360				$( ".search-container" ).slideToggle();
3361			}
3362			return false;
3363		});
3364
3365
3366		// Toggle search container
3367		$( ".search-toggle" ).on( "click", function() {
3368			$( this ).toggleClass( "active" );
3369			$( ".search-container" ).slideToggle();
3370			if ( $( ".nav-toggle" ).hasClass( "active" ) ) {
3371				$( ".nav-toggle" ).removeClass( "active" );
3372				$( ".header-bottom-menu" ).slideToggle();
3373			}
3374			return false;
3375		});
3376
3377		$( window ).resize( function() {
3378
3379			// Prevent the Android keyboard from triggering the following code
3380			if ( windowWidth !== $( window ).width() ) {
3381
3382				// Hide mobile menu/search container at resize
3383				if ( $(window).width() >= 851 ) {
3384					$( ".nav-toggle" ).removeClass( "active" );
3385					$( ".header-bottom-menu" ).show();
3386				}
3387
3388				if ( $(window).width() <= 850 ) {
3389					$( ".search-toggle" ).removeClass( "active" );
3390					$( ".search-container" ).hide();
3391				}
3392
3393				// Dropdown menus on touch devices, excluding mobile menu
3394				if ( $(window).width() <= 851 ) {
3395					$( ".header-bottom-menu li:has(ul)" ).doubleTapToGo();
3396					$( ".header-top-menu li:has(ul)" ).doubleTapToGo();
3397				}
3398
3399				if ( $(window).width() >= 850 ) {
3400					$( ".header-bottom-menu li:has(ul)" ).unbind();
3401					$( ".header-top-menu li:has(ul)" ).unbind();
3402				}
3403			}
3404
3405			windowWidth = $( window ).width();
3406
3407		}).resize();
3408
3409		// Load Flexslider
3410		$( ".flexslider" ).flexslider( {
3411			animation: "slide",
3412			controlNav: false,
3413			prevText: "",
3414			nextText: "",
3415			smoothHeight: true
3416		});
3417
3418		// smooth scroll to the top
3419		$( ".to-the-top" ).click( function() {
3420			$( "html, body" ).animate( { scrollTop: 0 }, 500 );
3421				return false;
3422		});
3423
3424	});
3425
3426
3427	/**
3428	 * On load
3429	 */
3430	$( document.body ).on( 'post-load', function () {
3431
3432		// Load Flexslider after new posts have been added to the page.
3433		$( ".flexslider" ).flexslider( {
3434			animation: "slide",
3435			controlNav: false,
3436			prevText: "",
3437			nextText: "",
3438			smoothHeight: true
3439		});
3440
3441	});
3442
3443} ) ( jQuery );
3444;
3445window.addComment=function(a){function b(){c(),g()}function c(a){if(t&&(m=j(r.cancelReplyId),n=j(r.commentFormId),m)){m.addEventListener("touchstart",e),m.addEventListener("click",e);for(var b,c=d(a),g=0,h=c.length;g<h;g++)b=c[g],b.addEventListener("touchstart",f),b.addEventListener("click",f)}}function d(a){var b,c=r.commentReplyClass;return a&&a.childNodes||(a=q),b=q.getElementsByClassName?a.getElementsByClassName(c):a.querySelectorAll("."+c)}function e(a){var b=this,c=r.temporaryFormId,d=j(c);d&&o&&(j(r.parentIdFieldId).value="0",d.parentNode.replaceChild(o,d),b.style.display="none",a.preventDefault())}function f(b){var c,d=this,e=i(d,"belowelement"),f=i(d,"commentid"),g=i(d,"respondelement"),h=i(d,"postid");e&&f&&g&&h&&(c=a.addComment.moveForm(e,f,g,h),!1===c&&b.preventDefault())}function g(){if(s){var a={childList:!0,subTree:!0};p=new s(h),p.observe(q.body,a)}}function h(a){for(var b=a.length;b--;)if(a[b].addedNodes.length)return void c()}function i(a,b){return u?a.dataset[b]:a.getAttribute("data-"+b)}function j(a){return q.getElementById(a)}function k(b,c,d,e){var f=j(b);o=j(d);var g,h,i,k=j(r.parentIdFieldId),p=j(r.postIdFieldId);if(f&&o&&k){l(o),e&&p&&(p.value=e),k.value=c,m.style.display="",f.parentNode.insertBefore(o,f.nextSibling),m.onclick=function(){return!1};try{for(var s=0;s<n.elements.length;s++)if(g=n.elements[s],h=!1,"getComputedStyle"in a?i=a.getComputedStyle(g):q.documentElement.currentStyle&&(i=g.currentStyle),(g.offsetWidth<=0&&g.offsetHeight<=0||"hidden"===i.visibility)&&(h=!0),"hidden"!==g.type&&!g.disabled&&!h){g.focus();break}}catch(t){}return!1}}function l(a){var b=r.temporaryFormId,c=j(b);c||(c=q.createElement("div"),c.id=b,c.style.display="none",a.parentNode.insertBefore(c,a))}var m,n,o,p,q=a.document,r={commentReplyClass:"comment-reply-link",cancelReplyId:"cancel-comment-reply-link",commentFormId:"commentform",temporaryFormId:"wp-temp-form-div",parentIdFieldId:"comment_parent",postIdFieldId:"comment_post_ID"},s=a.MutationObserver||a.WebKitMutationObserver||a.MozMutationObserver,t="querySelector"in q&&"addEventListener"in a,u=!!q.documentElement.dataset;return t&&"loading"!==q.readyState?b():t&&a.addEventListener("DOMContentLoaded",b,!1),{init:c,moveForm:k}}(window);;
3446/* global FB, jpfbembed */
3447( function( window ) {
3448	var facebookEmbed = function() {
3449		var fbroot, src;
3450
3451		if ( 'undefined' !== typeof FB && FB.XFBML ) {
3452			FB.XFBML.parse();
3453		} else {
3454			fbroot = document.createElement( 'div' );
3455			fbroot.id = 'fb-root';
3456			document.getElementsByTagName( 'body' )[ 0 ].appendChild( fbroot );
3457
3458			src = 'https://connect.facebook.net/' + jpfbembed.locale + '/sdk.js#xfbml=1';
3459			if ( jpfbembed.appid ) {
3460				src += '&appId=' + jpfbembed.appid;
3461			}
3462			src += '&version=v2.3';
3463			jQuery.getScript( src );
3464		}
3465	};
3466
3467	window.fbAsyncInit = function() {
3468		FB.init( {
3469			appId: jpfbembed.appid,
3470			version: 'v2.3',
3471		} );
3472
3473		FB.XFBML.parse();
3474	};
3475
3476	if ( 'undefined' !== typeof infiniteScroll ) {
3477		jQuery( document.body ).on( 'post-load', facebookEmbed );
3478	}
3479
3480	facebookEmbed();
3481} )( this );
3482;
3483/***
3484 * Warning: This file is remotely enqueued in Jetpack's Masterbar module.
3485 * Changing it will also affect Jetpack sites.
3486 */
3487jQuery( document ).ready( function( $, wpcom ) {
3488	var masterbar,
3489		menupops = $( 'li#wp-admin-bar-blog.menupop, li#wp-admin-bar-newdash.menupop, li#wp-admin-bar-my-account.menupop' ),
3490		newmenu = $( '#wp-admin-bar-new-post-types' );
3491
3492	// Unbind hoverIntent, we want clickable menus.
3493	menupops
3494		.unbind( 'mouseenter mouseleave' )
3495		.removeProp( 'hoverIntent_t' )
3496		.removeProp( 'hoverIntent_s' )
3497		.on( 'mouseover', function(e) {
3498			var li = $(e.target).closest( 'li.menupop' );
3499			menupops.not(li).removeClass( 'ab-hover' );
3500			li.toggleClass( 'ab-hover' );
3501		} )
3502		.on( 'click touchstart', function(e) {
3503			var $target = $( e.target );
3504
3505			if ( masterbar.focusSubMenus( $target ) ) {
3506				return;
3507			}
3508
3509			e.preventDefault();
3510			masterbar.toggleMenu( $target );
3511		} );
3512
3513	masterbar = {
3514		focusSubMenus: function( $target ) {
3515			// Handle selection of menu items
3516			if ( ! $target.closest( 'ul' ).hasClass( 'ab-top-menu' ) ) {
3517				$target
3518					.closest( 'li' );
3519
3520				return true;
3521			}
3522
3523			return false;
3524		},
3525
3526		toggleMenu: function( $target ) {
3527			var $li = $target.closest( 'li.menupop' ),
3528				$html = $( 'html' );
3529
3530			$( 'body' ).off( 'click.ab-menu' );
3531			$( '#wpadminbar li.menupop' ).not($li).removeClass( 'ab-active wpnt-stayopen wpnt-show' );
3532
3533			if ( $li.hasClass( 'ab-active' ) ) {
3534				$li.removeClass( 'ab-active' );
3535				$html.removeClass( 'ab-menu-open' );
3536			} else {
3537				$li.addClass( 'ab-active' );
3538				$html.addClass( 'ab-menu-open' );
3539
3540				$( 'body' ).on( 'click.ab-menu', function( e ) {
3541					if ( ! $( e.target ).parents( '#wpadminbar' ).length ) {
3542						e.preventDefault();
3543						masterbar.toggleMenu( $li );
3544						$( 'body' ).off( 'click.ab-menu' );
3545					}
3546				} );
3547			}
3548		}
3549	};
3550} );;
3551/*globals JSON */
3552( function( $ ) {
3553	var eventName = 'wpcom_masterbar_click';
3554
3555	var linksTracksEvents = {
3556		//top level items
3557		'wp-admin-bar-blog'                        : 'my_sites',
3558		'wp-admin-bar-newdash'                     : 'reader',
3559		'wp-admin-bar-ab-new-post'                 : 'write_button',
3560		'wp-admin-bar-my-account'                  : 'my_account',
3561		'wp-admin-bar-notes'                       : 'notifications',
3562		//my sites - top items
3563		'wp-admin-bar-switch-site'                 : 'my_sites_switch_site',
3564		'wp-admin-bar-blog-info'                   : 'my_sites_site_info',
3565		'wp-admin-bar-site-view'                   : 'my_sites_view_site',
3566		'wp-admin-bar-blog-stats'                  : 'my_sites_site_stats',
3567		'wp-admin-bar-plan'                        : 'my_sites_plan',
3568		'wp-admin-bar-plan-badge'                  : 'my_sites_plan_badge',
3569		//my sites - manage
3570		'wp-admin-bar-edit-page'                   : 'my_sites_manage_site_pages',
3571		'wp-admin-bar-new-page-badge'              : 'my_sites_manage_add_page',
3572		'wp-admin-bar-edit-post'                   : 'my_sites_manage_blog_posts',
3573		'wp-admin-bar-new-post-badge'              : 'my_sites_manage_add_post',
3574		'wp-admin-bar-edit-attachment'             : 'my_sites_manage_media',
3575		'wp-admin-bar-new-attachment-badge'        : 'my_sites_manage_add_media',
3576		'wp-admin-bar-comments'                    : 'my_sites_manage_comments',
3577		'wp-admin-bar-edit-jetpack-testimonial'    : 'my_sites_manage_testimonials',
3578		'wp-admin-bar-new-jetpack-testimonial'     : 'my_sites_manage_add_testimonial',
3579		'wp-admin-bar-edit-jetpack-portfolio'      : 'my_sites_manage_portfolio',
3580		'wp-admin-bar-new-jetpack-portfolio'       : 'my_sites_manage_add_portfolio',
3581		//my sites - personalize
3582		'wp-admin-bar-themes'                      : 'my_sites_personalize_themes',
3583		'wp-admin-bar-cmz'                         : 'my_sites_personalize_themes_customize',
3584		//my sites - configure
3585		'wp-admin-bar-sharing'                     : 'my_sites_configure_sharing',
3586		'wp-admin-bar-people'                      : 'my_sites_configure_people',
3587		'wp-admin-bar-people-add'                  : 'my_sites_configure_people_add_button',
3588		'wp-admin-bar-plugins'                     : 'my_sites_configure_plugins',
3589		'wp-admin-bar-domains'                     : 'my_sites_configure_domains',
3590		'wp-admin-bar-domains-add'                 : 'my_sites_configure_add_domain',
3591		'wp-admin-bar-blog-settings'               : 'my_sites_configure_settings',
3592		'wp-admin-bar-legacy-dashboard'            : 'my_sites_configure_wp_admin',
3593		//reader
3594		'wp-admin-bar-followed-sites'              : 'reader_followed_sites',
3595		'wp-admin-bar-reader-followed-sites-manage': 'reader_manage_followed_sites',
3596		'wp-admin-bar-discover-discover'           : 'reader_discover',
3597		'wp-admin-bar-discover-search'             : 'reader_search',
3598		'wp-admin-bar-my-activity-my-likes'        : 'reader_my_likes',
3599		//account
3600		'wp-admin-bar-user-info'                   : 'my_account_user_name',
3601		// account - profile
3602		'wp-admin-bar-my-profile'                  : 'my_account_profile_my_profile',
3603		'wp-admin-bar-account-settings'            : 'my_account_profile_account_settings',
3604		'wp-admin-bar-billing'                     : 'my_account_profile_manage_purchases',
3605		'wp-admin-bar-security'                    : 'my_account_profile_security',
3606		'wp-admin-bar-notifications'               : 'my_account_profile_notifications',
3607		//account - special
3608		'wp-admin-bar-get-apps'                    : 'my_account_special_get_apps',
3609		'wp-admin-bar-next-steps'                  : 'my_account_special_next_steps',
3610		'wp-admin-bar-help'                        : 'my_account_special_help',
3611	};
3612
3613	var notesTracksEvents = {
3614		openSite: function( data ) {
3615			return {
3616				clicked: 'masterbar_notifications_panel_site',
3617				site_id: data.siteId
3618			};
3619		},
3620		openPost: function( data ) {
3621			return {
3622				clicked: 'masterbar_notifications_panel_post',
3623				site_id: data.siteId,
3624				post_id: data.postId
3625			};
3626		},
3627		openComment: function( data ) {
3628			return {
3629				clicked: 'masterbar_notifications_panel_comment',
3630				site_id: data.siteId,
3631				post_id: data.postId,
3632				comment_id: data.commentId
3633			};
3634		}
3635	};
3636
3637	function recordTracksEvent( eventProps ) {
3638		eventProps = eventProps || {};
3639		window._tkq = window._tkq || [];
3640		window._tkq.push( [ 'recordEvent', eventName, eventProps ] );
3641	}
3642
3643	function parseJson( s, defaultValue ) {
3644		try {
3645			return JSON.parse( s );
3646		} catch ( e ) {
3647			return defaultValue;
3648		}
3649	}
3650
3651	$( document ).ready( function() {
3652		var trackableLinks = '.mb-trackable .ab-item:not(div),' +
3653			'#wp-admin-bar-notes .ab-item,' +
3654			'#wp-admin-bar-user-info .ab-item,' +
3655			'.mb-trackable .ab-secondary';
3656
3657		$( trackableLinks ).on( 'click touchstart', function( e ) {
3658			var $target = $( e.target ),
3659				$parent = $target.closest( 'li' );
3660
3661			if ( ! $parent ) {
3662				return;
3663			}
3664
3665			var trackingId = $target.attr( 'ID' ) || $parent.attr( 'ID' );
3666
3667			if ( ! linksTracksEvents.hasOwnProperty( trackingId ) ) {
3668				return;
3669			}
3670
3671			var eventProps = { 'clicked': linksTracksEvents[ trackingId ] };
3672
3673			recordTracksEvent( eventProps );
3674		} );
3675	} );
3676
3677	// listen for postMessage events from the notifications iframe
3678	$( window ).on( 'message', function( e ) {
3679		var event = ! e.data && e.originalEvent.data ? e.originalEvent : e;
3680		if ( event.origin !== 'https://widgets.wp.com' ) {
3681			return;
3682		}
3683
3684		var data = ( 'string' === typeof event.data ) ? parseJson( event.data, {} ) : event.data;
3685		if ( 'notesIframeMessage' !== data.type ) {
3686			return;
3687		}
3688
3689		var eventData = notesTracksEvents[ data.action ];
3690		if ( ! eventData ) {
3691			return;
3692		}
3693
3694		recordTracksEvent( eventData( data ) );
3695	} );
3696
3697} )( jQuery );
3698;
3699var wpcom = window.wpcom || {};
3700wpcom.actionbar = {};
3701wpcom.actionbar.data = actionbardata;
3702
3703// This might be better in another file, but is here for now
3704(function($){
3705	var fbd = wpcom.actionbar.data,
3706			d = document,
3707			docHeight = $( d ).height(),
3708			b = d.getElementsByTagName( 'body' )[0],
3709			lastScrollTop = 0,
3710			lastScrollDir, fb, fhtml, fbhtml, fbHtmlLi,
3711			followingbtn, followbtn, fbdf, action,
3712			slkhtml = '', foldhtml = '', reporthtml = '',
3713			customizeIcon, editIcon, statsIcon, themeHtml = '', signupHtml = '', loginHtml = '',
3714			viewReaderHtml = '', editSubsHtml = '', editFollowsHtml = '',
3715			toggleactionbar, $actionbar;
3716
3717	// Don't show actionbar when iframed
3718	if ( window != window.top ) {
3719		return;
3720	}
3721
3722	fhtml = '<ul>';
3723
3724	// Customize Icon
3725	customizeIcon = '<svg class="gridicon gridicon__customize" height="20px" width="20px" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M2 6c0-1.505.78-3.08 2-4 0 .845.69 2 2 2 1.657 0 3 1.343 3 3 0 .386-.08.752-.212 1.09.74.594 1.476 1.19 2.19 1.81L8.9 11.98c-.62-.716-1.214-1.454-1.807-2.192C6.753 9.92 6.387 10 6 10c-2.21 0-4-1.79-4-4zm12.152 6.848l1.34-1.34c.607.304 1.283.492 2.008.492 2.485 0 4.5-2.015 4.5-4.5 0-.725-.188-1.4-.493-2.007L18 9l-2-2 3.507-3.507C18.9 3.188 18.225 3 17.5 3 15.015 3 13 5.
3725015 13 7.5c0 .725.188 1.4.493 2.007L3 20l2 2 6.848-6.848c1.885 1.928 3.874 3.753 5.977 5.45l1.425 1.148 1.5-1.5-1.15-1.425c-1.695-2.103-3.52-4.092-5.448-5.977z" data-reactid=".2.1.1:0.1b.0"></path></g></svg>';
3726
3727	if ( fbd.canCustomizeSite && fbd.isLoggedIn ) {
3728		fhtml += '<li class="actnbr-btn actnbr-customize"><a href="'+ fbd.customizeLink +'">' + customizeIcon + '<span>' + fbd.i18n.customize + '<span></a></li>';
3729	}
3730
3731	// Edit Icon
3732	editIcon = '<svg class="gridicon gridicon__pencil" height="20px" width="20px" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M13 6l5 5-9.507 9.507c-.686-.686-.69-1.794-.012-2.485l-.002-.003c-.69.676-1.8.673-2.485-.013-.677-.677-.686-1.762-.036-2.455l-.008-.008c-.694.65-1.78.64-2.456-.036L13 6zm7.586-.414l-2.172-2.172c-.78-.78-2.047-.78-2.828 0L14 5l5 5 1.586-1.586c.78-.78.78-2.047 0-2.828zM3 18v3h3c0-1.657-1.343-3-3-3z"></path></g></svg>';
3733
3734	if ( fbd.canEditPost ) {
3735		fhtml += '<li class="actnbr-btn actnbr-edit"><a href="'+ fbd.editLink +'">' + editIcon + '<span>' + fbd.i18n.edit + '</span></a></li>';
3736	}
3737
3738	// Stats Icon
3739	statsIcon = '<svg class="gridicon gridicon__stats-alt" height="20px" width="20px" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M21,21H3v-2h18V21z M8,10H4v7h4V10z M14,3h-4v14h4V3z M20,6h-4v11h4V6z"/></path></g></svg>';
3740
3741	if ( fbd.canEditPost ) {
3742		fhtml += '<li class="actnbr-btn actnbr-stats"><a href="'+ fbd.statsLink +'">' + statsIcon + '<span>' + fbd.i18n.stats + '</span></a></li>';
3743	}
3744
3745	// Follow/Unfollow Icon
3746	followingbtn = '<svg class="gridicon gridicon__following" height="24px" width="24px" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M23 13.482L15.508 21 12 17.4l1.412-1.388 2.106 2.188 6.094-6.094L23 13.482zm-7.455 1.862L20 10.89V2H2v14c0 1.1.9 2 2 2h4.538l4.913-4.832 2.095 2.176zM8 13H4v-1h4v1zm3-2H4v-1h7v1zm0-2H4V8h7v1zm7-3H4V4h14v2z"/></g></svg>';
3747	followbtn = '<svg class="gridicon gridicon__follow" height="24px" width="24px" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M23 16v2h-3v3h-2v-3h-3v-2h3v-3h2v3h3zM20 2v9h-4v3h-3v4H4c-1.1 0-2-.9-2-2V2h18zM8 13v-1H4v1h4zm3-3H4v1h7v-1zm0-2H4v1h7V8zm7-4H4v2h14V4z"/></g></svg>';
3748
3749	fbhtml = '<a class="actnbr-action actnbr-actn-follow" href="">' + followbtn + '<span>' + fbd.i18n.follow + '</span></a>';
3750	if ( fbd.isFollowing ) {
3751		fbhtml = '<a class="actnbr-action actnbr-actn-following" href="">' + followingbtn + '<span>' + fbd.i18n.following + '</span></a>';
3752	}
3753
3754	// Show follow/unfollow icon on top level, when this is not your own site
3755	if ( fbd.canFollow && ! ( fbd.canEditPost || fbd.canCustomizeSite ) ) {
3756		fhtml += '<li class="actnbr-btn actnbr-hidden"> \
3757			    	' + fbhtml + ' \
3758			    	<div class="actnbr-popover tip tip-top-left actnbr-notice"> \
3759			    		<div class="tip-arrow"></div> \
3760			    		<div class="tip-inner actnbr-follow-bubble"></div> \
3761			    	</div> \
3762			    </li>';
3763	}
3764
3765	if ( ! fbd.canCustomizeSite ) {
3766		// Report Link
3767		reporthtml = '<li class="flb-report"><a href="https://en.wordpress.com/abuse/">' + fbd.i18n.report + '</a></li>';
3768	}
3769
3770	// Show shortlink on single posts
3771	if ( fbd.isSingular ) {
3772		slkhtml = '<li class="actnbr-shortlink"><a href="' + fbd.shortlink + '">' + fbd.i18n.shortlink + '</a></li>'
3773	}
3774
3775	// Set up fold/unfold menu item
3776	foldhtml = '<li class="actnbr-fold"><a href="">' + fbd.i18n.foldBar + '</a></li>'
3777	if ( fbd.isFolded ) {
3778		foldhtml = '<li class="actnbr-fold"><a href="">' + fbd.i18n.unfoldBar + '</a></li>'
3779	}
3780	if ( ! fbd.isLoggedIn && ! fbd.canFollow ) {
3781		foldhtml = '';
3782	}
3783
3784	if ( fbd.isLoggedIn ) {
3785		if ( '' != fbd.themeURL ) {
3786			themeHtml = '<li class="actnbr-theme"><a href="' + fbd.themeURL + '">' + fbd.i18n.themeInfo.replace( /{theme}/, fbd.themeName ) + '</a></li>';
3787		}
3788		if ( fbd.canFollow ) {
3789			if ( fbd.isSingular ) {
3790				viewReaderHtml = '<li class="actnbr-reader"><a href="https://wordpress.com/read/blogs/' + fbd.siteID + '/posts/' + fbd.postID +'">' + fbd.i18n.viewReadPost + '</a></li>';
3791			} else {
3792				viewReaderHtml = '<li class="actnbr-reader"><a href="https://wordpress.com/read/' + ( fbd.feedID ? 'feeds/' + fbd.feedID : 'blogs/' + fbd.siteID ) + '">' + fbd.i18n.viewReader + '</a></li>';
3793			}
3794		}
3795		editFollowsHtml = '<li class="actnbr-follows"><a href="https://wordpress.com/following/edit">' + fbd.i18n.editSubs + '</a></li>';
3796	} else {
3797		loginHtml += '<li class="actnbr-login"><a href="' + fbd.loginURL + '">' + fbd.i18n.login + '</a></li>';
3798		signupHtml = '<li class="actnbr-signup"><a href="' + fbd.signupURL + '">' + fbd.i18n.signup + '</a></li>';
3799		editSubsHtml = '<li class="actnbr-subs"><a href="https://subscribe.wordpress.com/">' + fbd.i18n.editSubs + '</a></li>';
3800	}
3801
3802	// Hide follow/unfollow completely for static sites, and sites not allowing followers (VIP, private, etc).
3803	if ( ! fbd.canFollow ) {
3804		fbHtmlLi = '';
3805	} else {
3806		fbHtmlLi = '<li class="actnbr-folded-follow">' + fbhtml + '</li>';
3807	}
3808
3809	// Ellipsis Menu
3810	fhtml += '<li class="actnbr-ellipsis actnbr-hidden"> \
3811			  <svg class="gridicon gridicon__ellipsis" height="24" width="24" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><circle cx="5" cy="12" r="2"/><circle cx="19" cy="12" r="2"/><circle cx="12" cy="12" r="2"/></g></svg> \
3812			  <div class="actnbr-popover tip tip-top-left actnbr-more"> \
3813			  	<div class="tip-arrow"></div> \
3814			  	<div class="tip-inner"> \
3815				  <ul> \
3816				    <li class="actnbr-sitename"><a href="' + fbd.siteURL + '">' + fbd.icon + ' ' + actionBarEscapeHtml( fbd.siteName ) + '</a></li> \
3817				   	<li class="actnbr-folded-customize"><a href="'+ fbd.customizeLink +'">' + customizeIcon + '<span>' + fbd.i18n.customize + '<span></a></li> \
3818				    ' + fbHtmlLi + ' \
3819					' + signupHtml + ' \
3820				    ' + loginHtml + ' \
3821				    ' + themeHtml + ' \
3822				    ' + slkhtml + ' \
3823				    ' + reporthtml + ' \
3824				    ' + viewReaderHtml + ' \
3825				    ' + editFollowsHtml + ' \
3826				    ' + editSubsHtml + ' \
3827				    ' + foldhtml + ' \
3828			      </ul> \
3829			    </div> \
3830		      </div> \
3831		    </li> \
3832	      </ul>';
3833
3834	fbdf = d.createElement( 'div' );
3835	fbdf.id = 'actionbar';
3836	fbdf.innerHTML = fhtml;
3837	b.appendChild( fbdf );
3838
3839	$actionbar = $( '#actionbar' ).addClass( 'actnbr-' + fbd.themeSlug.replace( '/', '-' ) );
3840
3841	// Add classes based on contents
3842	if ( fbd.canCustomizeSite ) {
3843		$actionbar.addClass( 'actnbr-has-customize' );
3844	}
3845
3846	if ( fbd.canEditPost ) {
3847		$actionbar.addClass( 'actnbr-has-edit' );
3848	}
3849
3850	if ( ! fbd.canCustomizeSite ) {
3851		$actionbar.addClass( 'actnbr-has-follow' );
3852	}
3853
3854	if ( fbd.isFolded ) {
3855		$actionbar.addClass( 'actnbr-folded' );
3856	}
3857
3858	// Show status message if available
3859	if ( fbd.statusMessage ) {
3860		showActionBarStatusMessage( fbd.statusMessage );
3861	}
3862
3863	// *** Actions *****************
3864
3865	// Follow Site
3866	$actionbar.on(  'click', '.actnbr-actn-follow', function(e) {
3867		e.preventDefault();
3868
3869		if ( fbd.isLoggedIn ) {
3870			showActionBarStatusMessage( '<div class="actnbr-reader">' + fbd.i18n.followedText + '</div>' );
3871			bumpStat( 'followed' );
3872			var eventProps = {
3873				'follow_source': 'actionbar',
3874				'url': fbd.siteURL
3875			};
3876			recordTracksEvent( 'wpcom_actionbar_site_followed', eventProps );
3877
3878			request( 'ab_subscribe_to_blog' );
3879		} else {
3880			showActionBarFollowForm();
3881		}
3882	} )
3883
3884	// UnFollow Site
3885	.on(  'click', '.actnbr-actn-following', function(e) {
3886		e.preventDefault();
3887		$( '#actionbar .actnbr-actn-following' ).replaceWith( '<a class="actnbr-action actnbr-actn-follow" href="">' + followbtn + '<span>' + fbd.i18n.follow + '</span></a>' );
3888
3889		bumpStat( 'unfollowed' );
3890		var eventProps = {
3891			'follow_source': 'actionbar',
3892			'url': fbd.siteURL
3893		};
3894		recordTracksEvent( 'wpcom_actionbar_site_unfollowed', eventProps );
3895
3896		request( 'ab_unsubscribe_from_blog' );
3897	} )
3898
3899	// Show shortlink prompt
3900	.on( 'click', '.actnbr-shortlink a', function(e) {
3901		e.preventDefault();
3902		window.prompt( "Shortlink: ", fbd.shortlink );
3903	} )
3904
3905	// Toggle more menu
3906	.on( 'click', '.actnbr-ellipsis', function(e) {
3907		if ( $( e.target ).closest( 'a' ).hasClass( 'actnbr-action' ) ) {
3908			return false;
3909		}
3910
3911		var popoverLi = $( '#actionbar .actnbr-ellipsis' );
3912		popoverLi.toggleClass( 'actnbr-hidden' );
3913
3914		setTimeout( function() {
3915			if ( ! popoverLi.hasClass( 'actnbr-hidden' ) ) {
3916				bumpStat( 'show_more_menu' );
3917
3918				$( document ).on( 'click.actnbr-body-click', function() {
3919					popoverLi.addClass( 'actnbr-hidden' );
3920
3921					$( document ).off( 'click.actnbr-body-click' );
3922				} );
3923			}
3924		}, 10 );
3925	})
3926
3927	// Fold/Unfold
3928	.on( 'click', '.actnbr-fold', function(e) {
3929		e.preventDefault();
3930
3931		if ( $( '#actionbar' ).hasClass( 'actnbr-folded' ) ) {
3932			$( '.actnbr-fold a' ).html( fbd.i18n.foldBar );
3933			$( '#actionbar' ).removeClass( 'actnbr-folded' );
3934
3935			$.post( fbd.xhrURL, { 'action': 'unfold_actionbar' } );
3936		} else {
3937			$( '.actnbr-fold a' ).html( fbd.i18n.unfoldBar );
3938			$( '#actionbar' ).addClass( 'actnbr-folded' );
3939
3940			$.post( fbd.xhrURL, { 'action': 'fold_actionbar' } );
3941		}
3942	})
3943
3944	// Record stats for clicks
3945	.on( 'click', '.actnbr-sitename a', createStatsBumperEventHandler( 'clicked_site_title' ) )
3946	.on( 'click', '.actnbr-customize a', createStatsBumperEventHandler( 'customized' ) )
3947	.on( 'click', '.actnbr-folded-customize a', createStatsBumperEventHandler( 'customized' ) )
3948	.on( 'click', '.actnbr-theme a', createStatsBumperEventHandler( 'explored_theme' ) )
3949	.on( 'click', '.actnbr-edit a', createStatsBumperEventHandler( 'edited' ) )
3950	.on( 'click', '.actnbr-stats a', createStatsBumperEventHandler( 'clicked_stats' ) )
3951	.on( 'click', '.flb-report a', createStatsBumperEventHandler( 'reported_content' ) )
3952	.on( 'click', '.actnbr-follows a', createStatsBumperEventHandler( 'managed_following' ) )
3953	.on( 'click', '.actnbr-shortlink a', function() {
3954		bumpStat( 'copied_shortlink' );
3955	} )
3956	.on( 'click', '.actnbr-reader a', createStatsBumperEventHandler( 'view_reader' ) )
3957	.on( 'submit', '.actnbr-follow-bubble form', createStatsBumperEventHandler( 'submit_follow_form', function() {
3958		$( '#actionbar .actnbr-follow-bubble form button' ).attr( 'disabled', true );
3959	} ) )
3960	.on( 'click', '.actnbr-login-nudge a', createStatsBumperEventHandler( 'clicked_login_nudge' ) )
3961	.on( 'click', '.actnbr-signup a', createStatsBumperEventHandler( 'clicked_signup_link' ) )
3962	.on( 'click', '.actnbr-login a', createStatsBumperEventHandler( 'clicked_login_link' ) )
3963	.on( 'click', '.actnbr-subs a', createStatsBumperEventHandler( 'clicked_manage_subs_link' ) );
3964
3965	// Make Follow/Unfollow requests
3966	var request = function( action ) {
3967		$.post( fbd.xhrURL, {
3968			'action': action,
3969			'_wpnonce': fbd.nonce,
3970			'source': 'actionbar',
3971			'blog_id': fbd.siteID
3972		});
3973	};
3974
3975	// Show/Hide actionbar on scroll
3976	fb = $('#actionbar');
3977	toggleactionbar = function() {
3978		var st = $(window).scrollTop(),
3979			topOffset = 0;
3980
3981		if ( $(window).scrollTop() < 0 ) {
3982			return;
3983		}
3984
3985		// Still
3986		if ( lastScrollTop == 0 || ( ( st == lastScrollTop ) && lastScrollDir == 'up' ) ) {
3987			fb.removeClass( 'actnbr-hidden' );
3988
3989		// Moving
3990		} else {
3991			 // Scrolling Up
3992		    if ( st < lastScrollTop ){
3993				fb.removeClass( 'actnbr-hidden' );
3994				lastScrollDir = 'up';
3995
3996			// Scrolling Down
3997			} else {
3998				// check if there are any popovers open, and only hide action bar if not
3999				if ( $( '#actionbar > ul > li:not(.actnbr-hidden) > .actnbr-popover' ).length === 0 ) {
4000					fb.addClass( 'actnbr-hidden' );
4001					lastScrollDir = 'down';
4002
4003					// Hide any menus
4004					$( '#actionbar li' ).addClass( 'actnbr-hidden' );
4005				}
4006			}
4007		}
4008
4009		lastScrollTop = st;
4010	};
4011	setInterval( toggleactionbar, 100 );
4012
4013	var bumpStat = function( stat ) {
4014		return $.post( fbd.xhrURL, {
4015			'action': 'actionbar_stats',
4016			'stat': stat
4017		} );
4018	};
4019
4020	var recordTracksEvent =	function( eventName, eventProps ) {
4021		eventProps = eventProps || {};
4022		window._tkq = window._tkq || [];
4023		window._tkq.push( [ 'recordEvent', eventName, eventProps ] );
4024	};
4025
4026	/**
4027	 * A factory method for creating an event handler function that will bump a specific stat and ONLY THEN re-dispatch
4028	 * the event. This will ensure that the bumped stat is indeed recorded before navigating the page away, as otherwise
4029	 * some browsers may very well decide to cancel the stat request in that case.
4030	 *
4031	 * @param {String} stat the name of the stat to bump
4032	 * @param {Function} additionalEffect an additional function that should be called after the stat is bumped
4033	 */
4034	function createStatsBumperEventHandler( stat, additionalEffect ) {
4035		var completedEvents = {};
4036
4037		return function eventHandler( event ) {
4038			if ( completedEvents[ event.timeStamp ] ) {
4039				delete completedEvents[ event.timeStamp ];
4040
4041				// hack-around to submit forms, dispatching "submit" event is not enough for them
4042				if ( event.type === 'submit' ) {
4043					event.target.submit();
4044				}
4045
4046				if ( typeof additionalEffect === 'function' ) {
4047					return additionalEffect( event );
4048				}
4049
4050				return true;
4051			}
4052
4053			event.preventDefault();
4054			event.stopPropagation();
4055
4056			function dispatchOriginalEvent() {
4057				var newEvent;
4058
4059				// Retrieves the native event object created by the browser from the jQuery event object
4060				var originalEvent = event.originalEvent;
4061
4062				/**
4063				 * Handles Internet Explorer that doesn't support Event nor CustomEvent constructors
4064				 *
4065				 * @see https://developer.mozilla.org/en-US/docs/Web/API/Event/Event
4066				 * @see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
4067				 * @see https://stackoverflow.com/questions/26596123/internet-explorer-9-10-11-event-constructor-doesnt-work/
4068				 */
4069				if ( typeof window.CustomEvent !== 'function' ) {
4070					newEvent = document.createEvent( 'CustomEvent' );
4071					newEvent.initCustomEvent(
4072						originalEvent.type,
4073						originalEvent.bubbles,
4074						originalEvent.cancelable,
4075						originalEvent.detail
4076					);
4077				} else {
4078					newEvent = new originalEvent.constructor( originalEvent.type, originalEvent );
4079				}
4080
4081				completedEvents[ newEvent.timeStamp ] = true;
4082
4083				originalEvent.target.dispatchEvent( newEvent );
4084			}
4085
4086			bumpStat( stat ).then( dispatchOriginalEvent, dispatchOriginalEvent );
4087		}
4088	}
4089
4090	function actionBarEscapeHtml(string) {
4091		return String(string).replace(/[&<>
4091"'\/]/g, function (s) {
4092			var entityMap = {
4093				"&": "&amp;",
4094				"<": "&lt;",
4095				">": "&gt;",
4096				'"': '&quot;',
4097				"'": '&#39;',
4098				"/": '&#x2F;'
4099			};
4100			return entityMap[s];
4101		});
4102	}
4103
4104	function showActionBarStatusMessage( message ) {
4105		$( '#actionbar .actnbr-actn-follow' ).replaceWith( '<a class="actnbr-action actnbr-actn-following" href="">' + followingbtn + '<span>' + fbd.i18n.following + '</span></a>' );
4106		$( '#actionbar .actnbr-follow-bubble' ).html( ' \
4107			<ul> \
4108				<li class="actnbr-sitename"><a href="' + fbd.siteURL + '">' + fbd.icon + ' ' + actionBarEscapeHtml( fbd.siteName ) + '</a></li> \
4109				<li class="actnbr-message">' + message + '</li> \
4110			</ul> \
4111		');
4112
4113		var btn = $( '#actionbar .actnbr-btn' );
4114		btn.removeClass( 'actnbr-hidden' );
4115
4116		setTimeout( function() {
4117			if ( ! btn.hasClass( 'actnbr-hidden' ) ) {
4118				$( '#actionbar .actnbr-email-field' ).focus();
4119				$( document ).on( 'click.actnbr-body-click', function(e) {
4120					if ( $( e.target ).closest( '.actnbr-popover' )[0] ) {
4121						return;
4122					}
4123					btn.addClass( 'actnbr-hidden' );
4124					$( document ).off( 'click.actnbr-body-click' );
4125				} );
4126			}
4127		}, 10 );
4128	}
4129
4130	function showActionBarFollowForm() {
4131		var btn = $( '#actionbar .actnbr-btn' );
4132		btn.toggleClass( 'actnbr-hidden' );
4133
4134		var form = $('<form>');
4135
4136		if ( fbd.i18n.followers ) {
4137			form.append( $( '<div class="actnbr-follow-count">' ).html( fbd.i18n.followers ) );
4138		}
4139
4140		form.append($('<div>').append($('<input>').attr({"type": "email", "name": "email", "placeholder": fbd.i18n.enterEmail, "class": "actnbr-email-field"})));
4141		form.append($('<input type="hidden" name="action" value="subscribe"/>'));
4142		form.append($('<input type="hidden" name="blog_id">').attr('value', fbd.siteID));
4143		form.append($('<input type="hidden" name="source">').attr('value', fbd.referer));
4144		form.append($('<input type="hidden" name="sub-type" value="actionbar-follow"/>'));
4145		form.append($(fbd.subscribeNonce));
4146		form.append($('<div class="actnbr-button-wrap">').append($('<button type="submit">').attr('value', fbd.i18n.subscribe).html(fbd.i18n.subscribe)));
4147		form.attr('method', 'post');
4148		form.attr('action', 'https://subscribe.wordpress.com');
4149		form.attr('accept-charset', 'utf-8');
4150
4151		var html = $('<ul/>');
4152		html.append($('<li class="actnbr-sitename">').append($('<a>').attr('href', fbd.siteURL).append($(fbd.icon)).append(' ' + actionBarEscapeHtml( fbd.siteName ))))
4153		html.append($('<li>').append(form));
4154		html.append($('<li class="actnbr-login-nudge">').append($('<div>').html(fbd.i18n.alreadyUser)));
4155
4156		$( '#actionbar .actnbr-follow-bubble' ).empty().append(html);
4157
4158		setTimeout( function() {
4159			if ( ! btn.hasClass( 'actnbr-hidden' ) ) {
4160				bumpStat( 'show_follow_form' );
4161
4162				$( '#actionbar .actnbr-email-field' ).focus();
4163
4164				$( document ).on( 'click.actnbr-body-click', function( event ) {
4165					if ( $( event.target ).closest( '.actnbr-popover' )[ 0 ] ) {
4166						return;
4167					}
4168
4169					btn.addClass( 'actnbr-hidden' );
4170
4171					$( document ).off( 'click.actnbr-body-click' );
4172				} );
4173			}
4174		}, 10 );
4175	}
4176})(jQuery);
4177;
4178//fgnass.github.com/spin.js#v1.3
4179
4180/**
4181 * Copyright (c) 2011-2013 Felix Gnass
4182 * Licensed under the MIT license
4183 */
4184(function(root, factory) {
4185
4186  /* CommonJS */
4187  if (typeof exports == 'object')  module.exports = factory()
4188
4189  /* AMD module */
4190  else if (typeof define == 'function' && define.amd) define(factory)
4191
4192  /* Browser global */
4193  else root.Spinner = factory()
4194}
4195(this, function() {
4196  "use strict";
4197
4198  var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
4199    , animations = {} /* Animation rules keyed by their name */
4200    , useCssAnimations /* Whether to use CSS animations or setTimeout */
4201
4202  /**
4203   * Utility function to create elements. If no tag name is given,
4204   * a DIV is created. Optionally properties can be passed.
4205   */
4206  function createEl(tag, prop) {
4207    var el = document.createElement(tag || 'div')
4208      , n
4209
4210    for(n in prop) el[n] = prop[n]
4211    return el
4212  }
4213
4214  /**
4215   * Appends children and returns the parent.
4216   */
4217  function ins(parent /* child1, child2, ...*/) {
4218    for (var i=1, n=arguments.length; i<n; i++)
4219      parent.appendChild(arguments[i])
4220
4221    return parent
4222  }
4223
4224  /**
4225   * Insert a new stylesheet to hold the @keyframe or VML rules.
4226   */
4227  var sheet = (function() {
4228    var el = createEl('style', {type : 'text/css'})
4229    ins(document.getElementsByTagName('head')[0], el)
4230    return el.sheet || el.styleSheet
4231  }())
4232
4233  /**
4234   * Creates an opacity keyframe animation rule and returns its name.
4235   * Since most mobile Webkits have timing issues with animation-delay,
4236   * we create separate rules for each line/segment.
4237   */
4238  function addAnimation(alpha, trail, i, lines) {
4239    var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
4240      , start = 0.01 + i/lines * 100
4241      , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
4242      , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
4243      , pre = prefix && '-' + prefix + '-' || ''
4244
4245    if (!animations[name]) {
4246      sheet.insertRule(
4247        '@' + pre + 'keyframes ' + name + '{' +
4248        '0%{opacity:' + z + '}' +
4249        start + '%{opacity:' + alpha + '}' +
4250        (start+0.01) + '%{opacity:1}' +
4251        (start+trail) % 100 + '%{opacity:' + alpha + '}' +
4252        '100%{opacity:' + z + '}' +
4253        '}', sheet.cssRules.length)
4254
4255      animations[name] = 1
4256    }
4257
4258    return name
4259  }
4260
4261  /**
4262   * Tries various vendor prefixes and returns the first supported property.
4263   */
4264  function vendor(el, prop) {
4265    var s = el.style
4266      , pp
4267      , i
4268
4269    if(s[prop] !== undefined) return prop
4270    prop = prop.charAt(0).toUpperCase() + prop.slice(1)
4271    for(i=0; i<prefixes.length; i++) {
4272      pp = prefixes[i]+prop
4273      if(s[pp] !== undefined) return pp
4274    }
4275  }
4276
4277  /**
4278   * Sets multiple style properties at once.
4279   */
4280  function css(el, prop) {
4281    for (var n in prop)
4282      el.style[vendor(el, n)||n] = prop[n]
4283
4284    return el
4285  }
4286
4287  /**
4288   * Fills in default values.
4289   */
4290  function merge(obj) {
4291    for (var i=1; i < arguments.length; i++) {
4292      var def = arguments[i]
4293      for (var n in def)
4294        if (obj[n] === undefined) obj[n] = def[n]
4295    }
4296    return obj
4297  }
4298
4299  /**
4300   * Returns the absolute page-offset of the given element.
4301   */
4302  function pos(el) {
4303    var o = { x:el.offsetLeft, y:el.offsetTop }
4304    while((el = el.offsetParent))
4305      o.x+=el.offsetLeft, o.y+=el.offsetTop
4306
4307    return o
4308  }
4309
4310  // Built-in defaults
4311
4312  var defaults = {
4313    lines: 12,            // The number of lines to draw
4314    length: 7,            // The length of each line
4315    width: 5,             // The line thickness
4316    radius: 10,           // The radius of the inner circle
4317    rotate: 0,            // Rotation offset
4318    corners: 1,           // Roundness (0..1)
4319    color: '#000',        // #rgb or #rrggbb
4320    direction: 1,         // 1: clockwise, -1: counterclockwise
4321    speed: 1,             // Rounds per second
4322    trail: 100,           // Afterglow percentage
4323    opacity: 1/4,         // Opacity of the lines
4324    fps: 20,              // Frames per second when using setTimeout()
4325    zIndex: 2e9,          // Use a high z-index by default
4326    className: 'spinner', // CSS class to assign to the element
4327    top: 'auto',          // center vertically
4328    left: 'auto',         // center horizontally
4329    position: 'relative'  // element position
4330  }
4331
4332  /** The constructor */
4333  function Spinner(o) {
4334    if (typeof this == 'undefined') return new Spinner(o)
4335    this.opts = merge(o || {}, Spinner.defaults, defaults)
4336  }
4337
4338  // Global defaults that override the built-ins:
4339  Spinner.defaults = {}
4340
4341  merge(Spinner.prototype, {
4342
4343    /**
4344     * Adds the spinner to the given target element. If this instance is already
4345     * spinning, it is automatically removed from its previous target b calling
4346     * stop() internally.
4347     */
4348    spin: function(target) {
4349      this.stop()
4350
4351      var self = this
4352        , o = self.opts
4353        , el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
4354        , mid = o.radius+o.length+o.width
4355        , ep // element position
4356        , tp // target position
4357
4358      if (target) {
4359        target.insertBefore(el, target.firstChild||null)
4360        tp = pos(target)
4361        ep = pos(el)
4362        css(el, {
4363          left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
4364          top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid)  + 'px'
4365        })
4366      }
4367
4368      el.setAttribute('role', 'progressbar')
4369      self.lines(el, self.opts)
4370
4371      if (!useCssAnimations) {
4372        // No CSS animation support, use setTimeout() instead
4373        var i = 0
4374          , start = (o.lines - 1) * (1 - o.direction) / 2
4375          , alpha
4376          , fps = o.fps
4377          , f = fps/o.speed
4378          , ostep = (1-o.opacity) / (f*o.trail / 100)
4379          , astep = f/o.lines
4380
4381        ;(function anim() {
4382          i++;
4383          for (var j = 0; j < o.lines; j++) {
4384            alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
4385
4386            self.opacity(el, j * o.direction + start, alpha, o)
4387          }
4388          self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
4389        })()
4390      }
4391      return self
4392    },
4393
4394    /**
4395     * Stops and removes the Spinner.
4396     */
4397    stop: function() {
4398      var el = this.el
4399      if (el) {
4400        clearTimeout(this.timeout)
4401        if (el.parentNode) el.parentNode.removeChild(el)
4402        this.el = undefined
4403      }
4404      return this
4405    },
4406
4407    /**
4408     * Internal method that draws the individual lines. Will be overwritten
4409     * in VML fallback mode below.
4410     */
4411    lines: function(el, o) {
4412      var i = 0
4413        , start = (o.lines - 1) * (1 - o.direction) / 2
4414        , seg
4415
4416      function fill(color, shadow) {
4417        return css(createEl(), {
4418          position: 'absolute',
4419          width: (o.length+o.width) + 'px',
4420          height: o.width + 'px',
4421          background: color,
4422          boxShadow: shadow,
4423          transformOrigin: 'left',
4424          transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
4425          borderRadius: (o.corners * o.width>>1) + 'px'
4426        })
4427      }
4428
4429      for (; i < o.lines; i++) {
4430        seg = css(createEl(), {
4431          position: 'absolute',
4432          top: 1+~(o.width/2) + 'px',
4433          transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
4434          opacity: o.opacity,
4435          animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
4436        })
4437
4438        if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
4439
4440        ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)')))
4441      }
4442      return el
4443    },
4444
4445    /**
4446     * Internal method that adjusts the opacity of a single line.
4447     * Will be overwritten in VML fallback mode below.
4448     */
4449    opacity: function(el, i, val) {
4450      if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
4451    }
4452
4453  })
4454
4455
4456  function initVML() {
4457
4458    /* Utility function to create a VML tag */
4459    function vml(tag, attr) {
4460      return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
4461    }
4462
4463    // No CSS transforms but VML support, add a CSS rule for VML elements:
4464    sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
4465
4466    Spinner.prototype.lines = function(el, o) {
4467      var r = o.length+o.width
4468        , s = 2*r
4469
4470      function grp() {
4471        return css(
4472          vml('group', {
4473            coordsize: s + ' ' + s,
4474            coordorigin: -r + ' ' + -r
4475          }),
4476          { width: s, height: s }
4477        )
4478      }
4479
4480      var margin = -(o.width+o.length)*2 + 'px'
4481        , g = css(grp(), {position: 'absolute', top: margin, left: margin})
4482        , i
4483
4484      function seg(i, dx, filter) {
4485        ins(g,
4486          ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
4487            ins(css(vml('roundrect', {arcsize: o.corners}), {
4488                width: r,
4489                height: o.width,
4490                left: o.radius,
4491                top: -o.width>>1,
4492                filter: filter
4493              }),
4494              vml('fill', {color: o.color, opacity: o.opacity}),
4495              vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
4496            )
4497          )
4498        )
4499      }
4500
4501      if (o.shadow)
4502        for (i = 1; i <= o.lines; i++)
4503          seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
4504
4505      for (i = 1; i <= o.lines; i++) seg(i)
4506      return ins(el, g)
4507    }
4508
4509    Spinner.prototype.opacity = function(el, i, val, o) {
4510      var c = el.firstChild
4511      o = o.shadow && o.lines || 0
4512      if (c && i+o < c.childNodes.length) {
4513        c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
4514        if (c) c.opacity = val
4515      }
4516    }
4517  }
4518
4519  var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
4520
4521  if (!vendor(probe, 'transform') && probe.adj) initVML()
4522  else useCssAnimations = vendor(probe, 'animation')
4523
4524  return Spinner
4525
4526}));
4527;
4528/**
4529 * Copyright (c) 2011-2013 Felix Gnass
4530 * Licensed under the MIT license
4531 */
4532
4533/*
4534
4535Basic Usage:
4536============
4537
4538$('#el').spin(); // Creates a default Spinner using the text color of #el.
4539$('#el').spin({ ... }); // Creates a Spinner using the provided options.
4540
4541$('#el').spin(false); // Stops and removes the spinner.
4542
4543Using Presets:
4544==============
4545
4546$('#el').spin('small'); // Creates a 'small' Spinner using the text color of #el.
4547$('#el').spin('large', '#fff'); // Creates a 'large' white Spinner.
4548
4549Adding a custom preset:
4550=======================
4551
4552$.fn.spin.presets.flower = {
4553  lines: 9
4554  length: 10
4555  width: 20
4556  radius: 0
4557}
4558
4559$('#el').spin('flower', 'red');
4560
4561*/
4562
4563(function(factory) {
4564
4565  if (typeof exports == 'object') {
4566    // CommonJS
4567    factory(require('jquery'), require('spin'))
4568  }
4569  else if (typeof define == 'function' && define.amd) {
4570    // AMD, register as anonymous module
4571    define(['jquery', 'spin'], factory)
4572  }
4573  else {
4574    // Browser globals
4575    if (!window.Spinner) throw new Error('Spin.js not present')
4576    factory(window.jQuery, window.Spinner)
4577  }
4578
4579}(function($, Spinner) {
4580
4581  $.fn.spin = function(opts, color) {
4582
4583    return this.each(function() {
4584      var $this = $(this),
4585        data = $this.data();
4586
4587      if (data.spinner) {
4588        data.spinner.stop();
4589        delete data.spinner;
4590      }
4591      if (opts !== false) {
4592        opts = $.extend(
4593          { color: color || $this.css('color') },
4594          $.fn.spin.presets[opts] || opts
4595        )
4596        // Begin WordPress Additions
4597        // To use opts.right, you need to have specified a length, width, and radius.
4598        if ( typeof opts.right !== 'undefined' && typeof opts.length !== 'undefined'
4599          && typeof opts.width !== 'undefined' && typeof opts.radius !== 'undefined' ) {
4600          var pad = $this.css( 'padding-left' );
4601          pad = ( typeof pad === 'undefined' ) ? 0 : parseInt( pad, 10 );
4602          opts.left = $this.outerWidth() - ( 2 * ( opts.length + opts.width + opts.radius ) ) - pad - opts.right;
4603          delete opts.right;
4604        }
4605        // End WordPress Additions
4606        data.spinner = new Spinner(opts).spin(this)
4607      }
4608    })
4609  }
4610
4611  $.fn.spin.presets = {
4612    tiny: { lines: 8, length: 2, width: 2, radius: 3 },
4613    small: { lines: 8, length: 4, width: 3, radius: 5 },
4614    large: { lines: 10, length: 8, width: 4, radius: 8 }
4615  }
4616
4617}));
4618
4619// Jetpack Presets Overrides:
4620(function($){
4621	$.fn.spin.presets.wp = { trail: 60, speed: 1.3 };
4622	$.fn.spin.presets.small  = $.extend( { lines:  8, length: 2, width: 2, radius: 3 }, $.fn.spin.presets.wp );
4623	$.fn.spin.presets.medium = $.extend( { lines:  8, length: 4, width: 3, radius: 5 }, $.fn.spin.presets.wp );
4624	$.fn.spin.presets.large  = $.extend( { lines: 10, length: 6, width: 4, radius: 7 }, $.fn.spin.presets.wp );
4625	$.fn.spin.presets['small-left']   = $.extend( { left:  5 }, $.fn.spin.presets.small );
4626	$.fn.spin.presets['small-right']  = $.extend( { right: 5 }, $.fn.spin.presets.small );
4627	$.fn.spin.presets['medium-left']  = $.extend( { left:  5 }, $.fn.spin.presets.medium );
4628	$.fn.spin.presets['medium-right'] = $.extend( { right: 5 }, $.fn.spin.presets.medium );
4629	$.fn.spin.presets['large-left']   = $.extend( { left:  5 }, $.fn.spin.presets.large );
4630	$.fn.spin.presets['large-right']  = $.extend( { right: 5 }, $.fn.spin.presets.large );
4631})(jQuery);
4632;
4633/* jshint sub: true, onevar: false, multistr: true, devel: true, smarttabs: true */
4634/* global jetpackCarouselStrings, DocumentTouch */
4635
4636// @start-hide-in-jetpack
4637if (typeof wpcom === 'undefined') {
4638	var wpcom = {};
4639}
4640wpcom.carousel = (function(/*$*/) {
4641	var prebuilt_widths = jetpackCarouselStrings.widths;
4642	var pageviews_stats_args = jetpackCarouselStrings.stats_query_args;
4643
4644	var findFirstLargeEnoughWidth = function(original_w, original_h, dest_w, dest_h) {
4645		var inverse_ratio = original_h / original_w;
4646
4647		for ( var i = 0; i < prebuilt_widths.length; ++i ) {
4648			if ( prebuilt_widths[i] >= dest_w || prebuilt_widths[i] * inverse_ratio >
4648= dest_h ) {
4649				return prebuilt_widths[i];
4650			}
4651		}
4652
4653		return original_w;
4654	};
4655
4656	var addWidthToImageURL = function(url, width) {
4657		width = parseInt(width, 10);
4658		// Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android phones = 1.5, etc)
4659		if ('undefined' !== typeof window.devicePixelRatio && window.devicePixelRatio > 1) {
4660			width = Math.round( width * window.devicePixelRatio );
4661		}
4662		url = addArgToURL(url, 'w', width);
4663		url = addArgToURL(url, 'h', '');
4664		return url;
4665	};
4666
4667	var addArgToURL = function(url, arg, value) {
4668		var re = new RegExp(arg+'=[^?&]+');
4669		if ( url.match(re) ) {
4670			return url.replace(re, arg + '=' + value);
4671		} else {
4672			var divider = url.indexOf('?') !== -1 ? '&' : '?';
4673			return url + divider + arg + '=' + value;
4674		}
4675	};
4676
4677	var stat = function ( names ) {
4678		if ( typeof names !== 'string' ) {
4679			names = names.join( ',' );
4680		}
4681
4682		new Image().src = window.location.protocol +
4683			'../g.gifv=wpcom-no-pv' +
4684			'&x_carousel=' + names +
4685			'&baba=' + Math.random();
4686	};
4687
4688	var pageview = function ( post_id ) {
4689		new Image().src = window.location.protocol +
4690			'../g.gifhost=' + encodeURIComponent( window.location.host ) +
4691			'&ref=' + encodeURIComponent( document.referrer ) +
4692			'&rand=' + Math.random() +
4693			'&' + pageviews_stats_args +
4694			'&post=' + encodeURIComponent( post_id );
4695	};
4696
4697
4698	return {
4699		findFirstLargeEnoughWidth: findFirstLargeEnoughWidth,
4700		addWidthToImageURL: addWidthToImageURL,
4701		stat: stat,
4702		pageview: pageview
4703	};
4704
4705})(jQuery);
4706// @end-hide-in-jetpack
4707
4708jQuery( document ).ready( function( $ ) {
4709	// gallery faded layer and container elements
4710	var overlay,
4711		comments,
4712		gallery,
4713		container,
4714		nextButton,
4715		previousButton,
4716		info,
4717		transitionBegin,
4718		caption,
4719		resizeTimeout,
4720		photo_info,
4721		close_hint,
4722		commentInterval,
4723		lastSelectedSlide,
4724		screenPadding = 110,
4725		originalOverflow = $( 'body' ).css( 'overflow' ),
4726		originalHOverflow = $( 'html' ).css( 'overflow' ),
4727		proportion = 85,
4728		last_known_location_hash = '',
4729		imageMeta,
4730		titleAndDescription,
4731		commentForm,
4732		leftColWrapper,
4733		scrollPos;
4734
4735	if ( window.innerWidth <= 760 ) {
4736		screenPadding = Math.round( ( window.innerWidth / 760 ) * 110 );
4737
4738		if (
4739			screenPadding < 40 &&
4740			( 'ontouchstart' in window || ( window.DocumentTouch && document instanceof DocumentTouch ) )
4741		) {
4742			screenPadding = 0;
4743		}
4744	}
4745
4746	// Adding a polyfill for browsers that do not have Date.now
4747	if ( 'undefined' === typeof Date.now ) {
4748		Date.now = function now() {
4749			return new Date().getTime();
4750		};
4751	}
4752
4753	var keyListener = function( e ) {
4754		switch ( e.which ) {
4755			case 38: // up
4756				e.preventDefault();
4757				container.scrollTop( container.scrollTop() - 100 );
4758				break;
4759			case 40: // down
4760				e.preventDefault();
4761				container.scrollTop( container.scrollTop() + 100 );
4762				break;
4763			case 39: // right
4764				e.preventDefault();
4765				gallery.jp_carousel( 'next' );
4766				break;
4767			case 37: // left
4768			case 8: // backspace
4769				e.preventDefault();
4770				gallery.jp_carousel( 'previous' );
4771				break;
4772			case 27: // escape
4773				e.preventDefault();
4774				container.jp_carousel( 'close' );
4775				break;
4776			default:
4777				// making jslint happy
4778				break;
4779		}
4780	};
4781
4782	var resizeListener = function(/*e*/) {
4783		clearTimeout( resizeTimeout );
4784		resizeTimeout = setTimeout( function() {
4785			gallery.jp_carousel( 'slides' ).jp_carousel( 'fitSlide', true );
4786			gallery.jp_carousel( 'updateSlidePositions', true );
4787			gallery.jp_carousel( 'fitMeta', true );
4788		}, 200 );
4789	};
4790
4791	var prepareGallery = function(/*dataCarouselExtra*/) {
4792		if ( ! overlay ) {
4793			overlay = $( '<div></div>' )
4794				.addClass( 'jp-carousel-overlay' )
4795				.css( {
4796					position: 'fixed',
4797					top: 0,
4798					right: 0,
4799					bottom: 0,
4800					left: 0,
4801				} );
4802
4803			var buttons =
4804				'<a class="jp-carousel-commentlink" href="#">' + jetpackCarouselStrings.comment + '</a>';
4805			if ( 1 === Number( jetpackCarouselStrings.is_logged_in ) ) {
4806// @start-hide-in-jetpack
4807				if ( 1 === Number( jetpackCarouselStrings.is_public && 1 === Number( jetpackCarouselStrings.reblog_enabled ) ) ) {
4808					buttons += '<a class="jp-carousel-reblog" href="#">
4808' + jetpackCarouselStrings.reblog + '</a>';
4809				}
4810// @end-hide-in-jetpack
4811			}
4812
4813			buttons = $( '<div class="jp-carousel-buttons">' + buttons + '</div>' );
4814
4815			caption = $( '<h2 itemprop="caption description"></h2>' );
4816			photo_info = $( '<div class="jp-carousel-photo-info"></div>' ).append( caption );
4817
4818			imageMeta = $( '<div></div>' )
4819				.addClass( 'jp-carousel-image-meta' )
4820				.css( {
4821					float: 'right',
4822					'margin-top': '20px',
4823					width: '250px',
4824				} );
4825
4826			imageMeta
4827				.append( buttons )
4828				.append( "<ul class='jp-carousel-image-exif' style='display:none;'></ul>" )
4829				.append( "<a class='jp-carousel-image-download' style='display:none;'></a>" )
4830				.append( "<div class='jp-carousel-image-map' style='display:none;'></div>" );
4831
4832			titleAndDescription = $( '<div></div>' )
4833				.addClass( 'jp-carousel-titleanddesc' )
4834				.css( {
4835					width: '100%',
4836					'margin-top': imageMeta.css( 'margin-top' ),
4837				} );
4838
4839			var commentFormMarkup = '<div id="jp-carousel-comment-form-container">';
4840
4841			if (
4842				jetpackCarouselStrings.local_comments_commenting_as &&
4843				jetpackCarouselStrings.local_comments_commenting_as.length
4844			) {
4845				// Comments not enabled, fallback to local comments
4846
4847				if (
4848					1 !== Number( jetpackCarouselStrings.is_logged_in ) &&
4849					1 === Number( jetpackCarouselStrings.comment_registration )
4850				) {
4851					commentFormMarkup +=
4852						'<div id="jp-carousel-comment-form-commenting-as">' +
4853						jetpackCarouselStrings.local_comments_commenting_as +
4854						'</div>';
4855				} else {
4856					commentFormMarkup += '<form id="jp-carousel-comment-form">';
4857					commentFormMarkup +=
4858						'<textarea name="comment" class="jp-carousel-comment-form-field jp-carousel-comment-form-textarea" id="jp-carousel-comment-form-comment-field" placeholder="' +
4859						jetpackCarouselStrings.write_comment +
4860						'"></textarea>';
4861					commentFormMarkup += '<div id="jp-carousel-comment-form-submit-and-info-wrapper">';
4862					commentFormMarkup +=
4863						'<div id="jp-carousel-comment-form-commenting-as">' +
4864						jetpackCarouselStrings.local_comments_commenting_as +
4865						'</div>';
4866					commentFormMarkup +=
4867						'<input type="submit" name="submit" class="jp-carousel-comment-form-button" id="jp-carousel-comment-form-button-submit" value="' +
4868						jetpackCarouselStrings.post_comment +
4869						'" />';
4870					commentFormMarkup += '<span id="jp-carousel-comment-form-spinner">&nbsp;</span>';
4871					commentFormMarkup += '<div id="jp-carousel-comment-post-results"></div>';
4872					commentFormMarkup += '</div>';
4873					commentFormMarkup += '</form>';
4874				}
4875			}
4876			commentFormMarkup += '</div>';
4877
4878			commentForm = $( commentFormMarkup ).css( {
4879				width: '100%',
4880				'margin-top': '20px',
4881				color: '#999',
4882			} );
4883
4884			comments = $( '<div></div>' )
4885				.addClass( 'jp-carousel-comments' )
4886				.css( {
4887					width: '100%',
4888					bottom: '10px',
4889					'margin-top': '20px',
4890				} );
4891
4892			var commentsLoading = $(
4893				'<div id="jp-carousel-comments-loading"><span>' +
4894					jetpackCarouselStrings.loading_comments +
4895					'</span></div>'
4896			).css( {
4897				width: '100%',
4898				bottom: '10px',
4899				'margin-top': '20px',
4900			} );
4901
4902			var leftWidth = $( window ).width() - screenPadding * 2 - ( imageMeta.width() + 40 );
4903			leftWidth += 'px';
4904
4905			leftColWrapper = $( '<div></div>' )
4906				.addClass( 'jp-carousel-left-column-wrapper' )
4907				.css( {
4908					width: Math.floor( leftWidth ),
4909				} )
4910				.append( titleAndDescription )
4911				.append( commentForm )
4912				.append( comments )
4913				.append( commentsLoading );
4914
4915			var fadeaway = $( '<div></div>' ).addClass( 'jp-carousel-fadeaway' );
4916
4917			info = $( '<div></div>' )
4918				.addClass( 'jp-carousel-info' )
4919				.css( {
4920					top: Math.floor( ( $( window ).height() / 100 ) * proportion ),
4921					left: screenPadding,
4922					right: screenPadding,
4923				} )
4924				.append( photo_info )
4925				.append( imageMeta );
4926
4927			if ( window.innerWidth <= 760 ) {
4928				photo_info.remove().insertAfter( titleAndDescription );
4929				info.prepend( leftColWrapper );
4930			} else {
4931				info.append( leftColWrapper );
4932			}
4933
4934			var targetBottomPos = $( window ).height() - parseInt( info.css( 'top' ), 10 ) + 'px';
4935
4936			nextButton = $( '<div><span></span></div>' )
4937				.addClass( 'jp-carousel-next-button' )
4938				.css( {
4939					right: '15px',
4940				} )
4941				.hide();
4942
4943			previousButton = $( '<div><span></span></div>' )
4944				.addClass( 'jp-carousel-previous-button' )
4945				.css( {
4946					left: 0,
4947				} )
4948				.hide();
4949
4950			nextButton.add( previousButton ).css( {
4951				position: 'fixed',
4952				top: '40px',
4953				bottom: targetBottomPos,
4954				width: screenPadding,
4955			} );
4956
4957			gallery = $( '<div></div>' )
4958				.addClass( 'jp-carousel' )
4959				.css( {
4960					position: 'absolute',
4961					top: 0,
4962					bottom: targetBottomPos,
4963					left: 0,
4964					right: 0,
4965				} );
4966
4967			close_hint = $( '<div class="jp-carousel-close-hint"><span>&times;</span></div>' ).css( {
4968				position: 'fixed',
4969			} );
4970
4971			container = $( '<div></div>' )
4972				.addClass( 'jp-carousel-wrap' )
4973				.addClass( 'jp-carousel-transitions' );
4974			if ( 'white' === jetpackCarouselStrings.background_color ) {
4975				container.addClass( 'jp-carousel-light' );
4976			}
4977
4978			container.attr( 'itemscope', '' );
4979
4980			container.attr( 'itemtype', 'https://schema.org/ImageGallery' );
4981
4982			container
4983				.css( {
4984					position: 'fixed',
4985					top: 0,
4986					right: 0,
4987					bottom: 0,
4988					left: 0,
4989					'z-index': 2147483647,
4990					'overflow-x': 'hidden',
4991					'overflow-y': 'auto',
4992					direction: 'ltr',
4993				} )
4994				.hide()
4995				.append( overlay )
4996				.append( gallery )
4997				.append( fadeaway )
4998				.append( info )
4999				.append( nextButton )
5000				.append( previousButton )
5001				.append( close_hint )
5002				.appendTo( $( 'body' ) )
5003				.click( function( e ) {
5004					var target = $( e.target ),
5005						wrap = target.parents( 'div.jp-carousel-wrap' ),
5006						data = wrap.data( 'carousel-extra' ),
5007						slide = wrap.find( 'div.selected' ),
5008						attachment_id = slide.data( 'attachment-id' );
5009					data = data || [];
5010
5011					if (
5012						target.is( gallery ) ||
5013						target
5014							.parents()
5015							.add( target )
5016							.is( close_hint )
5017					) {
5018						container.jp_carousel( 'close' );
5019// @start-hide-in-jetpack
5020					} else if ( target.hasClass('jp-carousel-reblog') ) {
5021						e.preventDefault();
5022						e.stopPropagation();
5023						if ( !target.hasClass('reblogged') ) {
5024							target.jp_carousel('show_reblog_box');
5025							wpcom.carousel.stat('reblog_show_box');
5026						}
5027					} else if ( target.parents('#carousel-reblog-box').length ) {
5028						if ( target.is('a.cancel') ) {
5029							e.preventDefault();
5030							e.stopPropagation();
5031							target.jp_carousel('hide_reblog_box');
5032							wpcom.carousel.stat('reblog_cancel');
5033						} else if ( target.is( 'input[type="submit"]' ) ) {
5034							e.preventDefault();
5035							e.stopPropagation();
5036
5037							var note = $('#carousel-reblog-box textarea').val();
5038							if ( jetpackCarouselStrings.reblog_add_thoughts === note ) {
5039								note = '';
5040							}
5041
5042							$('#carousel-reblog-submit').val( jetpackCarouselStrings.reblogging );
5043							$('#carousel-reblog-submit').prop('disabled', true);
5044							$( '#carousel-reblog-box div.submit span.canceltext' ).spin( 'small' );
5045
5046							$.post( jetpackCarouselStrings.ajaxurl, {
5047								'action': 'post_reblog',
5048								'reblog_source': 'carousel',
5049								'original_blog_id': $('#carousel-reblog-box input#carousel-reblog-blog-id').val(),
5050								'original_post_id': $('.jp-carousel div.selected').data('attachment-id'),
5051								'blog_id': $('#carousel-reblog-box select').val(),
5052								'blog_url': $('#carousel-reblog-box input#carousel-reblog-blog-url').val(),
5053								'blog_title': $('#carousel-reblog-box input#carousel-reblog-blog-title').val(),
5054								'post_url': $('#carousel-reblog-box input#carousel-reblog-post-url').val(),
5055								'post_title': slide.data( 'caption' ) || $('#carousel-reblog-box input#carousel-reblog-post-title').val(),
5056								'note': note,
5057								'_wpnonce': $('#carousel-reblog-box #_wpnonce').val()
5058							},
5059							function(/*result*/) {
5060								$('#carousel-reblog-box').css({ 'height': $('#carousel-reblog-box').height() + 'px' }).slideUp('fast');
5061								$('a.jp-carousel-reblog').html( jetpackCarouselStrings.reblogged ).removeClass( 'reblog' ).addClass( 'reblogged' );
5062								$( '#carousel-reblog-box div.submit span.canceltext' ).spin( false );
5063								$('#carousel-reblog-submit').val( jetpackCarouselStrings.post_reblog );
5064								$('div.jp-carousel-info').children().not('#carousel-reblog-box').fadeIn('fast');
5065								slide.data('reblogged', 1);
5066								$('div.gallery').find('img[data-attachment-id="' + slide.data('attachment-id') + '"]').data('reblogged', 1);
5067
5068
5069							}, 'json' );
5070							wpcom.carousel.stat('reblog_submit');
5071						}
5072					} else if ( target.hasClass( 'jp-carousel-image-download' ) ) {
5073						wpcom.carousel.stat( 'download_original_click' );
5074// @end-hide-in-jetpack
5075					} else if ( target.hasClass( 'jp-carousel-commentlink' ) ) {
5076						e.preventDefault();
5077						e.stopPropagation();
5078						$( window ).unbind( 'keydown', keyListener );
5079						container.animate( { scrollTop: parseInt( info.position()[ 'top' ], 10 ) }, 'fast' );
5080						$( '#jp-carousel-comment-form-submit-and-info-wrapper' ).slideDown( 'fast' );
5081						$( '#jp-carousel-comment-form-comment-field' ).focus();
5082					} else if ( target.hasClass( 'jp-carousel-comment-login' ) ) {
5083						var url = jetpackCarouselStrings.login_url + '%23jp-carousel-' + attachment_id;
5084
5085						window.location.href = url;
5086					} else if ( target.parents( '#jp-carousel-comment-form-container' ).length ) {
5087						var textarea = $( '#jp-carousel-comment-form-comment-field' )
5088							.blur( function() {
5089								$( window ).bind( 'keydown', keyListener );
5090							} )
5091							.focus( function() {
5092								$( window ).unbind( 'keydown', keyListener );
5093							} );
5094
5095						var emailField = $( '#jp-carousel-comment-form-email-field' )
5096							.blur( function() {
5097								$( window ).bind( 'keydown', keyListener );
5098							} )
5099							.focus( function() {
5100								$( window ).unbind( 'keydown', keyListener );
5101							} );
5102
5103						var authorField = $( '#jp-carousel-comment-form-author-field' )
5104							.blur( function() {
5105								$( window ).bind( 'keydown', keyListener );
5106							} )
5107							.focus( function() {
5108								$( window ).unbind( 'keydown', keyListener );
5109							} );
5110
5111						var urlField = $( '#jp-carousel-comment-form-url-field' )
5112							.blur( function() {
5113								$( window ).bind( 'keydown', keyListener );
5114							} )
5115							.focus( function() {
5116								$( window ).unbind( 'keydown', keyListener );
5117							} );
5118
5119						if ( textarea && textarea.attr( 'id' ) === target.attr( 'id' ) ) {
5120							// For first page load
5121							$( window ).unbind( 'keydown', keyListener );
5122							$( '#jp-carousel-comment-form-submit-and-info-wrapper' ).slideDown( 'fast' );
5123						} else if ( target.is( 'input[type="submit"]' ) ) {
5124							e.preventDefault();
5125							e.stopPropagation();
5126
5127							$( '#jp-carousel-comment-form-spinner' ).spin( 'small', 'white' );
5128
5129							var ajaxData = {
5130								action: 'post_attachment_comment',
5131								nonce: jetpackCarouselStrings.nonce,
5132								blog_id: data[ 'blog_id' ],
5133								id: attachment_id,
5134								comment: textarea.val(),
5135							};
5136
5137							if ( ! ajaxData[ 'comment' ].length ) {
5138								gallery.jp_carousel( 'postCommentError', {
5139									field: 'jp-carousel-comment-form-comment-field',
5140									error: jetpackCarouselStrings.no_comment_text,
5141								} );
5142								return;
5143							}
5144
5145							if ( 1 !== Number( jetpackCarouselStrings.is_logged_in ) ) {
5146								ajaxData[ 'email' ] = emailField.val();
5147								ajaxData[ 'author' ] = authorField.val();
5148								ajaxData[ 'url' ] = urlField.val();
5149
5150								if ( 1 === Number( jetpackCarouselStrings.require_name_email ) ) {
5151									if ( ! ajaxData[ 'email' ].length || ! ajaxData[ 'email' ].match( '@' ) ) {
5152										gallery.jp_carousel( 'postCommentError', {
5153											field: 'jp-carousel-comment-form-email-field',
5154											error: jetpackCarouselStrings.no_comment_email,
5155										} );
5156										return;
5157									} else if ( ! ajaxData[ 'author' ].length ) {
5158										gallery.jp_carousel( 'postCommentError', {
5159											field: 'jp-carousel-comment-form-author-field',
5160											error: jetpackCarouselStrings.no_comment_author,
5161										} );
5162										return;
5163									}
5164								}
5165							}
5166
5167							$.ajax( {
5168								type: 'POST',
5169								url: jetpackCarouselStrings.ajaxurl,
5170								data: ajaxData,
5171								dataType: 'json',
5172								success: function( response /*, status, xhr*/ ) {
5173									if ( 'approved' === response.comment_status ) {
5174										$( '#jp-carousel-comment-post-results' )
5175											.slideUp( 'fast' )
5176											.html(
5177												'<span class="jp-carousel-comment-post-success">' +
5178													jetpackCarouselStrings.comment_approved +
5179													'</span>'
5180											)
5181											.slideDown( 'fast' );
5182									} else if ( 'unapproved' === response.comment_status ) {
5183										$( '#jp-carousel-comment-post-results' )
5184											.slideUp( 'fast' )
5185											.html(
5186												'<span class="jp-carousel-comment-post-success">' +
5187													jetpackCarouselStrings.comment_unapproved +
5188													'</span>'
5189											)
5190											.slideDown( 'fast' );
5191									} else {
5192										// 'deleted', 'spam', false
5193										$( '#jp-carousel-comment-post-results' )
5194											.slideUp( 'fast' )
5195											.html(
5196												'<span class="jp-carousel-comment-post-error">' +
5197													jetpackCarouselStrings.comment_post_error +
5198													'</span>'
5199											)
5200											.slideDown( 'fast' );
5201									}
5202									gallery.jp_carousel( 'clearCommentTextAreaValue' );
5203									gallery.jp_carousel( 'getComments', {
5204										attachment_id: attachment_id,
5205										offset: 0,
5206										clear: true,
5207									} );
5208									$( '#jp-carousel-comment-form-button-submit' ).val(
5209										jetpackCarouselStrings.post_comment
5210									);
5211									$( '#jp-carousel-comment-form-spinner' ).spin( false );
5212								},
5213								error: function(/*xhr, status, error*/) {
5214									// TODO: Add error handling and display here
5215									gallery.jp_carousel( 'postCommentError', {
5216										field: 'jp-carousel-comment-form-comment-field',
5217										error: jetpackCarouselStrings.comment_post_error,
5218									} );
5219									return;
5220								},
5221							} );
5222						}
5223					} else if ( ! target.parents( '.jp-carousel-info' ).length ) {
5224						container.jp_carousel( 'next' );
5225					}
5226				} )
5227				.bind( 'jp_carousel.afterOpen', function() {
5228					$( window ).bind( 'keydown', keyListener );
5229					$( window ).bind( 'resize', resizeListener );
5230					gallery.opened = true;
5231
5232					resizeListener();
5233				} )
5234				.bind( 'jp_carousel.beforeClose', function() {
5235					var scroll = $( window ).scrollTop();
5236
5237					$( window ).unbind( 'keydown', keyListener );
5238					$( window ).unbind( 'resize', resizeListener );
5239					$( window ).scrollTop( scroll );
5240					$( '.jp-carousel-previous-button' ).hide();
5241					$( '.jp-carousel-next-button' ).hide();
5242					// Set height to original value
5243					// Fix some themes where closing carousel brings view back to top
5244					$( 'html' ).css( 'height', '' );
5245
5246					gallery.jp_carousel( 'hide_reblog_box' ); // @hide-in-jetpack
5247				})
5248				.bind( 'jp_carousel.afterClose', function() {
5249					if ( window.location.hash && history.back ) {
5250						history.back();
5251					}
5252					last_known_location_hash = '';
5253					gallery.opened = false;
5254				} )
5255				.on( 'transitionend.jp-carousel ', '.jp-carousel-slide', function( e ) {
5256					// If the movement transitions take more than twice the allotted time, disable them.
5257					// There is some wiggle room in the 2x, since some of that time is taken up in
5258					// JavaScript, setting up the transition and calling the events.
5259					if ( 'transform' === e.originalEvent.propertyName ) {
5260						var transitionMultiplier =
5261							( Date.now() - transitionBegin ) / 1000 / e.originalEvent.elapsedTime;
5262
5263						container.off( 'transitionend.jp-carousel' );
5264
5265						if ( transitionMultiplier >= 2 ) {
5266							$( '.jp-carousel-transitions' ).removeClass( 'jp-carousel-transitions' );
5267						}
5268					}
5269				} );
5270
5271			$( '.jp-carousel-wrap' ).touchwipe( {
5272				wipeLeft: function( e ) {
5273					e.preventDefault();
5274					gallery.jp_carousel( 'next' );
5275				},
5276				wipeRight: function( e ) {
5277					e.preventDefault();
5278					gallery.jp_carousel( 'previous' );
5279				},
5280				preventDefaultEvents: false,
5281			} );
5282
5283			nextButton.add( previousButton ).click( function( e ) {
5284				e.preventDefault();
5285				e.stopPropagation();
5286				if ( nextButton.is( this ) ) {
5287					gallery.jp_carousel( 'next' );
5288				} else {
5289					gallery.jp_carousel( 'previous' );
5290				}
5291			} );
5292		}
5293	};
5294
5295	var processSingleImageGallery = function() {
5296		// process links that contain img tag with attribute data-attachment-id
5297		$( 'a img[data-attachment-id]' ).each( function() {
5298			var container = $( this ).parent();
5299
5300			// skip if image was already added to gallery by shortcode
5301			if ( container.parent( '.gallery-icon' ).length ) {
5302				return;
5303			}
5304
5305			// skip if the container is not a link
5306			if ( 'undefined' === typeof $( container ).attr( 'href' ) ) {
5307				return;
5308			}
5309
5310			var valid = false;
5311
5312			// if link points to 'Media File' (ignoring GET parameters) and flag is set allow it
5313			if (
5314				$( container )
5315					.attr( 'href' )
5316					.split( '?' )[ 0 ] ===
5317					$( this )
5318						.attr( 'data-orig-file' )
5319						.split( '?' )[ 0 ] &&
5320				1 === Number( jetpackCarouselStrings.single_image_gallery_media_file )
5321			) {
5322				valid = true;
5323			}
5324
5325			// if link points to 'Attachment Page' allow it
5326			if ( $( container ).attr( 'href' ) === $( this ).attr( 'data-permalink' ) ) {
5327				valid = true;
5328			}
5329
5330			// links to 'Custom URL' or 'Media File' when flag not set are not valid
5331			if ( ! valid ) {
5332				return;
5333			}
5334
5335			// make this node a gallery recognizable by event listener above
5336			$( container ).addClass( 'single-image-gallery' );
5337			// blog_id is needed to allow posting comments to correct blog
5338			$( container ).data( 'carousel-extra', {
5339				blog_id: Number( jetpackCarouselStrings.blog_id ),
5340			} );
5341		} );
5342	};
5343
5344	var methods = {
5345		testForData: function( gallery ) {
5346			gallery = $( gallery ); // make sure we have it as a jQuery object.
5347			return ! ( ! gallery.length || ! gallery.data( 'carousel-extra' ) );
5348		},
5349
5350		testIfOpened: function() {
5351			return !! (
5352				'undefined' !== typeof gallery &&
5353				'undefined' !== typeof gallery.opened &&
5354				gallery.opened
5355			);
5356		},
5357
5358		openOrSelectSlide: function( index ) {
5359			// The `open` method triggers an asynchronous effect, so we will get an
5360			// error if we try to use `open` then `selectSlideAtIndex` immediately
5361			// after it. We can only use `selectSlideAtIndex` if the carousel is
5362			// already open.
5363			if ( ! $( this ).jp_carousel( 'testIfOpened' ) ) {
5364				// The `open` method selects the correct slide during the
5365				// initialization.
5366				$( this ).jp_carousel( 'open', { start_index: index } );
5367			} else {
5368				gallery.jp_carousel( 'selectSlideAtIndex', index );
5369			}
5370		},
5371
5372		open: function( options ) {
5373			var settings = {
5374					items_selector:
5375						'.gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id], img[data-attachment-id]',
5376					start_index: 0,
5377				},
5378				data = $( this ).data( 'carousel-extra' );
5379
5380			if ( ! data ) {
5381				return; // don't run if the default gallery functions weren't used
5382			}
5383
5384			prepareGallery( data );
5385
5386			if ( gallery.jp_carousel( 'testIfOpened' ) ) {
5387				return; // don't open if already opened
5388			}
5389
5390			// make sure to stop the page from scrolling behind the carousel overlay, so we don't trigger
5391			// infiniscroll for it when enabled (Reader, theme infiniscroll, etc).
5392			originalOverflow = $( 'body' ).css( 'overflow' );
5393			$( 'body' ).css( 'overflow', 'hidden' );
5394			// prevent html from overflowing on some of the new themes.
5395			originalHOverflow = $( 'html' ).css( 'overflow' );
5396			$( 'html' ).css( 'overflow', 'hidden' );
5397			scrollPos = $( window ).scrollTop();
5398
5399			container.data( 'carousel-extra', data );
5400// @start-hide-in-jetpack
5401			wpcom.carousel.stat( ['open', 'view_image'] );
5402// @end-hide-in-jetpack
5403
5404			return this.each( function() {
5405				// If options exist, lets merge them
5406				// with our default settings
5407				var $this = $( this );
5408
5409				if ( options ) {
5410					$.extend( settings, options );
5411				}
5412				if ( -1 === settings.start_index ) {
5413					settings.start_index = 0; //-1 returned if can't find index, so start from beginning
5414				}
5415
5416				container.trigger( 'jp_carousel.beforeOpen' ).fadeIn( 'fast', function() {
5417					container.trigger( 'jp_carousel.afterOpen' );
5418					gallery
5419						.jp_carousel(
5420							'initSlides',
5421							$this.find( settings.items_selector ),
5422							settings.start_index
5423						)
5424						.jp_carousel( 'selectSlideAtIndex', settings.start_index );
5425				} );
5426				gallery.html( '' );
5427			} );
5428		},
5429
5430		selectSlideAtIndex: function( index ) {
5431			var slides = this.jp_carousel( 'slides' ),
5432				selected = slides.eq( index );
5433
5434			if ( 0 === selected.length ) {
5435				selected = slides.eq( 0 );
5436			}
5437
5438			gallery.jp_carousel( 'selectSlide', selected, false );
5439			return this;
5440		},
5441
5442		close: function() {
5443			// make sure to let the page scroll again
5444			$( 'body' ).css( 'overflow', originalOverflow );
5445			$( 'html' ).css( 'overflow', originalHOverflow );
5446			this.jp_carousel( 'clearCommentTextAreaValue' );
5447			return container.trigger( 'jp_carousel.beforeClose' ).fadeOut( 'fast', function() {
5448				container.trigger( 'jp_carousel.afterClose' );
5449				$( window ).scrollTop( scrollPos );
5450			} );
5451		},
5452
5453		next: function() {
5454			this.jp_carousel( 'previousOrNext', 'nextSlide' );
5455            gallery.jp_carousel( 'hide_reblog_box' ); // @hide-in-jetpack
5456		},
5457
5458		previous: function() {
5459			this.jp_carousel( 'previousOrNext', 'prevSlide' );
5460            gallery.jp_carousel( 'hide_reblog_box' ); // @hide-in-jetpack
5461		},
5462
5463		previousOrNext: function( slideSelectionMethodName ) {
5464			if ( ! this.jp_carousel( 'hasMultipleImages' ) ) {
5465				return false;
5466			}
5467
5468			var slide = gallery.jp_carousel( slideSelectionMethodName );
5469
5470			if ( slide ) {
5471				container.animate( { scrollTop: 0 }, 'fast' );
5472				this.jp_carousel( 'clearCommentTextAreaValue' );
5473				this.jp_carousel( 'selectSlide', slide );
5474                wpcom.carousel.stat( ['previous', 'view_image'] ); // @hide-in-jetpack
5475			}
5476		},
5477
5478        // @start-hide-in-jetpack
5479       resetButtons : function(current) {
5480		   if ( current.data( 'reblogged' ) ) {
5481                $('.jp-carousel-buttons a.jp-carousel-reblog').addClass( 'reblogged' ).text( jetpackCarouselStrings.reblogged );
5482		   } else {
5483                $('.jp-carousel-buttons a.jp-carousel-reblog').removeClass( 'reblogged' ).text( jetpackCarouselStrings.reblog );
5484		   }
5485           // Must also take care of reblog/reblogged here
5486        },
5487        // @end-hide-in-jetpack
5488
5489
5490		selectedSlide: function() {
5491			return this.find( '.selected' );
5492		},
5493
5494		setSlidePosition: function( x ) {
5495			transitionBegin = Date.now();
5496
5497			return this.css( {
5498				'-webkit-transform': 'translate3d(' + x + 'px,0,0)',
5499				'-moz-transform': 'translate3d(' + x + 'px,0,0)',
5500				'-ms-transform': 'translate(' + x + 'px,0)',
5501				'-o-transform': 'translate(' + x + 'px,0)',
5502				transform: 'translate3d(' + x + 'px,0,0)',
5503			} );
5504		},
5505
5506		updateSlidePositions: function( animate ) {
5507			var current = this.jp_carousel( 'selectedSlide' ),
5508				galleryWidth = gallery.width(),
5509				currentWidth = current.width(),
5510				previous = gallery.jp_carousel( 'prevSlide' ),
5511				next = gallery.jp_carousel( 'nextSlide' ),
5512				previousPrevious = previous.prev(),
5513				nextNext = next.next(),
5514				left = Math.floor( ( galleryWidth - currentWidth ) * 0.5 );
5515
5516			current.jp_carousel( 'setSlidePosition', left ).show();
5517
5518			// minimum width
5519			gallery.jp_carousel( 'fitInfo', animate );
5520
5521			// prep the slides
5522			var direction = lastSelectedSlide.is( current.prevAll() ) ? 1 : -1;
5523
5524			// Since we preload the `previousPrevious` and `nextNext` slides, we need
5525			// to make sure they technically visible in the DOM, but invisible to the
5526			// user. To hide them from the user, we position them outside the edges
5527			// of the window.
5528			//
5529			// This section of code only applies when there are more than three
5530			// slides. Otherwise, the `previousPrevious` and `nextNext` slides will
5531			// overlap with the `previous` and `next` slides which must be visible
5532			// regardless.
5533			if ( 1 === direction ) {
5534				if ( ! nextNext.is( previous ) ) {
5535					nextNext.jp_carousel( 'setSlidePosition', galleryWidth + next.width() ).show();
5536				}
5537
5538				if ( ! previousPrevious.is( next ) ) {
5539					previousPrevious
5540						.jp_carousel( 'setSlidePosition', -previousPrevious.width() - currentWidth )
5541						.show();
5542				}
5543			} else {
5544				if ( ! nextNext.is( previous ) ) {
5545					nextNext.jp_carousel( 'setSlidePosition', galleryWidth + currentWidth ).show();
5546				}
5547			}
5548
5549			previous
5550				.jp_carousel( 'setSlidePosition', Math.floor( -previous.width() + screenPadding * 0.75 ) )
5551				.show();
5552			next
5553				.jp_carousel( 'setSlidePosition', Math.ceil( galleryWidth - screenPadding * 0.75 ) )
5554				.show();
5555		},
5556
5557		selectSlide: function( slide, animate ) {
5558			lastSelectedSlide = this.find( '.selected' ).removeClass( 'selected' );
5559
5560			var slides = gallery.jp_carousel( 'slides' ).css( { position: 'fixed' } ),
5561				current = $( slide )
5562					.addClass( 'selected' )
5563					.css( { position: 'relative' } ),
5564				attachmentId = current.data( 'attachment-id' ),
5565				previous = gallery.jp_carousel( 'prevSlide' ),
5566				next = gallery.jp_carousel( 'nextSlide' ),
5567				previousPrevious = previous.prev(),
5568				nextNext = next.next(),
5569				animated,
5570				captionHtml;
5571
5572			// center the main image
5573			gallery.jp_carousel( 'loadFullImage', current );
5574
5575			caption.hide();
5576
5577			if ( next.length === 0 && slides.length <= 2 ) {
5578				$( '.jp-carousel-next-button' ).hide();
5579			} else {
5580				$( '.jp-carousel-next-button' ).show();
5581			}
5582
5583			if ( previous.length === 0 && slides.length <= 2 ) {
5584				$( '.jp-carousel-previous-button' ).hide();
5585			} else {
5586				$( '.jp-carousel-previous-button' ).show();
5587			}
5588
5589			animated = current
5590				.add( previous )
5591				.add( previousPrevious )
5592				.add( next )
5593				.add( nextNext )
5594				.jp_carousel( 'loadSlide' );
5595
5596			// slide the whole view to the x we want
5597			slides.not( animated ).hide();
5598
5599			gallery.jp_carousel( 'updateSlidePositions', animate );
5600
5601			gallery.jp_carousel( 'resetButtons', current ); // @hide-in-jetpack
5602            container.trigger( 'jp_carousel.selectSlide', [ current ] );
5603
5604			gallery.jp_carousel( 'getTitleDesc', {
5605				title: current.data( 'title' ),
5606				desc: current.data( 'desc' ),
5607			} );
5608
5609			var imageMeta = current.data( 'image-meta' );
5610			gallery.jp_carousel( 'updateExif', imageMeta );
5611			gallery.jp_carousel( 'updateFullSizeLink', current );
5612			gallery.jp_carousel( 'updateMap', imageMeta );
5613			gallery.jp_carousel( 'testCommentsOpened', current.data( 'comments-opened' ) );
5614			gallery.jp_carousel( 'getComments', {
5615				attachment_id: attachmentId,
5616				offset: 0,
5617				clear: true,
5618			} );
5619			$( '#jp-carousel-comment-post-results' ).slideUp();
5620
5621			// $('<div />').text(sometext).html() is a trick to go to HTML to plain
5622			// text (including HTML entities decode, etc)
5623			if ( current.data( 'caption' ) ) {
5624				captionHtml = $( '<div />' )
5625					.text( current.data( 'caption' ) )
5626					.html();
5627
5628				if (
5629					captionHtml ===
5630					$( '<div />' )
5631						.text( current.data( 'title' ) )
5632						.html()
5633				) {
5634					$( '.jp-carousel-titleanddesc-title' )
5635						.fadeOut( 'fast' )
5636						.empty();
5637				}
5638
5639				if (
5640					captionHtml ===
5641					$( '<div />' )
5642						.text( current.data( 'desc' ) )
5643						.html()
5644				) {
5645					$( '.jp-carousel-titleanddesc-desc' )
5646						.fadeOut( 'fast' )
5647						.empty();
5648				}
5649
5650				caption.html( current.data( 'caption' ) ).fadeIn( 'slow' );
5651			} else {
5652				caption.fadeOut( 'fast' ).empty();
5653			}
5654
5655			// Record pageview in WP Stats, for each new image loaded full-screen.
5656			if ( jetpackCarouselStrings.stats ) {
5657				new Image().src =
5658					document.location.protocol +
5659					'../g.gif' +
5660					jetpackCarouselStrings.stats +
5661					'&post=' +
5662					encodeURIComponent( attachmentId ) +
5663					'&rand=' +
5664					Math.random();
5665			}
5666            wpcom.carousel.pageview( attachmentId ); // @hide-in-jetpack
5667			// Load the images for the next and previous slides.
5668			$( next )
5669				.add( previous )
5670				.each( function() {
5671					gallery.jp_carousel( 'loadFullImage', $( this ) );
5672				} );
5673
5674			window.location.hash = last_known_location_hash = '#jp-carousel-' + attachmentId;
5675		},
5676
5677		slides: function() {
5678			return this.find( '.jp-carousel-slide' );
5679		},
5680
5681		slideDimensions: function() {
5682			return {
5683				width: $( window ).width() - screenPadding * 2,
5684				height: Math.floor( ( $( window ).height() / 100 ) * proportion - 60 ),
5685			};
5686		},
5687
5688		loadSlide: function() {
5689			return this.each( function() {
5690				var slide = $( this );
5691				slide.find( 'img' ).one( 'load', function() {
5692					// set the width/height of the image if it's too big
5693					slide.jp_carousel( 'fitSlide', false );
5694				} );
5695			} );
5696		},
5697
5698		bestFit: function() {
5699			var max = gallery.jp_carousel( 'slideDimensions' ),
5700				orig = this.jp_carousel( 'originalDimensions' ),
5701				orig_ratio = orig.width / orig.height,
5702				w_ratio = 1,
5703				h_ratio = 1,
5704				width,
5705				height;
5706
5707			if ( orig.width > max.width ) {
5708				w_ratio = max.width / orig.width;
5709			}
5710			if ( orig.height > max.height ) {
5711				h_ratio = max.height / orig.height;
5712			}
5713
5714			if ( w_ratio < h_ratio ) {
5715				width = max.width;
5716				height = Math.floor( width / orig_ratio );
5717			} else if ( h_ratio < w_ratio ) {
5718				height = max.height;
5719				width = Math.floor( height * orig_ratio );
5720			} else {
5721				width = orig.width;
5722				height = orig.height;
5723			}
5724
5725			return {
5726				width: width,
5727				height: height,
5728			};
5729		},
5730
5731		fitInfo: function(/*animated*/) {
5732			var current = this.jp_carousel( 'selectedSlide' ),
5733				size = current.jp_carousel( 'bestFit' );
5734
5735			photo_info.css( {
5736				left: Math.floor( ( info.width() - size.width ) * 0.5 ),
5737				width: Math.floor( size.width ),
5738			} );
5739
5740			return this;
5741		},
5742
5743		fitMeta: function( animated ) {
5744			var newInfoTop = {
5745				top: Math.floor( ( $( window ).height() / 100 ) * proportion + 5 ) + 'px',
5746			};
5747			var newLeftWidth = { width: info.width() - ( imageMeta.width() + 80 ) + 'px' };
5748
5749			if ( animated ) {
5750				info.animate( newInfoTop );
5751				leftColWrapper.animate( newLeftWidth );
5752			} else {
5753				info.animate( newInfoTop );
5754				leftColWrapper.css( newLeftWidth );
5755			}
5756		},
5757
5758		fitSlide: function(/*animated*/) {
5759			return this.each( function() {
5760				var $this = $( this ),
5761					dimensions = $this.jp_carousel( 'bestFit' ),
5762					method = 'css',
5763					max = gallery.jp_carousel( 'slideDimensions' );
5764
5765				dimensions.left = 0;
5766				dimensions.top = Math.floor( ( max.height - dimensions.height ) * 0.5 ) + 40;
5767				$this[ method ]( dimensions );
5768			} );
5769		},
5770
5771		texturize: function( text ) {
5772			text = '' + text; // make sure we get a string. Title "1" came in as int 1, for example, which did not support .replace().
5773			text = text
5774				.replace( /'/g, '&#8217;' )
5775				.replace( /&#039;/g, '&#8217;' )
5776				.replace( /[\u2019]/g, '&#8217;' );
5777			text = text
5778				.replace( /"/g, '&#8221;' )
5779				.replace( /&#034;/g, '&#8221;' )
5780				.replace( /&quot;/g, '&#8221;' )
5781				.replace( /[\u201D]/g, '&#8221;' );
5782			text = text.replace( /([\w]+)=&#[\d]+;(.+?)&#[\d]+;/g, '$1="$2"' ); // untexturize allowed HTML tags params double-quotes
5783			return $.trim( text );
5784		},
5785
5786		initSlides: function( items, start_index ) {
5787			if ( items.length < 2 ) {
5788				$( '.jp-carousel-next-button, .jp-carousel-previous-button' ).hide();
5789			} else {
5790				$( '.jp-carousel-next-button, .jp-carousel-previous-button' ).show();
5791			}
5792
5793			// Calculate the new src.
5794			items.each( function(/*i*/) {
5795				var src_item = $( this ),
5796					orig_size = src_item.data( 'orig-size' ) || '',
5797					max = gallery.jp_carousel( 'slideDimensions' ),
5798					parts = orig_size.split( ',' ),
5799					medium_file = src_item.data( 'medium-file' ) || '',
5800					large_file = src_item.data( 'large-file' ) || '',
5801					src;
5802				orig_size = { width: parseInt( parts[ 0 ], 10 ), height: parseInt( parts[ 1 ], 10 ) };
5803
5804// @start-hide-in-jetpack
5805				 if ( 'undefined' !== typeof wpcom ) {
5806					src = src_item.attr('src') || src_item.attr('original') || src_item.data('original') || src_item.data('lazy-src');
5807					if (src.indexOf('imgpress') !== -1) {
5808						src = src_item.data('orig-file');
5809					}
5810					src = wpcom.carousel.addWidthToImageURL( src, wpcom.carousel.findFirstLargeEnoughWidth( orig_size.width, orig_size.height, max.width, max.height ) );
5811				} else {
5812
5813// @end-hide-in-jetpack
5814                src = src_item.data( 'orig-file' );
5815
5816				src = gallery.jp_carousel( 'selectBestImageSize', {
5817					orig_file: src,
5818					orig_width: orig_size.width,
5819					orig_height: orig_size.height,
5820					max_width: max.width,
5821					max_height: max.height,
5822					medium_file: medium_file,
5823					large_file: large_file,
5824				} );
5825// @start-hide-in-jetpack
5826				 } // end else of if ( 'undefined' != typeof wpcom )
5827// @end-hide-in-jetpack
5828
5829				// Set the final src
5830				$( this ).data( 'gallery-src', src );
5831			} );
5832
5833			// If the start_index is not 0 then preload the clicked image first.
5834			if ( 0 !== start_index ) {
5835				$( '<img/>' )[ 0 ].src = $( items[ start_index ] ).data( 'gallery-src' );
5836			}
5837
5838			var useInPageThumbnails =
5839				items.first().closest( '.tiled-gallery.type-rectangular' ).length > 0;
5840
5841			// create the 'slide'
5842			items.each( function( i ) {
5843				var src_item = $( this ),
5844					reblogged       = src_item.data( 'reblogged' ) || 0, // @hide-in-jetpack
5845					attachment_id = src_item.data( 'attachment-id' ) || 0,
5846					comments_opened = src_item.data( 'comments-opened' ) || 0,
5847					image_meta = src_item.data( 'image-meta' ) || {},
5848					orig_size = src_item.data( 'orig-size' ) || '',
5849					thumb_size = { width: src_item[ 0 ].naturalWidth, height: src_item[ 0 ].naturalHeight },
5850					title = src_item.data( 'image-title' ) || '',
5851					description = src_item.data( 'image-description' ) || '',
5852					caption =
5853						src_item
5854							.parents( '.gallery-item' )
5855							.find( '.gallery-caption' )
5856							.html() || '',
5857					src = src_item.data( 'gallery-src' ) || '',
5858					medium_file = src_item.data( 'medium-file' ) || '',
5859					large_file = src_item.data( 'large-file' ) || '',
5860					orig_file = src_item.data( 'orig-file' ) || '';
5861
5862				var tiledCaption = src_item
5863					.parents( 'div.tiled-gallery-item' )
5864					.find( 'div.tiled-gallery-caption' )
5865					.html();
5866				if ( tiledCaption ) {
5867					caption = tiledCaption;
5868				}
5869
5870				if ( attachment_id && orig_size.length ) {
5871					title = gallery.jp_carousel( 'texturize', title );
5872					description = gallery.jp_carousel( 'texturize', description );
5873					caption = gallery.jp_carousel( 'texturize', caption );
5874
5875					// Initially, the image is a 1x1 transparent gif.  The preview is shown as a background image on the slide itself.
5876					var image = $( '<img/>' )
5877						.attr(
5878							'src',
5879							'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
5880						)
5881						.css( 'width', '100%' )
5882						.css( 'height', '100%' );
5883
5884					var slide = $(
5885						'<div class="jp-carousel-slide" itemprop="associatedMedia" itemscope itemtype="https://schema.org/ImageObject"></div>'
5886					)
5887						.hide()
5888						.css( {
5889							//'position' : 'fixed',
5890							left: i < start_index ? -1000 : gallery.width(),
5891						} )
5892						.append( image )
5893						.appendTo( gallery )
5894						.data( 'src', src )
5895						.data( 'title', title )
5896						.data( 'desc', description )
5897						.data( 'caption', caption )
5898						.data( 'attachment-id', attachment_id )
5899						.data( 'permalink', src_item.parents( 'a' ).attr( 'href' ) )
5900						.data( 'orig-size', orig_size )
5901						.data( 'comments-opened', comments_opened )
5902						.data( 'image-meta', image_meta )
5903						.data( 'medium-file', medium_file )
5904						.data( 'large-file', large_file )
5905						.data( 'orig-file', orig_file )
5906						.data( 'thumb-size', thumb_size )
5907                        .data( 'reblogged', reblogged ) // @hide-in-jetpack
5908                        ;
5909
5910						if ( useInPageThumbnails ) {
5911						// Use the image already loaded in the gallery as a preview.
5912						slide.data( 'preview-image', src_item.attr( 'src' ) ).css( {
5913							'background-image': 'url("' + src_item.attr( 'src' ) + '")',
5914							'background-size': '100% 100%',
5915							'background-position': 'center center',
5916						} );
5917					}
5918
5919					slide.jp_carousel( 'fitSlide', false );
5920				}
5921			} );
5922			return this;
5923		},
5924
5925		selectBestImageSize: function( args ) {
5926			if ( 'object' !== typeof args ) {
5927				args = {};
5928			}
5929
5930			if ( 'undefined' === typeof args.orig_file ) {
5931				return '';
5932			}
5933
5934			if ( 'undefined' === typeof args.orig_width || 'undefined' === typeof args.max_width ) {
5935				return args.orig_file;
5936			}
5937
5938			if ( 'undefined' === typeof args.medium_file || 'undefined' === typeof args.large_file ) {
5939				return args.orig_file;
5940			}
5941
5942			// Check if the image is being served by Photon (using a regular expression on the hostname).
5943
5944			var imageLinkParser = document.createElement( 'a' );
5945			imageLinkParser.href = args.large_file;
5946
5947			var isPhotonUrl = /^i[0-2].wp.com$/i.test( imageLinkParser.hostname );
5948
5949			var medium_size_parts = gallery.jp_carousel(
5950				'getImageSizeParts',
5951				args.medium_file,
5952				args.orig_width,
5953				isPhotonUrl
5954			);
5955			var large_size_parts = gallery.jp_carousel(
5956				'getImageSizeParts',
5957				args.large_file,
5958				args.orig_width,
5959				isPhotonUrl
5960			);
5961
5962			var large_width = parseInt( large_size_parts[ 0 ], 10 ),
5963				large_height = parseInt( large_size_parts[ 1 ], 10 ),
5964				medium_width = parseInt( medium_size_parts[ 0 ], 10 ),
5965				medium_height = parseInt( medium_size_parts[ 1 ], 10 );
5966
5967			// Assign max width and height.
5968			args.orig_max_width = args.max_width;
5969			args.orig_max_height = args.max_height;
5970
5971			// Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android ph
5971ones = 1.5, etc)
5972			if ( 'undefined' !== typeof window.devicePixelRatio && window.devicePixelRatio > 1 ) {
5973				args.max_width = args.max_width * window.devicePixelRatio;
5974				args.max_height = args.max_height * window.devicePixelRatio;
5975			}
5976
5977			if ( large_width >= args.max_width || large_height >= args.max_height ) {
5978				return args.large_file;
5979			}
5980
5981			if ( medium_width >= args.max_width || medium_height >= args.max_height ) {
5982				return args.medium_file;
5983			}
5984
5985			if ( isPhotonUrl ) {
5986				// args.orig_file doesn't point to a Photon url, so in this case we use args.large_file
5987				// to return the photon url of the original image.
5988				var largeFileIndex = args.large_file.lastIndexOf( '?' );
5989				var origPhotonUrl = args.large_file;
5990				if ( -1 !== largeFileIndex ) {
5991					origPhotonUrl = args.large_file.substring( 0, largeFileIndex );
5992					// If we have a really large image load a smaller version
5993					// that is closer to the viewable size
5994					if ( args.orig_width > args.max_width || args.orig_height > args.max_height ) {
5995						origPhotonUrl += '?fit=' + args.orig_max_width + '%2C' + args.orig_max_height;
5996					}
5997				}
5998				return origPhotonUrl;
5999			}
6000
6001			return args.orig_file;
6002		},
6003
6004		getImageSizeParts: function( file, orig_width, isPhotonUrl ) {
6005			var size = isPhotonUrl
6006				? file.replace( /.*=([\d]+%2C[\d]+).*$/, '$1' )
6007				: file.replace( /.*-([\d]+x[\d]+)\..+$/, '$1' );
6008
6009			var size_parts =
6010				size !== file
6011					? isPhotonUrl
6012						? size.split( '%2C' )
6013						: size.split( 'x' )
6014					: [ orig_width, 0 ];
6015
6016			// If one of the dimensions is set to 9999, then the actual value of that dimension can't be retrieved from the url.
6017			// In that case, we set the value to 0.
6018			if ( '9999' === size_parts[ 0 ] ) {
6019				size_parts[ 0 ] = '0';
6020			}
6021
6022			if ( '9999' === size_parts[ 1 ] ) {
6023				size_parts[ 1 ] = '0';
6024			}
6025
6026			return size_parts;
6027		},
6028
6029// @start-hide-in-jetpack
6030        show_reblog_box: function() {
6031            $('#carousel-reblog-box textarea').val(jetpackCarouselStrings.reblog_add_thoughts);
6032            //t.addClass('selected');
6033            $('#carousel-reblog-box p.response').remove();
6034            $('#carousel-reblog-box div.submit, #carousel-reblog-box div.submit span.canceltext').show();
6035            $('#carousel-reblog-box div.submit input[type=submit]').prop('disabled', false);
6036
6037            var current = $('.jp-carousel div.selected');
6038            $('#carousel-reblog-box input#carousel-reblog-post-url').val( current.data('permalink') );
6039            $('#carousel-reblog-box input#carousel-reblog-post-title').val( $('div.jp-carousel-info').children('h2').text() );
6040
6041            $('div.jp-carousel-info').append( $('#carousel-reblog-box') ).children().fadeOut('fast');
6042            $('#carousel-reblog-box').fadeIn('fast');
6043        },
6044
6045        hide_reblog_box: function () {
6046            $( 'div.jp-carousel-info' ).children().not( '#carousel-reblog-box' ).fadeIn( 'fast' );
6047            $( '#carousel-reblog-box' ).fadeOut( 'fast' );
6048        },
6049// @end-hide-in-jetpack
6050
6051		originalDimensions: function() {
6052			var splitted = $( this )
6053				.data( 'orig-size' )
6054				.split( ',' );
6055			return { width: parseInt( splitted[ 0 ], 10 ), height: parseInt( splitted[ 1 ], 10 ) };
6056		},
6057
6058		format: function( args ) {
6059			if ( 'object' !== typeof args ) {
6060				args = {};
6061			}
6062			if ( ! args.text || 'undefined' === typeof args.text ) {
6063				return;
6064			}
6065			if ( ! args.replacements || 'undefined' === typeof args.replacements ) {
6066				return args.text;
6067			}
6068			return args.text.replace( /{(\d+)}/g, function( match, number ) {
6069				return typeof args.replacements[ number ] !== 'undefined'
6070					? args.replacements[ number ]
6071					: match;
6072			} );
6073		},
6074
6075		/**
6076		 * Returns a number in a fraction format that represents the shutter speed.
6077		 * @param Number speed
6078		 * @return String
6079		 */
6080		shutterSpeed: function( speed ) {
6081			var denominator;
6082
6083			// round to one decimal if value > 1s by multiplying it by 10, rounding, then dividing by 10 again
6084			if ( speed >= 1 ) {
6085				return Math.round( speed * 10 ) / 10 + 's';
6086			}
6087
6088			// If the speed is less than one, we find the denominator by invert
6088ing
6089			// the number. Since cameras usually use rational numbers as shutter
6090			// speeds, we should get a nice round number. Or close to one in cases
6091			// like 1/30. So we round it.
6092			denominator = Math.round( 1 / speed );
6093
6094			return '1/' + denominator + 's';
6095		},
6096
6097		parseTitleDesc: function( value ) {
6098			if ( ! value.match( ' ' ) && value.match( '_' ) ) {
6099				return '';
6100			}
6101
6102			return value;
6103		},
6104
6105		getTitleDesc: function( data ) {
6106			var title = '',
6107				desc = '',
6108				markup = '',
6109				target;
6110
6111			target = $( 'div.jp-carousel-titleanddesc', 'div.jp-carousel-wrap' );
6112			target.hide();
6113
6114			title = gallery.jp_carousel( 'parseTitleDesc', data.title ) || '';
6115			desc = gallery.jp_carousel( 'parseTitleDesc', data.desc ) || '';
6116
6117			if ( title.length || desc.length ) {
6118				// Convert from HTML to plain text (including HTML entities decode, etc)
6119				if (
6120					$( '<div />' )
6121						.html( title )
6122						.text() ===
6123					$( '<div />' )
6124						.html( desc )
6125						.text()
6126				) {
6127					title = '';
6128				}
6129
6130				markup = title.length
6131					? '<div class="jp-carousel-titleanddesc-title">' + title + '</div>'
6132					: '';
6133				markup += desc.length
6134					? '<div class="jp-carousel-titleanddesc-desc">' + desc + '</div>'
6135					: '';
6136
6137				target.html( markup ).fadeIn( 'slow' );
6138			}
6139
6140			$( 'div#jp-carousel-comment-form-container' ).css( 'margin-top', '20px' );
6141			$( 'div#jp-carousel-comments-loading' ).css( 'margin-top', '20px' );
6142		},
6143
6144		// updateExif updates the contents of the exif UL (.jp-carousel-image-exif)
6145		updateExif: function( meta ) {
6146			if ( ! meta || 1 !== Number( jetpackCarouselStrings.display_exif ) ) {
6147				return false;
6148			}
6149
6150			var $ul = $( "<ul class='jp-carousel-image-exif'></ul>" );
6151
6152			$.each( meta, function( key, val ) {
6153				if (
6154					0 === parseFloat( val ) ||
6155					! val.length ||
6156					-1 === $.inArray( key, $.makeArray( jetpackCarouselStrings.meta_data ) )
6157				) {
6158					return;
6159				}
6160
6161				switch ( key ) {
6162					case 'focal_length':
6163						val = val + 'mm';
6164						break;
6165					case 'shutter_speed':
6166						val = gallery.jp_carousel( 'shutterSpeed', val );
6167						break;
6168					case 'aperture':
6169						val = 'f/' + val;
6170						break;
6171				}
6172
6173				$ul.append( '<li><h5>' + jetpackCarouselStrings[ key ] + '</h5>' + val + '</li>' );
6174			} );
6175
6176			// Update (replace) the content of the ul
6177			$( 'div.jp-carousel-image-meta ul.jp-carousel-image-exif' ).replaceWith( $ul );
6178		},
6179
6180		// updateFullSizeLink updates the contents of the jp-carousel-image-download link
6181		updateFullSizeLink: function( current ) {
6182			if ( ! current || ! current.data ) {
6183				return false;
6184			}
6185			var original,
6186				origSize = current.data( 'orig-size' ).split( ',' ),
6187				imageLinkParser = document.createElement( 'a' );
6188
6189			imageLinkParser.href = current.data( 'src' ).replace( /\?.+$/, '' );
6190
6191			// Is this a Photon URL?
6192			if ( imageLinkParser.hostname.match( /^i[\d]{1}.wp.com$/i ) !== null ) {
6193				original = imageLinkParser.href;
6194			} else {
6195				original = current.data( 'orig-file' ).replace( /\?.+$/, '' );
6196			}
6197
6198			var permalink = $(
6199				'<a>' +
6200					gallery.jp_carousel( 'format', {
6201						text: jetpackCarouselStrings.download_original,
6202						replacements: origSize,
6203					} ) +
6204					'</a>'
6205			)
6206				.addClass( 'jp-carousel-image-download' )
6207				.attr( 'href', original )
6208				.attr( 'target', '_blank' );
6209
6210			// Update (replace) the content of the anchor
6211			$( 'div.jp-carousel-image-meta a.jp-carousel-image-download' ).replaceWith( permalink );
6212		},
6213
6214		updateMap: function( meta ) {
6215			if (
6216				! meta.latitude ||
6217				! meta.longitude ||
6218				1 !== Number( jetpackCarouselStrings.display_geo )
6219			) {
6220				return;
6221			}
6222
6223			var latitude = meta.latitude,
6224				longitude = meta.longitude,
6225				$metabox = $( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ),
6226				$mapbox = $( '<div></div>' ),
6227				style =
6228					'&scale=2&style=feature:all|element:all|invert_lightness:true|hue:0x0077FF|saturation:-50|lightness:-5|gamma:0.91';
6229
6230			$mapbox
6231				.addClass( 'jp-carousel-image-map' )
6232				.html(
6233					'<img width="154" height="154" src="https://maps.googleapis.com/maps/api/staticmap?\
6234							center=' +
6235						latitude +
6236						',' +
6237						longitude +
6238						'&\
6239							zoom=8&\
6240							size=154x154&\
6241							sensor=false&\
6242							markers=size:medium%7Ccolor:blue%7C' +
6243						latitude +
6244						',' +
6245						longitude +
6246						style +
6247						'" class="gmap-main" />\
6248							\
6249						<div class="gmap-topright"><div class="imgclip"><img width="175" height="154" src="https://maps.googleapis.com/ma
6249ps/api/staticmap?\
6250							center=' +
6251						latitude +
6252						',' +
6253						longitude +
6254						'&\
6255							zoom=3&\
6256							size=175x154&\
6257							sensor=false&\
6258							markers=size:small%7Ccolor:blue%7C' +
6259						latitude +
6260						',' +
6261						longitude +
6262						style +
6263						'"c /></div></div>\
6264							\
6265						'
6266				)
6267				.prependTo( $metabox );
6268		},
6269
6270		testCommentsOpened: function( opened ) {
6271			if ( 1 === parseInt( opened, 10 ) ) {
6272// @start-hide-in-jetpack
6273				if ( 1 === Number( jetpackCarouselStrings.is_logged_in ) ) {
6274					$('.jp-carousel-commentlink').fadeIn('fast');
6275				} else {
6276// @end-hide-in-jetpack
6277					$( '.jp-carousel-buttons' ).fadeIn( 'fast' );
6278// @start-hide-in-jetpack
6279				}
6280// @end-hide-in-jetpack
6281				commentForm.fadeIn( 'fast' );
6282			} else {
6283// @start-hide-in-jetpack
6284				if ( 1 === Number( jetpackCarouselStrings.is_logged_in ) ) {
6285					$('.jp-carousel-commentlink').fadeOut('fast');
6286				} else {
6287// @end-hide-in-jetpack
6288					$( '.jp-carousel-buttons' ).fadeOut( 'fast' );
6289// @start-hide-in-jetpack
6290				}
6291// @end-hide-in-jetpack
6292				commentForm.fadeOut( 'fast' );
6293			}
6294		},
6295
6296        getComments: function( args ) {
6297            clearInterval( commentInterval );
6298
6299            if ( 'object' !== typeof args ) {
6300                return;
6301            }
6302
6303            if ( 'undefined' === typeof args.attachment_id || ! args.attachment_id ) {
6304                return;
6305            }
6306
6307            if ( ! args.offset || 'undefined' === typeof args.offset || args.offset < 1 ) {
6308                args.offset = 0;
6309            }
6310
6311            var comments = $( '.jp-carousel-comments' ),
6312                commentsLoading = $( '#jp-carousel-comments-loading' ).show();
6313
6314            if ( args.clear ) {
6315                comments.hide().empty();
6316            }
6317
6318            $.ajax( {
6319                type: 'GET',
6320                url: jetpackCarouselStrings.ajaxurl,
6321                dataType: 'json',
6322                data: {
6323                    action: 'get_attachment_comments',
6324                    nonce: jetpackCarouselStrings.nonce,
6325                    id: args.attachment_id,
6326                    offset: args.offset,
6327                },
6328                success: function( data /*, status, xhr*/ ) {
6329                    if ( args.clear ) {
6330                        comments.fadeOut( 'fast' ).empty();
6331                    }
6332
6333                    $( data ).each( function() {
6334                        var comment = $( '<div></div>' )
6335                            .addClass( 'jp-carousel-comment' )
6336                            .attr( 'id', 'jp-carousel-comment-' + this[ 'id' ] )
6337                            .html(
6338                                '<div class="comment-gravatar">' +
6339                                this[ 'gravatar_markup' ] +
6340                                '</div>' +
6341                                '<div class="comment-author">' +
6342                                this[ 'author_markup' ] +
6343                                '</div>' +
6344                                '<div class="comment-date">' +
6345                                this[ 'date_gmt' ] +
6346                                '</div>' +
6347                                '<div class="comment-content">' +
6348                                this[ 'content' ] +
6349                                '</div>'
6350                            );
6351                        comments.append( comment );
6352
6353                        // Set the interval to check for a new page of comments.
6354                        clearInterval( commentInterval );
6355                        commentInterval = setInterval( function() {
6356                            if (
6357                                $( '.jp-carousel-overlay' ).height() - 150 <
6358                                $( '.jp-carousel-wrap' ).scrollTop() + $( window ).height()
6359                            ) {
6360                                gallery.jp_carousel( 'getComments', {
6361                                    attachment_id: args.attachment_id,
6362                                    offset: args.offset + 10,
6363                                    clear: false,
6364                                } );
6365                                clearInterval( commentInterval );
6366                            }
6367                        }, 300 );
6368                    } );
6369
6370                    // Verify (late) that the user didn't repeatldy click the arrows really fast, in which case the requested
6371                    // attachment id might no longer match the current attachment id by the time we get the data back or a now
6372                    // registered infiniscroll event kicks in, so we don't ever display comments for the wrong image by mistake.
6373                    var current = $( '.jp-carousel div.selected' );
6374                    if (
6375                        current &&
6376                        current.data &&
6377                        current.data( 'attachment-id' ) != args.attachment_id // jshint ignore:line
6378                    ) {
6379                        comments.fadeOut( 'fast' );
6380                        comments.empty();
6381                        return;
6382                    }
6383
6384                    // Increase the height of the background, semi-transparent overlay to match the new length of the comments list.
6385                    $( '.jp-carousel-overlay' ).height(
6386                        $( window ).height() +
6387                        titleAndDescription.height() +
6388                        commentForm.height() +
6389                        ( comments.height() > 0 ? comments.height() : imageMeta.height() ) +
6390                        200
6391                    );
6392
6393                    comments.show();
6394                    commentsLoading.hide();
6395                },
6396                error: function( xhr, status, error ) {
6397                    // TODO: proper error handling
6398                    console.log( 'Comment get fail...', xhr, status, error );
6399                    comments.fadeIn( 'fast' );
6400                    commentsLoading.fadeOut( 'fast' );
6401                },
6402            } );
6403        },
6404
6405        postCommentError: function( args ) {
6406            if ( 'object' !== typeof args ) {
6407                args = {};
6408            }
6409            if (
6410                ! args.field ||
6411                'undefined' === typeof args.field ||
6412                ! args.error ||
6413                'undefined' === typeof args.error
6414            ) {
6415                return;
6416            }
6417            $( '#jp-carousel-comment-post-results' )
6418                .slideUp( 'fast' )
6419                .html( '<span class="jp-carousel-comment-post-error">' + args.error + '</span>' )
6420                .slideDown( 'fast' );
6421            $( '#jp-carousel-comment-form-spinner' ).spin( false );
6422        },
6423
6424        setCommentIframeSrc: function( attachment_id ) {
6425            var iframe = $( '#jp-carousel-comment-iframe' );
6426            // Set the proper irame src for the current attachment id
6427            if ( iframe && iframe.length ) {
6428                iframe.attr( 'src', iframe.attr( 'src' ).replace( /(postid=)\d+/, '$1' + attachment_id ) );
6429                iframe.attr(
6430                    'src',
6431                    iframe.attr( 'src' ).replace( /(%23.+)?$/, '%23jp-carousel-' + attachment_id )
6432                );
6433            }
6434        },
6435
6436        clearCommentTextAreaValue: function() {
6437            var commentTextArea = $( '#jp-carousel-comment-form-comment-field' );
6438            if ( commentTextArea ) {
6439                commentTextArea.val( '' );
6440            }
6441        },
6442
6443        nextSlide: function() {
6444            var slides = this.jp_carousel( 'slides' );
6445            var selected = this.jp_carousel( 'selectedSlide' );
6446
6447            if ( selected.length === 0 || ( slides.length > 2 && selected.is( slides.last() ) ) ) {
6448                return slides.first();
6449            }
6450
6451            return selected.next();
6452        },
6453
6454        prevSlide: function() {
6455            var slides = this.jp_carousel( 'slides' );
6456            var selected = this.jp_carousel( 'selectedSlide' );
6457
6458            if ( selected.length === 0 || ( slides.length > 2 && selected.is( slides.first() ) ) ) {
6459                return slides.last();
6460            }
6461
6462            return selected.prev();
6463        },
6464
6465        loadFullImage: function( slide ) {
6466            var image = slide.find( 'img:first' );
6467
6468            if ( ! image.data( 'loaded' ) ) {
6469                // If the width of the slide is smaller than the width of the "thumbnail" we're already using,
6470                // don't load the full image.
6471
6472                image.on( 'load.jetpack', function() {
6473                    image.off( 'load.jetpack' );
6474                    $( this )
6475                        .closest( '.jp-carousel-slide' )
6476                        .css( 'background-image', '' );
6477                } );
6478
6479                if (
6480                    ! slide.data( 'preview-image' ) ||
6481                    ( slide.data( 'thumb-size' ) && slide.width() > slide.data( 'thumb-size' ).width )
6482                ) {
6483                    image
6484                        .attr( 'src', image.closest( '.jp-carousel-slide' ).data( 'src' ) )
6485                        .attr( 'itemprop', 'image' );
6486                } else {
6487                    image.attr( 'src', slide.data( 'preview-image' ) ).attr( 'itemprop', 'image' );
6488                }
6489
6490                image.data( 'loaded', 1 );
6491            }
6492        },
6493
6494        hasMultipleImages: function() {
6495            return gallery.jp_carousel( 'slides' ).length > 1;
6496        },
6497    };
6498
6499    $.fn.jp_carousel = function( method ) {
6500        // ask for the HTML of the gallery
6501        // Method calling logic
6502        if ( methods[ method ] ) {
6503            return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
6504        } else if ( typeof method === 'object' || ! method ) {
6505            return methods.open.apply( this, arguments );
6506        } else {
6507            $.error( 'Method ' + method + ' does not exist on jQuery.jp_carousel' );
6508        }
6509    };
6510
6511    // register the event listener for starting the gallery
6512    $( document.body ).on(
6513        'click.jp-carousel',
6514        'div.gallery, div.tiled-gallery, ul.wp-block-gallery, div.wp-block-jetpack-tiled-gallery, a.single-image-gallery',
6515        function( e ) {
6516            if ( ! $( this ).jp_carousel( 'testForData', e.currentTarget ) ) {
6517                return;
6518            }
6519
6520            // Do not open the modal if we are looking at a gallery caption from before WP5, which may contain a link.
6521            if (
6522                $( e.target )
6523                    .parent()
6524                    .hasClass( 'gallery-caption' )
6525            ) {
6526                return;
6527            }
6528
6529            // Do not open the modal if we are looking at a caption of a gallery block, which may contain a link.
6530            if (
6531                $( e.target )
6532                    .parent()
6533                    .is( 'figcaption' )
6534            ) {
6535                return;
6536            }
6537
6538            // Set height to auto
6539            // Fix some themes where closing carousel brings view back to top
6540            $( 'html' ).css( 'height', 'auto' );
6541
6542            e.preventDefault();
6543
6544            // Stopping propagation in case there are parent elements
6545            // with .gallery or .tiled-gallery class
6546            e.stopPropagation();
6547            $( this ).jp_carousel( 'open', {
6548                start_index: $( this )
6549                    .find( '.gallery-item, .tiled-gallery-item, .blocks-gallery-item, .tiled-gallery__item' )
6550                    .index(
6551                        $( e.target ).parents(
6552                            '.gallery-item, .tiled-gallery-item, .blocks-gallery-item, .tiled-gallery__item'
6553                        )
6554                    ),
6555            } );
6556        }
6557    );
6558
6559    // handle lightbox (single image gallery) for images linking to 'Attachment Page'
6560    if ( 1 === Number( jetpackCarouselStrings.single_image_gallery ) ) {
6561        processSingleImageGallery();
6562        $( document.body ).on( 'post-load', function() {
6563            processSingleImageGallery();
6564        } );
6565    }
6566
6567    // Makes carousel work on page load and when back button leads to same URL with carousel hash (ie: no actual document.ready trigger)
6568    $( window ).on( 'hashchange.jp-carousel', function() {
6569        var hashRegExp = /jp-carousel-(\d+)/,
6570            matches,
6571            attachmentId,
6572            galleries,
6573            selectedThumbnail;
6574
6575        if ( ! window.location.hash || ! hashRegExp.test( window.location.hash ) ) {
6576            if ( gallery && gallery.opened ) {
6577                container.jp_carousel( 'close' );
6578            }
6579
6580            return;
6581        }
6582
6583        if ( window.location.hash === last_known_location_hash && gallery.opened ) {
6584            return;
6585        }
6586
6587        if ( window.location.hash && gallery && ! gallery.opened && history.back ) {
6588            history.back();
6589            return;
6590        }
6591
6592        last_known_location_hash = window.location.hash;
6593        matches = window.location.hash.match( hashRegExp );
6594        attachmentId = parseInt( matches[ 1 ], 10 );
6595        galleries = $(
6596            'div.gallery, div.tiled-gallery, a.single-image-gallery, ul.wp-block-gallery, div.wp-block-jetpack-tiled-gallery'
6597        );
6598
6599        // Find the first thumbnail that matches the attachment ID in the location
6600        // hash, then open the gallery that contains it.
6601        galleries.each( function( _, galleryEl ) {
6602            $( galleryEl )
6603                .find( 'img' )
6604                .each( function( imageIndex, imageEl ) {
6605                    if ( $( imageEl ).data( 'attachment-id' ) === parseInt( attachmentId, 10 ) ) {
6606                        selectedThumbnail = { index: imageIndex, gallery: galleryEl };
6607                        return false;
6608                    }
6609                } );
6610
6611            if ( selectedThumbnail ) {
6612                $( selectedThumbnail.gallery ).jp_carousel( 'openOrSelectSlide', selectedThumbnail.index );
6613                return false;
6614            }
6615        } );
6616    } );
6617
6618    if ( window.location.hash ) {
6619        $( window ).trigger( 'hashchange' );
6620    }
6621} );
6622
6623/**
6624 * jQuery Plugin to obtain touch gestures from iPhone, iPod Touch and iPad, should also work with Android mobile phones (not tested yet!)
6625 * Common usage: wipe images (left and right to show the previous or next image)
6626 *
6627 * @author Andreas Waltl, netCU Internetagentur (https://www.netcu.de)
6628 * Version 1.1.1, modified to pass the touchmove event to the callbacks.
6629 */
6630( function( $ ) {
6631    $.fn.touchwipe = function( settings ) {
6632        var config = {
6633            min_move_x: 20,
6634            min_move_y: 20,
6635            wipeLeft: function(/*e*/) {},
6636            wipeRight: function(/*e*/) {},
6637            wipeUp: function(/*e*/) {},
6638            wipeDown: function(/*e*/) {},
6639            preventDefaultEvents: true,
6640        };
6641
6642        if ( settings ) {
6643            $.extend( config, settings );
6644        }
6645
6646        this.each( function() {
6647            var startX;
6648            var startY;
6649            var isMoving = false;
6650
6651            function cancelTouch() {
6652                this.removeEventListener( 'touchmove', onTouchMove );
6653                startX = null;
6654                isMoving = false;
6655            }
6656
6657            function onTouchMove( e ) {
6658                if ( config.preventDefaultEvents ) {
6659                    e.preventDefault();
6660                }
6661                if ( isMoving ) {
6662                    var x = e.touches[ 0 ].pageX;
6663                    var y = e.touches[ 0 ].pageY;
6664                    var dx = startX - x;
6665                    var dy = startY - y;
6666                    if ( Math.abs( dx ) >= config.min_move_x ) {
6667                        cancelTouch();
6668                        if ( dx > 0 ) {
6669                            config.wipeLeft( e );
6670                        } else {
6671                            config.wipeRight( e );
6672                        }
6673                    } else if ( Math.abs( dy ) >= config.min_move_y ) {
6674                        cancelTouch();
6675                        if ( dy > 0 ) {
6676                            config.wipeDown( e );
6677                        } else {
6678                            config.wipeUp( e );
6679                        }
6680                    }
6681                }
6682            }
6683
6684            function onTouchStart( e ) {
6685                if ( e.touches.length === 1 ) {
6686                    startX = e.touches[ 0 ].pageX;
6687                    startY = e.touches[ 0 ].pageY;
6688                    isMoving = true;
6689                    this.addEventListener( 'touchmove', onTouchMove, false );
6690                }
6691            }
6692            if ( 'ontouchstart' in document.documentElement ) {
6693                this.addEventListener( 'touchstart', onTouchStart, false );
6694            }
6695        } );
6696
6697        return this;
6698    };
6699} )( jQuery );
6700;

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.