PageSourceSearch

https://xanhislandsungroup.com/wp-content/plugins/ultimate-respons…/assets/js/jquery.sliderPro.js?ver=1.5.0

js xanhislandsungroup.com collected 2026-09-24 14:57:48 UTC 204,114 bytes, 6,205 lines download raw bytes

1/*!
2*  - v1.5.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() {
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 && this.settings.visibleSize !== 'auto' ? 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 && this.settings.visibleSize !== 'auto' ? 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.$slide.width() / this.$slide.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.$slide.width() / this.$slide.height() ) {
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					}
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		// Indicates whether the previous 'start' event was a 'touchstart' or 'mousedown'
3745		previousStartEvent: '',
3746
3747		initTouchSwipe: function() {
3748			var that = this;
3749
3750			// check if touch swipe is enabled
3751			if ( this.settings.touchSwipe === false ) {
3752				return;
3753			}
3754
3755			this.touchSwipeEvents.startEvent = 'touchstart' + '.' + NS + ' mousedown' + '.' + NS;
3756			this.touchSwipeEvents.moveEvent = 'touchmove' + '.' + NS + ' mousemove' + '.' + NS;
3757			this.touchSwipeEvents.endEvent = 'touchend' + '.' + this.uniqueId + '.' + NS + ' mouseup' + '.' + this.uniqueId + '.' + NS;
3758
3759			// Listen for touch swipe/mouse move events
3760			this.$slidesMask.on( this.touchSwipeEvents.startEvent, $.proxy( this._onTouchStart, this ) );
3761			this.$slidesMask.on( 'dragstart.' + NS, function( event ) {
3762				event.preventDefault();
3763			});
3764
3765			// Prevent 'click' events unless there is intention for a 'click'
3766			this.$slidesMask.find( 'a' ).on( 'click.' + NS, function( event ) {
3767				if ( that.$slider.hasClass( 'sp-swiping' ) ) {
3768					event.preventDefault();
3769				}
3770			});
3771
3772			// Add the grabbing icon
3773			this.$slidesMask.addClass( 'sp-grab' );
3774		},
3775
3776		// Called when the slides starts being dragged
3777		_onTouchStart: function( event ) {
3778
3779			// Return if a 'mousedown' event follows a 'touchstart' event
3780			if ( event.type === 'mousedown' && this.previousStartEvent === 'touchstart' ) {
3781				this.previousStartEvent = event.type;
3782				return;
3783			}
3784
3785			// Assign the new 'start' event
3786			this.previousStartEvent = event.type;
3787
3788			// Disable dragging if the element is set to allow selections
3789			if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) {
3790				return;
3791			}
3792
3793			var that = this,
3794				eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
3795
3796			// Get the initial position of the mouse pointer and the initial position
3797			// of the slides' container
3798			this.touchStartPoint.x = eventObject.pageX || eventObject.clientX;
3799			this.touchStartPoint.y = eventObject.pageY || eventObject.clientY;
3800			this.touchStartPosition = this.slidesPosition;
3801
3802			// Clear the previous distance values
3803			this.touchDistance.x = this.touchDistance.y = 0;
3804
3805			// If the slides are being grabbed while they're still animating, stop the
3806			// current movement
3807			if ( this.$slides.hasClass( 'sp-animated' ) ) {
3808				this.isTouchMoving = true;
3809				this._stopMovement();
3810				this.touchStartPosition = this.slidesPosition;
3811			}
3812
3813			// Listen for move and end events
3814			this.$slidesMask.on( this.touchSwipeEvents.moveEvent, $.proxy( this._onTouchMove, this ) );
3815			$( document ).on( this.touchSwipeEvents.endEvent, $.proxy( this._onTouchEnd, this ) );
3816
3817			// Swap grabbing icons
3818			this.$slidesMask.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' );
3819		},
3820
3821		// Called during the slides' dragging
3822		_onTouchMove: function( event ) {
3823			var eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
3824
3825			// Indicate that the move event is being fired
3826			this.isTouchMoving = true;
3827
3828			// Add 'sp-swiping' class to indicate that the slides are being swiped
3829			if ( this.$slider.hasClass( 'sp-swiping' ) === false ) {
3830				this.$slider.addClass( 'sp-swiping' );
3831			}
3832
3833			// Get the current position of the mouse pointer
3834			this.touchEndPoint.x = eventObject.pageX || eventObject.clientX;
3835			this.touchEndPoint.y = eventObject.pageY || eventObject.clientY;
3836
3837			// Calculate the distance of the movement on both axis
3838			this.touchDistance.x = this.touchEndPoint.x - this.touchStartPoint.x;
3839			this.touchDistance.y = this.touchEndPoint.y - this.touchStartPoint.y;
3840			
3841			// Calculate the distance of the swipe that takes place in the same direction as the orientation of the slides
3842			// and calculate the distance from the opposite direction.
3843			// 
3844			// For a swipe to be valid there should more distance in the same direction as the orientation of the slides.
3845			var distance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y,
3846				oppositeDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.y : this.touchDistance.x;
3847
3848			// If the movement is in the same direction as the orientation of the slides, the swipe is valid
3849			// and opposite scrolling will not be allowed.
3850			if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) {
3851				this.allowOppositeScrolling = false;
3852			}
3853
3854			// If opposite scrolling is still allowed, the swipe wasn't valid, so return.
3855			if ( this.allowOppositeScrolling === true ) {
3856				return;
3857			}
3858			
3859			// Don't allow opposite scrolling
3860			event.preventDefault();
3861
3862			if ( this.settings.loop === false ) {
3863				// Make the slides move slower if they're dragged outside its bounds
3864				if ( ( this.slidesPosition > this.touchStartPosition && this.selectedSlideIndex === 0 ) ||
3865					( this.slidesPosition < this.touchStartPosition && this.selectedSlideIndex === this.getTotalSlides() - 1 )
3866				) {
3867					distance = distance * 0.2;
3868				}
3869			}
3870
3871			this._moveTo( this.touchStartPosition + distance, true );
3872		},
3873
3874		// Called when the slides are released
3875		_onTouchEnd: function( event ) {
3876			var that = this,
3877				touchDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y;
3878
3879			// Remove the 'move' and 'end' listeners
3880			this.$slidesMask.off( this.touchSwipeEvents.moveEvent );
3881			$( document ).off( this.touchSwipeEvents.endEvent );
3882
3883			this.allowOppositeScrolling = true;
3884
3885			// Swap grabbing icons
3886			this.$slidesMask.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' );
3887
3888			// Remove the 'sp-swiping' class with a delay, to allow
3889			// other event listeners (i.e. click) to check the existance
3890			// of the swipe event.
3891			if ( this.$slider.hasClass( 'sp-swiping' ) ) {
3892				setTimeout(function() {
3893					that.$slider.removeClass( 'sp-swiping' );
3894				}, 100 );
3895			}
3896
3897			// Return if the slides didn't move
3898			if ( this.isTouchMoving === false ) {
3899				return;
3900			}
3901
3902			this.isTouchMoving = false;
3903
3904			// Calculate the old position of the slides in order to return to it if the swipe
3905			// is below the threshold
3906			var selectedSlideOffset = this.settings.centerSelectedSlide === true && this.settings.visibleSize !== 'auto' ? Math.round( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10 ) - this.getSlideAt( this.selectedSlideIndex ).getSize()[ this.sizeProperty ] ) / 2 ) : 0,
3907				oldSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + selectedSlideOffset;
3908
3909			if ( Math.abs( touchDistance ) < this.settings.touchSwipeThreshold ) {
3910				this._moveTo( oldSlidesPosition );
3911			} else {
3912				
3913				// Calculate by how many slides the slides container has moved
3914				var	slideArrayDistance = ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ? -1 : 1 ) * touchDistance / ( this.averageSlideSize + this.settings.slideDistance );
3915
3916				// Floor the obtained value and add or subtract 1, depending on the direction of the swipe
3917				slideArrayDistance = parseInt( slideArrayDistance, 10 ) + ( slideArrayDistance > 0 ? 1 : - 1 );
3918
3919				// Get the index of the currently selected slide and subtract the position index in order to obtain
3920				// the new index of the selected slide. 
3921				var nextSlideIndex = this.slidesOrder[ $.inArray( this.selectedSlideIndex, this.slidesOrder ) - slideArrayDistance ];
3922
3923				if ( this.settings.loop === true ) {
3924					this.gotoSlide( nextSlideIndex );
3925				} else {
3926					if ( typeof nextSlideIndex !== 'undefined' ) {
3927						this.gotoSlide( nextSlideIndex );
3928					} else {
3929						this._moveTo( oldSlidesPosition );
3930					}
3931				}
3932			}
3933		},
3934
3935		// Destroy the module
3936		destroyTouchSwipe: function() {
3937			this.$slidesMask.off( 'dragstart.' + NS );
3938			this.$slidesMask.find( 'a' ).off( 'click.' + NS );
3939
3940			this.$slidesMask.off( this.touchSwipeEvents.startEvent );
3941			this.$slidesMask.off( this.touchSwipeEvents.moveEvent );
3942			$( document ).off( this.touchSwipeEvents.endEvent );
3943			
3944			this.$slidesMask.removeClass( 'sp-grab' );
3945		},
3946
3947		touchSwipeDefaults: {
3948			
3949			// Indicates whether the touch swipe will be enabled
3950			touchSwipe: true,
3951
3952			// Sets the minimum amount that the slides should move
3953			touchSwipeThreshold: 50
3954		}
3955	};
3956
3957	$.SliderPro.addModule( 'TouchSwipe', TouchSwipe );
3958	
3959})( window, jQuery );
3960
3961// Caption module for Slider Pro.
3962// 
3963// Adds a corresponding caption for each slide. The caption
3964// will appear and disappear with the slide.
3965;(function( window, $ ) {
3966
3967	"use strict";
3968	
3969	var NS = 'Caption.' + $.SliderPro.namespace;
3970
3971	var Caption = {
3972
3973		// Reference to the container element that will hold the caption
3974		$captionContainer: null,
3975
3976		// The caption content/text
3977		captionContent: '',
3978
3979		initCaption: function() {
3980			this.on( 'update.' + NS, $.proxy( this._captionOnUpdate, this ) );
3981			this.on( 'gotoSlide.' + NS, $.proxy( this._updateCaptionContent, this ) );
3982		},
3983
3984		// Create the caption container and hide the captions inside the slides
3985		_captionOnUpdate: function() {
3986			this.$captionContainer = this.$slider.find( '.sp-caption-container' );
3987
3988			if ( this.$slider.find( '.sp-caption' ).length && this.$captionContainer.length === 0 ) {
3989				this.$captionContainer = $( '<div class="sp-caption-container"></div>' ).appendTo( this.$slider );
3990
3991				// Show the caption for the selected slide
3992				this._updateCaptionContent();
3993			}
3994
3995			// Hide the captions inside the slides
3996			this.$slides.find( '.sp-caption' ).each(function() {
3997				$( this ).css( 'display', 'none' );
3998			});
3999		},
4000
4001		// Show the caption content for the selected slide
4002		_updateCaptionContent: function() {
4003			var that = this,
4004				newCaptionField = this.$slider.find( '.sp-slide' ).eq( this.selectedSlideIndex ).find( '.sp-caption' ),
4005				newCaptionContent = newCaptionField.length !== 0 ? newCaptionField.html() : '';
4006
4007			// Either use a fade effect for swapping the captions or use an instant change
4008			if ( this.settings.fadeCaption === true ) {
4009				
4010				// If the previous slide had a caption, fade out that caption first and when the animation is over
4011				// fade in the current caption.
4012				// If the previous slide didn't have a caption, fade in the current caption directly.
4013				if ( this.captionContent !== '' ) {
4014
4015					// If the caption container has 0 opacity when the fade out transition starts, set it
4016					// to 1 because the transition wouldn't work if the initial and final values are the same,
4017					// and the callback functions wouldn't fire in this case.
4018					if ( parseFloat( this.$captionContainer.css( 'opacity' ), 10 ) === 0 ) {
4019						this.$captionContainer.css( this.vendorPrefix + 'transition', '' );
4020						this.$captionContainer.css( 'opacity', 1 );
4021					}
4022
4023					this._fadeCaptionTo( 0, function() {
4024						that.captionContent = newCaptionContent;
4025
4026						if ( newCaptionContent !== '' ) {
4027							that.$captionContainer.html( that.captionContent );
4028							that._fadeCaptionTo( 1 );
4029						} else {
4030							that.$captionContainer.empty();
4031						}
4032					});
4033				} else {
4034					this.captionContent = newCaptionContent;
4035					this.$captionContainer.html( this.captionContent );
4036					this.$captionContainer.css( 'opacity', 0 );
4037					this._fadeCaptionTo( 1 );
4038				}
4039			} else {
4040				this.captionContent = newCaptionContent;
4041				this.$captionContainer.html( this.captionContent );
4042			}
4043		},
4044
4045		// Fade the caption container to the specified opacity
4046		_fadeCaptionTo: function( opacity, callback ) {
4047			var that = this;
4048
4049			// Use CSS transitions if they are supported. If not, use JavaScript animation.
4050			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
4051				
4052				// There needs to be a delay between the moment the opacity is set
4053				// and the moment the transitions starts.
4054				setTimeout(function(){
4055					var css = { 'opacity': opacity };
4056					css[ that.vendorPrefix + 'transition' ] = 'opacity ' + that.settings.captionFadeDuration / 1000 + 's';
4057					that.$captionContainer.css( css );
4058				}, 1 );
4059
4060				this.$captionContainer.on( this.transitionEvent, function( event ) {
4061					if ( event.target !== event.currentTarget ) {
4062						return;
4063					}
4064
4065					that.$captionContainer.off( that.transitionEvent );
4066					that.$captionContainer.css( that.vendorPrefix + 'transition', '' );
4067
4068					if ( typeof callback === 'function' ) {
4069						callback();
4070					}
4071				});
4072			} else {
4073				this.$captionContainer.stop().animate({ 'opacity': opacity }, this.settings.captionFadeDuration, function() {
4074					if ( typeof callback === 'function' ) {
4075						callback();
4076					}
4077				});
4078			}
4079		},
4080
4081		// Destroy the module
4082		destroyCaption: function() {
4083			this.off( 'update.' + NS );
4084			this.off( 'gotoSlide.' + NS );
4085
4086			this.$captionContainer.remove();
4087
4088			this.$slider.find( '.sp-caption' ).each(function() {
4089				$( this ).css( 'display', '' );
4090			});
4091		},
4092
4093		captionDefaults: {
4094
4095			// Indicates whether or not the captions will be faded
4096			fadeCaption: true,
4097
4098			// Sets the duration of the fade animation
4099			captionFadeDuration: 500
4100		}
4101	};
4102
4103	$.SliderPro.addModule( 'Caption', Caption );
4104	
4105})( window, jQuery );
4106
4107// Deep Linking module for Slider Pro.
4108// 
4109// Updates the hash of the URL as the user navigates through the slides.
4110// Also, allows navigating to a specific slide by indicating it in the hash.
4111;(function( window, $ ) {
4112
4113	"use strict";
4114
4115	var NS = 'DeepLinking.' + $.SliderPro.namespace;
4116
4117	var DeepLinking = {
4118
4119		initDeepLinking: function() {
4120			var that = this;
4121
4122			// Parse the initial hash
4123			this.on( 'init.' + NS, function() {
4124				that._gotoHash( window.location.hash );
4125			});
4126
4127			// Update the hash when a new slide is selected
4128			this.on( 'gotoSlide.' + NS, function( event ) {
4129				if ( that.settings.updateHash === true ) {
4130
4131					// get the 'id' attribute of the slide
4132					var slideId = that.$slider.find( '.sp-slide' ).eq( event.index ).attr( 'id' );
4133
4134					// if the slide doesn't have an 'id' attribute, use the slide index
4135					if ( typeof slideId === 'undefined' ) {
4136						slideId = event.index;
4137					}
4138
4139					window.location.hash = that.$slider.attr( 'id' ) + '/' + slideId;
4140				}
4141			});
4142
4143			// Check when the hash changes and navigate to the indicated slide
4144			$( window ).on( 'hashchange.' + this.uniqueId + '.' + NS, function() {
4145				that._gotoHash( window.location.hash );
4146			});
4147		},
4148
4149		// Parse the hash and return the slider id and the slide id
4150		_parseHash: function( hash ) {
4151			if ( hash !== '' ) {
4152				// Eliminate the # symbol
4153				hash = hash.substring(1);
4154
4155				// Get the specified slider id and slide id
4156				var values = hash.split( '/' ),
4157					slideId = values.pop(),
4158					sliderId = hash.slice( 0, - slideId.toString().length - 1 );
4159
4160				if ( this.$slider.attr( 'id' ) === sliderId ) {
4161					return { 'sliderID': sliderId, 'slideId': slideId };
4162				}
4163			}
4164
4165			return false;
4166		},
4167
4168		// Navigate to the appropriate slide, based on the specified hash
4169		_gotoHash: function( hash ) {
4170			var result = this._parseHash( hash );
4171
4172			if ( result === false ) {
4173				return;
4174			}
4175
4176			var slideId = result.slideId,
4177				slideIdNumber = parseInt( slideId, 10 );
4178
4179			// check if the specified slide id is a number or string
4180			if ( isNaN( slideIdNumber ) ) {
4181				// get the index of the slide based on the specified id
4182				var slideIndex = this.$slider.find( '.sp-slide#' + slideId ).index();
4183
4184				if ( slideIndex !== -1 && slideIndex !== this.selectedSlideIndex ) {
4185					this.gotoSlide( slideIndex );
4186				}
4187			} else if ( slideIdNumber !== this.selectedSlideIndex ) {
4188				this.gotoSlide( slideIdNumber );
4189			}
4190		},
4191
4192		// Destroy the module
4193		destroyDeepLinking: function() {
4194			this.off( 'init.' + NS );
4195			this.off( 'gotoSlide.' + NS );
4196			$( window ).off( 'hashchange.' + this.uniqueId + '.' + NS );
4197		},
4198
4199		deepLinkingDefaults: {
4200
4201			// Indicates whether the hash will be updated when a new slide is selected
4202			updateHash: false
4203		}
4204	};
4205
4206	$.SliderPro.addModule( 'DeepLinking', DeepLinking );
4207	
4208})( window, jQuery );
4209
4210// Autoplay module for Slider Pro.
4211// 
4212// Adds automatic navigation through the slides by calling the
4213// 'nextSlide' or 'previousSlide' methods at certain time intervals.
4214;(function( window, $ ) {
4215
4216	"use strict";
4217	
4218	var NS = 'Autoplay.' + $.SliderPro.namespace;
4219
4220	var Autoplay = {
4221
4222		autoplayTimer: null,
4223
4224		isTimerRunning: false,
4225
4226		isTimerPaused: false,
4227
4228		initAutoplay: function() {
4229			this.on( 'update.' + NS, $.proxy( this._autoplayOnUpdate, this ) );
4230		},
4231
4232		// Start the autoplay if it's enabled, or stop it if it's disabled but running 
4233		_autoplayOnUpdate: function( event ) {
4234			if ( this.settings.autoplay === true ) {
4235				this.on( 'gotoSlide.' + NS, $.proxy( this._autoplayOnGotoSlide, this ) );
4236				this.on( 'mouseenter.' + NS, $.proxy( this._autoplayOnMouseEnter, this ) );
4237				this.on( 'mouseleave.' + NS, $.proxy( this._autoplayOnMouseLeave, this ) );
4238
4239				this.startAutoplay();
4240			} else {
4241				this.off( 'gotoSlide.' + NS );
4242				this.off( 'mouseenter.' + NS );
4243				this.off( 'mouseleave.' + NS );
4244
4245				this.stopAutoplay();
4246			}
4247		},
4248
4249		// Restart the autoplay timer when a new slide is selected
4250		_autoplayOnGotoSlide: function( event ) {
4251			// stop previous timers before starting a new one
4252			if ( this.isTimerRunning === true ) {
4253				this.stopAutoplay();
4254			}
4255			
4256			if ( this.isTimerPaused === false ) {
4257				this.startAutoplay();
4258			}
4259		},
4260
4261		// Pause the autoplay when the slider is hovered
4262		_autoplayOnMouseEnter: function( event ) {
4263			if ( this.isTimerRunning && ( this.settings.autoplayOnHover === 'pause' || this.settings.autoplayOnHover === 'stop' ) ) {
4264				this.stopAutoplay();
4265				this.isTimerPaused = true;
4266			}
4267		},
4268
4269		// Start the autoplay when the mouse moves away from the slider
4270		_autoplayOnMouseLeave: function( event ) {
4271			if ( this.settings.autoplay === true && this.isTimerRunning === false && this.settings.autoplayOnHover !== 'stop' ) {
4272				this.startAutoplay();
4273				this.isTimerPaused = false;
4274			}
4275		},
4276
4277		// Starts the autoplay
4278		startAutoplay: function() {
4279			var that = this;
4280			
4281			this.isTimerRunning = true;
4282
4283			this.autoplayTimer = setTimeout(function() {
4284				if ( that.settings.autoplayDirection === 'normal' ) {
4285					that.nextSlide();
4286				} else if ( that.settings.autoplayDirection === 'backwards' ) {
4287					that.previousSlide();
4288				}
4289			}, this.settings.autoplayDelay );
4290		},
4291
4292		// Stops the autoplay
4293		stopAutoplay: function() {
4294			this.isTimerRunning = false;
4295			this.isTimerPaused = false;
4296
4297			clearTimeout( this.autoplayTimer );
4298		},
4299
4300		// Destroy the module
4301		destroyAutoplay: function() {
4302			clearTimeout( this.autoplayTimer );
4303
4304			this.off( 'update.' + NS );
4305			this.off( 'gotoSlide.' + NS );
4306			this.off( 'mouseenter.' + NS );
4307			this.off( 'mouseleave.' + NS );
4308		},
4309
4310		autoplayDefaults: {
4311			// Indicates whether or not autoplay will be enabled
4312			autoplay: true,
4313
4314			// Sets the delay/interval at which the autoplay will run
4315			autoplayDelay: 5000,
4316
4317			// Indicates whether autoplay will navigate to the next slide or previous slide
4318			autoplayDirection: 'normal',
4319
4320			// Indicates if the autoplay will be paused or stopped when the slider is hovered.
4321			// Possible values are 'pause', 'stop' or 'none'.
4322			autoplayOnHover: 'pause'
4323		}
4324	};
4325
4326	$.SliderPro.addModule( 'Autoplay', Autoplay );
4327	
4328})(window, jQuery);
4329
4330// Keyboard module for Slider Pro.
4331// 
4332// Adds the possibility to navigate through slides using the keyboard arrow keys, or
4333// open the link attached to the main slide image by using the Enter key.
4334;(function( window, $ ) {
4335
4336	"use strict";
4337	
4338	var NS = 'Keyboard.' + $.SliderPro.namespace;
4339
4340	var Keyboard = {
4341
4342		initKeyboard: function() {
4343			var that = this,
4344				hasFocus = false;
4345
4346			if ( this.settings.keyboard === false ) {
4347				return;
4348			}
4349
4350			// Detect when the slide is in focus and when it's not, and, optionally, make it
4351			// responsive to keyboard input only when it's in focus
4352			this.$slider.on( 'focus.' + NS, function() {
4353				hasFocus = true;
4354			});
4355
4356			this.$slider.on( 'blur.' + NS, function() {
4357				hasFocus = false;
4358			});
4359
4360			$( document ).on( 'keydown.' + this.uniqueId + '.' + NS, function( event ) {
4361				if ( that.settings.keyboardOnlyOnFocus === true && hasFocus === false ) {
4362					return;
4363				}
4364
4365				// If the left arrow key is pressed, go to the previous slide.
4366				// If the right arrow key is pressed, go to the next slide.
4367				// If the Enter key is pressed, open the link attached to the main slide image.
4368				if ( event.which === 37 ) {
4369					that.previousSlide();
4370				} else if ( event.which === 39 ) {
4371					that.nextSlide();
4372				} else if ( event.which === 13 ) {
4373					var link = that.$slider.find( '.sp-slide' ).eq( that.selectedSlideIndex ).find( '.sp-image-container a' );
4374					
4375					if ( link.length !== 0 ) {
4376						link[0].click();
4377					}
4378				}
4379			});
4380		},
4381
4382		// Destroy the module
4383		destroyKeyboard: function() {
4384			this.$slider.off( 'focus.' + NS );
4385			this.$slider.off( 'blur.' + NS );
4386			$( document ).off( 'keydown.' + this.uniqueId + '.' + NS );
4387		},
4388
4389		keyboardDefaults: {
4390
4391			// Indicates whether keyboard navigation will be enabled
4392			keyboard: true,
4393
4394			// Indicates whether the slider will respond to keyboard input only when
4395			// the slider is in focus.
4396			keyboardOnlyOnFocus: false
4397		}
4398	};
4399
4400	$.SliderPro.addModule( 'Keyboard', Keyboard );
4401	
4402})( window, jQuery );
4403
4404// Full Screen module for Slider Pro.
4405// 
4406// Adds the possibility to open the slider full-screen, using the HTML5 FullScreen API.
4407;(function( window, $ ) {
4408
4409	"use strict";
4410
4411	var NS = 'FullScreen.' + $.SliderPro.namespace;
4412
4413	var FullScreen = {
4414
4415		// Indicates whether the slider is currently in full-screen mode
4416		isFullScreen: false,
4417
4418		// Reference to the full-screen button
4419		$fullScreenButton: null,
4420
4421		// Reference to a set of settings that influence the slider's size
4422		// before it goes full-screen
4423		sizeBeforeFullScreen: {},
4424
4425		initFullScreen: function() {
4426			if ( ! ( document.fullscreenEnabled ||
4427				document.webkitFullscreenEnabled ||
4428				document.mozFullScreenEnabled ||
4429				document.msFullscreenEnabled ) ) {
4430				return;
4431			}
4432		
4433			this.on( 'update.' + NS, $.proxy( this._fullScreenOnUpdate, this ) );
4434		},
4435
4436		// Create or remove the full-screen button depending on the value of the 'fullScreen' option
4437		_fullScreenOnUpdate: function() {
4438			if ( this.settings.fullScreen === true && this.$fullScreenButton === null ) {
4439				this._addFullScreen();
4440			} else if ( this.settings.fullScreen === false && this.$fullScreenButton !== null ) {
4441				this._removeFullScreen();
4442			}
4443
4444			if ( this.settings.fullScreen === true ) {
4445				if ( this.settings.fadeFullScreen === true ) {
4446					this.$fullScreenButton.addClass( 'sp-fade-full-screen' );
4447				} else if ( this.settings.fadeFullScreen === false ) {
4448					this.$fullScreenButton.removeClass( 'sp-fade-full-screen' );
4449				}
4450			}
4451		},
4452
4453		// Create the full-screen button
4454		_addFullScreen: function() {
4455			this.$fullScreenButton = $('<div class="sp-full-screen-button"></div>').appendTo( this.$slider );
4456			this.$fullScreenButton.on( 'click.' + NS, $.proxy( this._onFullScreenButtonClick, this ) );
4457
4458			document.addEventListener( 'fullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4459			document.addEventListener( 'mozfullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4460			document.addEventListener( 'webkitfullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4461			document.addEventListener( 'MSFullscreenChange', $.proxy( this._onFullScreenChange, this ) );
4462		},
4463
4464		// Remove the full-screen button
4465		_removeFullScreen: function() {
4466			if ( this.$fullScreenButton !== null ) {
4467				this.$fullScreenButton.off( 'click.' + NS );
4468				this.$fullScreenButton.remove();
4469				this.$fullScreenButton = null;
4470				document.removeEventListener( 'fullscreenchange', this._onFullScreenChange );
4471				document.removeEventListener( 'mozfullscreenchange', this._onFullScreenChange );
4472				document.removeEventListener( 'webkitfullscreenchange', this._onFullScreenChange );
4473				document.removeEventListener( 'MSFullscreenChange', this._onFullScreenChange );
4474			}
4475		},
4476
4477		// When the full-screen button is clicked, put the slider into full-screen mode, and
4478		// take it out of the full-screen mode when it's clicked again.
4479		_onFullScreenButtonClick: function() {
4480			if ( this.isFullScreen === false ) {
4481				if ( this.instance.requestFullScreen ) {
4482					this.instance.requestFullScreen();
4483				} else if ( this.instance.mozRequestFullScreen ) {
4484					this.instance.mozRequestFullScreen();
4485				} else if ( this.instance.webkitRequestFullScreen ) {
4486					this.instance.webkitRequestFullScreen();
4487				} else if ( this.instance.msRequestFullscreen ) {
4488					this.instance.msRequestFullscreen();
4489				}
4490			} else {
4491				if ( document.exitFullScreen ) {
4492					document.exitFullScreen();
4493				} else if ( document.mozCancelFullScreen ) {
4494					document.mozCancelFullScreen();
4495				} else if ( document.webkitCancelFullScreen ) {
4496					document.webkitCancelFullScreen();
4497				} else if ( document.msExitFullscreen ) {
4498					document.msExitFullscreen();
4499				}
4500			}
4501		},
4502
4503		// This will be called whenever the full-screen mode changes.
4504		// If the slider is in full-screen mode, set it to 'full window', and if it's
4505		// not in full-screen mode anymore, set it back to the original size.
4506		_onFullScreenChange: function() {
4507			this.isFullScreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement ? true : false;
4508
4509			if ( this.isFullScreen === true ) {
4510				this.sizeBeforeFullScreen = { forceSize: this.settings.forceSize, autoHeight: this.settings.autoHeight };
4511				this.$slider.addClass( 'sp-full-screen' );
4512				this.settings.forceSize = 'fullWindow';
4513				this.settings.autoHeight = false;
4514			} else {
4515				this.$slider.css( 'margin', '' );
4516				this.$slider.removeClass( 'sp-full-screen' );
4517				this.settings.forceSize = this.sizeBeforeFullScreen.forceSize;
4518				this.settings.autoHeight = this.sizeBeforeFullScreen.autoHeight;
4519			}
4520
4521			this.resize();
4522		},
4523
4524		// Destroy the module
4525		destroyFullScreen: function() {
4526			this.off( 'update.' + NS );
4527			this._removeFullScreen();
4528		},
4529
4530		fullScreenDefaults: {
4531
4532			// Indicates whether the full-screen button is enabled
4533			fullScreen: false,
4534
4535			// Indicates whether the button will fade in only on hover
4536			fadeFullScreen: true
4537		}
4538	};
4539
4540	$.SliderPro.addModule( 'FullScreen', FullScreen );
4541
4542})( window, jQuery );
4543
4544// Buttons module for Slider Pro.
4545// 
4546// Adds navigation buttons at the bottom of the slider.
4547;(function( window, $ ) {
4548
4549	"use strict";
4550	
4551	var NS = 'Buttons.' + $.SliderPro.namespace;
4552
4553	var Buttons = {
4554
4555		// Reference to the buttons container
4556		$buttons: null,
4557
4558		initButtons: function() {
4559			this.on( 'update.' + NS, $.proxy( this._buttonsOnUpdate, this ) );
4560		},
4561
4562		_buttonsOnUpdate: function() {
4563			this.$buttons = this.$slider.find('.sp-buttons');
4564			
4565			// If there is more that one slide but the buttons weren't created yet, create the buttons.
4566			// If the buttons were created but their number differs from the total number of slides, re-create the buttons.
4567			// If the buttons were created but there are less than one slide, remove the buttons.s
4568			if ( this.settings.buttons === true && this.getTotalSlides() > 1 && this.$buttons.length === 0 ) {
4569				this._createButtons();
4570			} else if ( this.settings.buttons === true && this.getTotalSlides() !== this.$buttons.find( '.sp-button' ).length && this.$buttons.length !== 0 ) {
4571				this._adjustButtons();
4572			} else if ( this.settings.buttons === false || ( this.getTotalSlides() <= 1 && this.$buttons.length !== 0 ) ) {
4573				this._removeButtons();
4574			}
4575		},
4576
4577		// Create the buttons
4578		_createButtons: function() {
4579			var that = this;
4580
4581			// Create the buttons' container
4582			this.$buttons = $( '<div class="sp-buttons"></div>' ).appendTo( this.$slider );
4583
4584			// Create the buttons
4585			for ( var i = 0; i < this.getTotalSlides(); i++ ) {
4586				$( '<div class="sp-button"></div>' ).appendTo( this.$buttons );
4587			}
4588
4589			// Listen for button clicks 
4590			this.$buttons.on( 'click.' + NS, '.sp-button', function() {
4591				that.gotoSlide( $( this ).index() );
4592			});
4593
4594			// Set the initially selected button
4595			this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' );
4596
4597			// Select the corresponding button when the slide changes
4598			this.on( 'gotoSlide.' + NS, function( event ) {
4599				that.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' );
4600				that.$buttons.find( '.sp-button' ).eq( event.index ).addClass( 'sp-selected-button' );
4601			});
4602
4603			// Indicate that the slider has buttons 
4604			this.$slider.addClass( 'sp-has-buttons' );
4605		},
4606
4607		// Re-create the buttons. This is calles when the number of slides changes.
4608		_adjustButtons: function() {
4609			this.$buttons.empty();
4610
4611			// Create the buttons
4612			for ( var i = 0; i < this.getTotalSlides(); i++ ) {
4613				$( '<div class="sp-button"></div>' ).appendTo( this.$buttons );
4614			}
4615
4616			// Change the selected the buttons
4617			this.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' );
4618			this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' );
4619		},
4620
4621		// Remove the buttons
4622		_removeButtons: function() {
4623			this.$buttons.off( 'click.' + NS, '.sp-button' );
4624			this.off( 'gotoSlide.' + NS );
4625			this.$buttons.remove();
4626			this.$slider.removeClass( 'sp-has-buttons' );
4627		},
4628
4629		destroyButtons: function() {
4630			this._removeButtons();
4631			this.off( 'update.' + NS );
4632		},
4633
4634		buttonsDefaults: {
4635			
4636			// Indicates whether the buttons will be created
4637			buttons: true
4638		}
4639	};
4640
4641	$.SliderPro.addModule( 'Buttons', Buttons );
4642
4643})( window, jQuery );
4644
4645// Arrows module for Slider Pro.
4646// 
4647// Adds arrows for navigating to the next or previous slide.
4648;(function( window, $ ) {
4649
4650	"use strict";
4651
4652	var NS = 'Arrows.' + $.SliderPro.namespace;
4653
4654	var Arrows = {
4655
4656		// Reference to the arrows container
4657		$arrows: null,
4658
4659		// Reference to the previous arrow
4660		$previousArrow: null,
4661
4662		// Reference to the next arrow
4663		$nextArrow: null,
4664
4665		initArrows: function() {
4666			this.on( 'update.' + NS, $.proxy( this._arrowsOnUpdate, this ) );
4667			this.on( 'gotoSlide.' + NS, $.proxy( this._checkArrowsVisibility, this ) );
4668		},
4669
4670		_arrowsOnUpdate: function() {
4671			var that = this;
4672
4673			// Create the arrows if the 'arrows' option is set to true
4674			if ( this.settings.arrows === true && this.$arrows === null ) {
4675				this.$arrows = $( '<div class="sp-arrows"></div>' ).appendTo( this.$slidesContainer );
4676				
4677				this.$previousArrow = $( '<div class="sp-arrow sp-previous-arrow"></div>' ).appendTo( this.$arrows );
4678				this.$nextArrow = $( '<div class="sp-arrow sp-next-arrow"></div>' ).appendTo( this.$arrows );
4679
4680				this.$previousArrow.on( 'click.' + NS, function() {
4681					that.previousSlide();
4682				});
4683
4684				this.$nextArrow.on( 'click.' + NS, function() {
4685					that.nextSlide();
4686				});
4687
4688				this._checkArrowsVisibility();
4689			} else if ( this.settings.arrows === false && this.$arrows !== null ) {
4690				this._removeArrows();
4691			}
4692
4693			if ( this.settings.arrows === true ) {
4694				if ( this.settings.fadeArrows === true ) {
4695					this.$arrows.addClass( 'sp-fade-arrows' );
4696				} else if ( this.settings.fadeArrows === false ) {
4697					this.$arrows.removeClass( 'sp-fade-arrows' );
4698				}
4699			}
4700		},
4701
4702		// Show or hide the arrows depending on the position of the selected slide
4703		_checkArrowsVisibility: function() {
4704			if ( this.settings.arrows === false || this.settings.loop === true ) {
4705				return;
4706			}
4707
4708			if ( this.selectedSlideIndex === 0 ) {
4709				this.$previousArrow.css( 'display', 'none' );
4710			} else {
4711				this.$previousArrow.css( 'display', 'block' );
4712			}
4713
4714			if ( this.selectedSlideIndex === this.getTotalSlides() - 1 ) {
4715				this.$nextArrow.css( 'display', 'none' );
4716			} else {
4717				this.$nextArrow.css( 'display', 'block' );
4718			}
4719		},
4720		
4721		_removeArrows: function() {
4722			if ( this.$arrows !== null ) {
4723				this.$previousArrow.off( 'click.' + NS );
4724				this.$nextArrow.off( 'click.' + NS );
4725				this.$arrows.remove();
4726				this.$arrows = null;
4727			}
4728		},
4729
4730		destroyArrows: function() {
4731			this._removeArrows();
4732			this.off( 'update.' + NS );
4733			this.off( 'gotoSlide.' + NS );
4734		},
4735
4736		arrowsDefaults: {
4737
4738			// Indicates whether the arrow buttons will be created
4739			arrows: false,
4740
4741			// Indicates whether the arrows will fade in only on hover
4742			fadeArrows: true
4743		}
4744	};
4745
4746	$.SliderPro.addModule( 'Arrows', Arrows );
4747
4748})( window, jQuery );
4749
4750// Thumbnail Touch Swipe module for Slider Pro.
4751// 
4752// Adds touch-swipe functionality for thumbnails.
4753;(function( window, $ ) {
4754
4755	"use strict";
4756	
4757	var NS = 'ThumbnailTouchSwipe.' + $.SliderPro.namespace;
4758
4759	var ThumbnailTouchSwipe = {
4760
4761		// The x and y coordinates of the pointer/finger's starting position
4762		thumbnailTouchStartPoint: { x: 0, y: 0 },
4763
4764		// The x and y coordinates of the pointer/finger's end position
4765		thumbnailTouchEndPoint: { x: 0, y: 0 },
4766
4767		// The distance from the starting to the end position on the x and y axis
4768		thumbnailTouchDistance: { x: 0, y: 0 },
4769
4770		// The position of the thumbnail scroller when the touch swipe starts
4771		thumbnailTouchStartPosition: 0,
4772
4773		// Indicates if the thumbnail scroller is being swiped
4774		isThumbnailTouchMoving: false,
4775
4776		// Indicates if the touch swipe was initialized
4777		isThumbnailTouchSwipe: false,
4778
4779		// Stores the names of the events
4780		thumbnailTouchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' },
4781
4782		// Indicates whether the previous 'start' event was a 'touchstart' or 'mousedown'
4783		thumbnailPreviousStartEvent: '',
4784
4785		initThumbnailTouchSwipe: function() {
4786			this.on( 'update.' + NS, $.proxy( this._thumbnailTouchSwipeOnUpdate, this ) );
4787		},
4788
4789		_thumbnailTouchSwipeOnUpdate: function() {
4790
4791			// Return if there are no thumbnails
4792			if ( this.isThumbnailScroller === false ) {
4793				return;
4794			}
4795
4796			// Initialize the touch swipe functionality if it wasn't initialized yet
4797			if ( this.settings.thumbnailTouchSwipe === true && this.isThumbnailTouchSwipe === false ) {
4798				this.isThumbnailTouchSwipe = true;
4799
4800				this.thumbnailTouchSwipeEvents.startEvent = 'touchstart' + '.' + NS + ' mousedown' + '.' + NS;
4801				this.thumbnailTouchSwipeEvents.moveEvent = 'touchmove' + '.' + NS + ' mousemove' + '.' + NS;
4802				this.thumbnailTouchSwipeEvents.endEvent = 'touchend' + '.' + this.uniqueId + '.' + NS + ' mouseup' + '.' + this.uniqueId + '.' + NS;
4803				
4804				// Listen for touch swipe/mouse move events
4805				this.$thumbnails.on( this.thumbnailTouchSwipeEvents.startEvent, $.proxy( this._onThumbnailTouchStart, this ) );
4806				this.$thumbnails.on( 'dragstart.' + NS, function( event ) {
4807					event.preventDefault();
4808				});
4809			
4810				// Add the grabbing icon
4811				this.$thumbnails.addClass( 'sp-grab' );
4812			}
4813
4814			// Remove the default thumbnailClick
4815			$.each( this.thumbnails, function( index, thumbnail ) {
4816				thumbnail.off( 'thumbnailClick' );
4817			});
4818		},
4819
4820		// Called when the thumbnail scroller starts being dragged
4821		_onThumbnailTouchStart: function( event ) {
4822
4823			// Return if a 'mousedown' event follows a 'touchstart' event
4824			if ( event.type === 'mousedown' && this.thumbnailPreviousStartEvent === 'touchstart' ) {
4825				this.thumbnailPreviousStartEvent = event.type;
4826				return;
4827			}
4828
4829			// Assign the new 'start' event
4830			this.thumbnailPreviousStartEvent = event.type;
4831
4832			// Disable dragging if the element is set to allow selections
4833			if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) {
4834				return;
4835			}
4836
4837			var that = this,
4838				eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
4839
4840			// Prevent default behavior for mouse events
4841			if ( typeof event.originalEvent.touches === 'undefined' ) {
4842				event.preventDefault();
4843			}
4844
4845			// Disable click events on links
4846			$( event.target ).parents( '.sp-thumbnail-container' ).find( 'a' ).one( 'click.' + NS, function( event ) {
4847				event.preventDefault();
4848			});
4849
4850			// Get the initial position of the mouse pointer and the initial position
4851			// of the thumbnail scroller
4852			this.thumbnailTouchStartPoint.x = eventObject.pageX || eventObject.clientX;
4853			this.thumbnailTouchStartPoint.y = eventObject.pageY || eventObject.clientY;
4854			this.thumbnailTouchStartPosition = this.thumbnailsPosition;
4855
4856			// Clear the previous distance values
4857			this.thumbnailTouchDistance.x = this.thumbnailTouchDistance.y = 0;
4858
4859			// If the thumbnail scroller is being grabbed while it's still animating, stop the
4860			// current movement
4861			if ( this.$thumbnails.hasClass( 'sp-animated' ) ) {
4862				this.isThumbnailTouchMoving = true;
4863				this._stopThumbnailsMovement();
4864				this.thumbnailTouchStartPosition = this.thumbnailsPosition;
4865			}
4866
4867			// Listen for move and end events
4868			this.$thumbnails.on( this.thumbnailTouchSwipeEvents.moveEvent, $.proxy( this._onThumbnailTouchMove, this ) );
4869			$( document ).on( this.thumbnailTouchSwipeEvents.endEvent, $.proxy( this._onThumbnailTouchEnd, this ) );
4870
4871			// Swap grabbing icons
4872			this.$thumbnails.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' );
4873
4874			// Add 'sp-swiping' class to indicate that the thumbnail scroller is being swiped
4875			this.$thumbnailsContainer.addClass( 'sp-swiping' );
4876		},
4877
4878		// Called during the thumbnail scroller's dragging
4879		_onThumbnailTouchMove: function( event ) {
4880			var eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent;
4881
4882			// Indicate that the move event is being fired
4883			this.isThumbnailTouchMoving = true;
4884
4885			// Get the current position of the mouse pointer
4886			this.thumbnailTouchEndPoint.x = eventObject.pageX || eventObject.clientX;
4887			this.thumbnailTouchEndPoint.y = eventObject.pageY || eventObject.clientY;
4888
4889			// Calculate the distance of the movement on both axis
4890			this.thumbnailTouchDistance.x = this.thumbnailTouchEndPoint.x - this.thumbnailTouchStartPoint.x;
4891			this.thumbnailTouchDistance.y = this.thumbnailTouchEndPoint.y - this.thumbnailTouchStartPoint.y;
4892			
4893			// Calculate the distance of the swipe that takes place in the same direction as the orientation of the thumbnails
4894			// and calculate the distance from the opposite direction.
4895			// 
4896			// For a swipe to be valid there should more distance in the same direction as the orientation of the thumbnails.
4897			var distance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y,
4898				oppositeDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.y : this.thumbnailTouchDistance.x;
4899
4900			// If the movement is in the same direction as the orientation of the thumbnails, the swipe is valid
4901			if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) {
4902				event.preventDefault();
4903			} else {
4904				return;
4905			}
4906
4907			// Make the thumbnail scroller move slower if it's dragged outside its bounds
4908			if ( this.thumbnailsPosition >= 0 ) {
4909				var infOffset = - this.thumbnailTouchStartPosition;
4910				distance = infOffset + ( distance - infOffset ) * 0.2;
4911			} else if ( this.thumbnailsPosition <= - this.thumbnailsSize + this.thumbnailsContainerSize ) {
4912				var supOffset = this.thumbnailsSize - this.thumbnailsContainerSize + this.thumbnailTouchStartPosition;
4913				distance = - supOffset + ( distance + supOffset ) * 0.2;
4914			}
4915			
4916			this._moveThumbnailsTo( this.thumbnailTouchStartPosition + distance, true );
4917		},
4918
4919		// Called when the thumbnail scroller is released
4920		_onThumbnailTouchEnd: function( event ) {
4921			var that = this,
4922				thumbnailTouchDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y;
4923
4924			// Remove the move and end listeners
4925			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent );
4926			$( document ).off( this.thumbnailTouchSwipeEvents.endEvent );
4927
4928			// Swap grabbing icons
4929			this.$thumbnails.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' );
4930
4931			// Check if there is intention for a tap/click
4932			if ( this.isThumbnailTouchMoving === false ||
4933				this.isThumbnailTouchMoving === true &&
4934				Math.abs( this.thumbnailTouchDistance.x ) < 10 &&
4935				Math.abs( this.thumbnailTouchDistance.y ) < 10
4936			) {
4937				var targetThumbnail = $( event.target ).hasClass( 'sp-thumbnail-container' ) ? $( event.target ) : $( event.target ).parents( '.sp-thumbnail-container' ),
4938					index = targetThumbnail.index();
4939
4940				// If a link is cliked, navigate to that link, else navigate to the slide that corresponds to the thumbnail
4941				if ( $( event.target ).parents( 'a' ).length !== 0 ) {
4942					$( event.target ).parents( 'a' ).off( 'click.' + NS );
4943					this.$thumbnailsContainer.removeClass( 'sp-swiping' );
4944				} else if ( index !== this.selectedThumbnailIndex && index !== -1 ) {
4945					this.gotoSlide( index );
4946				}
4947
4948				return;
4949			}
4950
4951			this.isThumbnailTouchMoving = false;
4952
4953			$( event.target ).parents( '.sp-thumbnail' ).one( 'click', function( event ) {
4954				event.preventDefault();
4955			});
4956
4957			// Remove the 'sp-swiping' class but with a delay
4958			// because there might be other event listeners that check
4959			// the existence of this class, and this class should still be 
4960			// applied for those listeners, since there was a swipe event
4961			setTimeout(function() {
4962				that.$thumbnailsContainer.removeClass( 'sp-swiping' );
4963			}, 1 );
4964
4965			// Keep the thumbnail scroller inside the bounds
4966			if ( this.thumbnailsPosition > 0 ) {
4967				this._moveThumbnailsTo( 0 );
4968			} else if ( this.thumbnailsPosition < this.thumbnailsContainerSize - this.thumbnailsSize ) {
4969				this._moveThumbnailsTo( this.thumbnailsContainerSize - this.thumbnailsSize );
4970			}
4971
4972			// Fire the 'thumbnailsMoveComplete' event
4973			this.trigger({ type: 'thumbnailsMoveComplete' });
4974			if ( $.isFunction( this.settings.thumbnailsMoveComplete ) ) {
4975				this.settings.thumbnailsMoveComplete.call( this, { type: 'thumbnailsMoveComplete' });
4976			}
4977		},
4978
4979		// Destroy the module
4980		destroyThumbnailTouchSwipe: function() {
4981			this.off( 'update.' + NS );
4982
4983			if ( this.isThumbnailScroller === false ) {
4984				return;
4985			}
4986
4987			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.startEvent );
4988			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent );
4989			this.$thumbnails.off( 'dragstart.' + NS );
4990			$( document ).off( this.thumbnailTouchSwipeEvents.endEvent );
4991			this.$thumbnails.removeClass( 'sp-grab' );
4992		},
4993
4994		thumbnailTouchSwipeDefaults: {
4995
4996			// Indicates whether the touch swipe will be enabled for thumbnails
4997			thumbnailTouchSwipe: true
4998		}
4999	};
5000
5001	$.SliderPro.addModule( 'ThumbnailTouchSwipe', ThumbnailTouchSwipe );
5002
5003})( window, jQuery );
5004
5005// Thumbnail Arrows module for Slider Pro.
5006// 
5007// Adds thumbnail arrows for moving the thumbnail scroller.
5008;(function( window, $ ) {
5009
5010	"use strict";
5011	
5012	var NS = 'ThumbnailArrows.' + $.SliderPro.namespace;
5013
5014	var ThumbnailArrows = {
5015
5016		// Reference to the arrows container
5017		$thumbnailArrows: null,
5018
5019		// Reference to the 'previous' thumbnail arrow
5020		$previousThumbnailArrow: null,
5021
5022		// Reference to the 'next' thumbnail arrow
5023		$nextThumbnailArrow: null,
5024
5025		initThumbnailArrows: function() {
5026			var that = this;
5027
5028			this.on( 'update.' + NS, $.proxy( this._thumbnailArrowsOnUpdate, this ) );
5029			
5030			// Check if the arrows need to be visible or invisible when the thumbnail scroller
5031			// resizes and when the thumbnail scroller moves.
5032			this.on( 'sliderResize.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, function() {
5033				if ( that.isThumbnailScroller === true && that.settings.thumbnailArrows === true ) {
5034					that._checkThumbnailArrowsVisibility();
5035				}
5036			});
5037		},
5038		
5039		// Called when the slider is updated
5040		_thumbnailArrowsOnUpdate: function() {
5041			var that = this;
5042			
5043			if ( this.isThumbnailScroller === false ) {
5044				return;
5045			}
5046
5047			// Create or remove the thumbnail scroller arrows
5048			if ( this.settings.thumbnailArrows === true && this.$thumbnailArrows === null ) {
5049				this.$thumbnailArrows = $( '<div class="sp-thumbnail-arrows"></div>' ).appendTo( this.$thumbnailsContainer );
5050				
5051				this.$previousThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-previous-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows );
5052				this.$nextThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-next-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows );
5053
5054				this.$previousThumbnailArrow.on( 'click.' + NS, function() {
5055					var previousPosition = Math.min( 0, that.thumbnailsPosition + that.thumbnailsContainerSize );
5056					that._moveThumbnailsTo( previousPosition );
5057				});
5058
5059				this.$nextThumbnailArrow.on( 'click.' + NS, function() {
5060					var nextPosition = Math.max( that.thumbnailsContainerSize - that.thumbnailsSize, that.thumbnailsPosition - that.thumbnailsContainerSize );
5061					that._moveThumbnailsTo( nextPosition );
5062				});
5063			} else if ( this.settings.thumbnailArrows === false && this.$thumbnailArrows !== null ) {
5064				this._removeThumbnailArrows();
5065			}
5066
5067			// Add fading functionality and check if the arrows need to be visible or not
5068			if ( this.settings.thumbnailArrows === true ) {
5069				if ( this.settings.fadeThumbnailArrows === true ) {
5070					this.$thumbnailArrows.addClass( 'sp-fade-thumbnail-arrows' );
5071				} else if ( this.settings.fadeThumbnailArrows === false ) {
5072					this.$thumbnailArrows.removeClass( 'sp-fade-thumbnail-arrows' );
5073				}
5074
5075				this._checkThumbnailArrowsVisibility();
5076			}
5077		},
5078
5079		// Checks if the 'next' or 'previous' arrows need to be visible or hidden,
5080		// based on the position of the thumbnail scroller
5081		_checkThumbnailArrowsVisibility: function() {
5082			if ( this.thumbnailsPosition === 0 ) {
5083				this.$previousThumbnailArrow.css( 'display', 'none' );
5084			} else {
5085				this.$previousThumbnailArrow.css( 'display', 'block' );
5086			}
5087
5088			if ( this.thumbnailsPosition === this.thumbnailsContainerSize - this.thumbnailsSize ) {
5089				this.$nextThumbnailArrow.css( 'display', 'none' );
5090			} else {
5091				this.$nextThumbnailArrow.css( 'display', 'block' );
5092			}
5093		},
5094
5095		// Remove the thumbnail arrows
5096		_removeThumbnailArrows: function() {
5097			if ( this.$thumbnailArrows !== null ) {
5098				this.$previousThumbnailArrow.off( 'click.' + NS );
5099				this.$nextThumbnailArrow.off( 'click.' + NS );
5100				this.$thumbnailArrows.remove();
5101				this.$thumbnailArrows = null;
5102			}
5103		},
5104
5105		// Destroy the module
5106		destroyThumbnailArrows: function() {
5107			this._removeThumbnailArrows();
5108			this.off( 'update.' + NS );
5109			this.off( 'sliderResize.' + NS );
5110			this.off( 'thumbnailsMoveComplete.' + NS );
5111		},
5112
5113		thumbnailArrowsDefaults: {
5114
5115			// Indicates whether the thumbnail arrows will be enabled
5116			thumbnailArrows: false,
5117
5118			// Indicates whether the thumbnail arrows will be faded
5119			fadeThumbnailArrows: true
5120		}
5121	};
5122
5123	$.SliderPro.addModule( 'ThumbnailArrows', ThumbnailArrows );
5124
5125})( window, jQuery );
5126
5127// Video module for Slider Pro
5128//
5129// Adds automatic control for several video players and providers
5130;(function( window, $ ) {
5131
5132	"use strict";
5133
5134	var NS = 'Video.' + $.SliderPro.namespace;
5135	
5136	var Video = {
5137
5138		firstInit: false,
5139
5140		initVideo: function() {
5141			this.on( 'update.' + NS, $.proxy( this._videoOnUpdate, this ) );
5142			this.on( 'gotoSlide.' + NS, $.proxy( this._videoOnGotoSlide, this ) );
5143			this.on( 'gotoSlideComplete.' + NS, $.proxy( this._videoOnGotoSlideComplete, this ) );
5144		},
5145
5146		_videoOnUpdate: function() {
5147			var that = this;
5148
5149			// Find all the inline videos and initialize them
5150			this.$slider.find( '.sp-video' ).not( 'a, [data-video-init]' ).each(function() {
5151				var video = $( this );
5152				that._initVideo( video );
5153			});
5154
5155			// Find all the lazy-loaded videos and preinitialize them. They will be initialized
5156			// only when their play button is clicked.
5157			this.$slider.find( 'a.sp-video' ).not( '[data-video-preinit]' ).each(function() {
5158				var video = $( this );
5159				that._preinitVideo( video );
5160			});
5161
5162			// call the 'gotoSlideComplete' method in case the first slide contains a video that
5163			// needs to play automatically
5164			if ( this.firstInit === false ) {
5165				this.firstInit = true;
5166				this._videoOnGotoSlideComplete({ index: this.selectedSlideIndex, previousIndex: -1 });
5167			}
5168		},
5169
5170		// Initialize the target video
5171		_initVideo: function( video ) {
5172			var that = this;
5173
5174			video.attr( 'data-video-init', true )
5175				.videoController();
5176
5177			// When the video starts playing, pause the autoplay if it's running
5178			video.on( 'videoPlay.' + NS, function() {
5179				if ( that.settings.playVideoAction === 'stopAutoplay' && typeof that.stopAutoplay !== 'undefined' ) {
5180					that.stopAutoplay();
5181					that.settings.autoplay = false;
5182				}
5183
5184				// Fire the 'videoPlay' event
5185				var eventObject = { type: 'videoPlay', video: video };
5186				that.trigger( eventObject );
5187				if ( $.isFunction( that.settings.videoPlay ) ) {
5188					that.settings.videoPlay.call( that, eventObject );
5189				}
5190			});
5191
5192			// When the video is paused, restart the autoplay
5193			video.on( 'videoPause.' + NS, function() {
5194				if ( that.settings.pauseVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) {
5195					that.stopAutoplay();
5196					that.startAutoplay();
5197					that.settings.autoplay = true;
5198				}
5199
5200				// Fire the 'videoPause' event
5201				var eventObject = { type: 'videoPause', video: video };
5202				that.trigger( eventObject );
5203				if ( $.isFunction( that.settings.videoPause ) ) {
5204					that.settings.videoPause.call( that, eventObject );
5205				}
5206			});
5207
5208			// When the video ends, restart the autoplay (which was paused during the playback), or
5209			// go to the next slide, or replay the video
5210			video.on( 'videoEnded.' + NS, function() {
5211				if ( that.settings.endVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) {
5212					that.stopAutoplay();
5213					that.startAutoplay();
5214					that.settings.autoplay = true;
5215				} else if ( that.settings.endVideoAction === 'nextSlide' ) {
5216					that.nextSlide();
5217				} else if ( that.settings.endVideoAction === 'replayVideo' ) {
5218					video.videoController( 'replay' );
5219				}
5220
5221				// Fire the 'videoEnd' event
5222				var eventObject = { type: 'videoEnd', video: video };
5223				that.trigger( eventObject );
5224				if ( $.isFunction(that.settings.videoEnd ) ) {
5225					that.settings.videoEnd.call( that, eventObject );
5226				}
5227			});
5228		},
5229
5230		// Pre-initialize the video. This is for lazy loaded videos.
5231		_preinitVideo: function( video ) {
5232			var that = this;
5233
5234			video.attr( 'data-video-preinit', true );
5235
5236			// When the video poster is clicked, remove the poster and create
5237			// the inline video
5238			video.on( 'click.' + NS, function( event ) {
5239
5240				// If the video is being dragged, don't start the video
5241				if ( that.$slider.hasClass( 'sp-swiping' ) ) {
5242					return;
5243				}
5244
5245				event.preventDefault();
5246
5247				var href = video.attr( 'href' ),
5248					iframe,
5249					provider,
5250					regExp,
5251					match,
5252					id,
5253					src,
5254					videoAttributes,
5255					videoWidth = video.children( 'img' ).attr( 'width' ) || video.children( 'img' ).width(),
5256					videoHeight = video.children( 'img' ).attr( 'height') || video.children( 'img' ).height();
5257
5258				// Check if it's a youtube or vimeo video
5259				if ( href.indexOf( 'youtube' ) !== -1 || href.indexOf( 'youtu.be' ) !== -1 ) {
5260					provider = 'youtube';
5261				} else if ( href.indexOf( 'vimeo' ) !== -1 ) {
5262					provider = 'vimeo';
5263				}
5264
5265				// Get the id of the video
5266				regExp = provider === 'youtube' ? /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ : /http:\/\/(www\.)?vimeo.com\/(\d+)/;
5267				match = href.match( regExp );
5268				id = match[2];
5269
5270				// Get the source of the iframe that will be created
5271				src = provider === 'youtube' ? '//www.youtube.com/embed/' + id + '?enablejsapi=1&wmode=opaque' : '//player.vimeo.com/video/'+ id;
5272				
5273				// Get the attributes passed to the video link and then pass them to the iframe's src
5274				videoAttributes = href.split( '?' )[ 1 ];
5275
5276				if ( typeof videoAttributes !== 'undefined' ) {
5277					videoAttributes = videoAttributes.split( '&' );
5278
5279					$.each( videoAttributes, function( index, value ) {
5280						if ( value.indexOf( id ) === -1 ) {
5281							src += '&' + value;
5282						}
5283					});
5284				}
5285
5286				// Create the iframe
5287				iframe = $( '<iframe></iframe>' )
5288					.attr({
5289						'src': src,
5290						'width': videoWidth,
5291						'height': videoHeight,
5292						'class': video.attr( 'class' ),
5293						'frameborder': 0,
5294						'allowfullscreen': 'allowfullscreen'
5295					}).insertBefore( video );
5296
5297				// Initialize the video and play it
5298				that._initVideo( iframe );
5299				iframe.videoController( 'play' );
5300
5301				// Hide the video poster
5302				video.css( 'display', 'none' );
5303			});
5304		},
5305
5306		// Called when a new slide is selected
5307		_videoOnGotoSlide: function( event ) {
5308
5309			// Get the video from the previous slide
5310			var previousVideo = this.$slides.find( '.sp-slide' ).eq( event.previousIndex ).find( '.sp-video[data-video-init]' );
5311
5312			// Handle the video from the previous slide by stopping it, or pausing it,
5313			// or remove it, depending on the value of the 'leaveVideoAction' option.
5314			if ( event.previousIndex !== -1 && previousVideo.length !== 0 ) {
5315				if ( this.settings.leaveVideoAction === 'stopVideo' ) {
5316					previousVideo.videoController( 'stop' );
5317				} else if ( this.settings.leaveVideoAction === 'pauseVideo' ) {
5318					previousVideo.videoController( 'pause' );
5319				} else if ( this.settings.leaveVideoAction === 'removeVideo'  ) {
5320					// If the video was lazy-loaded, remove it and show the poster again. If the video
5321					// was not lazy-loaded, but inline, stop the video.
5322					if ( previousVideo.siblings( 'a.sp-video' ).length !== 0 ) {
5323						previousVideo.siblings( 'a.sp-video' ).css( 'display', '' );
5324						previousVideo.videoController( 'destroy' );
5325						previousVideo.remove();
5326					} else {
5327						previousVideo.videoController( 'stop' );
5328					}
5329				}
5330			}
5331		},
5332
5333		// Called when a new slide is selected, 
5334		// after the transition animation is complete.
5335		_videoOnGotoSlideComplete: function( event ) {
5336
5337			// Handle the video from the selected slide
5338			if ( this.settings.reachVideoAction === 'playVideo' && event.index === this.selectedSlideIndex ) {
5339				var loadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-video-init]' ),
5340					unloadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-video-preinit]' );
5341
5342				// If the video was already initialized, play it. If it's not initialized (because
5343				// it's lazy loaded) initialize it and play it.
5344				if ( loadedVideo.length !== 0 ) {
5345					loadedVideo.videoController( 'play' );
5346				} else if ( unloadedVideo.length !== 0 ) {
5347					unloadedVideo.trigger( 'click.' + NS );
5348				}
5349
5350				// Autoplay is stopped when the video starts playing
5351				// and the video's 'play' event is fired, but on slower connections,
5352				// the video's playing will be delayed and the 'play' event
5353				// will not fire in time to stop the autoplay, so we'll
5354				// stop it here as well.
5355				if ( ( loadedVideo.length !== 0 || unloadedVideo.length !== 0 ) && this.settings.playVideoAction === 'stopAutoplay' && typeof this.stopAutoplay !== 'undefined' ) {
5356					this.stopAutoplay();
5357					this.settings.autoplay = false;
5358				}
5359			}
5360		},
5361
5362		// Destroy the module
5363		destroyVideo: function() {
5364			this.$slider.find( '.sp-video[ data-video-preinit ]' ).each(function() {
5365				var video = $( this );
5366				video.removeAttr( 'data-video-preinit' );
5367				video.off( 'click.' + NS );
5368			});
5369
5370			// Loop through the all the videos and destroy them
5371			this.$slider.find( '.sp-video[ data-video-init ]' ).each(function() {
5372				var video = $( this );
5373				video.removeAttr( 'data-video-init' );
5374				video.off( 'Video' );
5375				video.videoController( 'destroy' );
5376			});
5377
5378			this.off( 'update.' + NS );
5379			this.off( 'gotoSlide.' + NS );
5380			this.off( 'gotoSlideComplete.' + NS );
5381		},
5382
5383		videoDefaults: {
5384
5385			// Sets the action that the video will perform when its slide container is selected
5386			// ( 'playVideo' and 'none' )
5387			reachVideoAction: 'none',
5388
5389			// Sets the action that the video will perform when another slide is selected
5390			// ( 'stopVideo', 'pauseVideo', 'removeVideo' and 'none' )
5391			leaveVideoAction: 'pauseVideo',
5392
5393			// Sets the action that the slider will perform when the video starts playing
5394			// ( 'stopAutoplay' and 'none' )
5395			playVideoAction: 'stopAutoplay',
5396
5397			// Sets the action that the slider will perform when the video is paused
5398			// ( 'startAutoplay' and 'none' )
5399			pauseVideoAction: 'none',
5400
5401			// Sets the action that the slider will perform when the video ends
5402			// ( 'startAutoplay', 'nextSlide', 'replayVideo' and 'none' )
5403			endVideoAction: 'none',
5404
5405			// Called when the video starts playing
5406			videoPlay: function() {},
5407
5408			// Called when the video is paused
5409			videoPause: function() {},
5410
5411			// Called when the video ends
5412			videoEnd: function() {}
5413		}
5414	};
5415
5416	$.SliderPro.addModule( 'Video', Video );
5417	
5418})( window, jQuery );
5419
5420// Video Controller jQuery plugin
5421// Creates a universal controller for multiple video types and providers
5422;(function( $ ) {
5423
5424	"use strict";
5425
5426// Check if an iOS device is used.
5427// This information is important because a video can not be
5428// controlled programmatically unless the user has started the video manually.
5429var	isIOS = window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ? true : false;
5430
5431var VideoController = function( instance, options ) {
5432	this.$video = $( instance );
5433	this.options = options;
5434	this.settings = {};
5435	this.player = null;
5436
5437	this._init();
5438};
5439
5440VideoController.prototype = {
5441
5442	_init: function() {
5443		this.settings = $.extend( {}, this.defaults, this.options );
5444
5445		var that = this,
5446			players = $.VideoController.players,
5447			videoID = this.$video.attr( 'id' );
5448
5449		// Loop through the available video players
5450		// and check if the targeted video element is supported by one of the players.
5451		// If a compatible type is found, store the video type.
5452		for ( var name in players ) {
5453			if ( typeof players[ name ] !== 'undefined' && players[ name ].isType( this.$video ) ) {
5454				this.player = new players[ name ]( this.$video );
5455				break;
5456			}
5457		}
5458
5459		// Return if the player could not be instantiated
5460		if ( this.player === null ) {
5461			return;
5462		}
5463
5464		// Add event listeners
5465		var events = [ 'ready', 'start', 'play', 'pause', 'ended' ];
5466		
5467		$.each( events, function( index, element ) {
5468			var event = 'video' + element.charAt( 0 ).toUpperCase() + element.slice( 1 );
5469
5470			that.player.on( element, function() {
5471				that.trigger({ type: event, video: videoID });
5472				if ( $.isFunction( that.settings[ event ] ) ) {
5473					that.settings[ event ].call( that, { type: event, video: videoID } );
5474				}
5475			});
5476		});
5477	},
5478	
5479	play: function() {
5480		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'playing' ) {
5481			return;
5482		}
5483
5484		this.player.play();
5485	},
5486	
5487	stop: function() {
5488		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'stopped' ) {
5489			return;
5490		}
5491
5492		this.player.stop();
5493	},
5494	
5495	pause: function() {
5496		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'paused' ) {
5497			return;
5498		}
5499
5500		this.player.pause();
5501	},
5502
5503	replay: function() {
5504		if ( isIOS === true && this.player.isStarted() === false ) {
5505			return;
5506		}
5507		
5508		this.player.replay();
5509	},
5510
5511	on: function( type, callback ) {
5512		return this.$video.on( type, callback );
5513	},
5514	
5515	off: function( type ) {
5516		return this.$video.off( type );
5517	},
5518
5519	trigger: function( data ) {
5520		return this.$video.triggerHandler( data );
5521	},
5522
5523	destroy: function() {
5524		if ( this.player.isStarted() === true ) {
5525			this.stop();
5526		}
5527
5528		this.player.off( 'ready' );
5529		this.player.off( 'start' );
5530		this.player.off( 'play' );
5531		this.player.off( 'pause' );
5532		this.player.off( 'ended' );
5533
5534		this.$video.removeData( 'videoController' );
5535	},
5536
5537	defaults: {
5538		videoReady: function() {},
5539		videoStart: function() {},
5540		videoPlay: function() {},
5541		videoPause: function() {},
5542		videoEnded: function() {}
5543	}
5544};
5545
5546$.VideoController = {
5547	players: {},
5548
5549	addPlayer: function( name, player ) {
5550		this.players[ name ] = player;
5551	}
5552};
5553
5554$.fn.videoController = function( options ) {
5555	var args = Array.prototype.slice.call( arguments, 1 );
5556
5557	return this.each(function() {
5558		// Instantiate the video controller or call a function on the current instance
5559		if ( typeof $( this ).data( 'videoController' ) === 'undefined' ) {
5560			var newInstance = new VideoController( this, options );
5561
5562			// Store a reference to the instance created
5563			$( this ).data( 'videoController', newInstance );
5564		} else if ( typeof options !== 'undefined' ) {
5565			var	currentInstance = $( this ).data( 'videoController' );
5566
5567			// Check the type of argument passed
5568			if ( typeof currentInstance[ options ] === 'function' ) {
5569				currentInstance[ options ].apply( currentInstance, args );
5570			} else {
5571				$.error( options + ' does not exist in videoController.' );
5572			}
5573		}
5574	});
5575};
5576
5577// Base object for the video players
5578var Video = function( video ) {
5579	this.$video = video;
5580	this.player = null;
5581	this.ready = false;
5582	this.started = false;
5583	this.state = '';
5584	this.events = $({});
5585
5586	this._init();
5587};
5588
5589Video.prototype = {
5590	_init: function() {},
5591
5592	play: function() {},
5593
5594	pause: function() {},
5595
5596	stop: function() {},
5597
5598	replay: function() {},
5599
5600	isType: function() {},
5601
5602	isReady: function() {
5603		return this.ready;
5604	},
5605
5606	isStarted: function() {
5607		return this.started;
5608	},
5609
5610	getState: function() {
5611		return this.state;
5612	},
5613
5614	on: function( type, callback ) {
5615		return this.events.on( type, callback );
5616	},
5617	
5618	off: function( type ) {
5619		return this.events.off( type );
5620	},
5621
5622	trigger: function( data ) {
5623		return this.events.triggerHandler( data );
5624	}
5625};
5626
5627// YouTube video
5628var YoutubeVideoHelper = {
5629	youtubeAPIAdded: false,
5630	youtubeVideos: []
5631};
5632
5633var YoutubeVideo = function( video ) {
5634	this.init = false;
5635	var youtubeAPILoaded = window.YT && window.YT.Player;
5636
5637	if ( typeof youtubeAPILoaded !== 'undefined' ) {
5638		Video.call( this, video );
5639	} else {
5640		YoutubeVideoHelper.youtubeVideos.push({ 'video': video, 'scope': this });
5641		
5642		if ( YoutubeVideoHelper.youtubeAPIAdded === false ) {
5643			YoutubeVideoHelper.youtubeAPIAdded = true;
5644
5645			var tag = document.createElement( 'script' );
5646			tag.src = "//www.youtube.com/player_api";
5647			var firstScriptTag = document.getElementsByTagName( 'script' )[0];
5648			firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
5649
5650			window.onYouTubePlayerAPIReady = function() {
5651				$.each( YoutubeVideoHelper.youtubeVideos, function( index, element ) {
5652					Video.call( element.scope, element.video );
5653				});
5654			};
5655		}
5656	}
5657};
5658
5659YoutubeVideo.prototype = new Video();
5660YoutubeVideo.prototype.constructor = YoutubeVideo;
5661$.VideoController.addPlayer( 'YoutubeVideo', YoutubeVideo );
5662
5663YoutubeVideo.isType = function( video ) {
5664	if ( video.is( 'iframe' ) ) {
5665		var src = video.attr( 'src' );
5666
5667		if ( src.indexOf( 'youtube.com' ) !== -1 || src.indexOf( 'youtu.be' ) !== -1 ) {
5668			return true;
5669		}
5670	}
5671
5672	return false;
5673};
5674
5675YoutubeVideo.prototype._init = function() {
5676	this.init = true;
5677	this._setup();
5678};
5679	
5680YoutubeVideo.prototype._setup = function() {
5681	var that = this;
5682
5683	// Get a reference to the player
5684	this.player = new YT.Player( this.$video[0], {
5685		events: {
5686			'onReady': function() {
5687				that.trigger({ type: 'ready' });
5688				that.ready = true;
5689			},
5690			
5691			'onStateChange': function( event ) {
5692				switch ( event.data ) {
5693					case YT.PlayerState.PLAYING:
5694						if (that.started === false) {
5695							that.started = true;
5696							that.trigger({ type: 'start' });
5697						}
5698
5699						that.state = 'playing';
5700						that.trigger({ type: 'play' });
5701						break;
5702					
5703					case YT.PlayerState.PAUSED:
5704						that.state = 'paused';
5705						that.trigger({ type: 'pause' });
5706						break;
5707					
5708					case YT.PlayerState.ENDED:
5709						that.state = 'ended';
5710						that.trigger({ type: 'ended' });
5711						break;
5712				}
5713			}
5714		}
5715	});
5716};
5717
5718YoutubeVideo.prototype.play = function() {
5719	var that = this;
5720
5721	if ( this.ready === true ) {
5722		this.player.playVideo();
5723	} else {
5724		var timer = setInterval(function() {
5725			if ( that.ready === true ) {
5726				clearInterval( timer );
5727				that.player.playVideo();
5728			}
5729		}, 100 );
5730	}
5731};
5732
5733YoutubeVideo.prototype.pause = function() {
5734	// On iOS, simply pausing the video can make other videos unresponsive
5735	// so we stop the video instead.
5736	if ( isIOS === true ) {
5737		this.stop();
5738	} else {
5739		this.player.pauseVideo();
5740	}
5741};
5742
5743YoutubeVideo.prototype.stop = function() {
5744	this.player.seekTo( 1 );
5745	this.player.stopVideo();
5746	this.state = 'stopped';
5747};
5748
5749YoutubeVideo.prototype.replay = function() {
5750	this.player.seekTo( 1 );
5751	this.player.playVideo();
5752};
5753
5754YoutubeVideo.prototype.on = function( type, callback ) {
5755	var that = this;
5756
5757	if ( this.init === true ) {
5758		Video.prototype.on.call( this, type, callback );
5759	} else {
5760		var timer = setInterval(function() {
5761			if ( that.init === true ) {
5762				clearInterval( timer );
5763				Video.prototype.on.call( that, type, callback );
5764			}
5765		}, 100 );
5766	}
5767};
5768
5769// Vimeo video
5770var VimeoVideoHelper = {
5771	vimeoAPIAdded: false,
5772	vimeoVideos: []
5773};
5774
5775var VimeoVideo = function( video ) {
5776	this.init = false;
5777
5778	if ( typeof window.Vimeo !== 'undefined' ) {
5779		Video.call( this, video );
5780	} else {
5781		VimeoVideoHelper.vimeoVideos.push({ 'video': video, 'scope': this });
5782
5783		if ( VimeoVideoHelper.vimeoAPIAdded === false ) {
5784			VimeoVideoHelper.vimeoAPIAdded = true;
5785
5786			var tag = document.createElement('script');
5787			tag.src = "//player.vimeo.com/api/player.js";
5788			var firstScriptTag = document.getElementsByTagName( 'script' )[0];
5789			firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
5790		
5791			var checkVimeoAPITimer = setInterval(function() {
5792				if ( typeof window.Vimeo !== 'undefined' ) {
5793					clearInterval( checkVimeoAPITimer );
5794					
5795					$.each( VimeoVideoHelper.vimeoVideos, function( index, element ) {
5796						Video.call( element.scope, element.video );
5797					});
5798				}
5799			}, 100 );
5800		}
5801	}
5802};
5803
5804VimeoVideo.prototype = new Video();
5805VimeoVideo.prototype.constructor = VimeoVideo;
5806$.VideoController.addPlayer( 'VimeoVideo', VimeoVideo );
5807
5808VimeoVideo.isType = function( video ) {
5809	if ( video.is( 'iframe' ) ) {
5810		var src = video.attr('src');
5811
5812		if ( src.indexOf( 'vimeo.com' ) !== -1 ) {
5813			return true;
5814		}
5815	}
5816
5817	return false;
5818};
5819
5820VimeoVideo.prototype._init = function() {
5821	this.init = true;
5822	this._setup();
5823};
5824
5825VimeoVideo.prototype._setup = function() {
5826	var that = this;
5827
5828	// Get a reference to the player
5829	this.player = new Vimeo.Player( this.$video[0] );
5830	
5831	that.ready = true;
5832	that.trigger({ type: 'ready' });
5833		
5834	that.player.on( 'play', function() {
5835		if ( that.started === false ) {
5836			that.started = true;
5837			that.trigger({ type: 'start' });
5838		}
5839
5840		that.state = 'playing';
5841		that.trigger({ type: 'play' });
5842	});
5843		
5844	that.player.on( 'pause', function() {
5845		that.state = 'paused';
5846		that.trigger({ type: 'pause' });
5847	});
5848		
5849	that.player.on( 'ended', function() {
5850		that.state = 'ended';
5851		that.trigger({ type: 'ended' });
5852	});
5853};
5854
5855VimeoVideo.prototype.play = function() {
5856	var that = this;
5857 
5858    if ( this.ready === true ) {
5859        this.player.play();
5860    } else {
5861        var timer = setInterval(function() {
5862            if ( that.ready === true ) {
5863                clearInterval( timer );
5864                that.player.play();
5865            }
5866        }, 100 );
5867    }
5868};
5869
5870VimeoVideo.prototype.pause = function() {
5871	this.player.pause();
5872};
5873
5874VimeoVideo.prototype.stop = function() {
5875	var that = this;
5876
5877	this.player.setCurrentTime( 0 ).then( function() {
5878		that.player.pause();
5879		that.state = 'stopped';
5880	} );
5881};
5882
5883VimeoVideo.prototype.replay = function() {
5884	var that = this;
5885
5886	this.player.setCurrentTime( 0 ).then( function() {
5887		that.player.play();
5888	} );
5889};
5890
5891VimeoVideo.prototype.on = function( type, callback ) {
5892	var that = this;
5893
5894	if ( this.init === true ) {
5895		Video.prototype.on.call( this, type, callback );
5896	} else {
5897		var timer = setInterval(function() {
5898			if ( that.init === true ) {
5899				clearInterval( timer );
5900				Video.prototype.on.call( that, type, callback );
5901			}
5902		}, 100 );
5903	}
5904};
5905
5906// HTML5 video
5907var HTML5Video = function( video ) {
5908	Video.call( this, video );
5909};
5910
5911HTML5Video.prototype = new Video();
5912HTML5Video.prototype.constructor = HTML5Video;
5913$.VideoController.addPlayer( 'HTML5Video', HTML5Video );
5914
5915HTML5Video.isType = function( video ) {
5916	if ( video.is( 'video' ) && video.hasClass( 'video-js' ) === false && video.hasClass( 'sublime' ) === false ) {
5917		return true;
5918	}
5919
5920	return false;
5921};
5922
5923HTML5Video.prototype._init = function() {
5924	var that = this;
5925
5926	// Get a reference to the player
5927	this.player = this.$video[0];
5928	
5929	var checkVideoReady = setInterval(function() {
5930		if ( that.player.readyState === 4 ) {
5931			clearInterval( checkVideoReady );
5932
5933			that.ready = true;
5934			that.trigger({ type: 'ready' });
5935
5936			that.player.addEventListener( 'play', function() {
5937				if ( that.started === false ) {
5938					that.started = true;
5939					that.trigger({ type: 'start' });
5940				}
5941
5942				that.state = 'playing';
5943				that.trigger({ type: 'play' });
5944			});
5945			
5946			that.player.addEventListener( 'pause', function() {
5947				that.state = 'paused';
5948				that.trigger({ type: 'pause' });
5949			});
5950			
5951			that.player.addEventListener( 'ended', function() {
5952				that.state = 'ended';
5953				that.trigger({ type: 'ended' });
5954			});
5955		}
5956	}, 100 );
5957};
5958
5959HTML5Video.prototype.play = function() {
5960	var that = this;
5961
5962	if ( this.ready === true ) {
5963		this.player.play();
5964	} else {
5965		var timer = setInterval(function() {
5966			if ( that.ready === true ) {
5967				clearInterval( timer );
5968				that.player.play();
5969			}
5970		}, 100 );
5971	}
5972};
5973
5974HTML5Video.prototype.pause = function() {
5975	this.player.pause();
5976};
5977
5978HTML5Video.prototype.stop = function() {
5979	this.player.currentTime = 0;
5980	this.player.pause();
5981	this.state = 'stopped';
5982};
5983
5984HTML5Video.prototype.replay = function() {
5985	this.player.currentTime = 0;
5986	this.player.play();
5987};
5988
5989// VideoJS video
5990var VideoJSVideo = function( video ) {
5991	Video.call( this, video );
5992};
5993
5994VideoJSVideo.prototype = new Video();
5995VideoJSVideo.prototype.constructor = VideoJSVideo;
5996$.VideoController.addPlayer( 'VideoJSVideo', VideoJSVideo );
5997
5998VideoJSVideo.isType = function( video ) {
5999	if ( ( typeof video.attr( 'data-videojs-id' ) !== 'undefined' || video.hasClass( 'video-js' ) ) && typeof videojs !== 'undefined' ) {
6000		return true;
6001	}
6002
6003	return false;
6004};
6005
6006VideoJSVideo.prototype._init = function() {
6007	var that = this,
6008		videoID = this.$video.hasClass( 'video-js' ) ? this.$video.attr( 'id' ) : this.$video.attr( 'data-vi
6008deojs-id' );
6009	
6010	this.player = videojs( videoID );
6011
6012	this.player.ready(function() {
6013		that.ready = true;
6014		that.trigger({ type: 'ready' });
6015
6016		that.player.on( 'play', function() {
6017			if ( that.started === false ) {
6018				that.started = true;
6019				that.trigger({ type: 'start' });
6020			}
6021
6022			that.state = 'playing';
6023			that.trigger({ type: 'play' });
6024		});
6025		
6026		that.player.on( 'pause', function() {
6027			that.state = 'paused';
6028			that.trigger({ type: 'pause' });
6029		});
6030		
6031		that.player.on( 'ended', function() {
6032			that.state = 'ended';
6033			that.trigger({ type: 'ended' });
6034		});
6035	});
6036};
6037
6038VideoJSVideo.prototype.play = function() {
6039	this.player.play();
6040};
6041
6042VideoJSVideo.prototype.pause = function() {
6043	this.player.pause();
6044};
6045
6046VideoJSVideo.prototype.stop = function() {
6047	this.player.currentTime( 0 );
6048	this.player.pause();
6049	this.state = 'stopped';
6050};
6051
6052VideoJSVideo.prototype.replay = function() {
6053	this.player.currentTime( 0 );
6054	this.player.play();
6055};
6056
6057// Sublime video
6058var SublimeVideo = function( video ) {
6059	Video.call( this, video );
6060};
6061
6062SublimeVideo.prototype = new Video();
6063SublimeVideo.prototype.constructor = SublimeVideo;
6064$.VideoController.addPlayer( 'SublimeVideo', SublimeVideo );
6065
6066SublimeVideo.isType = function( video ) {
6067	if ( video.hasClass( 'sublime' ) && typeof sublime !== 'undefined' ) {
6068		return true;
6069	}
6070
6071	return false;
6072};
6073
6074SublimeVideo.prototype._init = function() {
6075	var that = this;
6076
6077	sublime.ready(function() {
6078		// Get a reference to the player
6079		that.player = sublime.player( that.$video.attr( 'id' ) );
6080
6081		that.ready = true;
6082		that.trigger({ type: 'ready' });
6083
6084		that.player.on( 'play', function() {
6085			if ( that.started === false ) {
6086				that.started = true;
6087				that.trigger({ type: 'start' });
6088			}
6089
6090			that.state = 'playing';
6091			that.trigger({ type: 'play' });
6092		});
6093
6094		that.player.on( 'pause', function() {
6095			that.state = 'paused';
6096			that.trigger({ type: 'pause' });
6097		});
6098
6099		that.player.on( 'stop', function() {
6100			that.state = 'stopped';
6101			that.trigger({ type: 'stop' });
6102		});
6103
6104		that.player.on( 'end', function() {
6105			that.state = 'ended';
6106			that.trigger({ type: 'ended' });
6107		});
6108	});
6109};
6110
6111SublimeVideo.prototype.play = function() {
6112	this.player.play();
6113};
6114
6115SublimeVideo.prototype.pause = function() {
6116	this.player.pause();
6117};
6118
6119SublimeVideo.prototype.stop = function() {
6120	this.player.stop();
6121};
6122
6123SublimeVideo.prototype.replay = function() {
6124	this.player.stop();
6125	this.player.play();
6126};
6127
6128// JWPlayer video
6129var JWPlayerVideo = function( video ) {
6130	Video.call( this, video );
6131};
6132
6133JWPlayerVideo.prototype = new Video();
6134JWPlayerVideo.prototype.constructor = JWPlayerVideo;
6135$.VideoController.addPlayer( 'JWPlayerVideo', JWPlayerVideo );
6136
6137JWPlayerVideo.isType = function( video ) {
6138	if ( ( typeof video.attr( 'data-jwplayer-id' ) !== 'undefined' || video.hasClass( 'jwplayer' ) || video.find( "object[data*='jwplayer']" ).length !== 0 ) &&
6139		typeof jwplayer !== 'undefined') {
6140		return true;
6141	}
6142
6143	return false;
6144};
6145
6146JWPlayerVideo.prototype._init = function() {
6147	var that = this,
6148		videoID;
6149
6150	if ( this.$video.hasClass( 'jwplayer' ) ) {
6151		videoID = this.$video.attr( 'id' );
6152	} else if ( typeof this.$video.attr( 'data-jwplayer-id' ) !== 'undefined' ) {
6153		videoID = this.$video.attr( 'data-jwplayer-id');
6154	} else if ( this.$video.find( "object[data*='jwplayer']" ).length !== 0 ) {
6155		videoID = this.$video.find( 'object' ).attr( 'id' );
6156	}
6157
6158	// Get a reference to the player
6159	this.player = jwplayer( videoID );
6160
6161	this.player.onReady(function() {
6162		that.ready = true;
6163		that.trigger({ type: 'ready' });
6164	
6165		that.player.onPlay(function() {
6166			if ( that.started === false ) {
6167				that.started = true;
6168				that.trigger({ type: 'start' });
6169			}
6170
6171			that.state = 'playing';
6172			that.trigger({ type: 'play' });
6173		});
6174
6175		that.player.onPause(function() {
6176			that.state = 'paused';
6177			that.trigger({ type: 'pause' });
6178		});
6179		
6180		that.player.onComplete(function() {
6181			that.state = 'ended';
6182			that.trigger({ type: 'ended' });
6183		});
6184	});
6185};
6186
6187JWPlayerVideo.prototype.play = function() {
6188	this.player.play( true );
6189};
6190
6191JWPlayerVideo.prototype.pause = function() {
6192	this.player.pause( true );
6193};
6194
6195JWPlayerVideo.prototype.stop = function() {
6196	this.player.stop();
6197	this.state = 'stopped';
6198};
6199
6200JWPlayerVideo.prototype.replay = function() {
6201	this.player.seek( 0 );
6202	this.player.play( true );
6203};
6204
6205})( 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.