PageSourceSearch

https://st-shika.com/js/jquery.sliderPro.js

js st-shika.com collected 2026-10-04 15:11:27 UTC 188,011 bytes, 5,836 lines download raw bytes

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