PageSourceSearch

https://johntorbett.com/wp-content/plugins/ultimate-responsive-image-slider/js/jquery.sliderPro.js?ver=1.4.0

js johntorbett.com collected 2026-10-05 18:34:03 UTC 202,595 bytes, 6,160 lines download raw bytes

vendor: 9,086 bytes, lines 1-276
1/*!
2*  - v1.4.0
3* Homepage: http://bqworks.com/slider-pro/
4* Author: bqworks
5* Author URL: http://bqworks.com/
6*/
7;(function( window, $ ) {
8
9	"use strict";
10
11	// Static methods for Slider Pro
12	$.SliderPro = {
13
14		// List of added modules
15		modules: [],
16
17		// Add a module by extending the core prototype
18		addModule: function( name, module ) {
19			this.modules.push( name );
20			$.extend( SliderPro.prototype, module );
21		}
22	};
23
24	// namespace
25	var NS = $.SliderPro.namespace = 'SliderPro';
26
27	var SliderPro = function( instance, options ) {
28
29		// Reference to the slider instance
30		this.instance = instance;
31
32		// Reference to the slider jQuery element
33		this.$slider = $( this.instance );
34
35		// Reference to the slides (sp-slides) jQuery element
36		this.$slides = null;
37
38		// Reference to the mask (sp-mask) jQuery element
39		this.$slidesMask = null;
40
41		// Reference to the slides (sp-slides-container) jQuery element
42		this.$slidesContainer = null;
43
44		// Array of SliderProSlide objects, ordered by their DOM index
45		this.slides = [];
46
47		// Array of SliderProSlide objects, ordered by their left/top position in the slider.
48		// This will be updated continuously if the slider is loopable.
49		this.slidesOrder = [];
50
51		// Holds the options passed to the slider when it was instantiated
52		this.options = options;
53
54		// Holds the final settings of the slider after merging the specified
55		// ones with the default ones.
56		this.settings = {};
57
58		// Another reference to the settings which will not be altered by breakpoints or by other means
59		this.originalSettings = {};
60
61		// Reference to the original 'gotoSlide' method
62		this.originalGotoSlide = null;
63
64		// The index of the currently selected slide (starts with 0)
65		this.selectedSlideIndex = 0;
66
67		// The index of the previously selected slide
68		this.previousSlideIndex = 0;
69
70		// Indicates the position of the slide considered to be in the middle.
71		// If there are 5 slides (0, 1, 2, 3, 4) the middle position will be 2.
72		// If there are 6 slides (0, 1, 2, 3, 4, 5) the middle position will be approximated to 2.
73		this.middleSlidePosition = 0;
74
75		// Indicates the type of supported transition (CSS3 2D, CSS3 3D or JavaScript)
76		this.supportedAnimation = null;
77
78		// Indicates the required vendor prefix for CSS (i.e., -webkit, -moz, etc.)
79		this.vendorPrefix = null;
80
81		// Indicates the name of the CSS transition's complete event (i.e., transitionend, webkitTransitionEnd, etc.)
82		this.transitionEvent = null;
83
84		// Indicates the 'left' or 'top' position, depending on the orientation of the slides
85		this.positionProperty = null;
86
87		// Indicates the 'width' or 'height', depending on the orientation of the slides
88		this.sizeProperty = null;
89
90		// Indicates if the current browser is IE
91		this.isIE = null;
92
93		// The position of the slides container
94		this.slidesPosition = 0;
95
96		// The total width/height of the slides
97		this.slidesSize = 0;
98
99		// The average width/height of a slide
100		this.averageSlideSize = 0;
101
102		// The width of the individual slide
103		this.slideWidth = 0;
104
105		// The height of the individual slide
106		this.slideHeight = 0;
107
108		// Reference to the old slide width, used to check if the width has changed
109		this.previousSlideWidth = 0;
110
111		// Reference to the old slide height, used to check if the height has changed
112		this.previousSlideHeight = 0;
113		
114		// Reference to the old window width, used to check if the window width has changed
115		this.previousWindowWidth = 0;
116		
117		// Reference to the old window height, used to check if the window height has changed
118		this.previousWindowHeight = 0;
119
120		// Property used for deferring the resizing of the slider
121		this.allowResize = true;
122
123		// Unique ID to be used for event listening
124		this.uniqueId = new Date().valueOf();
125
126		// Stores size breakpoints
127		this.breakpoints = [];
128
129		// Indicates the current size breakpoint
130		this.currentBreakpoint = -1;
131
132		// An array of shuffled indexes, based on which the slides will be shuffled
133		this.shuffledIndexes = [];
134
135		// Initialize the slider
136		this._init();
137	};
138
139	SliderPro.prototype = {
140
141		// The starting place for the slider
142		_init: function() {
143			var that = this;
144
145			this.supportedAnimation = SliderProUtils.getSupportedAnimation();
146			this.vendorPrefix = SliderProUtils.getVendorPrefix();
147			this.transitionEvent = SliderProUtils.getTransitionEvent();
148			this.isIE = SliderProUtils.checkIE();
149
150			// Remove the 'sp-no-js' when the slider's JavaScript code starts running
151			this.$slider.removeClass( 'sp-no-js' );
152
153			// Add the 'ios' class if it's an iOS device
154			if ( window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ) {
155				this.$slider.addClass( 'ios' );
156			}
157
158			// Check if IE (older than 11) is used and add the version number as a class to the slider since
159			// older IE versions might need CSS tweaks.
160			var rmsie = /(msie) ([\w.]+)/,
161				ieVersion = rmsie.exec( window.navigator.userAgent.toLowerCase() );
162			
163			if ( this.isIE ) {
164				this.$slider.addClass( 'ie' );
165			}
166
167			if ( ieVersion !== null ) {
168				this.$slider.addClass( 'ie' + parseInt( ieVersion[2], 10 ) );
169			}
170
171			// Set up the slides containers
172			// slider-pro > sp-slides-container > sp-mask > sp-slides > sp-slide
173			this.$slidesContainer = $( '<div class="sp-slides-container"></div>' ).appendTo( this.$slider );
174			this.$slidesMask = $( '<div class="sp-mask"></div>' ).appendTo( this.$slidesContainer );
175			this.$slides = this.$slider.find( '.sp-slides' ).appendTo( this.$slidesMask );
176			this.$slider.find( '.sp-slide' ).appendTo( this.$slides );
177			
178			var modules = $.SliderPro.modules;
179
180			// Merge the modules' default settings with the core's default settings
181			if ( typeof modules !== 'undefined' ) {
182				for ( var i = 0; i < modules.length; i++ ) {
183					var defaults = modules[ i ].substring( 0, 1 ).toLowerCase() + modules[ i ].substring( 1 ) + 'Defaults';
184
185					if ( typeof this[ defaults ] !== 'undefined' ) {
186						$.extend( this.defaults, this[ defaults ] );
187					}
188				}
189			}
190
191			// Merge the specified setting with the default ones
192			this.settings = $.extend( {}, this.defaults, this.options );
193
194			// Initialize the modules
195			if ( typeof modules !== 'undefined' ) {
196				for ( var j = 0; j < modules.length; j++ ) {
197					if ( typeof this[ 'init' + modules[ j ] ] !== 'undefined' ) {
198						this[ 'init' + modules[ j ] ]();
199					}
200				}
201			}
202
203			// Keep a reference of the original settings and use it
204			// to restore the settings when the breakpoints are used.
205			this.originalSettings = $.extend( {}, this.settings );
206
207			// Get the reference to the 'gotoSlide' method
208			this.originalGotoSlide = this.gotoSlide;
209
210			// Parse the breakpoints object and store the values into an array,
211			// sorting them in ascending order based on the specified size.
212			if ( this.settings.breakpoints !== null ) {
213				for ( var sizes in this.settings.breakpoints ) {
214					this.breakpoints.push({ size: parseInt( sizes, 10 ), properties:this.settings.breakpoints[ sizes ] });
215				}
216
217				this.breakpoints = this.breakpoints.sort(function( a, b ) {
218					return a.size >= b.size ? 1: -1;
219				});
220			}
221
222			// Set which slide should be selected initially
223			this.selectedSlideIndex = this.settings.startSlide;
224
225			// Shuffle/randomize the slides
226			if ( this.settings.shuffle === true ) {
227				var slides = this.$slides.find( '.sp-slide' ),
228					shuffledSlides = [];
229
230				// Populate the 'shuffledIndexes' with index numbers
231				slides.each(function( index ) {
232					that.shuffledIndexes.push( index );
233				});
234
235				for ( var k = this.shuffledIndexes.length - 1; k > 0; k-- ) {
236					var l = Math.floor( Math.random() * ( k + 1 ) ),
237						temp = this.shuffledIndexes[ k ];
238
239					this.shuffledIndexes[ k ] = this.shuffledIndexes[ l ];
240					this.shuffledIndexes[ l ] = temp;
241				}
242
243				// Reposition the slides based on the order of the indexes in the
244				// 'shuffledIndexes' array
245				$.each( this.shuffledIndexes, function( index, element ) {
246					shuffledSlides.push( slides[ element ] );
247				});
248				
249				// Append the sorted slides to the slider
250				this.$slides.empty().append( shuffledSlides ) ;
251			}
252			
253			// Resize the slider when the browser window resizes.
254			// Also, deffer the resizing in order to not allow multiple
255			// resizes in a 200 milliseconds interval.
256			$( window ).on( 'resize.' + this.uniqueId + '.' + NS, function() {
257			
258				// Get the current width and height of the window
259				var newWindowWidth = $( window ).width(),
260					newWindowHeight = $( window ).height();
261				
262				// If the resize is not allowed yet or if the window size hasn't changed (this needs to be verified
263				// because in IE8 and lower the resize event is triggered whenever an element from the page changes
264				// its size) return early.
265				if ( that.allowResize === false ||
266					( that.previousWindowWidth === newWindowWidth && that.previousWindowHeight === newWindowHeight ) ) {
267					return;
268				}
269				
270				// Assign the new values for the window width and height
271				that.previousWindowWidth = newWindowWidth;
272				that.previousWindowHeight = newWindowHeight;
273			
274				that.allowResize = false;
275
276				setTimeout(function() {
vendor: 10,009 bytes, lines 276-522
276
277					that.resize();
278					that.allowResize = true;
279				}, 200 );
280			});
281
282			// Resize the slider when the 'update' method is called.
283			this.on( 'update.' + NS, function() {
284
285				// Reset the previous slide width
286				that.previousSlideWidth = 0;
287
288				// Some updates might require a resize
289				that.resize();
290			});
291
292			this.update();
293
294			// add the 'sp-selected' class to the initially selected slide
295			this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected' );
296
297			// Fire the 'init' event
298			this.trigger({ type: 'init' });
299			if ( $.isFunction( this.settings.init ) ) {
300				this.settings.init.call( this, { type: 'init' });
301			}
302		},
303
304		// Update the slider by checking for setting changes and for slides
305		// that weren't initialized yet.
306		update: function() {
307			var that = this;
308
309			// Check the current slider orientation and reset CSS that might have been
310			// added for a different orientation, since the orientation can be changed
311			// at runtime.
312			if ( this.settings.orientation === 'horizontal' ) {
313				this.$slider.removeClass( 'sp-vertical' ).addClass( 'sp-horizontal' );
314				this.$slider.css({ 'height': '', 'max-height': '' });
315				this.$slides.find( '.sp-slide' ).css( 'top', '' );
316			} else if ( this.settings.orientation === 'vertical' ) {
317				this.$slider.removeClass( 'sp-horizontal' ).addClass( 'sp-vertical' );
318				this.$slides.find( '.sp-slide' ).css( 'left', '' );
319			}
320
321			if ( this.settings.rightToLeft === true ) {
322				this.$slider.addClass( 'sp-rtl' );
323			} else {
324				this.$slider.removeClass( 'sp-rtl' );
325			}
326
327			this.positionProperty = this.settings.orientation === 'horizontal' ? 'left' : 'top';
328			this.sizeProperty = this.settings.orientation === 'horizontal' ? 'width' : 'height';
329
330			// Reset the 'gotoSlide' method
331			this.gotoSlide = this.originalGotoSlide;
332
333			// Loop through the array of SliderProSlide objects and if a stored slide is found
334			// which is not in the DOM anymore, destroy that slide.
335			for ( var i = this.slides.length - 1; i >= 0; i-- ) {
336				if ( this.$slider.find( '.sp-slide[data-index="' + i + '"]' ).length === 0 ) {
337					var slide = this.slides[ i ];
338
339					slide.off( 'imagesLoaded.' + NS );
340					slide.destroy();
341					this.slides.splice( i, 1 );
342				}
343			}
344
345			this.slidesOrder.length = 0;
346
347			// Loop through the list of slides and initialize newly added slides if any,
348			// and reset the index of each slide.
349			this.$slider.find( '.sp-slide' ).each(function( index ) {
350				var $slide = $( this );
351
352				if ( typeof $slide.attr( 'data-init' ) === 'undefined' ) {
353					that._createSlide( index, $slide );
354				} else {
355					that.slides[ index ].setIndex( index );
356				}
357
358				that.slidesOrder.push( index );
359			});
360
361			// Calculate the position/index of the middle slide
362			this.middleSlidePosition = parseInt( ( that.slidesOrder.length - 1 ) / 2, 10 );
363
364			// Arrange the slides in a loop
365			if ( this.settings.loop === true ) {
366				this._updateSlidesOrder();
367			}
368
369			// Fire the 'update' event
370			this.trigger({ type: 'update' });
371			if ( $.isFunction( this.settings.update ) ) {
372				this.settings.update.call( this, { type: 'update' } );
373			}
374		},
375
376		// Create a SliderProSlide instance for the slide passed as a jQuery element
377		_createSlide: function( index, element ) {
378			var that = this,
379				slide = new SliderProSlide( $( element ), index, this.settings );
380
381			this.slides.splice( index, 0, slide );
382
383			slide.on( 'imagesLoaded.' + NS, function( event ) {
384				if ( that.settings.autoSlideSize === true ) {
385					if ( that.$slides.hasClass( 'sp-animated' ) === false ) {
386						that._resetSlidesPosition();
387					}
388
389					that._calculateSlidesSize();
390				}
391
392				if ( that.settings.autoHeight === true && event.index === that.selectedSlideIndex ) {
393					that._resizeHeightTo( slide.getSize().height);
394				}
395			});
396		},
397
398		// Arrange the slide elements in a loop inside the 'slidesOrder' array
399		_updateSlidesOrder: function() {
400			var	slicedItems,
401				i,
402
403				// Calculate the distance between the selected element and the middle position
404				distance = $.inArray( this.selectedSlideIndex, this.slidesOrder ) - this.middleSlidePosition;
405
406			// If the distance is negative it means that the selected slider is before the middle position, so
407			// slides from the end of the array will be added at the beginning, in order to shift the selected slide
408			// forward.
409			// 
410			// If the distance is positive, slides from the beginning of the array will be added at the end.
411			if ( distance < 0 ) {
412				slicedItems = this.slidesOrder.splice( distance, Math.abs( distance ) );
413
414				for ( i = slicedItems.length - 1; i >= 0; i-- ) {
415					this.slidesOrder.unshift( slicedItems[ i ] );
416				}
417			} else if ( distance > 0 ) {
418				slicedItems = this.slidesOrder.splice( 0, distance );
419
420				for ( i = 0; i <= slicedItems.length - 1; i++ ) {
421					this.slidesOrder.push( slicedItems[ i ] );
422				}
423			}
424		},
425
426		// Set the left/top position of the slides based on their position in the 'slidesOrder' array
427		_updateSlidesPosition: function() {
428			var selectedSlidePixelPosition = parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ),
429				slide,
430				$slideElement,
431				slideIndex,
432				previousPosition = selectedSlidePixelPosition,
433				directionMultiplier,
434				slideSize;
435			
436			if ( this.settings.autoSlideSize === true ) {
437				if ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) {
438					for ( slideIndex = this.middleSlidePosition; slideIndex >= 0; slideIndex-- ) {
439						slide = this.getSlideAt( this.slidesOrder[ slideIndex ] );
440						$slideElement = slide.$slide;
441						$slideElement.css( this.positionProperty, previousPosition );
442						previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ) + slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance;
443					}
444
445					previousPosition = selectedSlidePixelPosition;
446
447					for ( slideIndex = this.middleSlidePosition + 1; slideIndex < this.slidesOrder.length; slideIndex++ ) {
448						slide = this.getSlideAt( this.slidesOrder[ slideIndex ] );
449						$slideElement = slide.$slide;
450						$slideElement.css( this.positionProperty, previousPosition - ( slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance ) );
451						previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 );
452					}
453				} else {
454					for ( slideIndex = this.middleSlidePosition - 1; slideIndex >= 0; slideIndex-- ) {
455						slide = this.getSlideAt( this.slidesOrder[ slideIndex ] );
456						$slideElement = slide.$slide;
457						$slideElement.css( this.positionProperty, previousPosition - ( slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance ) );
458						previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 );
459					}
460
461					previousPosition = selectedSlidePixelPosition;
462
463					for ( slideIndex = this.middleSlidePosition; slideIndex < this.slidesOrder.length; slideIndex++ ) {
464						slide = this.getSlideAt( this.slidesOrder[ slideIndex ] );
465						$slideElement = slide.$slide;
466						$slideElement.css( this.positionProperty, previousPosition );
467						previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ) + slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance;
468					}
469				}
470			} else {
471				directionMultiplier = ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) ? -1 : 1;
472				slideSize = ( this.settings.orientation === 'horizontal' ) ? this.slideWidth : this.slideHeight;
473
474				for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) {
475					$slideElement = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ slideIndex ] );
476					$slideElement.css( this.positionProperty, selectedSlidePixelPosition + directionMultiplier * ( slideIndex - this.middleSlidePosition  ) * ( slideSize + this.settings.slideDistance ) );
477				}
478			}
479		},
480
481		// Set the left/top position of the slides based on their position in the 'slidesOrder' array,
482		// and also set the position of the slides container.
483		_resetSlidesPosition: function() {
484			var previousPosition = 0,
485				slide,
486				$slideElement,
487				slideIndex,
488				selectedSlideSize,
489				directionMultiplier,
490				slideSize;
491
492			if ( this.settings.autoSlideSize === true ) {
493				if ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) {
494					for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) {
495						slide = this.getSlideAt( this.slidesOrder[ slideIndex ] );
496						$slideElement = slide.$slide;
497						$slideElement.css( this.positionProperty, previousPosition - ( slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance ) );
498						previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 );
499					}
500				} else {
501					for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) {
502						slide = this.getSlideAt( this.slidesOrder[ slideIndex ] );
503						$slideElement = slide.$slide;
504						$slideElement.css( this.positionProperty, previousPosition );
505						previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ) + slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance;
506					}
507				}
508
509				selectedSlideSize = this.getSlideAt( this.selectedSlideIndex ).getSize()[ this.sizeProperty ];
510			} else {
511				directionMultiplier = ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) === true ? -1 : 1;
512				slideSize = ( this.settings.orientation === 'horizontal' ) ? this.slideWidth : this.slideHeight;
513 
514				for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) {
515					$slideElement = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ slideIndex ] );
516					$slideElement.css( this.positionProperty, directionMultiplier * slideIndex * ( slideSize + this.settings.slideDistance ) );
517				}
518
519				selectedSlideSize = slideSize;
520			}
521
522			var selectedSlideOffset = this.settings.centerSelectedSlide === true 
vendor: 28,297 bytes, lines 522-1326
522? Math.round( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10 ) - selectedSlideSize ) / 2 ) : 0,
523				newSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + selectedSlideOffset;
524			
525			this._moveTo( newSlidesPosition, true );
526		},
527
528		// Calculate the total size of the slides and the average size of a single slide
529		_calculateSlidesSize: function() {
530			if ( this.settings.autoSlideSize === true ) {
531				var firstSlide = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ 0 ] ),
532					firstSlidePosition = parseInt( firstSlide.css( this.positionProperty ), 10 ),
533					lastSlide = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ this.slidesOrder.length - 1 ] ),
534					lastSlidePosition = parseInt( lastSlide.css( this.positionProperty ), 10 ) + ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ? -1 : 1 ) * parseInt( lastSlide.css( this.sizeProperty ), 10 );
535				
536				this.slidesSize = Math.abs( lastSlidePosition - firstSlidePosition );
537				this.averageSlideSize = Math.round( this.slidesSize / this.slides.length );
538			} else {
539				this.slidesSize = ( ( this.settings.orientation === 'horizontal' ? this.slideWidth : this.slideHeight ) + this.settings.slideDistance ) * this.slides.length - this.settings.slideDistance;
540				this.averageSlideSize = this.settings.orientation === 'horizontal' ? this.slideWidth : this.slideHeight;
541			}
542		},
543
544		// Called when the slider needs to resize
545		resize: function() {
546			var that = this;
547
548			// Check if the current window width is bigger than the biggest breakpoint
549			// and if necessary reset the properties to the original settings.
550			// 
551			// If the window width is smaller than a certain breakpoint, apply the settings specified
552			// for that breakpoint but only after merging them with the original settings
553			// in order to make sure that only the specified settings for the breakpoint are applied
554			if ( this.settings.breakpoints !== null && this.breakpoints.length > 0 ) {
555				if ( $( window ).width() > this.breakpoints[ this.breakpoints.length - 1 ].size && this.currentBreakpoint !== -1 ) {
556					this.currentBreakpoint = -1;
557					this._setProperties( this.originalSettings, false );
558				} else {
559					for ( var i = 0, n = this.breakpoints.length; i < n; i++ ) {
560						if ( $( window ).width() <= this.breakpoints[ i ].size ) {
561							if ( this.currentBreakpoint !== this.breakpoints[ i ].size ) {
562								var eventObject = { type: 'breakpointReach', size: this.breakpoints[ i ].size, settings: this.breakpoints[ i ].properties };
563								this.trigger( eventObject );
564								if ( $.isFunction( this.settings.breakpointReach ) )
565									this.settings.breakpointReach.call( this, eventObject );
566
567								this.currentBreakpoint = this.breakpoints[ i ].size;
568								var settings = $.extend( {}, this.originalSettings, this.breakpoints[ i ].properties );
569								this._setProperties( settings, false );
570								
571								return;
572							}
573
574							break;
575						}
576					}
577				}
578			}
579
580			// Set the width of the main slider container based on whether or not the slider is responsive,
581			// full width or full size
582			if ( this.settings.responsive === true ) {
583				if ( ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) &&
584					( this.settings.visibleSize === 'auto' || this.settings.visibleSize !== 'auto' && this.settings.orientation === 'vertical' )
585				) {
586					this.$slider.css( 'margin', 0 );
587					this.$slider.css({ 'width': $( window ).width(), 'max-width': '', 'marginLeft': - this.$slider.offset().left });
588				} else {
589					this.$slider.css({ 'width': '100%', 'max-width': this.settings.width, 'marginLeft': '' });
590				}
591			} else {
592				this.$slider.css({ 'width': this.settings.width });
593			}
594
595			// Calculate the aspect ratio of the slider
596			if ( this.settings.aspectRatio === -1 ) {
597				this.settings.aspectRatio = this.settings.width / this.settings.height;
598			}
599			
600			// Initially set the slide width to the size of the slider.
601			// Later, this will be set to less if there are multiple visible slides.
602			this.slideWidth = this.$slider.width();
603
604			// Set the height to the same size as the browser window if the slider is set to be 'fullWindow',
605			// or calculate the height based on the width and the aspect ratio.
606			if ( this.settings.forceSize === 'fullWindow' ) {
607				this.slideHeight = $( window ).height();
608			} else {
609				this.slideHeight = isNaN( this.settings.aspectRatio ) ? this.settings.height : this.slideWidth / this.settings.aspectRatio;
610			}
611
612			// Resize the slider only if the size of the slider has changed
613			// If it hasn't, return.
614			if ( this.previousSlideWidth !== this.slideWidth ||
615				this.previousSlideHeight !== this.slideHeight ||
616				this.settings.visibleSize !== 'auto' ||
617				this.$slider.outerWidth() > this.$slider.parent().width() ||
618				this.$slider.width() !== this.$slidesMask.width()
619			) {
620				this.previousSlideWidth = this.slideWidth;
621				this.previousSlideHeight = this.slideHeight;
622			} else {
623				return;
624			}
625
626			this._resizeSlides();
627
628			// Set the initial size of the mask container to the size of an individual slide
629			this.$slidesMask.css({ 'width': this.slideWidth, 'height': this.slideHeight });
630
631			// Adjust the height if it's set to 'auto'
632			if ( this.settings.autoHeight === true ) {
633
634				// Delay the resizing of the height to allow for other resize handlers
635				// to execute first before calculating the final height of the slide
636				setTimeout( function() {
637					that._resizeHeight();
638				}, 1 );
639			} else {
640				this.$slidesMask.css( this.vendorPrefix + 'transition', '' );
641			}
642
643			// The 'visibleSize' option can be set to fixed or percentage size to make more slides
644			// visible at a time.
645			// By default it's set to 'auto'.
646			if ( this.settings.visibleSize !== 'auto' ) {
647				if ( this.settings.orientation === 'horizontal' ) {
648
649					// If the size is forced to full width or full window, the 'visibleSize' option will be
650					// ignored and the slider will become as wide as the browser window.
651					if ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) {
652						this.$slider.css( 'margin', 0 );
653						this.$slider.css({ 'width': $( window ).width(), 'max-width': '', 'marginLeft': - this.$slider.offset().left });
654					} else {
655						this.$slider.css({ 'width': this.settings.visibleSize, 'max-width': '100%', 'marginLeft': 0 });
656					}
657					
658					this.$slidesMask.css( 'width', this.$slider.width() );
659				} else {
660
661					// If the size is forced to full window, the 'visibleSize' option will be
662					// ignored and the slider will become as high as the browser window.
663					if ( this.settings.forceSize === 'fullWindow' ) {
664						this.$slider.css({ 'height': $( window ).height(), 'max-height': '' });
665					} else {
666						this.$slider.css({ 'height': this.settings.visibleSize, 'max-height': '100%' });
667					}
668
669					this.$slidesMask.css( 'height', this.$slider.height() );
670				}
671			}
672
673			this._resetSlidesPosition();
674			this._calculateSlidesSize();
675
676			// Fire the 'sliderResize' event
677			this.trigger({ type: 'sliderResize' });
678			if ( $.isFunction( this.settings.sliderResize ) ) {
679				this.settings.sliderResize.call( this, { type: 'sliderResize' });
680			}
681		},
682
683		// Resize each individual slide
684		_resizeSlides: function() {
685			var slideWidth = this.slideWidth,
686				slideHeight = this.slideHeight;
687
688			if ( this.settings.autoSlideSize === true ) {
689				if ( this.settings.orientation === 'horizontal' ) {
690					slideWidth = 'auto';
691				} else if ( this.settings.orientation === 'vertical' ) {
692					slideHeight = 'auto';
693				}
694			} else if ( this.settings.autoHeight === true ) {
695				slideHeight = 'auto';
696			}
697
698			// Loop through the existing slides and reset their size.
699			$.each( this.slides, function( index, element ) {
700				element.setSize( slideWidth, slideHeight );
701			});
702		},
703
704		// Resize the height of the slider to the height of the selected slide.
705		// It's used when the 'autoHeight' option is set to 'true'.
706		_resizeHeight: function() {
707			var that = this,
708				selectedSlide = this.getSlideAt( this.selectedSlideIndex );
709
710			this._resizeHeightTo( selectedSlide.getSize().height );
711		},
712
713		// Open the slide at the specified index
714		gotoSlide: function( index ) {
715			if ( index === this.selectedSlideIndex || typeof this.slides[ index ] === 'undefined' ) {
716				return;
717			}
718
719			var that = this;
720
721			this.previousSlideIndex = this.selectedSlideIndex;
722			this.selectedSlideIndex = index;
723
724			// Re-assign the 'sp-selected' class to the currently selected slide
725			this.$slides.find( '.sp-selected' ).removeClass( 'sp-selected' );
726			this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected' );
727
728			// If the slider is loopable reorder the slides to have the selected slide in the middle
729			// and update the slides' position.
730			if ( this.settings.loop === true ) {
731				this._updateSlidesOrder();
732				this._updateSlidesPosition();
733			}
734
735			// Adjust the height of the slider
736			if ( this.settings.autoHeight === true ) {
737				this._resizeHeight();
738			}
739
740			var selectedSlideOffset = this.settings.centerSelectedSlide === true ? Math.round( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10 ) - this.getSlideAt( this.selectedSlideIndex ).getSize()[ this.sizeProperty ] ) / 2 ) : 0,
741				newSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + selectedSlideOffset;
742
743			// Move the slides container to the new position
744			this._moveTo( newSlidesPosition, false, function() {
745				that._resetSlidesPosition();
746
747				// Fire the 'gotoSlideComplete' event
748				that.trigger({ type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex });
749				if ( $.isFunction( that.settings.gotoSlideComplete ) ) {
750					that.settings.gotoSlideComplete.call( that, { type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex } );
751				}
752			});
753
754			// Fire the 'gotoSlide' event
755			this.trigger({ type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex });
756			if ( $.isFunction( this.settings.gotoSlide ) ) {
757				this.settings.gotoSlide.call( this, { type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex } );
758			}
759		},
760
761		// Open the next slide
762		nextSlide: function() {
763			var index = ( this.selectedSlideIndex >= this.getTotalSlides() - 1 ) ? 0 : ( this.selectedSlideIndex + 1 );
764			this.gotoSlide( index );
765		},
766
767		// Open the previous slide
768		previousSlide: function() {
769			var index = this.selectedSlideIndex <= 0 ? ( this.getTotalSlides() - 1 ) : ( this.selectedSlideIndex - 1 );
770			this.gotoSlide( index );
771		},
772
773		// Move the slides container to the specified position.
774		// The movement can be instant or animated.
775		_moveTo: function( position, instant, callback ) {
776			var that = this,
777				css = {};
778
779			if ( position === this.slidesPosition ) {
780				return;
781			}
782			
783			this.slidesPosition = position;
784			
785			if ( ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) && this.isIE === false ) {
786				var transition,
787					left = this.settings.orientation === 'horizontal' ? position : 0,
788					top = this.settings.orientation === 'horizontal' ? 0 : position;
789
790				if ( this.supportedAnimation === 'css-3d' ) {
791					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
792				} else {
793					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
794				}
795
796				if ( typeof instant !== 'undefined' && instant === true ) {
797					transition = '';
798				} else {
799					this.$slides.addClass( 'sp-animated' );
800					transition = this.vendorPrefix + 'transform ' + this.settings.slideAnimationDuration / 1000 + 's';
801
802					this.$slides.on( this.transitionEvent, function( event ) {
803						if ( event.target !== event.currentTarget ) {
804							return;
805						}
806
807						that.$slides.off( that.transitionEvent );
808						that.$slides.removeClass( 'sp-animated' );
809						
810						if ( typeof callback === 'function' ) {
811							callback();
812						}
813					});
814				}
815
816				css[ this.vendorPrefix + 'transition' ] = transition;
817
818				this.$slides.css( css );
819			} else {
820				css[ 'margin-' + this.positionProperty ] = position;
821
822				if ( typeof instant !== 'undefined' && instant === true ) {
823					this.$slides.css( css );
824				} else {
825					this.$slides.addClass( 'sp-animated' );
826					this.$slides.animate( css, this.settings.slideAnimationDuration, function() {
827						that.$slides.removeClass( 'sp-animated' );
828
829						if ( typeof callback === 'function' ) {
830							callback();
831						}
832					});
833				}
834			}
835		},
836
837		// Stop the movement of the slides
838		_stopMovement: function() {
839			var css = {};
840
841			if ( ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) && this.isIE === false) {
842
843				// Get the current position of the slides by parsing the 'transform' property
844				var	matrixString = this.$slides.css( this.vendorPrefix + 'transform' ),
845					matrixType = matrixString.indexOf( 'matrix3d' ) !== -1 ? 'matrix3d' : 'matrix',
846					matrixArray = matrixString.replace( matrixType, '' ).match( /-?[0-9\.]+/g ),
847					left = matrixType === 'matrix3d' ? parseInt( matrixArray[ 12 ], 10 ) : parseInt( matrixArray[ 4 ], 10 ),
848					top = matrixType === 'matrix3d' ? parseInt( matrixArray[ 13 ], 10 ) : parseInt( matrixArray[ 5 ], 10 );
849					
850				// Set the transform property to the value that the transform had when the function was called
851				if ( this.supportedAnimation === 'css-3d' ) {
852					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
853				} else {
854					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
855				}
856
857				css[ this.vendorPrefix + 'transition' ] = '';
858
859				this.$slides.css( css );
860				this.$slides.off( this.transitionEvent );
861				this.slidesPosition = this.settings.orientation === 'horizontal' ? left : top;
862			} else {
863				this.$slides.stop();
864				this.slidesPosition = parseInt( this.$slides.css( 'margin-' + this.positionProperty ), 10 );
865			}
866
867			this.$slides.removeClass( 'sp-animated' );
868		},
869
870		// Resize the height of the slider to the specified value
871		_resizeHeightTo: function( height ) {
872			var that = this,
873				css = { 'height': height };
874
875			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
876				css[ this.vendorPrefix + 'transition' ] = 'height ' + this.settings.heightAnimationDuration / 1000 + 's';
877
878				this.$slidesMask.off( this.transitionEvent );
879				this.$slidesMask.on( this.transitionEvent, function( event ) {
880					if ( event.target !== event.currentTarget ) {
881						return;
882					}
883
884					that.$slidesMask.off( that.transitionEvent );
885
886					// Fire the 'resizeHeightComplete' event
887					that.trigger({ type: 'resizeHeightComplete' });
888					if ( $.isFunction( that.settings.resizeHeightComplete ) ) {
889						that.settings.resizeHeightComplete.call( that, { type: 'resizeHeightComplete' } );
890					}
891				});
892
893				this.$slidesMask.css( css );
894			} else {
895				this.$slidesMask.stop().animate( css, this.settings.heightAnimationDuration, function( event ) {
896					// Fire the 'resizeHeightComplete' event
897					that.trigger({ type: 'resizeHeightComplete' });
898					if ( $.isFunction( that.settings.resizeHeightComplete ) ) {
899						that.settings.resizeHeightComplete.call( that, { type: 'resizeHeightComplete' } );
900					}
901				});
902			}
903		},
904
905		// Destroy the slider instance
906		destroy: function() {
907			// Remove the stored reference to this instance
908			this.$slider.removeData( 'sliderPro' );
909			
910			// Clean the CSS
911			this.$slider.removeAttr( 'style' );
912			this.$slides.removeAttr( 'style' );
913
914			// Remove event listeners
915			this.off( 'update.' + NS );
916			$( window ).off( 'resize.' + this.uniqueId + '.' + NS );
917
918			// Destroy modules
919			var modules = $.SliderPro.modules;
920
921			if ( typeof modules !== 'undefined' ) {
922				for ( var i = 0; i < modules.length; i++ ) {
923					if ( typeof this[ 'destroy' + modules[ i ] ] !== 'undefined' ) {
924						this[ 'destroy' + modules[ i ] ]();
925					}
926				}
927			}
928
929			// Destroy all slides
930			$.each( this.slides, function( index, element ) {
931				element.destroy();
932			});
933
934			this.slides.length = 0;
935
936			// Move the slides to their initial position in the DOM and 
937			// remove the container elements created dynamically.
938			this.$slides.prependTo( this.$slider );
939			this.$slidesContainer.remove();
940		},
941
942		// Set properties on runtime
943		_setProperties: function( properties, store ) {
944			// Parse the properties passed as an object
945			for ( var prop in properties ) {
946				this.settings[ prop ] = properties[ prop ];
947
948				// Alter the original settings as well unless 'false' is passed to the 'store' parameter
949				if ( store !== false ) {
950					this.originalSettings[ prop ] = properties[ prop ];
951				}
952			}
953
954			this.update();
955		},
956
957		// Attach an event handler to the slider
958		on: function( type, callback ) {
959			return this.$slider.on( type, callback );
960		},
961
962		// Detach an event handler
963		off: function( type ) {
964			return this.$slider.off( type );
965		},
966
967		// Trigger an event on the slider
968		trigger: function( data ) {
969			return this.$slider.triggerHandler( data );
970		},
971
972		// Return the slide at the specified index
973		getSlideAt: function( index ) {
974			return this.slides[ index ];
975		},
976
977		// Return the index of the currently opened slide
978		getSelectedSlide: function() {
979			return this.selectedSlideIndex;
980		},
981
982		// Return the total amount of slides
983		getTotalSlides: function() {
984			return this.slides.length;
985		},
986
987		// The default options of the slider
988		defaults: {
989			// Width of the slide
990			width: 500,
991
992			// Height of the slide
993			height: 300,
994
995			// Indicates if the slider is responsive
996			responsive: true,
997
998			// The aspect ratio of the slider (width/height)
999			aspectRatio: -1,
1000
1001			// The scale mode for images (cover, contain, exact and none)
1002			imageScaleMode: 'cover',
1003
1004			// Indicates if the image will be centered
1005			centerImage: true,
1006
1007			// Indicates if the image can be scaled up more than its original size
1008			allowScaleUp: true,
1009
1010			// Indicates if height of the slider will be adjusted to the
1011			// height of the selected slide
1012			autoHeight: false,
1013
1014			// Will maintain all the slides at the same height, but will allow the width
1015			// of the slides to be variable if the orientation of the slides is horizontal
1016			// and vice-versa if the orientation is vertical
1017			autoSlideSize: false,
1018
1019			// Indicates the initially selected slide
1020			startSlide: 0,
1021
1022			// Indicates if the slides will be shuffled
1023			shuffle: false,
1024
1025			// Indicates whether the slides will be arranged horizontally
1026			// or vertically. Can be set to 'horizontal' or 'vertical'.
1027			orientation: 'horizontal',
1028
1029			// Indicates if the size of the slider will be forced to 'fullWidth' or 'fullWindow'
1030			forceSize: 'none',
1031
1032			// Indicates if the slider will be loopable
1033			loop: true,
1034
1035			// The distance between slides
1036			slideDistance: 10,
1037
1038			// The duration of the slide animation
1039			slideAnimationDuration: 700,
1040
1041			// The duration of the height animation
1042			heightAnimationDuration: 700,
1043
1044			// Sets the size of the visible area, allowing the increase of it in order
1045			// to make more slides visible.
1046			// By default, only the selected slide will be visible. 
1047			visibleSize: 'auto',
1048
1049			// Indicates whether the selected slide will be in the center of the slider, when there
1050			// are more slides visible at a time. If set to false, the selected slide will be in the
1051			// left side of the slider.
1052			centerSelectedSlide: true,
1053
1054			// Indicates if the direction of the slider will be from right to left,
1055			// instead of the default left to right
1056			rightToLeft: false,
1057
1058			// Breakpoints for allowing the slider's options to be changed
1059			// based on the size of the window.
1060			breakpoints: null,
1061
1062			// Called when the slider is initialized
1063			init: function() {},
1064
1065			// Called when the slider is updates
1066			update: function() {},
1067
1068			// Called when the slider is resized
1069			sliderResize: function() {},
1070
1071			// Called when a new slide is selected
1072			gotoSlide: function() {},
1073
1074			// Called when the navigation to the newly selected slide is complete
1075			gotoSlideComplete: function() {},
1076
1077			// Called when the height animation of the slider is complete
1078			resizeHeightComplete: function() {},
1079
1080			// Called when a breakpoint is reached
1081			breakpointReach: function() {}
1082		}
1083	};
1084
1085	var SliderProSlide = function( slide, index, settings ) {
1086
1087		// Reference to the slide jQuery element
1088		this.$slide = slide;
1089
1090		// Reference to the main slide image
1091		this.$mainImage = null;
1092
1093		// Reference to the container that will hold the main image
1094		this.$imageContainer = null;
1095
1096		// Indicates whether the slide has a main image
1097		this.hasMainImage = false;
1098
1099		// Indicates whether the main image is loaded
1100		this.isMainImageLoaded = false;
1101
1102		// Indicates whether the main image is in the process of being loaded
1103		this.isMainImageLoading = false;
1104
1105		// Indicates whether the slide has any image. There could be other images (i.e., in layers)
1106		// besides the main slide image.
1107		this.hasImages = false;
1108
1109		// Indicates if all the images in the slide are loaded
1110		this.areImagesLoaded = false;
1111
1112		// Indicates if the images inside the slide are in the process of being loaded
1113		this.areImagesLoading = false;
1114
1115		// The width and height of the slide
1116		this.width = 0;
1117		this.height = 0;
1118
1119		// Reference to the global settings of the slider
1120		this.settings = settings;
1121
1122		// Set the index of the slide
1123		this.setIndex( index );
1124
1125		// Initialize the slide
1126		this._init();
1127	};
1128
1129	SliderProSlide.prototype = {
1130
1131		// The starting point for the slide
1132		_init: function() {
1133			var that = this;
1134
1135			// Mark the slide as initialized
1136			this.$slide.attr( 'data-init', true );
1137
1138			// Get the main slide image if there is one
1139			this.$mainImage = this.$slide.find( '.sp-image' ).length !== 0 ? this.$slide.find( '.sp-image' ) : null;
1140
1141			// If there is a main slide image, create a container for it and add the image to this container.
1142			// The container will allow the isolation of the image from the rest of the slide's content. This is
1143			// helpful when you want to show some content below the image and not cover it.
1144			if ( this.$mainImage !== null ) {
1145				this.hasMainImage = true;
1146
1147				this.$imageContainer = $( '<div class="sp-image-container"></div>' ).prependTo( this.$slide );
1148
1149				if ( this.$mainImage.parent( 'a' ).length !== 0 ) {
1150					this.$mainImage.parent( 'a' ).appendTo( this.$imageContainer );
1151				} else {
1152					this.$mainImage.appendTo( this.$imageContainer );
1153				}
1154			}
1155
1156			this.hasImages = this.$slide.find( 'img' ).length !== 0 ? true : false;
1157		},
1158
1159		// Set the size of the slide
1160		setSize: function( width, height ) {
1161			var that = this;
1162
1163			this.width = width;
1164			this.height = height;
1165
1166			this.$slide.css({
1167				'width': this.width,
1168				'height': this.height
1169			});
1170
1171			if ( this.hasMainImage === true ) {
1172
1173				// Initially set the width and height of the container to the width and height
1174				// specified in the settings. This will prevent content overflowing if the width or height
1175				// are 'auto'. The 'auto' value will be passed only after the image is loaded.
1176				this.$imageContainer.css({
1177					'width': this.settings.width,
1178					'height': this.settings.height
1179				});
1180
1181				// Resize the main image if it's loaded. If the 'data-src' attribute is present it means
1182				// that the image will be lazy-loaded
1183				if ( typeof this.$mainImage.attr( 'data-src' ) === 'undefined' ) {
1184					this.resizeMainImage();
1185				}
1186			}
1187		},
1188
1189		// Get the size (width and height) of the slide
1190		getSize: function() {
1191			var that = this,
1192				size;
1193
1194			// Check if all images have loaded, and if they have, return the size, else, return
1195			// the original width and height of the slide
1196			if ( this.hasImages === true && this.areImagesLoaded === false && this.areImagesLoading === false ) {
1197				this.areImagesLoading = true;
1198				
1199				var status = SliderProUtils.checkImagesStatus( this.$slide );
1200
1201				if ( status !== 'complete' ) {
1202					SliderProUtils.checkImagesComplete( this.$slide, function() {
1203						that.areImagesLoaded = true;
1204						that.areImagesLoading = false;
1205						that.trigger({ type: 'imagesLoaded.' + NS, index: that.index });
1206					});
1207
1208					// if the image is not loaded yet, return the original width and height of the slider
1209					return {
1210						'width': this.settings.width,
1211						'height': this.settings.height
1212					};
1213				}
1214			}
1215
1216			size = this.calculateSize();
1217
1218			return {
1219				'width': size.width,
1220				'height': size.height
1221			};
1222		},
1223
1224		// Calculate the width and height of the slide by going
1225		// through all the child elements and measuring their 'bottom'
1226		// and 'right' properties. The element with the biggest
1227		// 'right'/'bottom' property will determine the slide's
1228		// width/height.
1229		calculateSize: function() {
1230			var width = this.$slide.width(),
1231				height = this.$slide.height();
1232
1233			this.$slide.children().each(function( index, element ) {
1234				var child = $( element );
1235
1236				if ( child.is( ':hidden' ) === true ) {
1237					return;
1238				}
1239
1240				var	rect = element.getBoundingClientRect(),
1241					bottom = child.position().top + ( rect.bottom - rect.top ),
1242					right = child.position().left + ( rect.right - rect.left );
1243
1244				if ( bottom > height ) {
1245					height = bottom;
1246				}
1247
1248				if ( right > width ) {
1249					width = right;
1250				}
1251			});
1252
1253			return {
1254				width: width,
1255				height: height
1256			};
1257		},
1258
1259		// Resize the main image.
1260		// 
1261		// Call this when the slide resizes or when the main image has changed to a different image.
1262		resizeMainImage: function( isNewImage ) {
1263			var that = this;
1264
1265			// If the main image has changed, reset the 'flags'
1266			if ( isNewImage === true ) {
1267				this.isMainImageLoaded = false;
1268				this.isMainImageLoading = false;
1269			}
1270
1271			// If the image was not loaded yet and it's not in the process of being loaded, load it
1272			if ( this.isMainImageLoaded === false && this.isMainImageLoading === false ) {
1273				this.isMainImageLoading = true;
1274
1275				SliderProUtils.checkImagesComplete( this.$mainImage, function() {
1276					that.isMainImageLoaded = true;
1277					that.isMainImageLoading = false;
1278					that.resizeMainImage();
1279					that.trigger({ type: 'imagesLoaded.' + NS, index: that.index });
1280				});
1281
1282				return;
1283			}
1284
1285			// Set the size of the image container element to the proper 'width' and 'height'
1286			// values, as they were calculated. Previous values were the 'width' and 'height'
1287			// from the settings. 
1288			this.$imageContainer.css({
1289				'width': this.width,
1290				'height': this.height
1291			});
1292
1293			if ( this.settings.allowScaleUp === false ) {
1294				// reset the image to its natural size
1295				this.$mainImage.css({ 'width': '', 'height': '', 'maxWidth': '', 'maxHeight': '' });
1296
1297				// set the boundaries
1298				this.$mainImage.css({ 'maxWidth': this.$mainImage.width(), 'maxHeight': this.$mainImage.height() });
1299			}
1300
1301			// After the main image has loaded, resize it
1302			if ( this.settings.autoSlideSize === true ) {
1303				if ( this.settings.orientation === 'horizontal' ) {
1304					this.$mainImage.css({ width: 'auto', height: '100%' });
1305
1306					// resize the slide's width to a fixed value instead of 'auto', to
1307					// prevent incorrect sizing caused by links added to the main image
1308					this.$slide.css( 'width', this.$mainImage.width() );
1309				} else if ( this.settings.orientation === 'vertical' ) {
1310					this.$mainImage.css({ width: '100%', height: 'auto' });
1311
1312					// resize the slide's height to a fixed value instead of 'auto', to
1313					// prevent incorrect sizing caused by links added to the main image
1314					this.$slide.css( 'height', this.$mainImage.height() );
1315				}
1316			} else if ( this.settings.autoHeight === true ) {
1317				this.$mainImage.css({ width: '100%', height: 'auto' });
1318			} else {
1319				if ( this.settings.imageScaleMode === 'cover' ) {
1320					if ( this.$mainImage.width() / this.$mainImage.height() <= this.width / this.height ) {
1321						this.$mainImage.css({ width: '100%', height: 'auto' });
1322					} else {
1323						this.$mainImage.css({ width: 'auto', height: '100%' });
1324					}
1325				} else if ( this.settings.imageScaleMode === 'contain' ) {
1326					if ( this.$mainImage.width() / this.$mainImage.height() >= this.width / this.height
vendor: 82,270 bytes, lines 1326-3744
1326 ) {
1327						this.$mainImage.css({ width: '100%', height: 'auto' });
1328					} else {
1329						this.$mainImage.css({ width: 'auto', height: '100%' });
1330					}
1331				} else if ( this.settings.imageScaleMode === 'exact' ) {
1332					this.$mainImage.css({ width: '100%', height: '100%' });
1333				}
1334
1335				if ( this.settings.centerImage === true ) {
1336					this.$mainImage.css({ 'marginLeft': ( this.$imageContainer.width() - this.$mainImage.width() ) * 0.5, 'marginTop': ( this.$imageContainer.height() - this.$mainImage.height() ) * 0.5 });
1337				}
1338			}
1339		},
1340
1341		// Destroy the slide
1342		destroy: function() {
1343			// Clean the slide element from attached styles and data
1344			this.$slide.removeAttr( 'style' );
1345			this.$slide.removeAttr( 'data-init' );
1346			this.$slide.removeAttr( 'data-index' );
1347			this.$slide.removeAttr( 'data-loaded' );
1348
1349			// If there is a main image, remove its container
1350			if ( this.hasMainImage === true ) {
1351				this.$slide.find( '.sp-image' )
1352					.removeAttr( 'style' )
1353					.appendTo( this.$slide );
1354
1355				this.$slide.find( '.sp-image-container' ).remove();
1356			}
1357		},
1358
1359		// Return the index of the slide
1360		getIndex: function() {
1361			return this.index;
1362		},
1363
1364		// Set the index of the slide
1365		setIndex: function( index ) {
1366			this.index = index;
1367			this.$slide.attr( 'data-index', this.index );
1368		},
1369
1370		// Attach an event handler to the slide
1371		on: function( type, callback ) {
1372			return this.$slide.on( type, callback );
1373		},
1374
1375		// Detach an event handler to the slide
1376		off: function( type ) {
1377			return this.$slide.off( type );
1378		},
1379
1380		// Trigger an event on the slide
1381		trigger: function( data ) {
1382			return this.$slide.triggerHandler( data );
1383		}
1384	};
1385
1386	window.SliderPro = SliderPro;
1387	window.SliderProSlide = SliderProSlide;
1388
1389	$.fn.sliderPro = function( options ) {
1390		var args = Array.prototype.slice.call( arguments, 1 );
1391
1392		return this.each(function() {
1393			// Instantiate the slider or alter it
1394			if ( typeof $( this ).data( 'sliderPro' ) === 'undefined' ) {
1395				var newInstance = new SliderPro( this, options );
1396
1397				// Store a reference to the instance created
1398				$( this ).data( 'sliderPro', newInstance );
1399			} else if ( typeof options !== 'undefined' ) {
1400				var	currentInstance = $( this ).data( 'sliderPro' );
1401
1402				// Check the type of argument passed
1403				if ( typeof currentInstance[ options ] === 'function' ) {
1404					currentInstance[ options ].apply( currentInstance, args );
1405				} else if ( typeof currentInstance.settings[ options ] !== 'undefined' ) {
1406					var obj = {};
1407					obj[ options ] = args[ 0 ];
1408					currentInstance._setProperties( obj );
1409				} else if ( typeof options === 'object' ) {
1410					currentInstance._setProperties( options );
1411				} else {
1412					$.error( options + ' does not exist in sliderPro.' );
1413				}
1414			}
1415		});
1416	};
1417
1418	// Contains useful utility functions
1419	var SliderProUtils = {
1420
1421		// Indicates what type of animations are supported in the current browser
1422		// Can be CSS 3D, CSS 2D or JavaScript
1423		supportedAnimation: null,
1424
1425		// Indicates the required vendor prefix for the current browser
1426		vendorPrefix: null,
1427
1428		// Indicates the name of the transition's complete event for the current browser
1429		transitionEvent: null,
1430
1431		// Indicates if the current browser is Internet Explorer (any version)
1432		isIE: null,
1433
1434		// Check whether CSS3 3D or 2D transforms are supported. If they aren't, use JavaScript animations
1435		getSupportedAnimation: function() {
1436			if ( this.supportedAnimation !== null ) {
1437				return this.supportedAnimation;
1438			}
1439
1440			var element = document.body || document.documentElement,
1441				elementStyle = element.style,
1442				isCSSTransitions = typeof elementStyle.transition !== 'undefined' ||
1443									typeof elementStyle.WebkitTransition !== 'undefined' ||
1444									typeof elementStyle.MozTransition !== 'undefined' ||
1445									typeof elementStyle.OTransition !== 'undefined';
1446
1447			if ( isCSSTransitions === true ) {
1448				var div = document.createElement( 'div' );
1449
1450				// Check if 3D transforms are supported
1451				if ( typeof div.style.WebkitPerspective !== 'undefined' || typeof div.style.perspective !== 'undefined' ) {
1452					this.supportedAnimation = 'css-3d';
1453				}
1454
1455				// Additional checks for Webkit
1456				if ( this.supportedAnimation === 'css-3d' && typeof div.styleWebkitPerspective !== 'undefined' ) {
1457					var style = document.createElement( 'style' );
1458					style.textContent = '@media (transform-3d),(-webkit-transform-3d){#test-3d{left:9px;position:absolute;height:5px;margin:0;padding:0;border:0;}}';
1459					document.getElementsByTagName( 'head' )[0].appendChild( style );
1460
1461					div.id = 'test-3d';
1462					document.body.appendChild( div );
1463
1464					if ( ! ( div.offsetLeft === 9 && div.offsetHeight === 5 ) ) {
1465						this.supportedAnimation = null;
1466					}
1467
1468					style.parentNode.removeChild( style );
1469					div.parentNode.removeChild( div );
1470				}
1471
1472				// If CSS 3D transforms are not supported, check if 2D transforms are supported
1473				if ( this.supportedAnimation === null && ( typeof div.style['-webkit-transform'] !== 'undefined' || typeof div.style.transform !== 'undefined' ) ) {
1474					this.supportedAnimation = 'css-2d';
1475				}
1476			} else {
1477				this.supportedAnimation = 'javascript';
1478			}
1479			
1480			return this.supportedAnimation;
1481		},
1482
1483		// Check what vendor prefix should be used in the current browser
1484		getVendorPrefix: function() {
1485			if ( this.vendorPrefix !== null ) {
1486				return this.vendorPrefix;
1487			}
1488
1489			var div = document.createElement( 'div' ),
1490				prefixes = [ 'Webkit', 'Moz', 'ms', 'O' ];
1491			
1492			if ( 'transform' in div.style ) {
1493				this.vendorPrefix = '';
1494				return this.vendorPrefix;
1495			}
1496			
1497			for ( var i = 0; i < prefixes.length; i++ ) {
1498				if ( ( prefixes[ i ] + 'Transform' ) in div.style ) {
1499					this.vendorPrefix = '-' + prefixes[ i ].toLowerCase() + '-';
1500					break;
1501				}
1502			}
1503			
1504			return this.vendorPrefix;
1505		},
1506
1507		// Check the name of the transition's complete event in the current browser
1508		getTransitionEvent: function() {
1509			if ( this.transitionEvent !== null ) {
1510				return this.transitionEvent;
1511			}
1512
1513			var div = document.createElement( 'div' ),
1514				transitions = {
1515					'transition': 'transitionend',
1516					'WebkitTransition': 'webkitTransitionEnd',
1517					'MozTransition': 'transitionend',
1518					'OTransition': 'oTransitionEnd'
1519				};
1520
1521			for ( var transition in transitions ) {
1522				if ( transition in div.style ) {
1523					this.transitionEvent = transitions[ transition ];
1524					break;
1525				}
1526			}
1527
1528			return this.transitionEvent;
1529		},
1530
1531		// If a single image is passed, check if it's loaded.
1532		// If a different element is passed, check if there are images
1533		// inside it, and check if these images are loaded.
1534		checkImagesComplete: function( target, callback ) {
1535			var that = this,
1536
1537				// Check the initial status of the image(s)
1538				status = this.checkImagesStatus( target );
1539
1540			// If there are loading images, wait for them to load.
1541			// If the images are loaded, call the callback function directly.
1542			if ( status === 'loading' ) {
1543				var checkImages = setInterval(function() {
1544					status = that.checkImagesStatus( target );
1545
1546					if ( status === 'complete' ) {
1547						clearInterval( checkImages );
1548
1549						if ( typeof callback === 'function' ) {
1550							callback();
1551						}
1552					}
1553				}, 100 );
1554			} else if ( typeof callback === 'function' ) {
1555				callback();
1556			}
1557
1558			return status;
1559		},
1560
1561		checkImagesStatus: function( target ) {
1562			var status = 'complete';
1563
1564			if ( target.is( 'img' ) && target[0].complete === false ) {
1565				status = 'loading';
1566			} else {
1567				target.find( 'img' ).each(function( index ) {
1568					var image = $( this )[0];
1569
1570					if ( image.complete === false ) {
1571						status = 'loading';
1572					}
1573				});
1574			}
1575
1576			return status;
1577		},
1578
1579		checkIE: function() {
1580			if ( this.isIE !== null ) {
1581				return this.isIE;
1582			}
1583
1584			var userAgent = window.navigator.userAgent,
1585				msie = userAgent.indexOf( 'MSIE' );
1586
1587			if ( userAgent.indexOf( 'MSIE' ) !== -1 || userAgent.match( /Trident.*rv\:11\./ ) ) {
1588				this.isIE = true;
1589			} else {
1590				this.isIE = false;
1591			}
1592
1593			return this.isIE;
1594		}
1595	};
1596
1597	window.SliderProUtils = SliderProUtils;
1598
1599})( window, jQuery );
1600
1601// Thumbnails module for Slider Pro.
1602// 
1603// Adds the possibility to create a thumbnail scroller, each thumbnail
1604// corresponding to a slide.
1605;(function( window, $ ) {
1606
1607	"use strict";
1608
1609	var NS = 'Thumbnails.' + $.SliderPro.namespace;
1610
1611	var Thumbnails = {
1612
1613		// Reference to the thumbnail scroller 
1614		$thumbnails: null,
1615
1616		// Reference to the container of the thumbnail scroller
1617		$thumbnailsContainer: null,
1618
1619		// List of Thumbnail objects
1620		thumbnails: null,
1621
1622		// Index of the selected thumbnail
1623		selectedThumbnailIndex: 0,
1624
1625		// Total size (width or height, depending on the orientation) of the thumbnails
1626		thumbnailsSize: 0,
1627
1628		// Size of the thumbnail's container
1629		thumbnailsContainerSize: 0,
1630
1631		// The position of the thumbnail scroller inside its container
1632		thumbnailsPosition: 0,
1633
1634		// Orientation of the thumbnails
1635		thumbnailsOrientation: null,
1636
1637		// Indicates the 'left' or 'top' position based on the orientation of the thumbnails
1638		thumbnailsPositionProperty: null,
1639
1640		// Indicates if there are thumbnails in the slider
1641		isThumbnailScroller: false,
1642
1643		initThumbnails: function() {
1644			var that = this;
1645
1646			this.thumbnails = [];
1647
1648			this.on( 'update.' + NS, $.proxy( this._thumbnailsOnUpdate, this ) );
1649			this.on( 'sliderResize.' + NS, $.proxy( this._thumbnailsOnResize, this ) );
1650			this.on( 'gotoSlide.' + NS, function( event ) {
1651				that._gotoThumbnail( event.index );
1652			});
1653		},
1654
1655		// Called when the slider is updated
1656		_thumbnailsOnUpdate: function() {
1657			var that = this;
1658
1659			if ( this.$slider.find( '.sp-thumbnail' ).length === 0 && this.thumbnails.length === 0 ) {
1660				this.isThumbnailScroller = false;
1661				return;
1662			}
1663
1664			this.isThumbnailScroller = true;
1665
1666			// Create the container of the thumbnail scroller, if it wasn't created yet
1667			if ( this.$thumbnailsContainer === null ) {
1668				this.$thumbnailsContainer = $( '<div class="sp-thumbnails-container"></div>' ).insertAfter( this.$slidesContainer );
1669			}
1670
1671			// If the thumbnails' main container doesn't exist, create it, and get a reference to it
1672			if ( this.$thumbnails === null ) {
1673				if ( this.$slider.find( '.sp-thumbnails' ).length !== 0 ) {
1674					this.$thumbnails = this.$slider.find( '.sp-thumbnails' ).appendTo( this.$thumbnailsContainer );
1675
1676					// Shuffle/randomize the thumbnails
1677					if ( this.settings.shuffle === true ) {
1678						var thumbnails = this.$thumbnails.find( '.sp-thumbnail' ),
1679							shuffledThumbnails = [];
1680
1681						// Reposition the thumbnails based on the order of the indexes in the
1682						// 'shuffledIndexes' array
1683						$.each( this.shuffledIndexes, function( index, element ) {
1684							var $thumbnail = $( thumbnails[ element ] );
1685
1686							if ( $thumbnail.parent( 'a' ).length !== 0 ) {
1687								$thumbnail = $thumbnail.parent( 'a' );
1688							}
1689
1690							shuffledThumbnails.push( $thumbnail );
1691						});
1692						
1693						// Append the sorted thumbnails to the thumbnail scroller
1694						this.$thumbnails.empty().append( shuffledThumbnails ) ;
1695					}
1696				} else {
1697					this.$thumbnails = $( '<div class="sp-thumbnails"></div>' ).appendTo( this.$thumbnailsContainer );
1698				}
1699			}
1700
1701			// Check if there are thumbnails inside the slides and move them in the thumbnails container
1702			this.$slides.find( '.sp-thumbnail' ).each( function( index ) {
1703				var $thumbnail = $( this ),
1704					thumbnailIndex = $thumbnail.parents( '.sp-slide' ).index(),
1705					lastThumbnailIndex = that.$thumbnails.find( '.sp-thumbnail' ).length - 1;
1706
1707				if ( $thumbnail.parent( 'a' ).length !== 0 ) {
1708					$thumbnail = $thumbnail.parent( 'a' );
1709				}
1710
1711				// If the index of the slide that contains the thumbnail is greater than the total number
1712				// of thumbnails from the thumbnails container, position the thumbnail at the end.
1713				// Otherwise, add the thumbnails at the corresponding position.
1714				if ( thumbnailIndex > lastThumbnailIndex ) {
1715					$thumbnail.appendTo( that.$thumbnails );
1716				} else {
1717					$thumbnail.insertBefore( that.$thumbnails.find( '.sp-thumbnail' ).eq( thumbnailIndex ) );
1718				}
1719			});
1720
1721			// Loop through the Thumbnail objects and if a corresponding element is not found in the DOM,
1722			// it means that the thumbnail might have been removed. In this case, destroy that Thumbnail instance.
1723			for ( var i = this.thumbnails.length - 1; i >= 0; i-- ) {
1724				if ( this.$thumbnails.find( '.sp-thumbnail[data-index="' + i + '"]' ).length === 0 ) {
1725					var thumbnail = this.thumbnails[ i ];
1726
1727					thumbnail.destroy();
1728					this.thumbnails.splice( i, 1 );
1729				}
1730			}
1731
1732			// Loop through the thumbnails and if there is any uninitialized thumbnail,
1733			// initialize it, else update the thumbnail's index.
1734			this.$thumbnails.find( '.sp-thumbnail' ).each(function( index ) {
1735				var $thumbnail = $( this );
1736
1737				if ( typeof $thumbnail.attr( 'data-init' ) === 'undefined' ) {
1738					that._createThumbnail( $thumbnail, index );
1739				} else {
1740					that.thumbnails[ index ].setIndex( index );
1741				}
1742			});
1743
1744			// Remove the previous class that corresponds to the position of the thumbnail scroller
1745			this.$thumbnailsContainer.removeClass( 'sp-top-thumbnails sp-bottom-thumbnails sp-left-thumbnails sp-right-thumbnails' );
1746
1747			// Check the position of the thumbnail scroller and assign it the appropriate class and styling
1748			if ( this.settings.thumbnailsPosition === 'top' ) {
1749				this.$thumbnailsContainer.addClass( 'sp-top-thumbnails' );
1750				this.thumbnailsOrientation = 'horizontal';
1751			} else if ( this.settings.thumbnailsPosition === 'bottom' ) {
1752				this.$thumbnailsContainer.addClass( 'sp-bottom-thumbnails' );
1753				this.thumbnailsOrientation = 'horizontal';
1754			} else if ( this.settings.thumbnailsPosition === 'left' ) {
1755				this.$thumbnailsContainer.addClass( 'sp-left-thumbnails' );
1756				this.thumbnailsOrientation = 'vertical';
1757			} else if ( this.settings.thumbnailsPosition === 'right' ) {
1758				this.$thumbnailsContainer.addClass( 'sp-right-thumbnails' );
1759				this.thumbnailsOrientation = 'vertical';
1760			}
1761
1762			// Check if the pointer needs to be created
1763			if ( this.settings.thumbnailPointer === true ) {
1764				this.$thumbnailsContainer.addClass( 'sp-has-pointer' );
1765			} else {
1766				this.$thumbnailsContainer.removeClass( 'sp-has-pointer' );
1767			}
1768
1769			// Mark the thumbnail that corresponds to the selected slide
1770			this.selectedThumbnailIndex = this.selectedSlideIndex;
1771			this.$thumbnails.find( '.sp-thumbnail-container' ).eq( this.selectedThumbnailIndex ).addClass( 'sp-selected-thumbnail' );
1772			
1773			// Calculate the total size of the thumbnails
1774			this.thumbnailsSize = 0;
1775
1776			$.each( this.thumbnails, function( index, thumbnail ) {
1777				thumbnail.setSize( that.settings.thumbnailWidth, that.settings.thumbnailHeight );
1778				that.thumbnailsSize += that.thumbnailsOrientation === 'horizontal' ? thumbnail.getSize().width : thumbnail.getSize().height;
1779			});
1780
1781			// Set the size of the thumbnails
1782			if ( this.thumbnailsOrientation === 'horizontal' ) {
1783				this.$thumbnails.css({ 'width': this.thumbnailsSize, 'height': this.settings.thumbnailHeight });
1784				this.$thumbnailsContainer.css( 'height', '' );
1785				this.thumbnailsPositionProperty = 'left';
1786			} else {
1787				this.$thumbnails.css({ 'width': this.settings.thumbnailWidth, 'height': this.thumbnailsSize });
1788				this.$thumbnailsContainer.css( 'width', '' );
1789				this.thumbnailsPositionProperty = 'top';
1790			}
1791
1792			// Fire the 'thumbnailsUpdate' event
1793			this.trigger({ type: 'thumbnailsUpdate' });
1794			if ( $.isFunction( this.settings.thumbnailsUpdate ) ) {
1795				this.settings.thumbnailsUpdate.call( this, { type: 'thumbnailsUpdate' } );
1796			}
1797		},
1798
1799		// Create an individual thumbnail
1800		_createThumbnail: function( element, index ) {
1801			var that = this,
1802				thumbnail = new Thumbnail( element, this.$thumbnails, index );
1803
1804			// When the thumbnail is clicked, navigate to the corresponding slide
1805			thumbnail.on( 'thumbnailClick.' + NS, function( event ) {
1806				that.gotoSlide( event.index );
1807			});
1808
1809			// Add the thumbnail at the specified index
1810			this.thumbnails.splice( index, 0, thumbnail );
1811		},
1812
1813		// Called when the slider is resized.
1814		// Resets the size and position of the thumbnail scroller container.
1815		_thumbnailsOnResize: function() {
1816			if ( this.isThumbnailScroller === false ) {
1817				return;
1818			}
1819
1820			var that = this,
1821				newThumbnailsPosition;
1822
1823			if ( this.thumbnailsOrientation === 'horizontal' ) {
1824				this.thumbnailsContainerSize = Math.min( this.$slidesMask.width(), this.thumbnailsSize );
1825				this.$thumbnailsContainer.css( 'width', this.thumbnailsContainerSize );
1826
1827				// Reduce the slide mask's height, to make room for the thumbnails
1828				if ( this.settings.forceSize === 'fullWindow' ) {
1829					this.$slidesMask.css( 'height', this.$slidesMask.height() - this.$thumbnailsContainer.outerHeight( true ) );
1830
1831					// Resize the slides
1832					this.slideHeight = this.$slidesMask.height();
1833					this._resizeSlides();
1834
1835					// Re-arrange the slides
1836					this._resetSlidesPosition();
1837				}
1838			} else if ( this.thumbnailsOrientation === 'vertical' ) {
1839
1840				// Check if the width of the slide mask plus the width of the thumbnail scroller is greater than
1841				// the width of the slider's container and if that's the case, reduce the slides container width
1842				// in order to make the entire slider fit inside the slider's container.
1843				if ( this.$slidesMask.width() + this.$thumbnailsContainer.outerWidth( true ) > this.$slider.parent().width() ) {
1844					// Reduce the slider's width, to make room for the thumbnails
1845					if ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) {
1846						this.$slider.css( 'max-width', $( window ).width() - this.$thumbnailsContainer.outerWidth( true ) );
1847					} else {
1848						this.$slider.css( 'max-width', this.$slider.parent().width() - this.$thumbnailsContainer.outerWidth( true ) );
1849					}
1850					
1851					this.$slidesMask.css( 'width', this.$slider.width() );
1852
1853					// If the slides are vertically oriented, update the width and height (to maintain the aspect ratio)
1854					// of the slides.
1855					if ( this.settings.orientation === 'vertical' ) {
1856						this.slideWidth = this.$slider.width();
1857
1858						this._resizeSlides();
1859					}
1860
1861					// Re-arrange the slides
1862					this._resetSlidesPosition();
1863				}
1864
1865				this.thumbnailsContainerSize = Math.min( this.$slidesMask.height(), this.thumbnailsSize );
1866				this.$thumbnailsContainer.css( 'height', this.thumbnailsContainerSize );
1867			}
1868
1869			// If the total size of the thumbnails is smaller than the thumbnail scroller' container (which has
1870			// the same size as the slides container), it means that all the thumbnails will be visible, so set
1871			// the position of the thumbnail scroller to 0.
1872			// 
1873			// If that's not the case, the thumbnail scroller will be positioned based on which thumbnail is selected.
1874			if ( this.thumbnailsSize <= this.thumbnailsContainerSize || this.$thumbnails.find( '.sp-selected-thumbnail' ).length === 0 ) {
1875				newThumbnailsPosition = 0;
1876			} else {
1877				newThumbnailsPosition = Math.max( - this.thumbnails[ this.selectedThumbnailIndex ].getPosition()[ this.thumbnailsPositionProperty ], this.thumbnailsContainerSize - this.thumbnailsSize );
1878			}
1879
1880			// Add a padding to the slider, based on the thumbnail scroller's orientation, to make room
1881			// for the thumbnails.
1882			if ( this.settings.thumbnailsPosition === 'top' ) {
1883				this.$slider.css({ 'paddingTop': this.$thumbnailsContainer.outerHeight( true ), 'paddingLeft': '', 'paddingRight': '' });
1884			} else if ( this.settings.thumbnailsPosition === 'bottom' ) {
1885				this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': '' });
1886			} else if ( this.settings.thumbnailsPosition === 'left' ) {
1887				this.$slider.css({ 'paddingTop': '', 'paddingLeft': this.$thumbnailsContainer.outerWidth( true ), 'paddingRight': '' });
1888			} else if ( this.settings.thumbnailsPosition === 'right' ) {
1889				this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': this.$thumbnailsContainer.outerWidth( true ) });
1890			}
1891
1892			this._moveThumbnailsTo( newThumbnailsPosition, true );
1893		},
1894
1895		// Selects the thumbnail at the indicated index and moves the thumbnail scroller
1896		// accordingly.
1897		_gotoThumbnail: function( index ) {
1898			if ( this.isThumbnailScroller === false || typeof this.thumbnails[ index ] === 'undefined' ) {
1899				return;
1900			}
1901
1902			var previousIndex = this.selectedThumbnailIndex,
1903				newThumbnailsPosition = this.thumbnailsPosition;
1904
1905			this.selectedThumbnailIndex = index;
1906
1907			// Set the 'selected' class to the appropriate thumbnail
1908			this.$thumbnails.find( '.sp-selected-thumbnail' ).removeClass( 'sp-selected-thumbnail' );
1909			this.$thumbnails.find( '.sp-thumbnail-container' ).eq( this.selectedThumbnailIndex ).addClass( 'sp-selected-thumbnail' );
1910
1911			// Calculate the new position that the thumbnail scroller needs to go to.
1912			// 
1913			// If the selected thumbnail has a higher index than the previous one, make sure that the thumbnail
1914			// that comes after the selected thumbnail will be visible, if the selected thumbnail is not the
1915			// last thumbnail in the list.
1916			// 
1917			// If the selected thumbnail has a lower index than the previous one, make sure that the thumbnail
1918			// that's before the selected thumbnail will be visible, if the selected thumbnail is not the
1919			// first thumbnail in the list.
1920			if ( this.settings.rightToLeft === true && this.thumbnailsOrientation === 'horizontal' ) {
1921				if ( this.selectedThumbnailIndex >= previousIndex ) {
1922					var rtlNextThumbnailIndex = this.selectedThumbnailIndex === this.thumbnails.length - 1 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex + 1,
1923						rtlNextThumbnail = this.thumbnails[ rtlNextThumbnailIndex ];
1924
1925					if ( rtlNextThumbnail.getPosition().left < - this.thumbnailsPosition ) {
1926						newThumbnailsPosition = - rtlNextThumbnail.getPosition().left;
1927					}
1928				} else if ( this.selectedThumbnailIndex < previousIndex ) {
1929					var rtlPreviousThumbnailIndex = this.selectedThumbnailIndex === 0 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex - 1,
1930						rtlPreviousThumbnail = this.thumbnails[ rtlPreviousThumbnailIndex ],
1931						rtlThumbnailsRightPosition = - this.thumbnailsPosition + this.thumbnailsContainerSize;
1932
1933					if ( rtlPreviousThumbnail.getPosition().right > rtlThumbnailsRightPosition ) {
1934						newThumbnailsPosition = this.thumbnailsPosition - ( rtlPreviousThumbnail.getPosition().right - rtlThumbnailsRightPosition );
1935					}
1936				}
1937			} else {
1938				if ( this.selectedThumbnailIndex >= previousIndex ) {
1939					var nextThumbnailIndex = this.selectedThumbnailIndex === this.thumbnails.length - 1 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex + 1,
1940						nextThumbnail = this.thumbnails[ nextThumbnailIndex ],
1941						nextThumbnailPosition = this.thumbnailsOrientation === 'horizontal' ? nextThumbnail.getPosition().right : nextThumbnail.getPosition().bottom,
1942						thumbnailsRightPosition = - this.thumbnailsPosition + this.thumbnailsContainerSize;
1943
1944					if ( nextThumbnailPosition > thumbnailsRightPosition ) {
1945						newThumbnailsPosition = this.thumbnailsPosition - ( nextThumbnailPosition - thumbnailsRightPosition );
1946					}
1947				} else if ( this.selectedThumbnailIndex < previousIndex ) {
1948					var previousThumbnailIndex = this.selectedThumbnailIndex === 0 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex - 1,
1949						previousThumbnail = this.thumbnails[ previousThumbnailIndex ],
1950						previousThumbnailPosition = this.thumbnailsOrientation === 'horizontal' ? previousThumbnail.getPosition().left : previousThumbnail.getPosition().top;
1951
1952					if ( previousThumbnailPosition < - this.thumbnailsPosition ) {
1953						newThumbnailsPosition = - previousThumbnailPosition;
1954					}
1955				}
1956			}
1957
1958			// Move the thumbnail scroller to the calculated position
1959			this._moveThumbnailsTo( newThumbnailsPosition );
1960
1961			// Fire the 'gotoThumbnail' event
1962			this.trigger({ type: 'gotoThumbnail' });
1963			if ( $.isFunction( this.settings.gotoThumbnail ) ) {
1964				this.settings.gotoThumbnail.call( this, { type: 'gotoThumbnail' });
1965			}
1966		},
1967
1968		// Move the thumbnail scroller to the indicated position
1969		_moveThumbnailsTo: function( position, instant, callback ) {
1970			var that = this,
1971				css = {};
1972
1973			// Return if the position hasn't changed
1974			if ( position === this.thumbnailsPosition ) {
1975				return;
1976			}
1977
1978			this.thumbnailsPosition = position;
1979
1980			// Use CSS transitions if they are supported. If not, use JavaScript animation
1981			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
1982				var transition,
1983					left = this.thumbnailsOrientation === 'horizontal' ? position : 0,
1984					top = this.thumbnailsOrientation === 'horizontal' ? 0 : position;
1985
1986				if ( this.supportedAnimation === 'css-3d' ) {
1987					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
1988				} else {
1989					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
1990				}
1991
1992				if ( typeof instant !== 'undefined' && instant === true ) {
1993					transition = '';
1994				} else {
1995					this.$thumbnails.addClass( 'sp-animated' );
1996					transition = this.vendorPrefix + 'transform ' + 700 / 1000 + 's';
1997
1998					this.$thumbnails.on( this.transitionEvent, function( event ) {
1999						if ( event.target !== event.currentTarget ) {
2000							return;
2001						}
2002
2003						that.$thumbnails.off( that.transitionEvent );
2004						that.$thumbnails.removeClass( 'sp-animated' );
2005
2006						if ( typeof callback === 'function' ) {
2007							callback();
2008						}
2009
2010						// Fire the 'thumbnailsMoveComplete' event
2011						that.trigger({ type: 'thumbnailsMoveComplete' });
2012						if ( $.isFunction( that.settings.thumbnailsMoveComplete ) ) {
2013							that.settings.thumbnailsMoveComplete.call( that, { type: 'thumbnailsMoveComplete' });
2014						}
2015					});
2016				}
2017
2018				css[ this.vendorPrefix + 'transition' ] = transition;
2019
2020				this.$thumbnails.css( css );
2021			} else {
2022				css[ 'margin-' + this.thumbnailsPositionProperty ] = position;
2023
2024				if ( typeof instant !== 'undefined' && instant === true ) {
2025					this.$thumbnails.css( css );
2026				} else {
2027					this.$thumbnails
2028						.addClass( 'sp-animated' )
2029						.animate( css, 700, function() {
2030							that.$thumbnails.removeClass( 'sp-animated' );
2031
2032							if ( typeof callback === 'function' ) {
2033								callback();
2034							}
2035
2036							// Fire the 'thumbnailsMoveComplete' event
2037							that.trigger({ type: 'thumbnailsMoveComplete' });
2038							if ( $.isFunction( that.settings.thumbnailsMoveComplete ) ) {
2039								that.settings.thumbnailsMoveComplete.call( that, { type: 'thumbnailsMoveComplete' });
2040							}
2041						});
2042				}
2043			}
2044		},
2045
2046		// Stop the movement of the thumbnail scroller
2047		_stopThumbnailsMovement: function() {
2048			var css = {};
2049
2050			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
2051				var	matrixString = this.$thumbnails.css( this.vendorPrefix + 'transform' ),
2052					matrixType = matrixString.indexOf( 'matrix3d' ) !== -1 ? 'matrix3d' : 'matrix',
2053					matrixArray = matrixString.replace( matrixType, '' ).match( /-?[0-9\.]+/g ),
2054					left = matrixType === 'matrix3d' ? parseInt( matrixArray[ 12 ], 10 ) : parseInt( matrixArray[ 4 ], 10 ),
2055					top = matrixType === 'matrix3d' ? parseInt( matrixArray[ 13 ], 10 ) : parseInt( matrixArray[ 5 ], 10 );
2056
2057				if ( this.supportedAnimation === 'css-3d' ) {
2058					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
2059				} else {
2060					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
2061				}
2062
2063				css[ this.vendorPrefix + 'transition' ] = '';
2064
2065				this.$thumbnails.css( css );
2066				this.$thumbnails.off( this.transitionEvent );
2067				this.thumbnailsPosition = this.thumbnailsOrientation === 'horizontal' ? parseInt( matrixArray[ 4 ] , 10 ) : parseInt( matrixArray[ 5 ] , 10 );
2068			} else {
2069				this.$thumbnails.stop();
2070				this.thumbnailsPosition = parseInt( this.$thumbnails.css( 'margin-' + this.thumbnailsPositionProperty ), 10 );
2071			}
2072
2073			this.$thumbnails.removeClass( 'sp-animated' );
2074		},
2075
2076		// Destroy the module
2077		destroyThumbnails: function() {
2078			var that = this;
2079
2080			// Remove event listeners
2081			this.off( 'update.' + NS );
2082
2083			if ( this.isThumbnailScroller === false ) {
2084				return;
2085			}
2086			
2087			this.off( 'sliderResize.' + NS );
2088			this.off( 'gotoSlide.' + NS );
2089			$( window ).off( 'resize.' + this.uniqueId + '.' + NS );
2090
2091			// Destroy the individual thumbnails
2092			this.$thumbnails.find( '.sp-thumbnail' ).each( function() {
2093				var $thumbnail = $( this ),
2094					index = parseInt( $thumbnail.attr( 'data-index' ), 10 ),
2095					thumbnail = that.thumbnails[ index ];
2096
2097				thumbnail.off( 'thumbnailClick.' + NS );
2098				thumbnail.destroy();
2099			});
2100
2101			this.thumbnails.length = 0;
2102
2103			// Add the thumbnail scroller directly in the slider and
2104			// remove the thumbnail scroller container
2105			this.$thumbnails.appendTo( this.$slider );
2106			this.$thumbnailsContainer.remove();
2107			
2108			// Remove any created padding
2109			this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': '' });
2110		},
2111
2112		thumbnailsDefaults: {
2113
2114			// Sets the width of the thumbnail
2115			thumbnailWidth: 100,
2116
2117			// Sets the height of the thumbnail
2118			thumbnailHeight: 80,
2119
2120			// Sets the position of the thumbnail scroller (top, bottom, right, left)
2121			thumbnailsPosition: 'bottom',
2122
2123			// Indicates if a pointer will be displayed for the selected thumbnail
2124			thumbnailPointer: false,
2125
2126			// Called when the thumbnails are updated
2127			thumbnailsUpdate: function() {},
2128
2129			// Called when a new thumbnail is selected
2130			gotoThumbnail: function() {},
2131
2132			// Called when the thumbnail scroller has moved
2133			thumbnailsMoveComplete: function() {}
2134		}
2135	};
2136
2137	var Thumbnail = function( thumbnail, thumbnails, index ) {
2138
2139		// Reference to the thumbnail jQuery element
2140		this.$thumbnail = thumbnail;
2141
2142		// Reference to the thumbnail scroller
2143		this.$thumbnails = thumbnails;
2144
2145		// Reference to the thumbnail's container, which will be 
2146		// created dynamically.
2147		this.$thumbnailContainer = null;
2148
2149		// The width and height of the thumbnail
2150		this.width = 0;
2151		this.height = 0;
2152
2153		// Indicates whether the thumbnail's image is loaded
2154		this.isImageLoaded = false;
2155
2156		// Set the index of the slide
2157		this.setIndex( index );
2158
2159		// Initialize the thumbnail
2160		this._init();
2161	};
2162
2163	Thumbnail.prototype = {
2164
2165		_init: function() {
2166			var that = this;
2167
2168			// Mark the thumbnail as initialized
2169			this.$thumbnail.attr( 'data-init', true );
2170
2171			// Create a container for the thumbnail and add the original thumbnail to this container.
2172			// Having a container will help crop the thumbnail image if it's too large.
2173			this.$thumbnailContainer = $( '<div class="sp-thumbnail-container"></div>' ).appendTo( this.$thumbnails );
2174
2175			if ( this.$thumbnail.parent( 'a' ).length !== 0 ) {
2176				this.$thumbnail.parent( 'a' ).appendTo( this.$thumbnailContainer );
2177			} else {
2178				this.$thumbnail.appendTo( this.$thumbnailContainer );
2179			}
2180
2181			// When the thumbnail container is clicked, fire an event
2182			this.$thumbnailContainer.on( 'click.' + NS, function() {
2183				that.trigger({ type: 'thumbnailClick.' + NS, index: that.index });
2184			});
2185		},
2186
2187		// Set the width and height of the thumbnail
2188		setSize: function( width, height ) {
2189			this.width = width;
2190			this.height = height;
2191
2192			// Apply the width and height to the thumbnail's container
2193			this.$thumbnailContainer.css({ 'width': this.width, 'height': this.height });
2194
2195			// If there is an image, resize it to fit the thumbnail container
2196			if ( this.$thumbnail.is( 'img' ) && typeof this.$thumbnail.attr( 'data-src' ) === 'undefined' ) {
2197				this.resizeImage();
2198			}
2199		},
2200
2201		// Return the width and height of the thumbnail
2202		getSize: function() {
2203			return {
2204				width: this.$thumbnailContainer.outerWidth( true ),
2205				height: this.$thumbnailContainer.outerHeight( true )
2206			};
2207		},
2208
2209		// Return the top, bottom, left and right position of the thumbnail
2210		getPosition: function() {
2211			return {
2212				left: this.$thumbnailContainer.position().left + parseInt( this.$thumbnailContainer.css( 'marginLeft' ) , 10 ),
2213				right: this.$thumbnailContainer.position().left + parseInt( this.$thumbnailContainer.css( 'marginLeft' ) , 10 ) + this.$thumbnailContainer.outerWidth(),
2214				top: this.$thumbnailContainer.position().top + parseInt( this.$thumbnailContainer.css( 'marginTop' ) , 10 ),
2215				bottom: this.$thumbnailContainer.position().top + parseInt( this.$thumbnailContainer.css( 'marginTop' ) , 10 ) + this.$thumbnailContainer.outerHeight()
2216			};
2217		},
2218
2219		// Set the index of the thumbnail
2220		setIndex: function( index ) {
2221			this.index = index;
2222			this.$thumbnail.attr( 'data-index', this.index );
2223		},
2224
2225		// Resize the thumbnail's image
2226		resizeImage: function() {
2227			var that = this;
2228
2229			// If the image is not loaded yet, load it
2230			if ( this.isImageLoaded === false ) {
2231				SliderProUtils.checkImagesComplete( this.$thumbnailContainer , function() {
2232					that.isImageLoaded = true;
2233					that.resizeImage();
2234				});
2235
2236				return;
2237			}
2238
2239			// Get the reference to the thumbnail image again because it was replaced by
2240			// another img element during the loading process
2241			this.$thumbnail = this.$thumbnailContainer.find( '.sp-thumbnail' );
2242
2243			// Calculate whether the image should stretch horizontally or vertically
2244			var imageWidth = this.$thumbnail.width(),
2245				imageHeight = this.$thumbnail.height();
2246
2247			if ( imageWidth / imageHeight <= this.width / this.height ) {
2248				this.$thumbnail.css({ width: '100%', height: 'auto' });
2249			} else {
2250				this.$thumbnail.css({ width: 'auto', height: '100%' });
2251			}
2252
2253			this.$thumbnail.css({ 'marginLeft': ( this.$thumbnailContainer.width() - this.$thumbnail.width() ) * 0.5, 'marginTop': ( this.$thumbnailContainer.height() - this.$thumbnail.height() ) * 0.5 });
2254		},
2255
2256		// Destroy the thumbnail
2257		destroy: function() {
2258			this.$thumbnailContainer.off( 'click.' + NS );
2259
2260			// Remove added attributes
2261			this.$thumbnail.removeAttr( 'data-init' );
2262			this.$thumbnail.removeAttr( 'data-index' );
2263
2264			// Remove the thumbnail's container and add the thumbnail
2265			// back to the thumbnail scroller container
2266			if ( this.$thumbnail.parent( 'a' ).length !== 0 ) {
2267				this.$thumbnail.parent( 'a' ).insertBefore( this.$thumbnailContainer );
2268			} else {
2269				this.$thumbnail.insertBefore( this.$thumbnailContainer );
2270			}
2271			
2272			this.$thumbnailContainer.remove();
2273		},
2274
2275		// Attach an event handler to the slide
2276		on: function( type, callback ) {
2277			return this.$thumbnailContainer.on( type, callback );
2278		},
2279
2280		// Detach an event handler to the slide
2281		off: function( type ) {
2282			return this.$thumbnailContainer.off( type );
2283		},
2284
2285		// Trigger an event on the slide
2286		trigger: function( data ) {
2287			return this.$thumbnailContainer.triggerHandler( data );
2288		}
2289	};
2290
2291	$.SliderPro.addModule( 'Thumbnails', Thumbnails );
2292
2293})( window, jQuery );
2294
2295// ConditionalImages module for Slider Pro.
2296// 
2297// Adds the possibility to specify multiple sources for each image and
2298// load the image that's the most appropriate for the size of the slider.
2299// For example, instead of loading a large image even if the slider will be small
2300// you can specify a smaller image that will be loaded instead.
2301;(function( window, $ ) {
2302
2303	"use strict";
2304
2305	var NS = 'ConditionalImages.' + $.SliderPro.namespace;
2306
2307	var ConditionalImages = {
2308
2309		// Reference to the previous size
2310		previousImageSize: null,
2311
2312		// Reference to the current size
2313		currentImageSize: null,
2314
2315		// Indicates if the current display supports high PPI
2316		isRetinaScreen: false,
2317
2318		initConditionalImages: function() {
2319			this.currentImageSize = this.previousImageSize = 'default';
2320			this.isRetinaScreen = ( typeof this._isRetina !== 'undefined' ) && ( this._isRetina() === true );
2321
2322			this.on( 'update.' + NS, $.proxy( this._conditionalImagesOnUpdate, this ) );
2323			this.on( 'sliderResize.' + NS, $.proxy( this._conditionalImagesOnResize, this ) );
2324		},
2325
2326		// Loop through all the existing images and specify the original path of the image
2327		// inside the 'data-default' attribute.
2328		_conditionalImagesOnUpdate: function() {
2329			$.each( this.slides, function( index, element ) {
2330				var $slide = element.$slide;
2331
2332				$slide.find( 'img:not([ data-default ])' ).each(function() {
2333					var $image = $( this );
2334
2335					if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) {
2336						$image.attr( 'data-default', $image.attr( 'data-src' ) );
2337					} else {
2338						$image.attr( 'data-default', $image.attr( 'src' ) );
2339					}
2340				});
2341			});
2342		},
2343
2344		// When the window resizes, identify the applyable image size based on the current size of the slider
2345		// and apply it to all images that have a version of the image specified for this size.
2346		_conditionalImagesOnResize: function() {
2347			if ( this.slideWidth <= this.settings.smallSize ) {
2348				this.currentImageSize = 'small';
2349			} else if ( this.slideWidth <= this.settings.mediumSize ) {
2350				this.currentImageSize = 'medium';
2351			} else if ( this.slideWidth <= this.settings.largeSize ) {
2352				this.currentImageSize = 'large';
2353			} else {
2354				this.currentImageSize = 'default';
2355			}
2356
2357			if ( this.previousImageSize !== this.currentImageSize ) {
2358				var that = this;
2359
2360				$.each( this.slides, function( index, element ) {
2361					var $slide = element.$slide;
2362
2363					$slide.find( 'img' ).each(function() {
2364						var $image = $( this ),
2365							imageSource = '';
2366
2367						// Check if the current display supports high PPI and if a retina version of the current size was specified
2368						if ( that.isRetinaScreen === true && typeof $image.attr( 'data-retina' + that.currentImageSize ) !== 'undefined' ) {
2369							imageSource = $image.attr( 'data-retina' + that.currentImageSize );
2370
2371							// If the retina image was not loaded yet, replace the default image source with the one
2372							// that corresponds to the current slider size
2373							if ( typeof $image.attr( 'data-retina' ) !== 'undefined' && $image.attr( 'data-retina' ) !== imageSource ) {
2374								$image.attr( 'data-retina', imageSource );
2375							}
2376						} else if ( ( that.isRetinaScreen === false || that.isRetinaScreen === true && typeof $image.attr( 'data-retina' ) === 'undefined' ) && typeof $image.attr( 'data-' + that.currentImageSize ) !== 'undefined' ) {
2377							imageSource = $image.attr( 'data-' + that.currentImageSize );
2378
2379							// If the image is set to lazy load, replace the image source with the one
2380							// that corresponds to the current slider size
2381							if ( typeof $image.attr( 'data-src' ) !== 'undefined' && $image.attr( 'data-src' ) !== imageSource ) {
2382								$image.attr( 'data-src', imageSource );
2383							}
2384						}
2385
2386						// If a new image was found
2387						if ( imageSource !== '' ) {
2388
2389							// The existence of the 'data-src' attribute indicates that the image
2390							// will be lazy loaded, so don't load the new image yet
2391							if ( typeof $image.attr( 'data-src' ) === 'undefined' && $image.attr( 'src' ) !== imageSource  ) {
2392								that._loadConditionalImage( $image, imageSource, function( newImage ) {
2393									if ( newImage.hasClass( 'sp-image' ) ) {
2394										element.$mainImage = newImage;
2395										element.resizeMainImage( true );
2396									}
2397								});
2398							}
2399						}
2400					});
2401				});
2402
2403				this.previousImageSize = this.currentImageSize;
2404			}
2405		},
2406
2407		// Replace the target image with a new image
2408		_loadConditionalImage: function( image, source, callback ) {
2409
2410			// Create a new image element
2411			var newImage = $( new Image() );
2412
2413			// Copy the class(es) and inline style
2414			newImage.attr( 'class', image.attr( 'class' ) );
2415			newImage.attr( 'style', image.attr( 'style' ) );
2416
2417			// Copy the data attributes
2418			$.each( image.data(), function( name, value ) {
2419				newImage.attr( 'data-' + name, value );
2420			});
2421
2422			// Copy the width and height attributes if they exist
2423			if ( typeof image.attr( 'width' ) !== 'undefined') {
2424				newImage.attr( 'width', image.attr( 'width' ) );
2425			}
2426
2427			if ( typeof image.attr( 'height' ) !== 'undefined') {
2428				newImage.attr( 'height', image.attr( 'height' ) );
2429			}
2430
2431			if ( typeof image.attr( 'alt' ) !== 'undefined' ) {
2432				newImage.attr( 'alt', image.attr( 'alt' ) );
2433			}
2434
2435			if ( typeof image.attr( 'title' ) !== 'undefined' ) {
2436				newImage.attr( 'title', image.attr( 'title' ) );
2437			}
2438
2439			newImage.attr( 'src', source );
2440
2441			// Add the new image in the same container and remove the older image
2442			newImage.insertAfter( image );
2443			image.remove();
2444			image = null;
2445				
2446			if ( typeof callback === 'function' ) {
2447				callback( newImage );
2448			}
2449		},
2450
2451		// Destroy the module
2452		destroyConditionalImages: function() {
2453			this.off( 'update.' + NS );
2454			this.off( 'sliderResize.' + NS );
2455		},
2456
2457		conditionalImagesDefaults: {
2458
2459			// If the slider size is below this size, the small version of the images will be used
2460			smallSize: 480,
2461
2462			// If the slider size is below this size, the small version of the images will be used
2463			mediumSize: 768,
2464
2465			// If the slider size is below this size, the small version of the images will be used
2466			largeSize: 1024
2467		}
2468	};
2469
2470	$.SliderPro.addModule( 'ConditionalImages', ConditionalImages );
2471
2472})( window, jQuery );
2473
2474// Retina module for Slider Pro.
2475// 
2476// Adds the possibility to load a different image when the slider is
2477// viewed on a retina screen.
2478;(function( window, $ ) {
2479
2480	"use strict";
2481	
2482	var NS = 'Retina.' + $.SliderPro.namespace;
2483
2484	var Retina = {
2485
2486		initRetina: function() {
2487			var that = this;
2488
2489			// Return if it's not a retina screen
2490			if ( this._isRetina() === false ) {
2491				return;
2492			}
2493			
2494			this.on( 'sliderResize.' + NS, $.proxy( this._checkRetinaImages, this ) );
2495
2496			if ( this.$slider.find( '.sp-thumbnail' ).length !== 0 ) {
2497				this.on( 'update.Thumbnails.' + NS, $.proxy( this._checkRetinaThumbnailImages, this ) );
2498			}
2499		},
2500
2501		// Checks if the current display supports high PPI
2502		_isRetina: function() {
2503			if ( window.devicePixelRatio >= 2 ) {
2504				return true;
2505			}
2506
2507			if ( window.matchMedia && ( window.matchMedia( "(-webkit-min-device-pixel-ratio: 2),(min-resolution: 2dppx)" ).matches ) ) {
2508				return true;
2509			}
2510
2511			return false;
2512		},
2513
2514		// Loop through the slides and replace the images with their retina version
2515		_checkRetinaImages: function() {
2516			var that = this;
2517
2518			$.each( this.slides, function( index, element ) {
2519				var $slide = element.$slide;
2520
2521				if ( typeof $slide.attr( 'data-retina-loaded' ) === 'undefined' ) {
2522					$slide.attr( 'data-retina-loaded', true );
2523
2524					$slide.find( 'img[data-retina]' ).each(function() {
2525						var $image = $( this );
2526
2527						if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) {
2528							$image.attr( 'data-src', $image.attr( 'data-retina' ) );
2529						} else {
2530							that._loadRetinaImage( $image, function( newImage ) {
2531								if ( newImage.hasClass( 'sp-image' ) ) {
2532									element.$mainImage = newImage;
2533									element.resizeMainImage( true );
2534								}
2535							});
2536						}
2537					});
2538				}
2539			});
2540		},
2541
2542		// Loop through the thumbnails and replace the images with their retina version
2543		_checkRetinaThumbnailImages: function() {
2544			var that = this;
2545
2546			$.each( this.thumbnails, function( index, element ) {
2547				var $thumbnail = element.$thumbnailContainer;
2548
2549				if ( typeof $thumbnail.attr( 'data-retina-loaded' ) === 'undefined' ) {
2550					$thumbnail.attr( 'data-retina-loaded', true );
2551
2552					$thumbnail.find( 'img[data-retina]' ).each(function() {
2553						var $image = $( this );
2554
2555						if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) {
2556							$image.attr( 'data-src', $image.attr( 'data-retina' ) );
2557						} else {
2558							that._loadRetinaImage( $image, function( newImage ) {
2559								if ( newImage.hasClass( 'sp-thumbnail' ) ) {
2560									element.resizeImage();
2561								}
2562							});
2563						}
2564					});
2565				}
2566			});
2567		},
2568
2569		// Load the retina image
2570		_loadRetinaImage: function( image, callback ) {
2571			var retinaFound = false,
2572				newImagePath = '';
2573
2574			// Check if there is a retina image specified
2575			if ( typeof image.attr( 'data-retina' ) !== 'undefined' ) {
2576				retinaFound = true;
2577
2578				newImagePath = image.attr( 'data-retina' );
2579			}
2580
2581			// Check if there is a lazy loaded, non-retina, image specified
2582			if ( typeof image.attr( 'data-src' ) !== 'undefined' ) {
2583				if ( retinaFound === false ) {
2584					newImagePath = image.attr( 'data-src') ;
2585				}
2586
2587				image.removeAttr('data-src');
2588			}
2589
2590			// Return if there isn't a retina or lazy loaded image
2591			if ( newImagePath === '' ) {
2592				return;
2593			}
2594
2595			// Create a new image element
2596			var newImage = $( new Image() );
2597
2598			// Copy the class(es) and inline style
2599			newImage.attr( 'class', image.attr('class') );
2600			newImage.attr( 'style', image.attr('style') );
2601
2602			// Copy the data attributes
2603			$.each( image.data(), function( name, value ) {
2604				newImage.attr( 'data-' + name, value );
2605			});
2606
2607			// Copy the width and height attributes if they exist
2608			if ( typeof image.attr( 'width' ) !== 'undefined' ) {
2609				newImage.attr( 'width', image.attr( 'width' ) );
2610			}
2611
2612			if ( typeof image.attr( 'height' ) !== 'undefined' ) {
2613				newImage.attr( 'height', image.attr( 'height' ) );
2614			}
2615
2616			if ( typeof image.attr( 'alt' ) !== 'undefined' ) {
2617				newImage.attr( 'alt', image.attr( 'alt' ) );
2618			}
2619
2620			if ( typeof image.attr( 'title' ) !== 'undefined' ) {
2621				newImage.attr( 'title', image.attr( 'title' ) );
2622			}
2623
2624			// Add the new image in the same container and remove the older image
2625			newImage.insertAfter( image );
2626			image.remove();
2627			image = null;
2628
2629			// Assign the source of the image
2630			newImage.attr( 'src', newImagePath );
2631
2632			if ( typeof callback === 'function' ) {
2633				callback( newImage );
2634			}
2635		},
2636
2637		// Destroy the module
2638		destroyRetina: function() {
2639			this.off( 'update.' + NS );
2640			this.off( 'update.Thumbnails.' + NS );
2641		}
2642	};
2643
2644	$.SliderPro.addModule( 'Retina', Retina );
2645	
2646})( window, jQuery );
2647
2648// Lazy Loading module for Slider Pro.
2649// 
2650// Adds the possibility to delay the loading of the images until the slides/thumbnails
2651// that contain them become visible. This technique improves the initial loading
2652// performance.
2653;(function( window, $ ) {
2654
2655	"use strict";
2656
2657	var NS = 'LazyLoading.' + $.SliderPro.namespace;
2658
2659	var LazyLoading = {
2660
2661		allowLazyLoadingCheck: true,
2662
2663		initLazyLoading: function() {
2664			var that = this;
2665
2666			// The 'resize' event is fired after every update, so it's possible to use it for checking
2667			// if the update made new slides become visible
2668			// 
2669			// Also, resizing the slider might make new slides or thumbnails visible
2670			this.on( 'sliderResize.' + NS, $.proxy( this._lazyLoadingOnResize, this ) );
2671
2672			// Check visible images when a new slide is selected
2673			this.on( 'gotoSlide.' + NS, $.proxy( this._checkAndLoadVisibleImages, this ) );
2674
2675			// Check visible thumbnail images when the thumbnails are updated because new thumbnail
2676			// might have been added or the settings might have been changed so that more thumbnail
2677			// images become visible
2678			// 
2679			// Also, check visible thumbnail images after the thumbnails have moved because new thumbnails might
2680			// have become visible
2681			this.on( 'thumbnailsUpdate.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, $.proxy( this._checkAndLoadVisibleThumbnailImages, this ) );
2682		},
2683
2684		_lazyLoadingOnResize: function() {
2685			var that = this;
2686
2687			if ( this.allowLazyLoadingCheck === false ) {
2688				return;
2689			}
2690
2691			this.allowLazyLoadingCheck = false;
2692			
2693			this._checkAndLoadVisibleImages();
2694
2695			if ( this.$slider.find( '.sp-thumbnail' ).length !== 0 ) {
2696				this._checkAndLoadVisibleThumbnailImages();
2697			}
2698
2699			// Use a timer to deffer the loading of images in order to prevent too many
2700			// checking attempts
2701			setTimeout(function() {
2702				that.allowLazyLoadingCheck = true;
2703			}, 500 );
2704		},
2705
2706		// Check visible slides and load their images
2707		_checkAndLoadVisibleImages: function() {
2708			if ( this.$slider.find( '.sp-slide:not([ data-loaded ])' ).length === 0 ) {
2709				return;
2710			}
2711
2712			var that = this,
2713
2714				// Use either the middle position or the index of the selected slide as a reference, depending on
2715				// whether the slider is loopable
2716				referencePosition = this.settings.loop === true ? this.middleSlidePosition : this.selectedSlideIndex,
2717
2718				// Calculate how many slides are visible at the sides of the selected slide
2719				visibleOnSides = Math.ceil( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10) - this.averageSlideSize ) / 2 / this.averageSlideSize ),
2720
2721				// Calculate the indexes of the first and last slide that will be checked
2722				from = this.settings.centerSelectedSlide === true ? Math.max( referencePosition - visibleOnSides - 1, 0 ) : Math.max( referencePosition - 1, 0 ),
2723				to = this.settings.centerSelectedSlide === true ? Math.min( referencePosition + visibleOnSides + 1, this.getTotalSlides() - 1 ) : Math.min( referencePosition + visibleOnSides * 2 + 1, this.getTotalSlides() - 1  ),
2724				
2725				// Get all the slides that need to be checked
2726				slidesToCheck = this.slidesOrder.slice( from, to + 1 );
2727
2728			// Loop through the selected slides and if the slide is not marked as having
2729			// been loaded yet, loop through its images and load them.
2730			$.each( slidesToCheck, function( index, element ) {
2731				var slide = that.slides[ element ],
2732					$slide = slide.$slide;
2733
2734				if ( typeof $slide.attr( 'data-loaded' ) === 'undefined' ) {
2735					$slide.attr( 'data-loaded', true );
2736
2737					$slide.find( 'img[ data-src ]' ).each(function() {
2738						var image = $( this );
2739						that._loadImage( image, function( newImage ) {
2740							if ( newImage.hasClass( 'sp-image' ) ) {
2741								slide.$mainImage = newImage;
2742								slide.resizeMainImage( true );
2743							}
2744						});
2745					});
2746				}
2747			});
2748		},
2749
2750		// Check visible thumbnails and load their images
2751		_checkAndLoadVisibleThumbnailImages: function() {
2752			if ( this.$slider.find( '.sp-thumbnail-container:not([ data-loaded ])' ).length === 0 ) {
2753				return;
2754			}
2755
2756			var that = this,
2757				thumbnailSize = this.thumbnailsSize / this.thumbnails.length,
2758
2759				// Calculate the indexes of the first and last thumbnail that will be checked
2760				from = Math.floor( Math.abs( this.thumbnailsPosition / thumbnailSize ) ),
2761				to = Math.floor( ( - this.thumbnailsPosition + this.thumbnailsContainerSize ) / thumbnailSize ),
2762
2763				// Get all the thumbnails that need to be checked
2764				thumbnailsToCheck = this.thumbnails.slice( from, to + 1 );
2765
2766			// Loop through the selected thumbnails and if the thumbnail is not marked as having
2767			// been loaded yet, load its image.
2768			$.each( thumbnailsToCheck, function( index, element ) {
2769				var $thumbnailContainer = element.$thumbnailContainer;
2770
2771				if ( typeof $thumbnailContainer.attr( 'data-loaded' ) === 'undefined' ) {
2772					$thumbnailContainer.attr( 'data-loaded', true );
2773
2774					$thumbnailContainer.find( 'img[ data-src ]' ).each(function() {
2775						var image = $( this );
2776
2777						that._loadImage( image, function() {
2778							element.resizeImage();
2779						});
2780					});
2781				}
2782			});
2783		},
2784
2785		// Load an image
2786		_loadImage: function( image, callback ) {
2787			// Create a new image element
2788			var newImage = $( new Image() );
2789
2790			// Copy the class(es) and inline style
2791			newImage.attr( 'class', image.attr( 'class' ) );
2792			newImage.attr( 'style', image.attr( 'style' ) );
2793
2794			// Copy the data attributes
2795			$.each( image.data(), function( name, value ) {
2796				newImage.attr( 'data-' + name, value );
2797			});
2798
2799			// Copy the width and height attributes if they exist
2800			if ( typeof image.attr( 'width' ) !== 'undefined') {
2801				newImage.attr( 'width', image.attr( 'width' ) );
2802			}
2803
2804			if ( typeof image.attr( 'height' ) !== 'undefined') {
2805				newImage.attr( 'height', image.attr( 'height' ) );
2806			}
2807
2808			if ( typeof image.attr( 'alt' ) !== 'undefined' ) {
2809				newImage.attr( 'alt', image.attr( 'alt' ) );
2810			}
2811
2812			if ( typeof image.attr( 'title' ) !== 'undefined' ) {
2813				newImage.attr( 'title', image.attr( 'title' ) );
2814			}
2815
2816			// Assign the source of the image
2817			newImage.attr( 'src', image.attr( 'data-src' ) );
2818			newImage.removeAttr( 'data-src' );
2819
2820			// Add the new image in the same container and remove the older image
2821			newImage.insertAfter( image );
2822			image.remove();
2823			image = null;
2824			
2825			if ( typeof callback === 'function' ) {
2826				callback( newImage );
2827			}
2828		},
2829
2830		// Destroy the module
2831		destroyLazyLoading: function() {
2832			this.off( 'update.' + NS );
2833			this.off( 'gotoSlide.' + NS );
2834			this.off( 'sliderResize.' + NS );
2835			this.off( 'thumbnailsUpdate.' + NS );
2836			this.off( 'thumbnailsMoveComplete.' + NS );
2837		}
2838	};
2839
2840	$.SliderPro.addModule( 'LazyLoading', LazyLoading );
2841
2842})( window, jQuery );
2843
2844// Layers module for Slider Pro.
2845// 
2846// Adds support for animated and static layers. The layers can contain any content,
2847// from simple text for video elements.
2848;(function( window, $ ) {
2849
2850	"use strict";
2851
2852	var NS = 'Layers.' +  $.SliderPro.namespace;
2853
2854	var Layers = {
2855
2856		// Reference to the original 'gotoSlide' method
2857		layersGotoSlideReference: null,
2858
2859		// Reference to the timer that will delay the overriding
2860		// of the 'gotoSlide' method
2861		waitForLayersTimer: null,
2862
2863		initLayers: function() {
2864			this.on( 'update.' + NS, $.proxy( this._layersOnUpdate, this ) );
2865			this.on( 'sliderResize.' + NS, $.proxy( this._layersOnResize, this ) );
2866			this.on( 'gotoSlide.' + NS, $.proxy( this._layersOnGotoSlide, this ) );
2867		},
2868
2869		// Loop through the slides and initialize all layers
2870		_layersOnUpdate: function( event ) {
2871			var that = this;
2872
2873			$.each( this.slides, function( index, element ) {
2874				var $slide = element.$slide;
2875
2876				// Initialize the layers
2877				this.$slide.find( '.sp-layer:not([ data-layer-init ])' ).each(function() {
2878					var layer = new Layer( $( this ) );
2879
2880					// Add the 'layers' array to the slide objects (instance of SliderProSlide)
2881					if ( typeof element.layers === 'undefined' ) {
2882						element.layers = [];
2883					}
2884
2885					element.layers.push( layer );
2886
2887					if ( $( this ).hasClass( 'sp-static' ) === false ) {
2888
2889						// Add the 'animatedLayers' array to the slide objects (instance of SliderProSlide)
2890						if ( typeof element.animatedLayers === 'undefined' ) {
2891							element.animatedLayers = [];
2892						}
2893
2894						element.animatedLayers.push( layer );
2895					}
2896				});
2897			});
2898
2899			// If the 'waitForLayers' option is enabled, the slider will not move to another slide
2900			// until all the layers from the previous slide will be hidden. To achieve this,
2901			// replace the current 'gotoSlide' function with another function that will include the 
2902			// required functionality.
2903			// 
2904			// Since the 'gotoSlide' method might be overridden by other modules as well, delay this
2905			// override to make sure it's the last override.
2906			if ( this.settings.waitForLayers === true ) {
2907				clearTimeout( this.waitForLayersTimer );
2908
2909				this.waitForLayersTimer = setTimeout(function() {
2910					that.layersGotoSlideReference = that.gotoSlide;
2911					that.gotoSlide = that._layersGotoSlide;
2912				}, 1 );
2913			}
2914
2915			// Show the layers for the initial slide
2916			// Delay the call in order to make sure the layers
2917			// are scaled properly before displaying them
2918			setTimeout(function() {
2919				that.showLayers( that.selectedSlideIndex );
2920			}, 1);
2921		},
2922
2923		// When the slider resizes, try to scale down the layers proportionally. The automatic scaling
2924		// will make use of an option, 'autoScaleReference', by comparing the current width of the slider
2925		// with the reference width. So, if the reference width is 1000 pixels and the current width is
2926		// 500 pixels, it means that the layers will be scaled down to 50% of their size.
2927		_layersOnResize: function() {
2928			var that = this,
2929				autoScaleReference,
2930				useAutoScale = this.settings.autoScaleLayers,
2931				scaleRatio;
2932
2933			if ( this.settings.autoScaleLayers === false ) {
2934				return;
2935			}
2936
2937			// If there isn't a reference for how the layers should scale down automatically, use the 'width'
2938			// option as a reference, unless the width was set to a percentage. If there isn't a set reference and
2939			// the width was set to a percentage, auto scaling will not be used because it's not possible to
2940			// calculate how much should the layers scale.
2941			if ( this.settings.autoScaleReference === -1 ) {
2942				if ( typeof this.settings.width === 'string' && this.settings.width.indexOf( '%' ) !== -1 ) {
2943					useAutoScale = false;
2944				} else {
2945					autoScaleReference = parseInt( this.settings.width, 10 );
2946				}
2947			} else {
2948				autoScaleReference = this.settings.autoScaleReference;
2949			}
2950
2951			if ( useAutoScale === true && this.slideWidth < autoScaleReference ) {
2952				scaleRatio = that.slideWidth / autoScaleReference;
2953			} else {
2954				scaleRatio = 1;
2955			}
2956
2957			$.each( this.slides, function( index, slide ) {
2958				if ( typeof slide.layers !== 'undefined' ) {
2959					$.each( slide.layers, function( index, layer ) {
2960						layer.scale( scaleRatio );
2961					});
2962				}
2963			});
2964		},
2965
2966		// Replace the 'gotoSlide' method with this one, which makes it possible to 
2967		// change the slide only after the layers from the previous slide are hidden.
2968		_layersGotoSlide: function( index ) {
2969			var that = this,
2970				animatedLayers = this.slides[ this.selectedSlideIndex ].animatedLayers;
2971
2972			// If the slider is dragged, don't wait for the layer to hide
2973			if ( this.$slider.hasClass( 'sp-swiping' ) || typeof animatedLayers === 'undefined' || animatedLayers.length === 0  ) {
2974				this.layersGotoSlideReference( index );
2975			} else {
2976				this.on( 'hideLayersComplete.' + NS, function() {
2977					that.off( 'hideLayersComplete.' + NS );
2978					that.layersGotoSlideReference( index );
2979				});
2980
2981				this.hideLayers( this.selectedSlideIndex );
2982			}
2983		},
2984
2985		// When a new slide is selected, hide the layers from the previous slide
2986		// and show the layers from the current slide.
2987		_layersOnGotoSlide: function( event ) {
2988			if ( this.previousSlideIndex !== this.selectedSlideIndex ) {
2989				this.hideLayers( this.previousSlideIndex );
2990			}
2991
2992			this.showLayers( this.selectedSlideIndex );
2993		},
2994
2995		// Show the animated layers from the slide at the specified index,
2996		// and fire an event when all the layers from the slide become visible.
2997		showLayers: function( index ) {
2998			var that = this,
2999				animatedLayers = this.slides[ index ].animatedLayers,
3000				layerCounter = 0;
3001
3002			if ( typeof animatedLayers === 'undefined' ) {
3003				return;
3004			}
3005
3006			$.each( animatedLayers, function( index, element ) {
3007
3008				// If the layer is already visible, increment the counter directly, else wait 
3009				// for the layer's showing animation to complete.
3010				if ( element.isVisible() === true ) {
3011					layerCounter++;
3012
3013					if ( layerCounter === animatedLayers.length ) {
3014						that.trigger({ type: 'showLayersComplete', index: index });
3015						if ( $.isFunction( that.settings.showLayersComplete ) ) {
3016							that.settings.showLayersComplete.call( that, { type: 'showLayersComplete', index: index });
3017						}
3018					}
3019				} else {
3020					element.show(function() {
3021						layerCounter++;
3022
3023						if ( layerCounter === animatedLayers.length ) {
3024							that.trigger({ type: 'showLayersComplete', index: index });
3025							if ( $.isFunction( that.settings.showLayersComplete ) ) {
3026								that.settings.showLayersComplete.call( that, { type: 'showLayersComplete', index: index });
3027							}
3028						}
3029					});
3030				}
3031			});
3032		},
3033
3034		// Hide the animated layers from the slide at the specified index,
3035		// and fire an event when all the layers from the slide become invisible.
3036		hideLayers: function( index ) {
3037			var that = this,
3038				animatedLayers = this.slides[ index ].animatedLayers,
3039				layerCounter = 0;
3040
3041			if ( typeof animatedLayers === 'undefined' ) {
3042				return;
3043			}
3044
3045			$.each( animatedLayers, function( index, element ) {
3046
3047				// If the layer is already invisible, increment the counter directly, else wait 
3048				// for the layer's hiding animation to complete.
3049				if ( element.isVisible() === false ) {
3050					layerCounter++;
3051
3052					if ( layerCounter === animatedLayers.length ) {
3053						that.trigger({ type: 'hideLayersComplete', index: index });
3054						if ( $.isFunction( that.settings.hideLayersComplete ) ) {
3055							that.settings.hideLayersComplete.call( that, { type: 'hideLayersComplete', index: index });
3056						}
3057					}
3058				} else {
3059					element.hide(function() {
3060						layerCounter++;
3061
3062						if ( layerCounter === animatedLayers.length ) {
3063							that.trigger({ type: 'hideLayersComplete', index: index });
3064							if ( $.isFunction( that.settings.hideLayersComplete ) ) {
3065								that.settings.hideLayersComplete.call( that, { type: 'hideLayersComplete', index: index });
3066							}
3067						}
3068					});
3069				}
3070			});
3071		},
3072
3073		// Destroy the module
3074		destroyLayers: function() {
3075			this.off( 'update.' + NS );
3076			this.off( 'sliderResize.' + NS );
3077			this.off( 'gotoSlide.' + NS );
3078			this.off( 'hideLayersComplete.' + NS );
3079		},
3080
3081		layersDefaults: {
3082
3083			// Indicates whether the slider will wait for the layers to disappear before
3084			// going to a new slide
3085			waitForLayers: false,
3086
3087			// Indicates whether the layers will be scaled automatically
3088			autoScaleLayers: true,
3089
3090			// Sets a reference width which will be compared to the current slider width
3091			// in order to determine how much the layers need to scale down. By default,
3092			// the reference width will be equal to the slide width. However, if the slide width
3093			// is set to a percentage value, then it's necessary to set a specific value for 'autoScaleReference'.
3094			autoScaleReference: -1,
3095
3096			// Called when all animated layers become visible
3097			showLayersComplete: function() {},
3098
3099			// Called when all animated layers become invisible
3100			hideLayersComplete: function() {}
3101		}
3102	};
3103
3104	// Override the slide's 'destroy' method in order to destroy the 
3105	// layers that where added to the slide as well.
3106	var slideDestroy = window.SliderProSlide.prototype.destroy;
3107
3108	window.SliderProSlide.prototype.destroy = function() {
3109		if ( typeof this.layers !== 'undefined' ) {
3110			$.each( this.layers, function( index, element ) {
3111				element.destroy();
3112			});
3113
3114			this.layers.length = 0;
3115		}
3116
3117		if ( typeof this.animatedLayers !== 'undefined' ) {
3118			this.animatedLayers.length = 0;
3119		}
3120
3121		slideDestroy.apply( this );
3122	};
3123
3124	var Layer = function( layer ) {
3125
3126		// Reference to the layer jQuery element
3127		this.$layer = layer;
3128
3129		// Indicates whether a layer is currently visible or hidden
3130		this.visible = false;
3131
3132		// Indicates whether the layer was styled
3133		this.styled = false;
3134
3135		// Holds the data attributes added to the layer
3136		this.data = null;
3137
3138		// Indicates the layer's reference point (topLeft, bottomLeft, topRight or bottomRight)
3139		this.position = null;
3140		
3141		// Indicates which CSS property (left or right) will be used for positioning the layer 
3142		this.horizontalProperty = null;
3143		
3144		// Indicates which CSS property (top or bottom) will be used for positioning the layer 
3145		this.verticalProperty = null;
3146
3147		// Indicates the value of the horizontal position
3148		this.horizontalPosition = null;
3149		
3150		// Indicates the value of the vertical position
3151		this.verticalPosition = null;
3152
3153		// Indicates how much the layers needs to be scaled
3154		this.scaleRatio = 1;
3155
3156		// Indicates the type of supported transition (CSS3 2D, CSS3 3D or JavaScript)
3157		this.supportedAnimation = SliderProUtils.getSupportedAnimation();
3158
3159		// Indicates the required vendor prefix for CSS (i.e., -webkit, -moz, etc.)
3160		this.vendorPrefix = SliderProUtils.getVendorPrefix();
3161
3162		// Indicates the name of the CSS transition's complete event (i.e., transitionend, webkitTransitionEnd, etc.)
3163		this.transitionEvent = SliderProUtils.getTransitionEvent();
3164
3165		// Reference to the timer that will be used to hide/show the layers
3166		this.delayTimer = null;
3167
3168		// Reference to the timer that will be used to hide the layers automatically after a given time interval
3169		this.stayTimer = null;
3170
3171		this._init();
3172	};
3173
3174	Layer.prototype = {
3175
3176		// Initialize the layers
3177		_init: function() {
3178			this.$layer.attr( 'data-layer-init', true );
3179
3180			if ( this.$layer.hasClass( 'sp-static' ) ) {
3181				this._setStyle();
3182			} else {
3183				this.$layer.css({ 'visibility': 'hidden' });
3184			}
3185		},
3186
3187		// Set the size and position of the layer
3188		_setStyle: function() {
3189			this.styled = true;
3190
3191			// Get the data attributes specified in HTML
3192			this.data = this.$layer.data();
3193			
3194			if ( typeof this.data.width !== 'undefined' ) {
3195				this.$layer.css( 'width', this.data.width );
3196			}
3197
3198			if ( typeof this.data.height !== 'undefined' ) {
3199				this.$layer.css( 'height', this.data.height );
3200			}
3201
3202			if ( typeof this.data.depth !== 'undefined' ) {
3203				this.$layer.css( 'z-index', this.data.depth );
3204			}
3205
3206			this.position = this.data.position ? ( this.data.position ).toLowerCase() : 'topleft';
3207
3208			if ( this.position.indexOf( 'right' ) !== -1 ) {
3209				this.horizontalProperty = 'right';
3210			} else if ( this.position.indexOf( 'left' ) !== -1 ) {
3211				this.horizontalProperty = 'left';
3212			} else {
3213				this.horizontalProperty = 'center';
3214			}
3215
3216			if ( this.position.indexOf( 'bottom' ) !== -1 ) {
3217				this.verticalProperty = 'bottom';
3218			} else if ( this.position.indexOf( 'top' ) !== -1 ) {
3219				this.verticalProperty = 'top';
3220			} else {
3221				this.verticalProperty = 'center';
3222			}
3223
3224			this._setPosition();
3225
3226			this.scale( this.scaleRatio );
3227		},
3228
3229		// Set the position of the layer
3230		_setPosition: function() {
3231			var inlineStyle = this.$layer.attr( 'style' );
3232
3233			this.horizontalPosition = typeof this.data.horizontal !== 'undefined' ? this.data.horizontal : 0;
3234			this.verticalPosition = typeof this.data.vertical !== 'undefined' ? this.data.vertical : 0;
3235
3236			// Set the horizontal position of the layer based on the data set
3237			if ( this.horizontalProperty === 'center' ) {
3238				
3239				// prevent content wrapping while setting the width
3240				if ( this.$layer.is( 'img' ) === false && ( typeof inlineStyle === 'undefined' || ( typeof inlineStyle !== 'undefined' && inlineStyle.indexOf( 'width' ) === -1 ) ) ) {
3241					this.$layer.css( 'white-space', 'nowrap' );
3242					this.$layer.css( 'width', this.$layer.outerWidth( true ) );
3243				}
3244
3245				this.$layer.css({ 'marginLeft': 'auto', 'marginRight': 'auto', 'left': this.horizontalPosition, 'right': 0 });
3246			} else {
3247				this.$layer.css( this.horizontalProperty, this.horizontalPosition );
3248			}
3249
3250			// Set the vertical position of the layer based on the data set
3251			if ( this.verticalProperty === 'center' ) {
3252
3253				// prevent content wrapping while setting the height
3254				if ( this.$layer.is( 'img' ) === false && ( typeof inlineStyle === 'undefined' || ( typeof inlineStyle !== 'undefined' && inlineStyle.indexOf( 'height' ) === -1 ) ) ) {
3255					this.$layer.css( 'white-space', 'nowrap' );
3256					this.$layer.css( 'height', this.$layer.outerHeight( true ) );
3257				}
3258
3259				this.$layer.css({ 'marginTop': 'auto', 'marginBottom': 'auto', 'top': this.verticalPosition, 'bottom': 0 });
3260			} else {
3261				this.$layer.css( this.verticalProperty, this.verticalPosition );
3262			}
3263		},
3264
3265		// Scale the layer
3266		scale: function( ratio ) {
3267
3268			// Return if the layer is set to be unscalable
3269			if ( this.$layer.hasClass( 'sp-no-scale' ) ) {
3270				return;
3271			}
3272
3273			// Store the ratio (even if the layer is not ready to be scaled yet)
3274			this.scaleRatio = ratio;
3275
3276			// Return if the layer is not styled yet
3277			if ( this.styled === false ) {
3278				return;
3279			}
3280
3281			var horizontalProperty = this.horizontalProperty === 'center' ? 'left' : this.horizontalProperty,
3282				verticalProperty = this.verticalProperty === 'center' ? 'top' : this.verticalProperty,
3283				css = {};
3284
3285			// Apply the scaling
3286			css[ this.vendorPrefix + 'transform-origin' ] = this.horizontalProperty + ' ' + this.verticalProperty;
3287			css[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3288
3289			// If the position is not set to a percentage value, apply the scaling to the position
3290			if ( typeof this.horizontalPosition !== 'string' ) {
3291				css[ horizontalProperty ] = this.horizontalPosition * this.scaleRatio;
3292			}
3293
3294			// If the position is not set to a percentage value, apply the scaling to the position
3295			if ( typeof this.verticalPosition !== 'string' ) {
3296				css[ verticalProperty ] = this.verticalPosition * this.scaleRatio;
3297			}
3298
3299			// If the width or height is set to a percentage value, increase the percentage in order to
3300			// maintain the same layer to slide proportions. This is necessary because otherwise the scaling
3301			// transform would minimize the layers more than intended.
3302			if ( typeof this.data.width === 'string' && this.data.width.indexOf( '%' ) !== -1 ) {
3303				css.width = ( parseInt( this.data.width, 10 ) / this.scaleRatio ).toString() + '%';
3304			}
3305
3306			if ( typeof this.data.height === 'string' && this.data.height.indexOf( '%' ) !== -1 ) {
3307				css.height = ( parseInt( this.data.height, 10 ) / this.scaleRatio ).toString() + '%';
3308			}
3309
3310			this.$layer.css( css );
3311		},
3312
3313		// Show the layer
3314		show: function( callback ) {
3315			if ( this.visible === true ) {
3316				return;
3317			}
3318
3319			this.visible = true;
3320
3321			// First, style the layer if it's not already styled
3322			if ( this.styled === false ) {
3323				this._setStyle();
3324			}
3325
3326			var that = this,
3327				offset = typeof this.data.showOffset !== 'undefined' ? this.data.showOffset : 50,
3328				duration = typeof this.data.showDuration !== 'undefined' ? this.data.showDuration / 1000 : 0.4,
3329				delay = typeof this.data.showDelay !== 'undefined' ? this.data.showDelay : 10,
3330				stayDuration = typeof that.data.stayDuration !== 'undefined' ? parseInt( that.data.stayDuration, 10 ) : -1;
3331
3332			// Animate the layers with CSS3 or with JavaScript
3333			if ( this.supportedAnimation === 'javascript' ) {
3334				this.$layer
3335					.stop()
3336					.delay( delay )
3337					.css({ 'opacity': 0, 'visibility': 'visible' })
3338					.animate( { 'opacity': 1 }, duration * 1000, function() {
3339
3340						// Hide the layer after a given time interval
3341						if ( stayDuration !== -1 ) {
3342							that.stayTimer = setTimeout(function() {
3343								that.hide();
3344								that.stayTimer = null;
3345							}, stayDuration );
3346						}
3347
3348						if ( typeof callback !== 'undefined' ) {
3349							callback();
3350						}
3351					});
3352			} else {
3353				var start = { 'opacity': 0, 'visibility': 'visible' },
3354					target = { 'opacity': 1 },
3355					transformValues = '';
3356
3357				start[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3358				target[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3359				target[ this.vendorPrefix + 'transition' ] = 'opacity ' + duration + 's';
3360
3361				if ( typeof this.data.showTransition !== 'undefined' ) {
3362					if ( this.data.showTransition === 'left' ) {
3363						transformValues = offset + 'px, 0';
3364					} else if ( this.data.showTransition === 'right' ) {
3365						transformValues = '-' + offset + 'px, 0';
3366					} else if ( this.data.showTransition === 'up' ) {
3367						transformValues = '0, ' + offset + 'px';
3368					} else if ( this.data.showTransition === 'down') {
3369						transformValues = '0, -' + offset + 'px';
3370					}
3371
3372					start[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(' + transformValues + ', 0)' : ' translate(' + transformValues + ')';
3373					target[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(0, 0, 0)' : ' translate(0, 0)';
3374					target[ this.vendorPrefix + 'transition' ] += ', ' + this.vendorPrefix + 'transform ' + duration + 's';
3375				}
3376
3377				// Listen when the layer animation is complete
3378				this.$layer.on( this.transitionEvent, function( event ) {
3379					if ( event.target !== event.currentTarget ) {
3380						return;
3381					}
3382
3383					that.$layer
3384						.off( that.transitionEvent )
3385						.css( that.vendorPrefix + 'transition', '' );
3386
3387					// Hide the layer after a given time interval
3388					if ( stayDuration !== -1 ) {
3389						that.stayTimer = setTimeout(function() {
3390							that.hide();
3391							that.stayTimer = null;
3392						}, stayDuration );
3393					}
3394
3395					if ( typeof callback !== 'undefined' ) {
3396						callback();
3397					}
3398				});
3399
3400				this.$layer.css( start );
3401
3402				this.delayTimer = setTimeout( function() {
3403					that.$layer.css( target );
3404				}, delay );
3405			}
3406		},
3407
3408		// Hide the layer
3409		hide: function( callback ) {
3410			if ( this.visible === false ) {
3411				return;
3412			}
3413
3414			var that = this,
3415				offset = typeof this.data.hideOffset !== 'undefined' ? this.data.hideOffset : 50,
3416				duration = typeof this.data.hideDuration !== 'undefined' ? this.data.hideDuration / 1000 : 0.4,
3417				delay = typeof this.data.hideDelay !== 'undefined' ? this.data.hideDelay : 10;
3418
3419			this.visible = false;
3420
3421			// If the layer is hidden before it hides automatically, clear the timer
3422			if ( this.stayTimer !== null ) {
3423				clearTimeout( this.stayTimer );
3424			}
3425
3426			// Animate the layers with CSS3 or with JavaScript
3427			if ( this.supportedAnimation === 'javascript' ) {
3428				this.$layer
3429					.stop()
3430					.delay( delay )
3431					.animate({ 'opacity': 0 }, duration * 1000, function() {
3432						$( this ).css( 'visibility', 'hidden' );
3433
3434						if ( typeof callback !== 'undefined' ) {
3435							callback();
3436						}
3437					});
3438			} else {
3439				var transformValues = '',
3440					target = { 'opacity': 0 };
3441
3442				target[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3443				target[ this.vendorPrefix + 'transition' ] = 'opacity ' + duration + 's';
3444
3445				if ( typeof this.data.hideTransition !== 'undefined' ) {
3446					if ( this.data.hideTransition === 'left' ) {
3447						transformValues = '-' + offset + 'px, 0';
3448					} else if ( this.data.hideTransition === 'right' ) {
3449						transformValues = offset + 'px, 0';
3450					} else if ( this.data.hideTransition === 'up' ) {
3451						transformValues = '0, -' + offset + 'px';
3452					} else if ( this.data.hideTransition === 'down' ) {
3453						transformValues = '0, ' + offset + 'px';
3454					}
3455
3456					target[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(' + transformValues + ', 0)' : ' translate(' + transformValues + ')';
3457					target[ this.vendorPrefix + 'transition' ] += ', ' + this.vendorPrefix + 'transform ' + duration + 's';
3458				}
3459
3460				// Listen when the layer animation is complete
3461				this.$layer.on( this.transitionEvent, function( event ) {
3462					if ( event.target !== event.currentTarget ) {
3463						return;
3464					}
3465
3466					that.$layer
3467						.off( that.transitionEvent )
3468						.css( that.vendorPrefix + 'transition', '' );
3469
3470					// Hide the layer after transition
3471					if ( that.visible === false ) {
3472						that.$layer.css( 'visibility', 'hidden' );
3473					}
3474
3475					if ( typeof callback !== 'undefined' ) {
3476						callback();
3477					}
3478				});
3479
3480				this.delayTimer = setTimeout( function() {
3481					that.$layer.css( target );
3482				}, delay );
3483			}
3484		},
3485
3486		isVisible: function() {
3487			if ( this.visible === false || this.$layer.is( ':hidden' ) ) {
3488				return false;
3489			}
3490
3491			return true;
3492		},
3493
3494		// Destroy the layer
3495		destroy: function() {
3496			this.$layer.removeAttr( 'style' );
3497			this.$layer.removeAttr( 'data-layer-init' );
3498			clearTimeout( this.delayTimer );
3499			clearTimeout( this.stayTimer );
3500			this.delayTimer = null;
3501			this.stayTimer = null;
3502		}
3503	};
3504
3505	$.SliderPro.addModule( 'Layers', Layers );
3506	
3507})( window, jQuery );
3508
3509// Fade module for Slider Pro.
3510// 
3511// Adds the possibility to navigate through slides using a cross-fade effect.
3512;(function( window, $ ) {
3513
3514	"use strict";
3515
3516	var NS = 'Fade.' + $.SliderPro.namespace;
3517
3518	var Fade = {
3519
3520		// Reference to the original 'gotoSlide' method
3521		fadeGotoSlideReference: null,
3522
3523		initFade: function() {
3524			this.on( 'update.' + NS, $.proxy( this._fadeOnUpdate, this ) );
3525		},
3526
3527		// If fade is enabled, store a reference to the original 'gotoSlide' method
3528		// and then assign a new function to 'gotoSlide'.
3529		_fadeOnUpdate: function() {
3530			if ( this.settings.fade === true ) {
3531				this.fadeGotoSlideReference = this.gotoSlide;
3532				this.gotoSlide = this._fadeGotoSlide;
3533			}
3534		},
3535
3536		// Will replace the original 'gotoSlide' function by adding a cross-fade effect
3537		// between the previous and the next slide.
3538		_fadeGotoSlide: function( index ) {
3539			if ( index === this.selectedSlideIndex ) {
3540				return;
3541			}
3542			
3543			// If the slides are being swiped/dragged, don't use fade, but call the original method instead.
3544			// If not, which means that a new slide was selected through a button, arrows or direct call, then
3545			// use fade.
3546			if ( this.$slider.hasClass( 'sp-swiping' ) ) {
3547				this.fadeGotoSlideReference( index );
3548			} else {
3549				var that = this,
3550					$nextSlide,
3551					$previousSlide,
3552					newIndex = index;
3553
3554				// Loop through all the slides and overlap the previous and next slide,
3555				// and hide the other slides.
3556				$.each( this.slides, function( index, element ) {
3557					var slideIndex = element.getIndex(),
3558						$slide = element.$slide;
3559
3560					if ( slideIndex === newIndex ) {
3561						$slide.css({ 'opacity': 0, 'left': 0, 'top': 0, 'z-index': 20, visibility: 'visible' });
3562						$nextSlide = $slide;
3563					} else if ( slideIndex === that.selectedSlideIndex ) {
3564						$slide.css({ 'opacity': 1, 'left': 0, 'top': 0, 'z-index': 10, visibility: 'visible' });
3565						$previousSlide = $slide;
3566					} else {
3567						$slide.css({ 'opacity': 1, visibility: 'hidden', 'z-index': '' });
3568					}
3569				});
3570
3571				// Set the new indexes for the previous and selected slides
3572				this.previousSlideIndex = this.selectedSlideIndex;
3573				this.selectedSlideIndex = index;
3574
3575				// Re-assign the 'sp-selected' class to the currently selected slide
3576				this.$slides.find( '.sp-selected' ).removeClass( 'sp-selected' );
3577				this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected' );
3578			
3579				// Rearrange the slides if the slider is loop-able
3580				if ( that.settings.loop === true ) {
3581					that._updateSlidesOrder();
3582				}
3583
3584				// Move the slides container so that the cross-fading slides (which now have the top and left
3585				// position set to 0) become visible.
3586				this._moveTo( 0, true );
3587
3588				// Fade in the selected slide
3589				this._fadeSlideTo( $nextSlide, 1, function() {
3590
3591					// This flag will indicate if all the fade transitions are complete,
3592					// in case there are multiple running at the same time, which happens
3593					// when the slides are navigated very quickly
3594					var allTransitionsComplete = true;
3595
3596					// Go through all the slides and check if there is at least one slide 
3597					// that is still transitioning.
3598					$.each( that.slides, function( index, element ) {
3599						if ( typeof element.$slide.attr( 'data-transitioning' ) !== 'undefined' ) {
3600							allTransitionsComplete = false;
3601						}
3602					});
3603
3604					if ( allTransitionsComplete === true ) {
3605
3606						// After all the transitions are complete, make all the slides visible again
3607						$.each( that.slides, function( index, element ) {
3608							var $slide = element.$slide;
3609							$slide.css({ 'visibility': '', 'opacity': '', 'z-index': '' });
3610						});
3611						
3612						// Reset the position of the slides and slides container
3613						that._resetSlidesPosition();
3614					}
3615
3616					// Fire the 'gotoSlideComplete' event
3617					that.trigger({ type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex });
3618					if ( $.isFunction( that.settings.gotoSlideComplete ) ) {
3619						that.settings.gotoSlideComplete.call( that, { type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex } );
3620					}
3621				});
3622
3623				// Fade out the previous slide, if indicated, in addition to fading in the next slide
3624				if ( this.settings.fadeOutPreviousSlide === true ) {
3625					this._fadeSlideTo( $previousSlide, 0 );
3626				}
3627
3628				if ( this.settings.autoHeight === true ) {
3629					this._resizeHeight();
3630				}
3631
3632				// Fire the 'gotoSlide' event
3633				this.trigger({ type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex });
3634				if ( $.isFunction( this.settings.gotoSlide ) ) {
3635					this.settings.gotoSlide.call( this, { type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex });
3636				}
3637			}
3638		},
3639
3640		// Fade the target slide to the specified opacity (0 or 1)
3641		_fadeSlideTo: function( target, opacity, callback ) {
3642			var that = this;
3643
3644			// apply the attribute only to slides that fade in
3645			if ( opacity === 1 ) {
3646				target.attr( 'data-transitioning', true );
3647			}
3648
3649			// Use CSS transitions if they are supported. If not, use JavaScript animation.
3650			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
3651
3652				// There needs to be a delay between the moment the opacity is set
3653				// and the moment the transitions starts.
3654				setTimeout(function(){
3655					var css = { 'opacity': opacity };
3656					css[ that.vendorPrefix + 'transition' ] = 'opacity ' + that.settings.fadeDuration / 1000 + 's';
3657					target.css( css );
3658				}, 100 );
3659
3660				target.on( this.transitionEvent, function( event ) {
3661					if ( event.target !== event.currentTarget ) {
3662						return;
3663					}
3664					
3665					target.off( that.transitionEvent );
3666					target.css( that.vendorPrefix + 'transition', '' );
3667					target.removeAttr( 'data-transitioning');
3668
3669					if ( typeof callback === 'function' ) {
3670						callback();
3671					}
3672				});
3673			} else {
3674				target.stop().animate({ 'opacity': opacity }, this.settings.fadeDuration, function() {
3675					target.removeAttr( 'data-transitioning' );
3676
3677					if ( typeof callback === 'function' ) {
3678						callback();
3679					}
3680				});
3681			}
3682		},
3683
3684		// Destroy the module
3685		destroyFade: function() {
3686			this.off( 'update.' + NS );
3687
3688			if ( this.fadeGotoSlideReference !== null ) {
3689				this.gotoSlide = this.fadeGotoSlideReference;
3690			}
3691		},
3692
3693		fadeDefaults: {
3694
3695			// Indicates if fade will be used
3696			fade: false,
3697
3698			// Indicates if the previous slide will be faded out (in addition to the next slide being faded in)
3699			fadeOutPreviousSlide: true,
3700
3701			// Sets the duration of the fade effect
3702			fadeDuration: 500
3703		}
3704	};
3705
3706	$.SliderPro.addModule( 'Fade', Fade );
3707
3708})( window, jQuery );
3709
3710// Touch Swipe module for Slider Pro.
3711// 
3712// Adds touch-swipe functionality for slides.
3713;(function( window, $ ) {
3714
3715	"use strict";
3716	
3717	var NS = 'TouchSwipe.' + $.SliderPro.namespace;
3718
3719	var TouchSwipe = {
3720
3721		// The x and y coordinates of the pointer/finger's starting position
3722		touchStartPoint: {x: 0, y: 0},
3723
3724		// The x and y coordinates of the pointer/finger's end position
3725		touchEndPoint: {x: 0, y: 0},
3726
3727		// The distance from the starting to the end position on the x and y axis
3728		touchDistance: {x: 0, y: 0},
3729
3730		// The position of the slides when the touch swipe starts
3731		touchStartPosition: 0,
3732
3733		// Indicates if the slides are being swiped
3734		isTouchMoving: false,
3735
3736		// Stores the names of the events
3737		touchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' },
3738
3739		// Indicates if scrolling (the page) in the opposite direction of the
3740		// slides' layout is allowed. This is used to block vertical (or horizontal)
3741		// scrolling when the user is scrolling through the slides.
3742		allowOppositeScrolling: true,
3743
3744		
vendor: 7,846 bytes, lines 3744-3932
3744initTouchSwipe: function() {
3745			var that = this;
3746
3747			// check if touch swipe is enabled
3748			if ( this.settings.touchSwipe === false ) {
3749				return;
3750			}
3751
3752			this.touchSwipeEvents.startEvent = 'touchstart' + '.' + NS + ' mousedown' + '.' + NS;
3753			this.touchSwipeEvents.moveEvent = 'touchmove' + '.' + NS + ' mousemove' + '.' + NS;
3754			this.touchSwipeEvents.endEvent = 'touchend' + '.' + this.uniqueId + '.' + NS + ' mouseup' + '.' + this.uniqueId + '.' + NS;
3755
3756			// Listen for touch swipe/mouse move events
3757			this.$slidesMask.on( this.touchSwipeEvents.startEvent, $.proxy( this._onTouchStart, this ) );
3758			this.$slidesMask.on( 'dragstart.' + NS, function( event ) {
3759				event.preventDefault();
3760			});
3761
3762			// Prevent 'click' events unless there is intention for a 'click'
3763			this.$slidesMask.find( 'a' ).on( 'click.' + NS, function( event ) {
3764				if ( typeof event.originalEvent.touches === 'undefined' && that.$slider.hasClass( 'sp-swiping' ) ) {
3765					event.preventDefault();
3766				}
3767			});
3768
3769			// Add the grabbing icon
3770			this.$slidesMask.addClass( 'sp-grab' );
3771		},
3772
3773		// Called when the slides starts being dragged
3774		_onTouchStart: function( event ) {
3775
3776			// Disable dragging if the element is set to allow selections
3777			if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) {
3778				return;
3779			}
3780
3781			var that = this,
3782				eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
3783
3784			// Get the initial position of the mouse pointer and the initial position
3785			// of the slides' container
3786			this.touchStartPoint.x = eventObject.pageX || eventObject.clientX;
3787			this.touchStartPoint.y = eventObject.pageY || eventObject.clientY;
3788			this.touchStartPosition = this.slidesPosition;
3789
3790			// Clear the previous distance values
3791			this.touchDistance.x = this.touchDistance.y = 0;
3792
3793			// If the slides are being grabbed while they're still animating, stop the
3794			// current movement
3795			if ( this.$slides.hasClass( 'sp-animated' ) ) {
3796				this.isTouchMoving = true;
3797				this._stopMovement();
3798				this.touchStartPosition = this.slidesPosition;
3799			}
3800
3801			// Listen for move and end events
3802			$( document ).on( this.touchSwipeEvents.moveEvent, $.proxy( this._onTouchMove, this ) );
3803			$( document ).on( this.touchSwipeEvents.endEvent, $.proxy( this._onTouchEnd, this ) );
3804
3805			// Swap grabbing icons
3806			this.$slidesMask.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' );
3807
3808			// Add 'sp-swiping' class to indicate that the slides are being swiped
3809			this.$slider.addClass( 'sp-swiping' );
3810		},
3811
3812		// Called during the slides' dragging
3813		_onTouchMove: function( event ) {
3814			var eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
3815
3816			// Indicate that the move event is being fired
3817			this.isTouchMoving = true;
3818
3819			// Get the current position of the mouse pointer
3820			this.touchEndPoint.x = eventObject.pageX || eventObject.clientX;
3821			this.touchEndPoint.y = eventObject.pageY || eventObject.clientY;
3822
3823			// Calculate the distance of the movement on both axis
3824			this.touchDistance.x = this.touchEndPoint.x - this.touchStartPoint.x;
3825			this.touchDistance.y = this.touchEndPoint.y - this.touchStartPoint.y;
3826			
3827			// Calculate the distance of the swipe that takes place in the same direction as the orientation of the slides
3828			// and calculate the distance from the opposite direction.
3829			// 
3830			// For a swipe to be valid there should more distance in the same direction as the orientation of the slides.
3831			var distance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y,
3832				oppositeDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.y : this.touchDistance.x;
3833
3834			// If the movement is in the same direction as the orientation of the slides, the swipe is valid
3835			// and opposite scrolling will not be allowed.
3836			if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) {
3837				this.allowOppositeScrolling = false;
3838			}
3839
3840			// If opposite scrolling is still allowed, the swipe wasn't valid, so return.
3841			if ( this.allowOppositeScrolling === true ) {
3842				return;
3843			}
3844			
3845			// Don't allow opposite scrolling
3846			event.preventDefault();
3847
3848			if ( this.settings.loop === false ) {
3849				// Make the slides move slower if they're dragged outside its bounds
3850				if ( ( this.slidesPosition > this.touchStartPosition && this.selectedSlideIndex === 0 ) ||
3851					( this.slidesPosition < this.touchStartPosition && this.selectedSlideIndex === this.getTotalSlides() - 1 )
3852				) {
3853					distance = distance * 0.2;
3854				}
3855			}
3856
3857			this._moveTo( this.touchStartPosition + distance, true );
3858		},
3859
3860		// Called when the slides are released
3861		_onTouchEnd: function( event ) {
3862			var that = this,
3863				touchDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y;
3864
3865			// Remove the 'move' and 'end' listeners
3866			$( document ).off( this.touchSwipeEvents.moveEvent );
3867			$( document ).off( this.touchSwipeEvents.endEvent );
3868			
3869			this.allowOppositeScrolling = true;
3870
3871			// Swap grabbing icons
3872			this.$slidesMask.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' );
3873
3874			// Check if there is intention for a tap and remove
3875			// the 'sp-swiping' class if that's the case
3876			if ( this.isTouchMoving === false || this.isTouchMoving === true && Math.abs( this.touchDistance.x ) < 10 && Math.abs( this.touchDistance.y ) < 10 ) {
3877				this.$slider.removeClass( 'sp-swiping' );
3878			}
3879
3880			// Remove the 'sp-swiping' class anyway, even if there was a swipe,
3881			// but in this case remove it with a delay, because there might be 
3882			// other event listeners that check the existence of this class,
3883			// and this class should still be applied for those listeners
3884			setTimeout(function() {
3885				that.$slider.removeClass( 'sp-swiping' );
3886			}, 1 );
3887
3888			// Return if the slides didn't move
3889			if ( this.isTouchMoving === false ) {
3890				return;
3891			}
3892
3893			this.isTouchMoving = false;
3894
3895			// Calculate the old position of the slides in order to return to it if the swipe
3896			// is below the threshold
3897			var selectedSlideOffset = this.settings.centerSelectedSlide === true ? Math.round( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10 ) - this.getSlideAt( this.selectedSlideIndex ).getSize()[ this.sizeProperty ] ) / 2 ) : 0,
3898				oldSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + selectedSlideOffset;
3899
3900			if ( Math.abs( touchDistance ) < this.settings.touchSwipeThreshold ) {
3901				this._moveTo( oldSlidesPosition );
3902			} else {
3903				
3904				// Calculate by how many slides the slides container has moved
3905				var	slideArrayDistance = ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ? -1 : 1 ) * touchDistance / ( this.averageSlideSize + this.settings.slideDistance );
3906
3907				// Floor the obtained value and add or subtract 1, depending on the direction of the swipe
3908				slideArrayDistance = parseInt( slideArrayDistance, 10 ) + ( slideArrayDistance > 0 ? 1 : - 1 );
3909
3910				// Get the index of the currently selected slide and subtract the position index in order to obtain
3911				// the new index of the selected slide. 
3912				var nextSlideIndex = this.slidesOrder[ $.inArray( this.selectedSlideIndex, this.slidesOrder ) - slideArrayDistance ];
3913
3914				if ( this.settings.loop === true ) {
3915					this.gotoSlide( nextSlideIndex );
3916				} else {
3917					if ( typeof nextSlideIndex !== 'undefined' ) {
3918						this.gotoSlide( nextSlideIndex );
3919					} else {
3920						this._moveTo( oldSlidesPosition );
3921					}
3922				}
3923			}
3924		},
3925
3926		// Destroy the module
3927		destroyTouchSwipe: function() {
3928			this.$slidesMask.off( 'dragstart.' + NS );
3929			this.$slidesMask.find( 'a' ).off( 'click.' + NS );
3930
3931			this.$slidesMask.off( this.touchSwipeEvents.startEvent );
3932			$( document )
vendor: 13,283 bytes, lines 3932-4394
3932.off( this.touchSwipeEvents.moveEvent );
3933			$( document ).off( this.touchSwipeEvents.endEvent );
3934			
3935			this.$slidesMask.removeClass( 'sp-grab' );
3936		},
3937
3938		touchSwipeDefaults: {
3939			
3940			// Indicates whether the touch swipe will be enabled
3941			touchSwipe: true,
3942
3943			// Sets the minimum amount that the slides should move
3944			touchSwipeThreshold: 50
3945		}
3946	};
3947
3948	$.SliderPro.addModule( 'TouchSwipe', TouchSwipe );
3949	
3950})( window, jQuery );
3951
3952// Caption module for Slider Pro.
3953// 
3954// Adds a corresponding caption for each slide. The caption
3955// will appear and disappear with the slide.
3956;(function( window, $ ) {
3957
3958	"use strict";
3959	
3960	var NS = 'Caption.' + $.SliderPro.namespace;
3961
3962	var Caption = {
3963
3964		// Reference to the container element that will hold the caption
3965		$captionContainer: null,
3966
3967		// The caption content/text
3968		captionContent: '',
3969
3970		initCaption: function() {
3971			this.on( 'update.' + NS, $.proxy( this._captionOnUpdate, this ) );
3972			this.on( 'gotoSlide.' + NS, $.proxy( this._updateCaptionContent, this ) );
3973		},
3974
3975		// Create the caption container and hide the captions inside the slides
3976		_captionOnUpdate: function() {
3977			this.$captionContainer = this.$slider.find( '.sp-caption-container' );
3978
3979			if ( this.$slider.find( '.sp-caption' ).length && this.$captionContainer.length === 0 ) {
3980				this.$captionContainer = $( '<div class="sp-caption-container"></div>' ).appendTo( this.$slider );
3981
3982				// Show the caption for the selected slide
3983				this._updateCaptionContent();
3984			}
3985
3986			// Hide the captions inside the slides
3987			this.$slides.find( '.sp-caption' ).each(function() {
3988				$( this ).css( 'display', 'none' );
3989			});
3990		},
3991
3992		// Show the caption content for the selected slide
3993		_updateCaptionContent: function() {
3994			var that = this,
3995				newCaptionField = this.$slider.find( '.sp-slide' ).eq( this.selectedSlideIndex ).find( '.sp-caption' ),
3996				newCaptionContent = newCaptionField.length !== 0 ? newCaptionField.html() : '';
3997
3998			// Either use a fade effect for swapping the captions or use an instant change
3999			if ( this.settings.fadeCaption === true ) {
4000				
4001				// If the previous slide had a caption, fade out that caption first and when the animation is over
4002				// fade in the current caption.
4003				// If the previous slide didn't have a caption, fade in the current caption directly.
4004				if ( this.captionContent !== '' ) {
4005
4006					// If the caption container has 0 opacity when the fade out transition starts, set it
4007					// to 1 because the transition wouldn't work if the initial and final values are the same,
4008					// and the callback functions wouldn't fire in this case.
4009					if ( parseFloat( this.$captionContainer.css( 'opacity' ), 10 ) === 0 ) {
4010						this.$captionContainer.css( this.vendorPrefix + 'transition', '' );
4011						this.$captionContainer.css( 'opacity', 1 );
4012					}
4013
4014					this._fadeCaptionTo( 0, function() {
4015						that.captionContent = newCaptionContent;
4016
4017						if ( newCaptionContent !== '' ) {
4018							that.$captionContainer.html( that.captionContent );
4019							that._fadeCaptionTo( 1 );
4020						} else {
4021							that.$captionContainer.empty();
4022						}
4023					});
4024				} else {
4025					this.captionContent = newCaptionContent;
4026					this.$captionContainer.html( this.captionContent );
4027					this.$captionContainer.css( 'opacity', 0 );
4028					this._fadeCaptionTo( 1 );
4029				}
4030			} else {
4031				this.captionContent = newCaptionContent;
4032				this.$captionContainer.html( this.captionContent );
4033			}
4034		},
4035
4036		// Fade the caption container to the specified opacity
4037		_fadeCaptionTo: function( opacity, callback ) {
4038			var that = this;
4039
4040			// Use CSS transitions if they are supported. If not, use JavaScript animation.
4041			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
4042				
4043				// There needs to be a delay between the moment the opacity is set
4044				// and the moment the transitions starts.
4045				setTimeout(function(){
4046					var css = { 'opacity': opacity };
4047					css[ that.vendorPrefix + 'transition' ] = 'opacity ' + that.settings.captionFadeDuration / 1000 + 's';
4048					that.$captionContainer.css( css );
4049				}, 1 );
4050
4051				this.$captionContainer.on( this.transitionEvent, function( event ) {
4052					if ( event.target !== event.currentTarget ) {
4053						return;
4054					}
4055
4056					that.$captionContainer.off( that.transitionEvent );
4057					that.$captionContainer.css( that.vendorPrefix + 'transition', '' );
4058
4059					if ( typeof callback === 'function' ) {
4060						callback();
4061					}
4062				});
4063			} else {
4064				this.$captionContainer.stop().animate({ 'opacity': opacity }, this.settings.captionFadeDuration, function() {
4065					if ( typeof callback === 'function' ) {
4066						callback();
4067					}
4068				});
4069			}
4070		},
4071
4072		// Destroy the module
4073		destroyCaption: function() {
4074			this.off( 'update.' + NS );
4075			this.off( 'gotoSlide.' + NS );
4076
4077			this.$captionContainer.remove();
4078
4079			this.$slider.find( '.sp-caption' ).each(function() {
4080				$( this ).css( 'display', '' );
4081			});
4082		},
4083
4084		captionDefaults: {
4085
4086			// Indicates whether or not the captions will be faded
4087			fadeCaption: true,
4088
4089			// Sets the duration of the fade animation
4090			captionFadeDuration: 500
4091		}
4092	};
4093
4094	$.SliderPro.addModule( 'Caption', Caption );
4095	
4096})( window, jQuery );
4097
4098// Deep Linking module for Slider Pro.
4099// 
4100// Updates the hash of the URL as the user navigates through the slides.
4101// Also, allows navigating to a specific slide by indicating it in the hash.
4102;(function( window, $ ) {
4103
4104	"use strict";
4105
4106	var NS = 'DeepLinking.' + $.SliderPro.namespace;
4107
4108	var DeepLinking = {
4109
4110		initDeepLinking: function() {
4111			var that = this;
4112
4113			// Parse the initial hash
4114			this.on( 'init.' + NS, function() {
4115				that._gotoHash( window.location.hash );
4116			});
4117
4118			// Update the hash when a new slide is selected
4119			this.on( 'gotoSlide.' + NS, function( event ) {
4120				if ( that.settings.updateHash === true ) {
4121
4122					// get the 'id' attribute of the slide
4123					var slideId = that.$slider.find( '.sp-slide' ).eq( event.index ).attr( 'id' );
4124
4125					// if the slide doesn't have an 'id' attribute, use the slide index
4126					if ( typeof slideId === 'undefined' ) {
4127						slideId = event.index;
4128					}
4129
4130					window.location.hash = that.$slider.attr( 'id' ) + '/' + slideId;
4131				}
4132			});
4133
4134			// Check when the hash changes and navigate to the indicated slide
4135			$( window ).on( 'hashchange.' + this.uniqueId + '.' + NS, function() {
4136				that._gotoHash( window.location.hash );
4137			});
4138		},
4139
4140		// Parse the hash and return the slider id and the slide id
4141		_parseHash: function( hash ) {
4142			if ( hash !== '' ) {
4143				// Eliminate the # symbol
4144				hash = hash.substring(1);
4145
4146				// Get the specified slider id and slide id
4147				var values = hash.split( '/' ),
4148					slideId = values.pop(),
4149					sliderId = hash.slice( 0, - slideId.toString().length - 1 );
4150
4151				if ( this.$slider.attr( 'id' ) === sliderId ) {
4152					return { 'sliderID': sliderId, 'slideId': slideId };
4153				}
4154			}
4155
4156			return false;
4157		},
4158
4159		// Navigate to the appropriate slide, based on the specified hash
4160		_gotoHash: function( hash ) {
4161			var result = this._parseHash( hash );
4162
4163			if ( result === false ) {
4164				return;
4165			}
4166
4167			var slideId = result.slideId,
4168				slideIdNumber = parseInt( slideId, 10 );
4169
4170			// check if the specified slide id is a number or string
4171			if ( isNaN( slideIdNumber ) ) {
4172				// get the index of the slide based on the specified id
4173				var slideIndex = this.$slider.find( '.sp-slide#' + slideId ).index();
4174
4175				if ( slideIndex !== -1 && slideIndex !== this.selectedSlideIndex ) {
4176					this.gotoSlide( slideIndex );
4177				}
4178			} else if ( slideIdNumber !== this.selectedSlideIndex ) {
4179				this.gotoSlide( slideIdNumber );
4180			}
4181		},
4182
4183		// Destroy the module
4184		destroyDeepLinking: function() {
4185			this.off( 'init.' + NS );
4186			this.off( 'gotoSlide.' + NS );
4187			$( window ).off( 'hashchange.' + this.uniqueId + '.' + NS );
4188		},
4189
4190		deepLinkingDefaults: {
4191
4192			// Indicates whether the hash will be updated when a new slide is selected
4193			updateHash: false
4194		}
4195	};
4196
4197	$.SliderPro.addModule( 'DeepLinking', DeepLinking );
4198	
4199})( window, jQuery );
4200
4201// Autoplay module for Slider Pro.
4202// 
4203// Adds automatic navigation through the slides by calling the
4204// 'nextSlide' or 'previousSlide' methods at certain time intervals.
4205;(function( window, $ ) {
4206
4207	"use strict";
4208	
4209	var NS = 'Autoplay.' + $.SliderPro.namespace;
4210
4211	var Autoplay = {
4212
4213		autoplayTimer: null,
4214
4215		isTimerRunning: false,
4216
4217		isTimerPaused: false,
4218
4219		initAutoplay: function() {
4220			this.on( 'update.' + NS, $.proxy( this._autoplayOnUpdate, this ) );
4221		},
4222
4223		// Start the autoplay if it's enabled, or stop it if it's disabled but running 
4224		_autoplayOnUpdate: function( event ) {
4225			if ( this.settings.autoplay === true ) {
4226				this.on( 'gotoSlide.' + NS, $.proxy( this._autoplayOnGotoSlide, this ) );
4227				this.on( 'mouseenter.' + NS, $.proxy( this._autoplayOnMouseEnter, this ) );
4228				this.on( 'mouseleave.' + NS, $.proxy( this._autoplayOnMouseLeave, this ) );
4229
4230				this.startAutoplay();
4231			} else {
4232				this.off( 'gotoSlide.' + NS );
4233				this.off( 'mouseenter.' + NS );
4234				this.off( 'mouseleave.' + NS );
4235
4236				this.stopAutoplay();
4237			}
4238		},
4239
4240		// Restart the autoplay timer when a new slide is selected
4241		_autoplayOnGotoSlide: function( event ) {
4242			// stop previous timers before starting a new one
4243			if ( this.isTimerRunning === true ) {
4244				this.stopAutoplay();
4245			}
4246			
4247			if ( this.isTimerPaused === false ) {
4248				this.startAutoplay();
4249			}
4250		},
4251
4252		// Pause the autoplay when the slider is hovered
4253		_autoplayOnMouseEnter: function( event ) {
4254			if ( this.isTimerRunning && ( this.settings.autoplayOnHover === 'pause' || this.settings.autoplayOnHover === 'stop' ) ) {
4255				this.stopAutoplay();
4256				this.isTimerPaused = true;
4257			}
4258		},
4259
4260		// Start the autoplay when the mouse moves away from the slider
4261		_autoplayOnMouseLeave: function( event ) {
4262			if ( this.settings.autoplay === true && this.isTimerRunning === false && this.settings.autoplayOnHover !== 'stop' ) {
4263				this.startAutoplay();
4264				this.isTimerPaused = false;
4265			}
4266		},
4267
4268		// Starts the autoplay
4269		startAutoplay: function() {
4270			var that = this;
4271			
4272			this.isTimerRunning = true;
4273
4274			this.autoplayTimer = setTimeout(function() {
4275				if ( that.settings.autoplayDirection === 'normal' ) {
4276					that.nextSlide();
4277				} else if ( that.settings.autoplayDirection === 'backwards' ) {
4278					that.previousSlide();
4279				}
4280			}, this.settings.autoplayDelay );
4281		},
4282
4283		// Stops the autoplay
4284		stopAutoplay: function() {
4285			this.isTimerRunning = false;
4286			this.isTimerPaused = false;
4287
4288			clearTimeout( this.autoplayTimer );
4289		},
4290
4291		// Destroy the module
4292		destroyAutoplay: function() {
4293			clearTimeout( this.autoplayTimer );
4294
4295			this.off( 'update.' + NS );
4296			this.off( 'gotoSlide.' + NS );
4297			this.off( 'mouseenter.' + NS );
4298			this.off( 'mouseleave.' + NS );
4299		},
4300
4301		autoplayDefaults: {
4302			// Indicates whether or not autoplay will be enabled
4303			autoplay: true,
4304
4305			// Sets the delay/interval at which the autoplay will run
4306			autoplayDelay: 5000,
4307
4308			// Indicates whether autoplay will navigate to the next slide or previous slide
4309			autoplayDirection: 'normal',
4310
4311			// Indicates if the autoplay will be paused or stopped when the slider is hovered.
4312			// Possible values are 'pause', 'stop' or 'none'.
4313			autoplayOnHover: 'pause'
4314		}
4315	};
4316
4317	$.SliderPro.addModule( 'Autoplay', Autoplay );
4318	
4319})(window, jQuery);
4320
4321// Keyboard module for Slider Pro.
4322// 
4323// Adds the possibility to navigate through slides using the keyboard arrow keys, or
4324// open the link attached to the main slide image by using the Enter key.
4325;(function( window, $ ) {
4326
4327	"use strict";
4328	
4329	var NS = 'Keyboard.' + $.SliderPro.namespace;
4330
4331	var Keyboard = {
4332
4333		initKeyboard: function() {
4334			var that = this,
4335				hasFocus = false;
4336
4337			if ( this.settings.keyboard === false ) {
4338				return;
4339			}
4340
4341			// Detect when the slide is in focus and when it's not, and, optionally, make it
4342			// responsive to keyboard input only when it's in focus
4343			this.$slider.on( 'focus.' + NS, function() {
4344				hasFocus = true;
4345			});
4346
4347			this.$slider.on( 'blur.' + NS, function() {
4348				hasFocus = false;
4349			});
4350
4351			$( document ).on( 'keydown.' + this.uniqueId + '.' + NS, function( event ) {
4352				if ( that.settings.keyboardOnlyOnFocus === true && hasFocus === false ) {
4353					return;
4354				}
4355
4356				// If the left arrow key is pressed, go to the previous slide.
4357				// If the right arrow key is pressed, go to the next slide.
4358				// If the Enter key is pressed, open the link attached to the main slide image.
4359				if ( event.which === 37 ) {
4360					that.previousSlide();
4361				} else if ( event.which === 39 ) {
4362					that.nextSlide();
4363				} else if ( event.which === 13 ) {
4364					var link = that.$slider.find( '.sp-slide' ).eq( that.selectedSlideIndex ).find( '.sp-image-container a' );
4365					
4366					if ( link.length !== 0 ) {
4367						link[0].click();
4368					}
4369				}
4370			});
4371		},
4372
4373		// Destroy the module
4374		destroyKeyboard: function() {
4375			this.$slider.off( 'focus.' + NS );
4376			this.$slider.off( 'blur.' + NS );
4377			$( document ).off( 'keydown.' + this.uniqueId + '.' + NS );
4378		},
4379
4380		keyboardDefaults: {
4381
4382			// Indicates whether keyboard navigation will be enabled
4383			keyboard: true,
4384
4385			// Indicates whether the slider will respond to keyboard input only when
4386			// the slider is in focus.
4387			keyboardOnlyOnFocus: false
4388		}
4389	};
4390
4391	$.SliderPro.addModule( 'Keyboard', Keyboard );
4392	
4393})( window, jQuery );
4394
vendor: 4,130 bytes, lines 4395-4498
4395// Full Screen module for Slider Pro.
4396// 
4397// Adds the possibility to open the slider full-screen, using the HMTL5 FullScreen API.
4398;(function( window, $ ) {
4399
4400	"use strict";
4401
4402	var NS = 'FullScreen.' + $.SliderPro.namespace;
4403
4404	var FullScreen = {
4405
4406		// Indicates whether the slider is currently in full-screen mode
4407		isFullScreen: false,
4408
4409		// Reference to the full-screen button
4410		$fullScreenButton: null,
4411
4412		// Reference to a set of settings that influence the slider's size
4413		// before it goes full-screen
4414		sizeBeforeFullScreen: {},
4415
4416		initFullScreen: function() {
4417			if ( ! ( document.fullscreenEnabled ||
4418				document.webkitFullscreenEnabled ||
4419				document.mozFullScreenEnabled ||
4420				document.msFullscreenEnabled ) ) {
4421				return;
4422			}
4423		
4424			this.on( 'update.' + NS, $.proxy( this._fullScreenOnUpdate, this ) );
4425		},
4426
4427		// Create or remove the full-screen button depending on the value of the 'fullScreen' option
4428		_fullScreenOnUpdate: function() {
4429			if ( this.settings.fullScreen === true && this.$fullScreenButton === null ) {
4430				this._addFullScreen();
4431			} else if ( this.settings.fullScreen === false && this.$fullScreenButton !== null ) {
4432				this._removeFullScreen();
4433			}
4434
4435			if ( this.settings.fullScreen === true ) {
4436				if ( this.settings.fadeFullScreen === true ) {
4437					this.$fullScreenButton.addClass( 'sp-fade-full-screen' );
4438				} else if ( this.settings.fadeFullScreen === false ) {
4439					this.$fullScreenButton.removeClass( 'sp-fade-full-screen' );
4440				}
4441			}
4442		},
4443
4444		// Create the full-screen button
4445		_addFullScreen: function() {
4446			this.$fullScreenButton = $('<div class="sp-full-screen-button"></div>').appendTo( this.$slider );
4447			this.$fullScreenButton.on( 'click.' + NS, $.proxy( this._onFullScreenButtonClick, this ) );
4448
4449			document.addEventListener( 'fullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4450			document.addEventListener( 'mozfullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4451			document.addEventListener( 'webkitfullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4452			document.addEventListener( 'MSFullscreenChange', $.proxy( this._onFullScreenChange, this ) );
4453		},
4454
4455		// Remove the full-screen button
4456		_removeFullScreen: function() {
4457			if ( this.$fullScreenButton !== null ) {
4458				this.$fullScreenButton.off( 'click.' + NS );
4459				this.$fullScreenButton.remove();
4460				this.$fullScreenButton = null;
4461				document.removeEventListener( 'fullscreenchange', this._onFullScreenChange );
4462				document.removeEventListener( 'mozfullscreenchange', this._onFullScreenChange );
4463				document.removeEventListener( 'webkitfullscreenchange', this._onFullScreenChange );
4464				document.removeEventListener( 'MSFullscreenChange', this._onFullScreenChange );
4465			}
4466		},
4467
4468		// When the full-screen button is clicked, put the slider into full-screen mode, and
4469		// take it out of the full-screen mode when it's clicked again.
4470		_onFullScreenButtonClick: function() {
4471			if ( this.isFullScreen === false ) {
4472				if ( this.instance.requestFullScreen ) {
4473					this.instance.requestFullScreen();
4474				} else if ( this.instance.mozRequestFullScreen ) {
4475					this.instance.mozRequestFullScreen();
4476				} else if ( this.instance.webkitRequestFullScreen ) {
4477					this.instance.webkitRequestFullScreen();
4478				} else if ( this.instance.msRequestFullscreen ) {
4479					this.instance.msRequestFullscreen();
4480				}
4481			} else {
4482				if ( document.exitFullScreen ) {
4483					document.exitFullScreen();
4484				} else if ( document.mozCancelFullScreen ) {
4485					document.mozCancelFullScreen();
4486				} else if ( document.webkitCancelFullScreen ) {
4487					document.webkitCancelFullScreen();
4488				} else if ( document.msExitFullscreen ) {
4489					document.msExitFullscreen();
4490				}
4491			}
4492		},
4493
4494		// This will be called whenever the full-screen mode changes.
4495		// If the slider is in full-screen mode, set it to 'full window', and if it's
4496		// not in full-screen mode anymore, set it back to the original size.
4497		_onFullScreenChange: function() {
4498			this.isFullScreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement ? true : false;
4499
4500			if ( this.isFullScreen === true ) {
4501				this.sizeBeforeFullScreen = { forceSize: this.settings.forceSize, autoHeight: this.settings.autoHeight };
4502				this.$slider.addClass( 'sp-full-screen' );
4503				this.settings.forceSize = 'fullWindow';
4504				this.settings.autoHeight = false;
4505			} else {
4506				this.$slider.css( 'margin', '' );
4507				this.$slider.removeClass( 'sp-full-screen' );
4508				this.settings.forceSize = this.sizeBeforeFullScreen.forceSize;
4509				this.settings.autoHeight = this.sizeBeforeFullScreen.autoHeight;
4510			}
4511
4512			this.resize();
4513		},
4514
4515		// Destroy the module
4516		destroyFullScreen: function() {
4517			this.off( 'update.' + NS );
4518			this._removeFullScreen();
4519		},
4520
4521		fullScreenDefaults: {
4522
4523			// Indicates whether the full-screen button is enabled
4524			fullScreen: false,
4525
4526			// Indicates whether the button will fade in only on hover
4527			fadeFullScreen: true
4528		}
4529	};
4530
4531	$.SliderPro.addModule( 'FullScreen', FullScreen );
4532
4533})( window, jQuery );
vendor: 7,005 bytes, lines 4533-4773
4533
4534
4535// Buttons module for Slider Pro.
4536// 
4537// Adds navigation buttons at the bottom of the slider.
4538;(function( window, $ ) {
4539
4540	"use strict";
4541	
4542	var NS = 'Buttons.' + $.SliderPro.namespace;
4543
4544	var Buttons = {
4545
4546		// Reference to the buttons container
4547		$buttons: null,
4548
4549		initButtons: function() {
4550			this.on( 'update.' + NS, $.proxy( this._buttonsOnUpdate, this ) );
4551		},
4552
4553		_buttonsOnUpdate: function() {
4554			this.$buttons = this.$slider.find('.sp-buttons');
4555			
4556			// If there is more that one slide but the buttons weren't created yet, create the buttons.
4557			// If the buttons were created but their number differs from the total number of slides, re-create the buttons.
4558			// If the buttons were created but there are less than one slide, remove the buttons.s
4559			if ( this.settings.buttons === true && this.getTotalSlides() > 1 && this.$buttons.length === 0 ) {
4560				this._createButtons();
4561			} else if ( this.settings.buttons === true && this.getTotalSlides() !== this.$buttons.find( '.sp-button' ).length && this.$buttons.length !== 0 ) {
4562				this._adjustButtons();
4563			} else if ( this.settings.buttons === false || ( this.getTotalSlides() <= 1 && this.$buttons.length !== 0 ) ) {
4564				this._removeButtons();
4565			}
4566		},
4567
4568		// Create the buttons
4569		_createButtons: function() {
4570			var that = this;
4571
4572			// Create the buttons' container
4573			this.$buttons = $( '<div class="sp-buttons"></div>' ).appendTo( this.$slider );
4574
4575			// Create the buttons
4576			for ( var i = 0; i < this.getTotalSlides(); i++ ) {
4577				$( '<div class="sp-button"></div>' ).appendTo( this.$buttons );
4578			}
4579
4580			// Listen for button clicks 
4581			this.$buttons.on( 'click.' + NS, '.sp-button', function() {
4582				that.gotoSlide( $( this ).index() );
4583			});
4584
4585			// Set the initially selected button
4586			this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' );
4587
4588			// Select the corresponding button when the slide changes
4589			this.on( 'gotoSlide.' + NS, function( event ) {
4590				that.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' );
4591				that.$buttons.find( '.sp-button' ).eq( event.index ).addClass( 'sp-selected-button' );
4592			});
4593
4594			// Indicate that the slider has buttons 
4595			this.$slider.addClass( 'sp-has-buttons' );
4596		},
4597
4598		// Re-create the buttons. This is calles when the number of slides changes.
4599		_adjustButtons: function() {
4600			this.$buttons.empty();
4601
4602			// Create the buttons
4603			for ( var i = 0; i < this.getTotalSlides(); i++ ) {
4604				$( '<div class="sp-button"></div>' ).appendTo( this.$buttons );
4605			}
4606
4607			// Change the selected the buttons
4608			this.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' );
4609			this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' );
4610		},
4611
4612		// Remove the buttons
4613		_removeButtons: function() {
4614			this.$buttons.off( 'click.' + NS, '.sp-button' );
4615			this.off( 'gotoSlide.' + NS );
4616			this.$buttons.remove();
4617			this.$slider.removeClass( 'sp-has-buttons' );
4618		},
4619
4620		destroyButtons: function() {
4621			this._removeButtons();
4622			this.off( 'update.' + NS );
4623		},
4624
4625		buttonsDefaults: {
4626			
4627			// Indicates whether the buttons will be created
4628			buttons: true
4629		}
4630	};
4631
4632	$.SliderPro.addModule( 'Buttons', Buttons );
4633
4634})( window, jQuery );
4635
4636// Arrows module for Slider Pro.
4637// 
4638// Adds arrows for navigating to the next or previous slide.
4639;(function( window, $ ) {
4640
4641	"use strict";
4642
4643	var NS = 'Arrows.' + $.SliderPro.namespace;
4644
4645	var Arrows = {
4646
4647		// Reference to the arrows container
4648		$arrows: null,
4649
4650		// Reference to the previous arrow
4651		$previousArrow: null,
4652
4653		// Reference to the next arrow
4654		$nextArrow: null,
4655
4656		initArrows: function() {
4657			this.on( 'update.' + NS, $.proxy( this._arrowsOnUpdate, this ) );
4658			this.on( 'gotoSlide.' + NS, $.proxy( this._checkArrowsVisibility, this ) );
4659		},
4660
4661		_arrowsOnUpdate: function() {
4662			var that = this;
4663
4664			// Create the arrows if the 'arrows' option is set to true
4665			if ( this.settings.arrows === true && this.$arrows === null ) {
4666				this.$arrows = $( '<div class="sp-arrows"></div>' ).appendTo( this.$slidesContainer );
4667				
4668				this.$previousArrow = $( '<div class="sp-arrow sp-previous-arrow"></div>' ).appendTo( this.$arrows );
4669				this.$nextArrow = $( '<div class="sp-arrow sp-next-arrow"></div>' ).appendTo( this.$arrows );
4670
4671				this.$previousArrow.on( 'click.' + NS, function() {
4672					that.previousSlide();
4673				});
4674
4675				this.$nextArrow.on( 'click.' + NS, function() {
4676					that.nextSlide();
4677				});
4678
4679				this._checkArrowsVisibility();
4680			} else if ( this.settings.arrows === false && this.$arrows !== null ) {
4681				this._removeArrows();
4682			}
4683
4684			if ( this.settings.arrows === true ) {
4685				if ( this.settings.fadeArrows === true ) {
4686					this.$arrows.addClass( 'sp-fade-arrows' );
4687				} else if ( this.settings.fadeArrows === false ) {
4688					this.$arrows.removeClass( 'sp-fade-arrows' );
4689				}
4690			}
4691		},
4692
4693		// Show or hide the arrows depending on the position of the selected slide
4694		_checkArrowsVisibility: function() {
4695			if ( this.settings.arrows === false || this.settings.loop === true ) {
4696				return;
4697			}
4698
4699			if ( this.selectedSlideIndex === 0 ) {
4700				this.$previousArrow.css( 'display', 'none' );
4701			} else {
4702				this.$previousArrow.css( 'display', 'block' );
4703			}
4704
4705			if ( this.selectedSlideIndex === this.getTotalSlides() - 1 ) {
4706				this.$nextArrow.css( 'display', 'none' );
4707			} else {
4708				this.$nextArrow.css( 'display', 'block' );
4709			}
4710		},
4711		
4712		_removeArrows: function() {
4713			if ( this.$arrows !== null ) {
4714				this.$previousArrow.off( 'click.' + NS );
4715				this.$nextArrow.off( 'click.' + NS );
4716				this.$arrows.remove();
4717				this.$arrows = null;
4718			}
4719		},
4720
4721		destroyArrows: function() {
4722			this._removeArrows();
4723			this.off( 'update.' + NS );
4724			this.off( 'gotoSlide.' + NS );
4725		},
4726
4727		arrowsDefaults: {
4728
4729			// Indicates whether the arrow buttons will be created
4730			arrows: false,
4731
4732			// Indicates whether the arrows will fade in only on hover
4733			fadeArrows: true
4734		}
4735	};
4736
4737	$.SliderPro.addModule( 'Arrows', Arrows );
4738
4739})( window, jQuery );
4740
4741// Thumbnail Touch Swipe module for Slider Pro.
4742// 
4743// Adds touch-swipe functionality for thumbnails.
4744;(function( window, $ ) {
4745
4746	"use strict";
4747	
4748	var NS = 'ThumbnailTouchSwipe.' + $.SliderPro.namespace;
4749
4750	var ThumbnailTouchSwipe = {
4751
4752		// The x and y coordinates of the pointer/finger's starting position
4753		thumbnailTouchStartPoint: { x: 0, y: 0 },
4754
4755		// The x and y coordinates of the pointer/finger's end position
4756		thumbnailTouchEndPoint: { x: 0, y: 0 },
4757
4758		// The distance from the starting to the end position on the x and y axis
4759		thumbnailTouchDistance: { x: 0, y: 0 },
4760
4761		// The position of the thumbnail scroller when the touch swipe starts
4762		thumbnailTouchStartPosition: 0,
4763
4764		// Indicates if the thumbnail scroller is being swiped
4765		isThumbnailTouchMoving: false,
4766
4767		// Indicates if the touch swipe was initialized
4768		isThumbnailTouchSwipe: false,
4769
4770		// Stores the names of the events
4771		thumbnailTouchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' },
4772
4773		
vendor: 1,431 bytes, lines 4773-4809
4773initThumbnailTouchSwipe: function() {
4774			this.on( 'update.' + NS, $.proxy( this._thumbnailTouchSwipeOnUpdate, this ) );
4775		},
4776
4777		_thumbnailTouchSwipeOnUpdate: function() {
4778
4779			// Return if there are no thumbnails
4780			if ( this.isThumbnailScroller === false ) {
4781				return;
4782			}
4783
4784			// Initialize the touch swipe functionality if it wasn't initialized yet
4785			if ( this.settings.thumbnailTouchSwipe === true && this.isThumbnailTouchSwipe === false ) {
4786				this.isThumbnailTouchSwipe = true;
4787
4788				this.thumbnailTouchSwipeEvents.startEvent = 'touchstart' + '.' + NS + ' mousedown' + '.' + NS;
4789				this.thumbnailTouchSwipeEvents.moveEvent = 'touchmove' + '.' + NS + ' mousemove' + '.' + NS;
4790				this.thumbnailTouchSwipeEvents.endEvent = 'touchend' + '.' + this.uniqueId + '.' + NS + ' mouseup' + '.' + this.uniqueId + '.' + NS;
4791				
4792				// Listen for touch swipe/mouse move events
4793				this.$thumbnails.on( this.thumbnailTouchSwipeEvents.startEvent, $.proxy( this._onThumbnailTouchStart, this ) );
4794				this.$thumbnails.on( 'dragstart.' + NS, function( event ) {
4795					event.preventDefault();
4796				});
4797			
4798				// Add the grabbing icon
4799				this.$thumbnails.addClass( 'sp-grab' );
4800			}
4801
4802			// Remove the default thumbnailClick
4803			$.each( this.thumbnails, function( index, thumbnail ) {
4804				thumbnail.off( 'thumbnailClick' );
4805			});
4806		},
4807
4808		// Called when the thumbnail scroller starts being dragged
4809		_onThumbnailTouchStart: function( event ) {
vendor: 11,735 bytes, lines 4809-5120
4809
4810			// Disable dragging if the element is set to allow selections
4811			if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) {
4812				return;
4813			}
4814
4815			var that = this,
4816				eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
4817
4818			// Prevent default behavior for mouse events
4819			if ( typeof event.originalEvent.touches === 'undefined' ) {
4820				event.preventDefault();
4821			}
4822
4823			// Disable click events on links
4824			$( event.target ).parents( '.sp-thumbnail-container' ).find( 'a' ).one( 'click.' + NS, function( event ) {
4825				event.preventDefault();
4826			});
4827
4828			// Get the initial position of the mouse pointer and the initial position
4829			// of the thumbnail scroller
4830			this.thumbnailTouchStartPoint.x = eventObject.pageX || eventObject.clientX;
4831			this.thumbnailTouchStartPoint.y = eventObject.pageY || eventObject.clientY;
4832			this.thumbnailTouchStartPosition = this.thumbnailsPosition;
4833
4834			// Clear the previous distance values
4835			this.thumbnailTouchDistance.x = this.thumbnailTouchDistance.y = 0;
4836
4837			// If the thumbnail scroller is being grabbed while it's still animating, stop the
4838			// current movement
4839			if ( this.$thumbnails.hasClass( 'sp-animated' ) ) {
4840				this.isThumbnailTouchMoving = true;
4841				this._stopThumbnailsMovement();
4842				this.thumbnailTouchStartPosition = this.thumbnailsPosition;
4843			}
4844
4845			// Listen for move and end events
4846			this.$thumbnails.on( this.thumbnailTouchSwipeEvents.moveEvent, $.proxy( this._onThumbnailTouchMove, this ) );
4847			$( document ).on( this.thumbnailTouchSwipeEvents.endEvent, $.proxy( this._onThumbnailTouchEnd, this ) );
4848
4849			// Swap grabbing icons
4850			this.$thumbnails.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' );
4851
4852			// Add 'sp-swiping' class to indicate that the thumbnail scroller is being swiped
4853			this.$thumbnailsContainer.addClass( 'sp-swiping' );
4854		},
4855
4856		// Called during the thumbnail scroller's dragging
4857		_onThumbnailTouchMove: function( event ) {
4858			var eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
4859
4860			// Indicate that the move event is being fired
4861			this.isThumbnailTouchMoving = true;
4862
4863			// Get the current position of the mouse pointer
4864			this.thumbnailTouchEndPoint.x = eventObject.pageX || eventObject.clientX;
4865			this.thumbnailTouchEndPoint.y = eventObject.pageY || eventObject.clientY;
4866
4867			// Calculate the distance of the movement on both axis
4868			this.thumbnailTouchDistance.x = this.thumbnailTouchEndPoint.x - this.thumbnailTouchStartPoint.x;
4869			this.thumbnailTouchDistance.y = this.thumbnailTouchEndPoint.y - this.thumbnailTouchStartPoint.y;
4870			
4871			// Calculate the distance of the swipe that takes place in the same direction as the orientation of the thumbnails
4872			// and calculate the distance from the opposite direction.
4873			// 
4874			// For a swipe to be valid there should more distance in the same direction as the orientation of the thumbnails.
4875			var distance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y,
4876				oppositeDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.y : this.thumbnailTouchDistance.x;
4877
4878			// If the movement is in the same direction as the orientation of the thumbnails, the swipe is valid
4879			if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) {
4880				event.preventDefault();
4881			} else {
4882				return;
4883			}
4884
4885			// Make the thumbnail scroller move slower if it's dragged outside its bounds
4886			if ( this.thumbnailsPosition >= 0 ) {
4887				var infOffset = - this.thumbnailTouchStartPosition;
4888				distance = infOffset + ( distance - infOffset ) * 0.2;
4889			} else if ( this.thumbnailsPosition <= - this.thumbnailsSize + this.thumbnailsContainerSize ) {
4890				var supOffset = this.thumbnailsSize - this.thumbnailsContainerSize + this.thumbnailTouchStartPosition;
4891				distance = - supOffset + ( distance + supOffset ) * 0.2;
4892			}
4893			
4894			this._moveThumbnailsTo( this.thumbnailTouchStartPosition + distance, true );
4895		},
4896
4897		// Called when the thumbnail scroller is released
4898		_onThumbnailTouchEnd: function( event ) {
4899			var that = this,
4900				thumbnailTouchDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y;
4901
4902			// Remove the move and end listeners
4903			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent );
4904			$( document ).off( this.thumbnailTouchSwipeEvents.endEvent );
4905
4906			// Swap grabbing icons
4907			this.$thumbnails.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' );
4908
4909			// Check if there is intention for a tap/click
4910			if ( this.isThumbnailTouchMoving === false ||
4911				this.isThumbnailTouchMoving === true &&
4912				Math.abs( this.thumbnailTouchDistance.x ) < 10 &&
4913				Math.abs( this.thumbnailTouchDistance.y ) < 10
4914			) {
4915				var targetThumbnail = $( event.target ).hasClass( 'sp-thumbnail-container' ) ? $( event.target ) : $( event.target ).parents( '.sp-thumbnail-container' ),
4916					index = targetThumbnail.index();
4917
4918				// If a link is cliked, navigate to that link, else navigate to the slide that corresponds to the thumbnail
4919				if ( $( event.target ).parents( 'a' ).length !== 0 ) {
4920					$( event.target ).parents( 'a' ).off( 'click.' + NS );
4921					this.$thumbnailsContainer.removeClass( 'sp-swiping' );
4922				} else if ( index !== this.selectedThumbnailIndex && index !== -1 ) {
4923					this.gotoSlide( index );
4924				}
4925
4926				return;
4927			}
4928
4929			this.isThumbnailTouchMoving = false;
4930
4931			$( event.target ).parents( '.sp-thumbnail' ).one( 'click', function( event ) {
4932				event.preventDefault();
4933			});
4934
4935			// Remove the 'sp-swiping' class but with a delay
4936			// because there might be other event listeners that check
4937			// the existence of this class, and this class should still be 
4938			// applied for those listeners, since there was a swipe event
4939			setTimeout(function() {
4940				that.$thumbnailsContainer.removeClass( 'sp-swiping' );
4941			}, 1 );
4942
4943			// Keep the thumbnail scroller inside the bounds
4944			if ( this.thumbnailsPosition > 0 ) {
4945				this._moveThumbnailsTo( 0 );
4946			} else if ( this.thumbnailsPosition < this.thumbnailsContainerSize - this.thumbnailsSize ) {
4947				this._moveThumbnailsTo( this.thumbnailsContainerSize - this.thumbnailsSize );
4948			}
4949
4950			// Fire the 'thumbnailsMoveComplete' event
4951			this.trigger({ type: 'thumbnailsMoveComplete' });
4952			if ( $.isFunction( this.settings.thumbnailsMoveComplete ) ) {
4953				this.settings.thumbnailsMoveComplete.call( this, { type: 'thumbnailsMoveComplete' });
4954			}
4955		},
4956
4957		// Destroy the module
4958		destroyThumbnailTouchSwipe: function() {
4959			this.off( 'update.' + NS );
4960
4961			if ( this.isThumbnailScroller === false ) {
4962				return;
4963			}
4964
4965			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.startEvent );
4966			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent );
4967			this.$thumbnails.off( 'dragstart.' + NS );
4968			$( document ).off( this.thumbnailTouchSwipeEvents.endEvent );
4969			this.$thumbnails.removeClass( 'sp-grab' );
4970		},
4971
4972		thumbnailTouchSwipeDefaults: {
4973
4974			// Indicates whether the touch swipe will be enabled for thumbnails
4975			thumbnailTouchSwipe: true
4976		}
4977	};
4978
4979	$.SliderPro.addModule( 'ThumbnailTouchSwipe', ThumbnailTouchSwipe );
4980
4981})( window, jQuery );
4982
4983// Thumbnail Arrows module for Slider Pro.
4984// 
4985// Adds thumbnail arrows for moving the thumbnail scroller.
4986;(function( window, $ ) {
4987
4988	"use strict";
4989	
4990	var NS = 'ThumbnailArrows.' + $.SliderPro.namespace;
4991
4992	var ThumbnailArrows = {
4993
4994		// Reference to the arrows container
4995		$thumbnailArrows: null,
4996
4997		// Reference to the 'previous' thumbnail arrow
4998		$previousThumbnailArrow: null,
4999
5000		// Reference to the 'next' thumbnail arrow
5001		$nextThumbnailArrow: null,
5002
5003		initThumbnailArrows: function() {
5004			var that = this;
5005
5006			this.on( 'update.' + NS, $.proxy( this._thumbnailArrowsOnUpdate, this ) );
5007			
5008			// Check if the arrows need to be visible or invisible when the thumbnail scroller
5009			// resizes and when the thumbnail scroller moves.
5010			this.on( 'sliderResize.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, function() {
5011				if ( that.isThumbnailScroller === true && that.settings.thumbnailArrows === true ) {
5012					that._checkThumbnailArrowsVisibility();
5013				}
5014			});
5015		},
5016		
5017		// Called when the slider is updated
5018		_thumbnailArrowsOnUpdate: function() {
5019			var that = this;
5020			
5021			if ( this.isThumbnailScroller === false ) {
5022				return;
5023			}
5024
5025			// Create or remove the thumbnail scroller arrows
5026			if ( this.settings.thumbnailArrows === true && this.$thumbnailArrows === null ) {
5027				this.$thumbnailArrows = $( '<div class="sp-thumbnail-arrows"></div>' ).appendTo( this.$thumbnailsContainer );
5028				
5029				this.$previousThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-previous-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows );
5030				this.$nextThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-next-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows );
5031
5032				this.$previousThumbnailArrow.on( 'click.' + NS, function() {
5033					var previousPosition = Math.min( 0, that.thumbnailsPosition + that.thumbnailsContainerSize );
5034					that._moveThumbnailsTo( previousPosition );
5035				});
5036
5037				this.$nextThumbnailArrow.on( 'click.' + NS, function() {
5038					var nextPosition = Math.max( that.thumbnailsContainerSize - that.thumbnailsSize, that.thumbnailsPosition - that.thumbnailsContainerSize );
5039					that._moveThumbnailsTo( nextPosition );
5040				});
5041			} else if ( this.settings.thumbnailArrows === false && this.$thumbnailArrows !== null ) {
5042				this._removeThumbnailArrows();
5043			}
5044
5045			// Add fading functionality and check if the arrows need to be visible or not
5046			if ( this.settings.thumbnailArrows === true ) {
5047				if ( this.settings.fadeThumbnailArrows === true ) {
5048					this.$thumbnailArrows.addClass( 'sp-fade-thumbnail-arrows' );
5049				} else if ( this.settings.fadeThumbnailArrows === false ) {
5050					this.$thumbnailArrows.removeClass( 'sp-fade-thumbnail-arrows' );
5051				}
5052
5053				this._checkThumbnailArrowsVisibility();
5054			}
5055		},
5056
5057		// Checks if the 'next' or 'previous' arrows need to be visible or hidden,
5058		// based on the position of the thumbnail scroller
5059		_checkThumbnailArrowsVisibility: function() {
5060			if ( this.thumbnailsPosition === 0 ) {
5061				this.$previousThumbnailArrow.css( 'display', 'none' );
5062			} else {
5063				this.$previousThumbnailArrow.css( 'display', 'block' );
5064			}
5065
5066			if ( this.thumbnailsPosition === this.thumbnailsContainerSize - this.thumbnailsSize ) {
5067				this.$nextThumbnailArrow.css( 'display', 'none' );
5068			} else {
5069				this.$nextThumbnailArrow.css( 'display', 'block' );
5070			}
5071		},
5072
5073		// Remove the thumbnail arrows
5074		_removeThumbnailArrows: function() {
5075			if ( this.$thumbnailArrows !== null ) {
5076				this.$previousThumbnailArrow.off( 'click.' + NS );
5077				this.$nextThumbnailArrow.off( 'click.' + NS );
5078				this.$thumbnailArrows.remove();
5079				this.$thumbnailArrows = null;
5080			}
5081		},
5082
5083		// Destroy the module
5084		destroyThumbnailArrows: function() {
5085			this._removeThumbnailArrows();
5086			this.off( 'update.' + NS );
5087			this.off( 'sliderResize.' + NS );
5088			this.off( 'thumbnailsMoveComplete.' + NS );
5089		},
5090
5091		thumbnailArrowsDefaults: {
5092
5093			// Indicates whether the thumbnail arrows will be enabled
5094			thumbnailArrows: false,
5095
5096			// Indicates whether the thumbnail arrows will be faded
5097			fadeThumbnailArrows: true
5098		}
5099	};
5100
5101	$.SliderPro.addModule( 'ThumbnailArrows', ThumbnailArrows );
5102
5103})( window, jQuery );
5104
5105// Video module for Slider Pro
5106//
5107// Adds automatic control for several video players and providers
5108;(function( window, $ ) {
5109
5110	"use strict";
5111
5112	var NS = 'Video.' + $.SliderPro.namespace;
5113	
5114	var Video = {
5115
5116		firstInit: false,
5117
5118		initVideo: function() {
5119			this.on( 'update.' + NS, $.proxy( this._videoOnUpdate, this ) );
5120			this.on( 'gotoSlide
vendor: 18,112 bytes, lines 5120-5776
5120Complete.' + NS, $.proxy( this._videoOnGotoSlideComplete, this ) );
5121		},
5122
5123		_videoOnUpdate: function() {
5124			var that = this;
5125
5126			// Find all the inline videos and initialize them
5127			this.$slider.find( '.sp-video' ).not( 'a, [data-video-init]' ).each(function() {
5128				var video = $( this );
5129				that._initVideo( video );
5130			});
5131
5132			// Find all the lazy-loaded videos and preinitialize them. They will be initialized
5133			// only when their play button is clicked.
5134			this.$slider.find( 'a.sp-video' ).not( '[data-video-preinit]' ).each(function() {
5135				var video = $( this );
5136				that._preinitVideo( video );
5137			});
5138
5139			// call the 'gotoSlideComplete' method in case the first slide contains a video that
5140			// needs to play automatically
5141			if ( this.firstInit === false ) {
5142				this.firstInit = true;
5143				this._videoOnGotoSlideComplete({ index: this.selectedSlideIndex, previousIndex: -1 });
5144			}
5145		},
5146
5147		// Initialize the target video
5148		_initVideo: function( video ) {
5149			var that = this;
5150
5151			video.attr( 'data-video-init', true )
5152				.videoController();
5153
5154			// When the video starts playing, pause the autoplay if it's running
5155			video.on( 'videoPlay.' + NS, function() {
5156				if ( that.settings.playVideoAction === 'stopAutoplay' && typeof that.stopAutoplay !== 'undefined' ) {
5157					that.stopAutoplay();
5158					that.settings.autoplay = false;
5159				}
5160
5161				// Fire the 'videoPlay' event
5162				var eventObject = { type: 'videoPlay', video: video };
5163				that.trigger( eventObject );
5164				if ( $.isFunction( that.settings.videoPlay ) ) {
5165					that.settings.videoPlay.call( that, eventObject );
5166				}
5167			});
5168
5169			// When the video is paused, restart the autoplay
5170			video.on( 'videoPause.' + NS, function() {
5171				if ( that.settings.pauseVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) {
5172					that.startAutoplay();
5173					that.settings.autoplay = true;
5174				}
5175
5176				// Fire the 'videoPause' event
5177				var eventObject = { type: 'videoPause', video: video };
5178				that.trigger( eventObject );
5179				if ( $.isFunction( that.settings.videoPause ) ) {
5180					that.settings.videoPause.call( that, eventObject );
5181				}
5182			});
5183
5184			// When the video ends, restart the autoplay (which was paused during the playback), or
5185			// go to the next slide, or replay the video
5186			video.on( 'videoEnded.' + NS, function() {
5187				if ( that.settings.endVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) {
5188					that.startAutoplay();
5189					that.settings.autoplay = true;
5190				} else if ( that.settings.endVideoAction === 'nextSlide' ) {
5191					that.nextSlide();
5192				} else if ( that.settings.endVideoAction === 'replayVideo' ) {
5193					video.videoController( 'replay' );
5194				}
5195
5196				// Fire the 'videoEnd' event
5197				var eventObject = { type: 'videoEnd', video: video };
5198				that.trigger( eventObject );
5199				if ( $.isFunction(that.settings.videoEnd ) ) {
5200					that.settings.videoEnd.call( that, eventObject );
5201				}
5202			});
5203		},
5204
5205		// Pre-initialize the video. This is for lazy loaded videos.
5206		_preinitVideo: function( video ) {
5207			var that = this;
5208
5209			video.attr( 'data-video-preinit', true );
5210
5211			// When the video poster is clicked, remove the poster and create
5212			// the inline video
5213			video.on( 'click.' + NS, function( event ) {
5214
5215				// If the video is being dragged, don't start the video
5216				if ( that.$slider.hasClass( 'sp-swiping' ) ) {
5217					return;
5218				}
5219
5220				event.preventDefault();
5221
5222				var href = video.attr( 'href' ),
5223					iframe,
5224					provider,
5225					regExp,
5226					match,
5227					id,
5228					src,
5229					videoAttributes,
5230					videoWidth = video.children( 'img' ).attr( 'width' ) || video.children( 'img' ).width(),
5231					videoHeight = video.children( 'img' ).attr( 'height') || video.children( 'img' ).height();
5232
5233				// Check if it's a youtube or vimeo video
5234				if ( href.indexOf( 'youtube' ) !== -1 || href.indexOf( 'youtu.be' ) !== -1 ) {
5235					provider = 'youtube';
5236				} else if ( href.indexOf( 'vimeo' ) !== -1 ) {
5237					provider = 'vimeo';
5238				}
5239
5240				// Get the id of the video
5241				regExp = provider === 'youtube' ? /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ : /http:\/\/(www\.)?vimeo.com\/(\d+)/;
5242				match = href.match( regExp );
5243				id = match[2];
5244
5245				// Get the source of the iframe that will be created
5246				src = provider === 'youtube' ? '//www.youtube.com/embed/' + id + '?enablejsapi=1&wmode=opaque' : '//player.vimeo.com/video/'+ id +'?api=1';
5247				
5248				// Get the attributes passed to the video link and then pass them to the iframe's src
5249				videoAttributes = href.split( '?' )[ 1 ];
5250
5251				if ( typeof videoAttributes !== 'undefined' ) {
5252					videoAttributes = videoAttributes.split( '&' );
5253
5254					$.each( videoAttributes, function( index, value ) {
5255						if ( value.indexOf( id ) === -1 ) {
5256							src += '&' + value;
5257						}
5258					});
5259				}
5260
5261				// Create the iframe
5262				iframe = $( '<iframe></iframe>' )
5263					.attr({
5264						'src': src,
5265						'width': videoWidth,
5266						'height': videoHeight,
5267						'class': video.attr( 'class' ),
5268						'frameborder': 0,
5269						'allowfullscreen': 'allowfullscreen'
5270					}).insertBefore( video );
5271
5272				// Initialize the video and play it
5273				that._initVideo( iframe );
5274				iframe.videoController( 'play' );
5275
5276				// Hide the video poster
5277				video.css( 'display', 'none' );
5278			});
5279		},
5280
5281		// Called when a new slide is selected
5282		_videoOnGotoSlideComplete: function( event ) {
5283
5284			// Get the video from the previous slide
5285			var previousVideo = this.$slides.find( '.sp-slide' ).eq( event.previousIndex ).find( '.sp-video[data-video-init]' );
5286
5287			// Handle the video from the previous slide by stopping it, or pausing it,
5288			// or remove it, depending on the value of the 'leaveVideoAction' option.
5289			if ( event.previousIndex !== -1 && previousVideo.length !== 0 ) {
5290				if ( this.settings.leaveVideoAction === 'stopVideo' ) {
5291					previousVideo.videoController( 'stop' );
5292				} else if ( this.settings.leaveVideoAction === 'pauseVideo' ) {
5293					previousVideo.videoController( 'pause' );
5294				} else if ( this.settings.leaveVideoAction === 'removeVideo'  ) {
5295					// If the video was lazy-loaded, remove it and show the poster again. If the video
5296					// was not lazy-loaded, but inline, stop the video.
5297					if ( previousVideo.siblings( 'a.sp-video' ).length !== 0 ) {
5298						previousVideo.siblings( 'a.sp-video' ).css( 'display', '' );
5299						previousVideo.videoController( 'destroy' );
5300						previousVideo.remove();
5301					} else {
5302						previousVideo.videoController( 'stop' );
5303					}
5304				}
5305			}
5306
5307			// Handle the video from the selected slide
5308			if ( this.settings.reachVideoAction === 'playVideo' ) {
5309				var loadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-video-init]' ),
5310					unloadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-video-preinit]' );
5311
5312				// If the video was already initialized, play it. If it's not initialized (because
5313				// it's lazy loaded) initialize it and play it.
5314				if ( loadedVideo.length !== 0 ) {
5315					loadedVideo.videoController( 'play' );
5316				} else if ( unloadedVideo.length !== 0 ) {
5317					unloadedVideo.trigger( 'click.' + NS );
5318				}
5319			}
5320		},
5321
5322		// Destroy the module
5323		destroyVideo: function() {
5324			this.$slider.find( '.sp-video[ data-video-preinit ]' ).each(function() {
5325				var video = $( this );
5326				video.removeAttr( 'data-video-preinit' );
5327				video.off( 'click.' + NS );
5328			});
5329
5330			// Loop through the all the videos and destroy them
5331			this.$slider.find( '.sp-video[ data-video-init ]' ).each(function() {
5332				var video = $( this );
5333				video.removeAttr( 'data-video-init' );
5334				video.off( 'Video' );
5335				video.videoController( 'destroy' );
5336			});
5337
5338			this.off( 'update.' + NS );
5339			this.off( 'gotoSlideComplete.' + NS );
5340		},
5341
5342		videoDefaults: {
5343
5344			// Sets the action that the video will perform when its slide container is selected
5345			// ( 'playVideo' and 'none' )
5346			reachVideoAction: 'none',
5347
5348			// Sets the action that the video will perform when another slide is selected
5349			// ( 'stopVideo', 'pauseVideo', 'removeVideo' and 'none' )
5350			leaveVideoAction: 'pauseVideo',
5351
5352			// Sets the action that the slider will perform when the video starts playing
5353			// ( 'stopAutoplay' and 'none' )
5354			playVideoAction: 'stopAutoplay',
5355
5356			// Sets the action that the slider will perform when the video is paused
5357			// ( 'startAutoplay' and 'none' )
5358			pauseVideoAction: 'none',
5359
5360			// Sets the action that the slider will perform when the video ends
5361			// ( 'startAutoplay', 'nextSlide', 'replayVideo' and 'none' )
5362			endVideoAction: 'none',
5363
5364			// Called when the video starts playing
5365			videoPlay: function() {},
5366
5367			// Called when the video is paused
5368			videoPause: function() {},
5369
5370			// Called when the video ends
5371			videoEnd: function() {}
5372		}
5373	};
5374
5375	$.SliderPro.addModule( 'Video', Video );
5376	
5377})( window, jQuery );
5378
5379// Video Controller jQuery plugin
5380// Creates a universal controller for multiple video types and providers
5381;(function( $ ) {
5382
5383	"use strict";
5384
5385// Check if an iOS device is used.
5386// This information is important because a video can not be
5387// controlled programmatically unless the user has started the video manually.
5388var	isIOS = window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ? true : false;
5389
5390var VideoController = function( instance, options ) {
5391	this.$video = $( instance );
5392	this.options = options;
5393	this.settings = {};
5394	this.player = null;
5395
5396	this._init();
5397};
5398
5399VideoController.prototype = {
5400
5401	_init: function() {
5402		this.settings = $.extend( {}, this.defaults, this.options );
5403
5404		var that = this,
5405			players = $.VideoController.players,
5406			videoID = this.$video.attr( 'id' );
5407
5408		// Loop through the available video players
5409		// and check if the targeted video element is supported by one of the players.
5410		// If a compatible type is found, store the video type.
5411		for ( var name in players ) {
5412			if ( typeof players[ name ] !== 'undefined' && players[ name ].isType( this.$video ) ) {
5413				this.player = new players[ name ]( this.$video );
5414				break;
5415			}
5416		}
5417
5418		// Return if the player could not be instantiated
5419		if ( this.player === null ) {
5420			return;
5421		}
5422
5423		// Add event listeners
5424		var events = [ 'ready', 'start', 'play', 'pause', 'ended' ];
5425		
5426		$.each( events, function( index, element ) {
5427			var event = 'video' + element.charAt( 0 ).toUpperCase() + element.slice( 1 );
5428
5429			that.player.on( element, function() {
5430				that.trigger({ type: event, video: videoID });
5431				if ( $.isFunction( that.settings[ event ] ) ) {
5432					that.settings[ event ].call( that, { type: event, video: videoID } );
5433				}
5434			});
5435		});
5436	},
5437	
5438	play: function() {
5439		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'playing' ) {
5440			return;
5441		}
5442
5443		this.player.play();
5444	},
5445	
5446	stop: function() {
5447		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'stopped' ) {
5448			return;
5449		}
5450
5451		this.player.stop();
5452	},
5453	
5454	pause: function() {
5455		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'paused' ) {
5456			return;
5457		}
5458
5459		this.player.pause();
5460	},
5461
5462	replay: function() {
5463		if ( isIOS === true && this.player.isStarted() === false ) {
5464			return;
5465		}
5466		
5467		this.player.replay();
5468	},
5469
5470	on: function( type, callback ) {
5471		return this.$video.on( type, callback );
5472	},
5473	
5474	off: function( type ) {
5475		return this.$video.off( type );
5476	},
5477
5478	trigger: function( data ) {
5479		return this.$video.triggerHandler( data );
5480	},
5481
5482	destroy: function() {
5483		if ( this.player.isStarted() === true ) {
5484			this.stop();
5485		}
5486
5487		this.player.off( 'ready' );
5488		this.player.off( 'start' );
5489		this.player.off( 'play' );
5490		this.player.off( 'pause' );
5491		this.player.off( 'ended' );
5492
5493		this.$video.removeData( 'videoController' );
5494	},
5495
5496	defaults: {
5497		videoReady: function() {},
5498		videoStart: function() {},
5499		videoPlay: function() {},
5500		videoPause: function() {},
5501		videoEnded: function() {}
5502	}
5503};
5504
5505$.VideoController = {
5506	players: {},
5507
5508	addPlayer: function( name, player ) {
5509		this.players[ name ] = player;
5510	}
5511};
5512
5513$.fn.videoController = function( options ) {
5514	var args = Array.prototype.slice.call( arguments, 1 );
5515
5516	return this.each(function() {
5517		// Instantiate the video controller or call a function on the current instance
5518		if ( typeof $( this ).data( 'videoController' ) === 'undefined' ) {
5519			var newInstance = new VideoController( this, options );
5520
5521			// Store a reference to the instance created
5522			$( this ).data( 'videoController', newInstance );
5523		} else if ( typeof options !== 'undefined' ) {
5524			var	currentInstance = $( this ).data( 'videoController' );
5525
5526			// Check the type of argument passed
5527			if ( typeof currentInstance[ options ] === 'function' ) {
5528				currentInstance[ options ].apply( currentInstance, args );
5529			} else {
5530				$.error( options + ' does not exist in videoController.' );
5531			}
5532		}
5533	});
5534};
5535
5536// Base object for the video players
5537var Video = function( video ) {
5538	this.$video = video;
5539	this.player = null;
5540	this.ready = false;
5541	this.started = false;
5542	this.state = '';
5543	this.events = $({});
5544
5545	this._init();
5546};
5547
5548Video.prototype = {
5549	_init: function() {},
5550
5551	play: function() {},
5552
5553	pause: function() {},
5554
5555	stop: function() {},
5556
5557	replay: function() {},
5558
5559	isType: function() {},
5560
5561	isReady: function() {
5562		return this.ready;
5563	},
5564
5565	isStarted: function() {
5566		return this.started;
5567	},
5568
5569	getState: function() {
5570		return this.state;
5571	},
5572
5573	on: function( type, callback ) {
5574		return this.events.on( type, callback );
5575	},
5576	
5577	off: function( type ) {
5578		return this.events.off( type );
5579	},
5580
5581	trigger: function( data ) {
5582		return this.events.triggerHandler( data );
5583	}
5584};
5585
5586// YouTube video
5587var YoutubeVideoHelper = {
5588	youtubeAPIAdded: false,
5589	youtubeVideos: []
5590};
5591
5592var YoutubeVideo = function( video ) {
5593	this.init = false;
5594	var youtubeAPILoaded = window.YT && window.YT.Player;
5595
5596	if ( typeof youtubeAPILoaded !== 'undefined' ) {
5597		Video.call( this, video );
5598	} else {
5599		YoutubeVideoHelper.youtubeVideos.push({ 'video': video, 'scope': this });
5600		
5601		if ( YoutubeVideoHelper.youtubeAPIAdded === false ) {
5602			YoutubeVideoHelper.youtubeAPIAdded = true;
5603
5604			var tag = document.createElement( 'script' );
5605			tag.src = "//www.youtube.com/player_api";
5606			var firstScriptTag = document.getElementsByTagName( 'script' )[0];
5607			firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
5608
5609			window.onYouTubePlayerAPIReady = function() {
5610				$.each( YoutubeVideoHelper.youtubeVideos, function( index, element ) {
5611					Video.call( element.scope, element.video );
5612				});
5613			};
5614		}
5615	}
5616};
5617
5618YoutubeVideo.prototype = new Video();
5619YoutubeVideo.prototype.constructor = YoutubeVideo;
5620$.VideoController.addPlayer( 'YoutubeVideo', YoutubeVideo );
5621
5622YoutubeVideo.isType = function( video ) {
5623	if ( video.is( 'iframe' ) ) {
5624		var src = video.attr( 'src' );
5625
5626		if ( src.indexOf( 'youtube.com' ) !== -1 || src.indexOf( 'youtu.be' ) !== -1 ) {
5627			return true;
5628		}
5629	}
5630
5631	return false;
5632};
5633
5634YoutubeVideo.prototype._init = function() {
5635	this.init = true;
5636	this._setup();
5637};
5638	
5639YoutubeVideo.prototype._setup = function() {
5640	var that = this;
5641
5642	// Get a reference to the player
5643	this.player = new YT.Player( this.$video[0], {
5644		events: {
5645			'onReady': function() {
5646				that.trigger({ type: 'ready' });
5647				that.ready = true;
5648			},
5649			
5650			'onStateChange': function( event ) {
5651				switch ( event.data ) {
5652					case YT.PlayerState.PLAYING:
5653						if (that.started === false) {
5654							that.started = true;
5655							that.trigger({ type: 'start' });
5656						}
5657
5658						that.state = 'playing';
5659						that.trigger({ type: 'play' });
5660						break;
5661					
5662					case YT.PlayerState.PAUSED:
5663						that.state = 'paused';
5664						that.trigger({ type: 'pause' });
5665						break;
5666					
5667					case YT.PlayerState.ENDED:
5668						that.state = 'ended';
5669						that.trigger({ type: 'ended' });
5670						break;
5671				}
5672			}
5673		}
5674	});
5675};
5676
5677YoutubeVideo.prototype.play = function() {
5678	var that = this;
5679
5680	if ( this.ready === true ) {
5681		this.player.playVideo();
5682	} else {
5683		var timer = setInterval(function() {
5684			if ( that.ready === true ) {
5685				clearInterval( timer );
5686				that.player.playVideo();
5687			}
5688		}, 100 );
5689	}
5690};
5691
5692YoutubeVideo.prototype.pause = function() {
5693	// On iOS, simply pausing the video can make other videos unresponsive
5694	// so we stop the video instead.
5695	if ( isIOS === true ) {
5696		this.stop();
5697	} else {
5698		this.player.pauseVideo();
5699	}
5700};
5701
5702YoutubeVideo.prototype.stop = function() {
5703	this.player.seekTo( 1 );
5704	this.player.stopVideo();
5705	this.state = 'stopped';
5706};
5707
5708YoutubeVideo.prototype.replay = function() {
5709	this.player.seekTo( 1 );
5710	this.player.playVideo();
5711};
5712
5713YoutubeVideo.prototype.on = function( type, callback ) {
5714	var that = this;
5715
5716	if ( this.init === true ) {
5717		Video.prototype.on.call( this, type, callback );
5718	} else {
5719		var timer = setInterval(function() {
5720			if ( that.init === true ) {
5721				clearInterval( timer );
5722				Video.prototype.on.call( that, type, callback );
5723			}
5724		}, 100 );
5725	}
5726};
5727
5728// Vimeo video
5729var VimeoVideoHelper = {
5730	vimeoAPIAdded: false,
5731	vimeoVideos: []
5732};
5733
5734var VimeoVideo = function( video ) {
5735	this.init = false;
5736
5737	if ( typeof window.Froogaloop !== 'undefined' ) {
5738		Video.call( this, video );
5739	} else {
5740		VimeoVideoHelper.vimeoVideos.push({ 'video': video, 'scope': this });
5741
5742		if ( VimeoVideoHelper.vimeoAPIAdded === false ) {
5743			VimeoVideoHelper.vimeoAPIAdded = true;
5744
5745			var tag = document.createElement('script');
5746			tag.src = "//a.vimeocdn.com/js/froogaloop2.min.js";
5747			var firstScriptTag = document.getElementsByTagName( 'script' )[0];
5748			firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
5749		
5750			var checkVimeoAPITimer = setInterval(function() {
5751				if ( typeof window.Froogaloop !== 'undefined' ) {
5752					clearInterval( checkVimeoAPITimer );
5753					
5754					$.each( VimeoVideoHelper.vimeoVideos, function( index, element ) {
5755						Video.call( element.scope, element.video );
5756					});
5757				}
5758			}, 100 );
5759		}
5760	}
5761};
5762
5763VimeoVideo.prototype = new Video();
5764VimeoVideo.prototype.constructor = VimeoVideo;
5765$.VideoController.addPlayer( 'VimeoVideo', VimeoVideo );
5766
5767VimeoVideo.isType = function( video ) {
5768	if ( video.is( 'iframe' ) ) {
5769		var src = video.attr('src');
5770
5771		if ( src.indexOf( 'vimeo.com' ) !== -1 ) {
5772			return true;
5773		}
5774	}
5775
5776	return false;
5777};
5778
5779VimeoVideo.prototype._init = function() {
5780	this.init = true;
5781	this._setup();
5782};
5783
5784VimeoVideo.prototype._setup = function() {
5785	var that = this;
5786
5787	// Get a reference to the player
5788	this.player = $f( this.$video[0] );
5789	
5790	this.player.addEvent( 'ready', function() {
5791		that.ready = true;
5792		that.trigger({ type: 'ready' });
5793		
5794		that.player.addEvent( 'play', function() {
5795			if ( that.started === false ) {
5796				that.started = true;
5797				that.trigger({ type: 'start' });
5798			}
5799
5800			that.state = 'playing';
5801			that.trigger({ type: 'play' });
5802		});
5803		
5804		that.player.addEvent( 'pause', function() {
5805			that.state = 'paused';
5806			that.trigger({ type: 'pause' });
5807		});
5808		
5809		that.player.addEvent( 'finish', function() {
5810			that.state = 'ended';
5811			that.trigger({ type: 'ended' });
5812		});
5813	});
5814};
5815
5816VimeoVideo.prototype.play = function() {
5817	var that = this;
5818
5819	if ( this.ready === true ) {
5820		this.player.api( 'play' );
5821	} else {
5822		var timer = setInterval(function() {
5823			if ( that.ready === true ) {
5824				clearInterval( timer );
5825				that.player.api( 'play' );
5826			}
5827		}, 100 );
5828	}
5829};
5830
5831VimeoVideo.prototype.pause = function() {
5832	this.player.api( 'pause' );
5833};
5834
5835VimeoVideo.prototype.stop = function() {
5836	this.player.api( 'seekTo', 0 );
5837	this.player.api( 'pause' );
5838	this.state = 'stopped';
5839};
5840
5841VimeoVideo.prototype.replay = function() {
5842	this.player.api( 'seekTo', 0 );
5843	this.player.api( 'play'
vendor: 7,074 bytes, lines 5843-6160
5843 );
5844};
5845
5846VimeoVideo.prototype.on = function( type, callback ) {
5847	var that = this;
5848
5849	if ( this.init === true ) {
5850		Video.prototype.on.call( this, type, callback );
5851	} else {
5852		var timer = setInterval(function() {
5853			if ( that.init === true ) {
5854				clearInterval( timer );
5855				Video.prototype.on.call( that, type, callback );
5856			}
5857		}, 100 );
5858	}
5859};
5860
5861// HTML5 video
5862var HTML5Video = function( video ) {
5863	Video.call( this, video );
5864};
5865
5866HTML5Video.prototype = new Video();
5867HTML5Video.prototype.constructor = HTML5Video;
5868$.VideoController.addPlayer( 'HTML5Video', HTML5Video );
5869
5870HTML5Video.isType = function( video ) {
5871	if ( video.is( 'video' ) && video.hasClass( 'video-js' ) === false && video.hasClass( 'sublime' ) === false ) {
5872		return true;
5873	}
5874
5875	return false;
5876};
5877
5878HTML5Video.prototype._init = function() {
5879	var that = this;
5880
5881	// Get a reference to the player
5882	this.player = this.$video[0];
5883	
5884	var checkVideoReady = setInterval(function() {
5885		if ( that.player.readyState === 4 ) {
5886			clearInterval( checkVideoReady );
5887
5888			that.ready = true;
5889			that.trigger({ type: 'ready' });
5890
5891			that.player.addEventListener( 'play', function() {
5892				if ( that.started === false ) {
5893					that.started = true;
5894					that.trigger({ type: 'start' });
5895				}
5896
5897				that.state = 'playing';
5898				that.trigger({ type: 'play' });
5899			});
5900			
5901			that.player.addEventListener( 'pause', function() {
5902				that.state = 'paused';
5903				that.trigger({ type: 'pause' });
5904			});
5905			
5906			that.player.addEventListener( 'ended', function() {
5907				that.state = 'ended';
5908				that.trigger({ type: 'ended' });
5909			});
5910		}
5911	}, 100 );
5912};
5913
5914HTML5Video.prototype.play = function() {
5915	var that = this;
5916
5917	if ( this.ready === true ) {
5918		this.player.play();
5919	} else {
5920		var timer = setInterval(function() {
5921			if ( that.ready === true ) {
5922				clearInterval( timer );
5923				that.player.play();
5924			}
5925		}, 100 );
5926	}
5927};
5928
5929HTML5Video.prototype.pause = function() {
5930	this.player.pause();
5931};
5932
5933HTML5Video.prototype.stop = function() {
5934	this.player.currentTime = 0;
5935	this.player.pause();
5936	this.state = 'stopped';
5937};
5938
5939HTML5Video.prototype.replay = function() {
5940	this.player.currentTime = 0;
5941	this.player.play();
5942};
5943
5944// VideoJS video
5945var VideoJSVideo = function( video ) {
5946	Video.call( this, video );
5947};
5948
5949VideoJSVideo.prototype = new Video();
5950VideoJSVideo.prototype.constructor = VideoJSVideo;
5951$.VideoController.addPlayer( 'VideoJSVideo', VideoJSVideo );
5952
5953VideoJSVideo.isType = function( video ) {
5954	if ( ( typeof video.attr( 'data-videojs-id' ) !== 'undefined' || video.hasClass( 'video-js' ) ) && typeof videojs !== 'undefined' ) {
5955		return true;
5956	}
5957
5958	return false;
5959};
5960
5961VideoJSVideo.prototype._init = function() {
5962	var that = this,
5963		videoID = this.$video.hasClass( 'video-js' ) ? this.$video.attr( 'id' ) : this.$video.attr( 'data-videojs-id' );
5964	
5965	this.player = videojs( videoID );
5966
5967	this.player.ready(function() {
5968		that.ready = true;
5969		that.trigger({ type: 'ready' });
5970
5971		that.player.on( 'play', function() {
5972			if ( that.started === false ) {
5973				that.started = true;
5974				that.trigger({ type: 'start' });
5975			}
5976
5977			that.state = 'playing';
5978			that.trigger({ type: 'play' });
5979		});
5980		
5981		that.player.on( 'pause', function() {
5982			that.state = 'paused';
5983			that.trigger({ type: 'pause' });
5984		});
5985		
5986		that.player.on( 'ended', function() {
5987			that.state = 'ended';
5988			that.trigger({ type: 'ended' });
5989		});
5990	});
5991};
5992
5993VideoJSVideo.prototype.play = function() {
5994	this.player.play();
5995};
5996
5997VideoJSVideo.prototype.pause = function() {
5998	this.player.pause();
5999};
6000
6001VideoJSVideo.prototype.stop = function() {
6002	this.player.currentTime( 0 );
6003	this.player.pause();
6004	this.state = 'stopped';
6005};
6006
6007VideoJSVideo.prototype.replay = function() {
6008	this.player.currentTime( 0 );
6009	this.player.play();
6010};
6011
6012// Sublime video
6013var SublimeVideo = function( video ) {
6014	Video.call( this, video );
6015};
6016
6017SublimeVideo.prototype = new Video();
6018SublimeVideo.prototype.constructor = SublimeVideo;
6019$.VideoController.addPlayer( 'SublimeVideo', SublimeVideo );
6020
6021SublimeVideo.isType = function( video ) {
6022	if ( video.hasClass( 'sublime' ) && typeof sublime !== 'undefined' ) {
6023		return true;
6024	}
6025
6026	return false;
6027};
6028
6029SublimeVideo.prototype._init = function() {
6030	var that = this;
6031
6032	sublime.ready(function() {
6033		// Get a reference to the player
6034		that.player = sublime.player( that.$video.attr( 'id' ) );
6035
6036		that.ready = true;
6037		that.trigger({ type: 'ready' });
6038
6039		that.player.on( 'play', function() {
6040			if ( that.started === false ) {
6041				that.started = true;
6042				that.trigger({ type: 'start' });
6043			}
6044
6045			that.state = 'playing';
6046			that.trigger({ type: 'play' });
6047		});
6048
6049		that.player.on( 'pause', function() {
6050			that.state = 'paused';
6051			that.trigger({ type: 'pause' });
6052		});
6053
6054		that.player.on( 'stop', function() {
6055			that.state = 'stopped';
6056			that.trigger({ type: 'stop' });
6057		});
6058
6059		that.player.on( 'end', function() {
6060			that.state = 'ended';
6061			that.trigger({ type: 'ended' });
6062		});
6063	});
6064};
6065
6066SublimeVideo.prototype.play = function() {
6067	this.player.play();
6068};
6069
6070SublimeVideo.prototype.pause = function() {
6071	this.player.pause();
6072};
6073
6074SublimeVideo.prototype.stop = function() {
6075	this.player.stop();
6076};
6077
6078SublimeVideo.prototype.replay = function() {
6079	this.player.stop();
6080	this.player.play();
6081};
6082
6083// JWPlayer video
6084var JWPlayerVideo = function( video ) {
6085	Video.call( this, video );
6086};
6087
6088JWPlayerVideo.prototype = new Video();
6089JWPlayerVideo.prototype.constructor = JWPlayerVideo;
6090$.VideoController.addPlayer( 'JWPlayerVideo', JWPlayerVideo );
6091
6092JWPlayerVideo.isType = function( video ) {
6093	if ( ( typeof video.attr( 'data-jwplayer-id' ) !== 'undefined' || video.hasClass( 'jwplayer' ) || video.find( "object[data*='jwplayer']" ).length !== 0 ) &&
6094		typeof jwplayer !== 'undefined') {
6095		return true;
6096	}
6097
6098	return false;
6099};
6100
6101JWPlayerVideo.prototype._init = function() {
6102	var that = this,
6103		videoID;
6104
6105	if ( this.$video.hasClass( 'jwplayer' ) ) {
6106		videoID = this.$video.attr( 'id' );
6107	} else if ( typeof this.$video.attr( 'data-jwplayer-id' ) !== 'undefined' ) {
6108		videoID = this.$video.attr( 'data-jwplayer-id');
6109	} else if ( this.$video.find( "object[data*='jwplayer']" ).length !== 0 ) {
6110		videoID = this.$video.find( 'object' ).attr( 'id' );
6111	}
6112
6113	// Get a reference to the player
6114	this.player = jwplayer( videoID );
6115
6116	this.player.onReady(function() {
6117		that.ready = true;
6118		that.trigger({ type: 'ready' });
6119	
6120		that.player.onPlay(function() {
6121			if ( that.started === false ) {
6122				that.started = true;
6123				that.trigger({ type: 'start' });
6124			}
6125
6126			that.state = 'playing';
6127			that.trigger({ type: 'play' });
6128		});
6129
6130		that.player.onPause(function() {
6131			that.state = 'paused';
6132			that.trigger({ type: 'pause' });
6133		});
6134		
6135		that.player.onComplete(function() {
6136			that.state = 'ended';
6137			that.trigger({ type: 'ended' });
6138		});
6139	});
6140};
6141
6142JWPlayerVideo.prototype.play = function() {
6143	this.player.play( true );
6144};
6145
6146JWPlayerVideo.prototype.pause = function() {
6147	this.player.pause( true );
6148};
6149
6150JWPlayerVideo.prototype.stop = function() {
6151	this.player.stop();
6152	this.state = 'stopped';
6153};
6154
6155JWPlayerVideo.prototype.replay = function() {
6156	this.player.seek( 0 );
6157	this.player.play( true );
6158};
6159
6160})( jQuery );

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.