PageSourceSearch

https://avicenna-klinik.com/typo3temp/assets/compressed/merged-566…84ac6c47ca663f98cd10ad48bf358.js?1791447303

js avicenna-klinik.com collected 2026-10-09 22:39:48 UTC 272,927 bytes, 9,585 lines download raw bytes

1
2if (!Array.isArray) {
3	Array.isArray = function (vArg) {
4		return Object.prototype.toString.call(vArg) === "[object Array]";
5	};
6}
7
8(function(h,o,g){var p=function(){for(var b=/audio(.min)?.js.*/,a=document.getElementsByTagName("script"),c=0,d=a.length;c<d;c++){var e=a[c].getAttribute("src");if(b.test(e))return e.replace(b,"")}}();g[h]={instanceCount:0,instances:{},flashSource:'      <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="$1" width="1" height="1" name="$1" style="position: absolute; left: -1px;">         <param name="movie" value="$2?playerInstance='+h+'.instances[\'$1\']&datetime=$3">         <param name="allowscriptaccess" value="always">         <embed name="$1" src="$2?playerInstance='+
9h+'.instances[\'$1\']&datetime=$3" width="1" height="1" allowscriptaccess="always">       </object>',settings:{autoplay:false,loop:false,preload:true,imageLocation:p+"player-graphics.gif",swfLocation:p+"audiojs.swf",useFlash:function(){var b=document.createElement("audio");return!(b.canPlayType&&b.canPlayType("audio/mpeg;").replace(/no/,""))}(),hasFlash:function(){if(navigator.plugins&&navigator.plugins.length&&navigator.plugins["Shockwave Flash"])return true;else if(navigator.mimeTypes&&navigator.mimeTypes.length){var b=
10navigator.mimeTypes["application/x-shockwave-flash"];return b&&b.enabledPlugin}else try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash");return true}catch(a){}return false}(),createPlayer:{markup:'          <div class="play-pause">             <p class="play"></p>             <p class="pause"></p>             <p class="loading"></p>             <p class="error"></p>           </div>           <div class="scrubber">             <div class="progress"></div>             <div class="loaded"></div>           </div>           <div class="time">             <em class="played">00:00</em>/<strong class="duration">00:00</strong>           </div>           <div class="error-message"></div>',
11playPauseClass:"play-pause",scrubberClass:"scrubber",progressClass:"progress",loaderClass:"loaded",timeClass:"time",durationClass:"duration",playedClass:"played",errorMessageClass:"error-message",playingClass:"playing",loadingClass:"loading",errorClass:"error"},css:'        .audiojs audio { position: absolute; left: -1px; }         .audiojs { width: 460px; height: 36px; background: #404040; overflow: hidden; font-family: monospace; font-size: 12px;           background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #444), color-stop(0.5, #555), color-stop(0.51, #444), color-stop(1, #444));           background-image: -moz-linear-gradient(center top, #444 0%, #555 50%, #444 51%, #444 100%);           -webkit-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); -moz-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3);           -o-box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.3); }         .audiojs .play-pause { width: 25px; height: 40px; padding: 4px 6px; margin: 0px; float: left; overflow: hidden; border-right: 1px solid #000; }         .audiojs p { display: none; width: 25px; height: 40px; margin: 0px; cursor: pointer; }         .audiojs .play { display: block; }         .audiojs .scrubber { position: relative; float: left; width: 280px; background: #5a5a5a; height: 14px; margin: 10px; border-top: 1px solid #3f3f3f; border-left: 0px; border-bottom: 0px; overflow: hidden; }         .audiojs .progress { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #ccc; z-index: 1;           background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ccc), color-stop(0.5, #ddd), color-stop(0.51, #ccc), color-stop(1, #ccc));           background-image: -moz-linear-gradient(center top, #ccc 0%, #ddd 50%, #ccc 51%, #ccc 100%); }         .audiojs .loaded { position: absolute; top: 0px; left: 0px; height: 14px; width: 0px; background: #000;           background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #222), color-stop(0.5, #333), color-stop(0.51, #222), color-stop(1, #222));           background-image: -moz-linear-gradient(center top, #222 0%, #333 50%, #222 51%, #222 100%); }         .audiojs .time { float: left; height: 36px; line-height: 36px; margin: 0px 0px 0px 6px; padding: 0px 6px 0px 12px; border-left: 1px solid #000; color: #ddd; text-shadow: 1px 1px 0px rgba(0, 0, 0, 0.5); }         .audiojs .time em { padding: 0px 2px 0px 0px; color: #f9f9f9; font-style: normal; }         .audiojs .time strong { padding: 0px 0px 0px 2px; font-weight: normal; }         .audiojs .error-message { float: left; display: none; margin: 0px 10px; height: 36px; width: 400px; overflow: hidden; line-height: 36px; white-space: nowrap; color: #fff;           text-overflow: ellipsis; -o-text-overflow: ellipsis; -icab-text-overflow: ellipsis; -khtml-text-overflow: ellipsis; -moz-text-overflow: ellipsis; -webkit-text-overflow: ellipsis; }         .audiojs .error-message a { color: #eee; text-decoration: none; padding-bottom: 1px; border-bottom: 1px solid #999; white-space: wrap; }                 .audiojs .play { background: url("$1") -2px -1px no-repeat; }         .audiojs .loading { background: url("$1") -2px -31px no-repeat; }         .audiojs .error { background: url("$1") -2px -61px no-repeat; }         .audiojs .pause { background: url("$1") -2px -91px no-repeat; }                 .playing .play, .playing .loading, .playing .error { display: none; }         .playing .pause { display: block; }                 .loading .play, .loading .pause, .loading .error { display: none; }         .loading .loading { display: block; }                 .error .time, .error .play, .error .pause, .error .scrubber, .error .loading { display: none; }         .error .error { display: block; }         .error .play-pause p { cursor: auto; }         .error .error-message { display: block; }',
12trackEnded:function(){},flashError:function(){var b=this.settings.createPlayer,a=j(b.errorMessageClass,this.wrapper),c='Missing <a href="http://get.adobe.com/flashplayer/">
12flash player</a> plugin.';if(this.mp3)c+=' <a href="'+this.mp3+'">Download audio file</a>.';g[h].helpers.removeClass(this.wrapper,b.loadingClass);g[h].helpers.addClass(this.wrapper,b.errorClass);a.innerHTML=c},loadError:function(){var b=this.settings.createPlayer,a=j(b.errorMessageClass,this.wrapper);g[h].helpers.removeClass(this.wrapper,
13b.loadingClass);g[h].helpers.addClass(this.wrapper,b.errorClass);a.innerHTML='Error loading: "'+this.mp3+'"'},init:function(){g[h].helpers.addClass(this.wrapper,this.settings.createPlayer.loadingClass)},loadStarted:function(){var b=this.settings.createPlayer,a=j(b.durationClass,this.wrapper),c=Math.floor(this.duration/60),d=Math.floor(this.duration%60);g[h].helpers.removeClass(this.wrapper,b.loadingClass);a.innerHTML=(c<10?"0":"")+c+":"+(d<10?"0":"")+d},loadProgress:function(b){var a=this.settings.createPlayer,
14c=j(a.scrubberClass,this.wrapper);j(a.loaderClass,this.wrapper).style.width=c.offsetWidth*b+"px"},playPause:function(){this.playing?this.settings.play():this.settings.pause()},play:function(){g[h].helpers.addClass(this.wrapper,this.settings.createPlayer.playingClass)},pause:function(){g[h].helpers.removeClass(this.wrapper,this.settings.createPlayer.playingClass)},updatePlayhead:function(b){var a=this.settings.createPlayer,c=j(a.scrubberClass,this.wrapper);j(a.progressClass,this.wrapper).style.width=
15c.offsetWidth*b+"px";a=j(a.playedClass,this.wrapper);c=this.duration*b;b=Math.floor(c/60);c=Math.floor(c%60);a.innerHTML=(b<10?"0":"")+b+":"+(c<10?"0":"")+c}},create:function(b,a){a=a||{};return b.length?this.createAll(a,b):this.newInstance(b,a)},createAll:function(b,a){var c=a||document.getElementsByTagName("audio"),d=[];b=b||{};for(var e=0,i=c.length;e<i;e++)d.push(this.newInstance(c[e],b));return d},newInstance:function(b,a){var c=this.helpers.clone(this.settings),d="audiojs"+this.instanceCount,
16e="audiojs_wrapper"+this.instanceCount;this.instanceCount++;
16if(b.getAttribute("autoplay")!=null)c.autoplay=true;if(b.getAttribute("loop")!=null)c.loop=true;if(b.getAttribute("preload")=="none")c.preload=false;a&&this.helpers.merge(c,a);if(c.createPlayer.markup)b=this.createPlayer(b,c.createPlayer,e);else b.parentNode.setAttribute("id",e);e=new g[o](b,c);c.css&&this.helpers.injectCss(e,c.css);if(c.useFlash&&c.hasFlash){this.injectFlash(e,d);this.attachFlashEvents(e.wrapper,e)}else c.useFlash&&!c.hasFlash&&
17this.settings.flashError.apply(e);if(!c.useFlash||c.useFlash&&c.hasFlash)this.attachEvents(e.wrapper,e);return this.instances[d]=e},createPlayer:function(b,a,c){var d=document.createElement("div"),e=b.cloneNode(true);d.setAttribute("class","audiojs");d.setAttribute("className","audiojs");d.setAttribute("id",c);if(e.outerHTML&&!document.createElement("audio").canPlayType){e=this.helpers.cloneHtml5Node(b);d.innerHTML=a.markup;d.appendChild(e);b.outerHTML=d.outerHTML;d=document.getElementById(c)}else{d.appendChild(e);
18d.innerHTML+=a.markup;b.parentNode.replaceChild(d,b)}return d.getElementsByTagName("audio")[0]},attachEvents:function(b,a){if(a.settings.createPlayer){var c=a.settings.createPlayer,d=j(c.playPauseClass,b),e=j(c.scrubberClass,b);g[h].events.addListener(d,"click",function(){a.playPause.apply(a)});g[h].events.addListener(e,"click",function(i){i=i.clientX;var f=this,k=0;if(f.offsetParent){do k+=f.offsetLeft;while(f=f.offsetParent)}a.skipTo((i-k)/e.offsetWidth)});if(!a.settings.useFlash){g[h].events.trackLoadProgress(a);
19g[h].events.addListener(a.element,"timeupdate",function(){a.updatePlayhead.apply(a)});g[h].events.addListener(a.element,"ended",function(){a.trackEnded.apply(a)});g[h].events.addListener(a.source,"error",function(){clearInterval(a.readyTimer);clearInterval(a.loadTimer);a.settings.loadError.apply(a)})}}},attachFlashEvents:function(b,a){a.swfReady=false;a.load=function(c){a.mp3=c;a.swfReady&&a.element.load(c)};a.loadProgress=function(c,d){a.loadedPercent=c;a.duration=d;a.settings.loadStarted.apply(a);
20a.settings.loadProgress.apply(a,[c])};a.skipTo=function(c){if(!(c>a.loadedPercent)){a.updatePlayhead.call(a,[c]);a.element.skipTo(c)}};a.updatePlayhead=function(c){a.settings.updatePlayhead.apply(a,[c])};a.play=function(){if(!a.settings.preload){a.settings.preload=true;a.element.init(a.mp3)}a.playing=true;a.element.pplay();a.settings.play.apply(a)};a.pause=function(){a.playing=false;a.element.ppause();a.settings.pause.apply(a)};a.setVolume=function(c){a.element.setVolume(c)};a.loadStarted=function(){a.swfReady=
21true;a.settings.preload&&a.element.init(a.mp3);a.settings.autoplay&&a.play.apply(a)}},injectFlash:function(b,a){var c=this.flashSource.replace(/\$1/g,a);c=c.replace(/\$2/g,b.settings.swfLocation);c=c.replace(/\$3/g,+new Date+Math.random());var d=b.wrapper.innerHTML,e=document.createElement("div");e.innerHTML=c+d;b.wrapper.innerHTML=e.innerHTML;b.element=this.helpers.getSwf(a)},helpers:{merge:function(b,a){for(attr in a)if(b.hasOwnProperty(attr)||a.hasOwnProperty(attr))b[attr]=a[attr]},clone:function(b){if(b==
22null||typeof b!=="object")return b;var a=new b.constructor,c;for(c in b)a[c]=arguments.callee(b[c]);return a},addClass:function(b,a){RegExp("(\\s|^)"+a+"(\\s|$)").test(b.className)||(b.className+=" "+a)},removeClass:function(b,a){b.className=b.className.replace(RegExp("(\\s|^)"+a+"(\\s|$)")," ")},injectCss:function(b,a){for(var c="",d=document.getElementsByTagName("style"),e=a.replace(/\$1/g,b.settings.imageLocation),i=0,f=d.length;i<f;i++){var k=d[i].getAttribute("title");if(k&&~k.indexOf("audiojs")){f=
23d[i];if(f.innerHTML===e)return;c=f.innerHTML;break}}d=document.getElementsByTagName("head")[0];i=d.firstChild;f=document.createElement("style");if(d){f.setAttribute("type","text/css");f.setAttribute("title","audiojs");if(f.styleSheet)f.styleSheet.cssText=c+e;else f.appendChild(document.createTextNode(c+e));i?d.insertBefore(f,i):d.appendChild(styleElement)}},cloneHtml5Node:function(b){var a=document.createDocumentFragment(),c=a.createElement?a:document;c.createElement("audio");c=c.createElement("div");
24a.appendChild(c);c.innerHTML=b.outerHTML;return c.firstChild},getSwf:function(b){b=document[b]||window[b];return b.length>1?b[b.length-1]:b}},events:{memoryLeaking:false,listeners:[],addListener:function(b,a,c){if(b.addEventListener)b.addEventListener(a,c,false);else if(b.attachEvent){this.listeners.push(b);if(!this.memoryLeaking){window.attachEvent("onunload",function(){if(this.listeners)for(var d=0,e=this.listeners.length;d<e;d++)g[h].events.purge(this.listeners[d])});this.memoryLeaking=true}b.attachEvent("on"+
25a,function(){c.call(b,window.event)})}},trackLoadProgress:function(b){if(b.settings.preload){var a,c;b=b;var d=/(ipod|iphone|ipad)/i.test(navigator.userAgent);a=setInterval(function(){if(b.element.readyState>
25-1)d||b.init.apply(b);if(b.element.readyState>1){b.settings.autoplay&&b.play.apply(b);clearInterval(a);c=setInterval(function(){b.loadProgress.apply(b);b.loadedPercent>=1&&clearInterval(c)})}},10);b.readyTimer=a;b.loadTimer=c}},purge:function(b){var a=b.attributes,c;if(a)for(c=0;c<a.length;c+=
261)if(typeof b[a[c].name]==="function")b[a[c].name]=null;if(a=b.childNodes)for(c=0;c<a.length;c+=1)purge(b.childNodes[c])},ready:function(){return function(b){var a=window,c=false,d=true,e=a.document,i=e.documentElement,f=e.addEventListener?"addEventListener":"attachEvent",k=e.addEventListener?"removeEventListener":"detachEvent",n=e.addEventListener?"":"on",m=function(l){if(!(l.type=="readystatechange"&&e.readyState!="complete")){(l.type=="load"?a:e)[k](n+l.type,m,false);if(!c&&(c=true))b.call(a,l.type||
27l)}},q=function(){try{i.doScroll("left")}catch(l){setTimeout(q,50);return}m("poll")};if(e.readyState=="complete")b.call(a,"lazy");else{if(e.createEventObject&&i.doScroll){try{d=!a.frameElement}catch(r){}d&&q()}e[f](n+"DOMContentLoaded",m,false);e[f](n+"readystatechange",m,false);a[f](n+"load",m,false)}}}()}};g[o]=function(b,a){this.element=b;this.wrapper=b.parentNode;this.source=b.getElementsByTagName("source")[0]||b;this.mp3=function(c){var d=c.getElementsByTagName("source")[0];return c.getAttribute("src")||
28(d?d.getAttribute("src"):null)}(b);this.settings=a;this.loadStartedCalled=false;this.loadedPercent=0;this.duration=1;this.playing=false};g[o].prototype={updatePlayhead:function(){this.settings.updatePlayhead.apply(this,[this.element.currentTime/this.duration])},skipTo:function(b){if(!(b>this.loadedPercent)){this.element.currentTime=this.duration*b;this.updatePlayhead()}},load:function(b){this.loadStartedCalled=false;this.source.setAttribute("src",b);this.element.load();this.mp3=b;g[h].events.trackLoadProgress(this)},
29loadError:function(){this.settings.loadError.apply(this)},init:function(){this.settings.init.apply(this)},loadStarted:function(){if(!this.element.duration)return false;this.duration=this.element.duration;this.updatePlayhead();this.settings.loadStarted.apply(this)},loadProgress:function(){if(this.element.buffered!=null&&this.element.buffered.length){if(!this.loadStartedCalled)this.loadStartedCalled=this.loadStarted();this.loadedPercent=this.element.buffered.end(this.element.buffered.length-1)/this.duration;
30this.settings.loadProgress.apply(this,[this.loadedPercent])}},playPause:function(){this.playing?this.pause():this.play()},play:function(){/(ipod|iphone|ipad)/i.test(navigator.userAgent)&&this.element.readyState==0&&this.init.apply(this);if(!this.settings.preload){this.settings.preload=true;this.element.setAttribute("preload","auto");g[h].events.trackLoadProgress(this)}this.playing=true;this.element.play();this.settings.play.apply(this)},pause:function(){this.playing=false;this.element.pause();this.settings.pause.apply(this)},
31setVolume:function(b){this.element.volume=b},trackEnded:function(){this.skipTo.apply(this,[0]);this.settings.loop||this.pause.apply(this);this.settings.trackEnded.apply(this)}};var j=function(b,a){var c=[];a=a||document;if(a.getElementsByClassName)c=a.getElementsByClassName(b);else{var d,e,i=a.getElementsByTagName("*"),f=RegExp("(^|\\s)"+b+"(\\s|$)");d=0;for(e=i.length;d<e;d++)f.test(i[d].className)&&c.push(i[d])}return c.length>1?c:c[0]}})("audiojs","audiojsInstance",this);
32
33/*!
34 * JavaScript Cookie v2.0.4
35 * https://github.com/js-cookie/js-cookie
36 *
37 * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
38 * Released under the MIT license
39 */
40(function (factory) {
41	if (typeof define === 'function' && define.amd) {
42		define(factory);
43	} else if (typeof exports === 'object') {
44		module.exports = factory();
45	} else {
46		var _OldCookies = window.Cookies;
47		var api = window.Cookies = factory();
48		api.noConflict = function () {
49			window.Cookies = _OldCookies;
50			return api;
51		};
52	}
53}(function () {
54	function extend () {
55		var i = 0;
56		var result = {};
57		for (; i < arguments.length; i++) {
58			var attributes = arguments[ i ];
59			for (var key in attributes) {
60				result[key] = attributes[key];
61			}
62		}
63		return result;
64	}
65
66	function init (converter) {
67		function api (key, value, attributes) {
68			var result;
69
70			// Write
71
72			if (arguments.length > 1) {
73				attributes = extend({
74					path: '/'
75				}, api.defaults, attributes);
76
77				if (typeof attributes.expires === 'number') {
78					var expires = new Date();
79					expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
80					attributes.expires = expires;
81				}
82
vendor: 2,067 bytes, lines 83-159
83				try {
84					result = JSON.stringify(value);
85					if (/^[\{\[]/.test(result)) {
86						value = result;
87					}
88				} catch (e) {}
89
90				if (!converter.write) {
91					value = encodeURIComponent(String(value))
92						.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
93				} else {
94					value = converter.write(value, key);
95				}
96
97				key = encodeURIComponent(String(key));
98				key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
99				key = key.replace(/[\(\)]/g, escape);
100
101				return (document.cookie = [
102					key, '=', value,
103					attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE
104					attributes.path    && '; path=' + attributes.path,
105					attributes.domain  && '; domain=' + attributes.domain,
106					attributes.secure ? '; secure' : ''
107				].join(''));
108			}
109
110			// Read
111
112			if (!key) {
113				result = {};
114			}
115
116			// To prevent the for loop in the first place assign an empty array
117			// in case there are no cookies at all. Also prevents odd result when
118			// calling "get()"
119			var cookies = document.cookie ? document.cookie.split('; ') : [];
120			var rdecode = /(%[0-9A-Z]{2})+/g;
121			var i = 0;
122
123			for (; i < cookies.length; i++) {
124				var parts = cookies[i].split('=');
125				var name = parts[0].replace(rdecode, decodeURIComponent);
126				var cookie = parts.slice(1).join('=');
127
128				if (cookie.charAt(0) === '"') {
129					cookie = cookie.slice(1, -1);
130				}
131
132				try {
133					cookie = converter.read ?
134						converter.read(cookie, name) : converter(cookie, name) ||
135					cookie.replace(rdecode, decodeURIComponent);
136
137					if (this.json) {
138						try {
139							cookie = JSON.parse(cookie);
140						} catch (e) {}
141					}
142
143					if (key === name) {
144						result = cookie;
145						break;
146					}
147
148					if (!key) {
149						result[name] = cookie;
150					}
151				} catch (e) {}
152			}
153
154			return result;
155		}
156
157		api.get = api.set = api;
158		api.getJSON = function () {
159			return api.apply({
160				json: true
161			}, [].slice.call(arguments));
162		};
163		api.defaults = {};
164
165		api.remove = function (key, attributes) {
166			api(key, '', extend(attributes, {
167				expires: -1
168			}));
169		};
170
171		api.withConverter = init;
172
173		return api;
174	}
175
176	return init(function () {});
177}));
178/**
179 * Owl carousel
180 * @version 2.0.0
181 * @author Bartosz Wojciechowski
182 * @license The MIT License (MIT)
183 * @todo Lazy Load Icon
184 * @todo prevent animationend bubling
185 * @todo itemsScaleUp
186 * @todo Test Zepto
187 * @todo stagePadding calculate wrong active classes
188 */
189;(function($, window, document, undefined) {
190
191	var drag, state, e;
192
193	/**
194	 * Template for status information about drag and touch events.
195	 * @private
196	 */
197	drag = {
198		start: 0,
199		startX: 0,
200		startY: 0,
201		current: 0,
202		currentX: 0,
203		currentY: 0,
204		offsetX: 0,
205		offsetY: 0,
206		distance: null,
207		startTime: 0,
208		endTime: 0,
209		updatedX: 0,
210		targetEl: null
211	};
212
213	/**
214	 * Template for some status informations.
215	 * @private
216	 */
217	state = {
218		isTouch: false,
219		isScrolling: false,
220		isSwiping: false,
221		direction: false,
222		inMotion: false
223	};
224
225	/**
226	 * Event functions references.
227	 * @private
228	 */
229	e = {
230		_onDragStart: null,
231		_onDragMove: null,
232		_onDragEnd: null,
233		_transitionEnd: null,
234		_resizer: null,
235		_responsiveCall: null,
236		_goToLoop: null,
237		_checkVisibile: null
238	};
239
240	/**
241	 * Creates a carousel.
242	 * @class The Owl Carousel.
243	 * @public
244	 * @param {HTMLElement|jQuery} element - The element to create the carousel for.
245	 * @param {Object} [options] - The options
246	 */
247	function Owl(element, options) {
248
249		/**
250		 * Current settings for the carousel.
251		 * @public
252		 */
253		this.settings = null;
254
255		/**
256		 * Current options set by the caller including defaults.
257		 * @public
258		 */
259		this.options = $.extend({}, Owl.Defaults, options);
260
261		/**
262		 * Plugin element.
263		 * @public
264		 */
265		this.$element = $(element);
266
267		/**
268		 * Caches informations about drag and touch events.
269		 */
270		this.drag = $.extend({}, drag);
271
272		/**
273		 * Caches some status informations.
274		 * @protected
275		 */
276		this.state = $.extend({}, state);
277
278		/**
279		 * @protected
280		 * @todo Must be documented
281		 */
282		this.e = $.extend({}, e);
283
284		/**
285		 * References to the running plugins of this carousel.
286		 * @protected
287		 */
288		this._plugins = {};
289
290		/**
291		 * Currently suppressed events to prevent them from beeing retriggered.
292		 * @protected
293		 */
294		this._supress = {};
295
296		/**
297		 * Absolute current position.
298		 * @protected
299		 */
300		this._current = null;
301
302		/**
303		 * Animation speed in milliseconds.
304		 * @protected
305		 */
306		this._speed = null;
307
308		/**
309		 * Coordinates of all items in pixel.
310		 * @todo The name of this member is missleading.
311		 * @protected
312		 */
313		this._coordinates = [];
314
315		/**
316		 * Current breakpoint.
317		 * @todo Real media queries would be nice.
318		 * @protected
319		 */
320		this._breakpoint = null;
321
322		/**
323		 * Current width of the plugin element.
324		 */
325		this._width = null;
326
327		/**
328		 * All real items.
329		 * @protected
330		 */
331		this._items = [];
332
333		/**
334		 * All cloned items.
335		 * @protected
336		 */
337		this._clones = [];
338
339		/**
340		 * Merge values of all items.
341		 * @todo Maybe this could be part of a plugin.
342		 * @protected
343		 */
344		this._mergers = [];
345
346		/**
347		 * Invalidated parts within the update process.
348		 * @protected
349		 */
350		this._invalidated = {};
351
352		/**
353		 * Ordered list of workers for the update process.
354		 * @protected
355		 */
356		this._pipe = [];
357
358		$.each(Owl.Plugins, $.proxy(function(key, plugin) {
359			this._plugins[key[0].toLowerCase() + key.slice(1)]
360				= new plugin(this);
361		}, this));
362
363		$.each(Owl.Pipe, $.proxy(function(priority, worker) {
364			this._pipe.push({
365				'filter': worker.filter,
366				'run': $.proxy(worker.run, this)
367			});
368		}, this));
369
370		this.setup();
371		this.initialize();
372	}
373
374	/**
375	 * Default options for the carousel.
376	 * @public
377	 */
378	Owl.Defaults = {
379		items: 3,
380		loop: false,
381		center: false,
382
383		mouseDrag: true,
384		touchDrag: true,
385		pullDrag: true,
386		freeDrag: false,
387
388		margin: 0,
389		stagePadding: 0,
390
391		merge: false,
392		mergeFit: true,
393		autoWidth: false,
394
395		startPosition: 0,
396		rtl: false,
397
398		smartSpeed: 250,
399		fluidSpeed: false,
400		dragEndSpeed: false,
401
402		responsive: {},
403		responsiveRefreshRate: 200,
404		responsiveBaseElement: window,
405		responsiveClass: false,
406
407		fallbackEasing: 'swing',
408
409		info: false,
410
411		nestedItemSelector: false,
412		itemElement: 'div',
413		stageElement: 'div',
414
415		// Classes and Names
416		themeClass: 'owl-theme',
417		baseClass: 'owl-carousel',
418		itemClass: 'owl-item',
419		centerClass: 'center',
420		activeClass: 'active'
421	};
422
423	/**
424	 * Enumeration for width.
425	 * @public
426	 * @readonly
427	 * @enum {String}
428	 */
429	Owl.Width = {
430		Default: 'default',
431		Inner: 'inner',
432		Outer: 'outer'
433	};
434
435	/**
436	 * Contains all registered plugins.
437	 * @public
438	 */
439	Owl.Plugins = {};
440
441	/**
442	 * Update pipe.
443	 */
444	Owl.Pipe = [ {
445		filter: [ 'width', 'items', 'settings' ],
446		run: function(cache) {
447			cache.current = this._items && this._items[this.relative(this._current)];
448		}
449	}, {
450		filter: [ 'items', 'settings' ],
451		run: function() {
452			var cached = this._clones,
453				clones = this.$stage.children('.cloned');
454
455			if (clones.length !== cached.length || (!this.settings.loop && cached.length > 0)) {
456				this.$stage.children('.cloned').remove();
457				this._clones = [];
458			}
459		}
460	}, {
461		filter: [ 'items', 'settings' ],
462		run: function() {
463			var i, n,
464				clones = this._clones,
465				items = this._items,
466				delta = this.settings.loop ? clones.length - Math.max(this.settings.items * 2, 4) : 0;
467
468			for (i = 0, n = Math.abs(delta / 2); i < n; i++) {
469				if (delta > 0) {
470					this.$stage.children().eq(items.length + clones.length - 1).remove();
471					clones.pop();
472					this.$stage.children().eq(0).remove();
473					clones.pop();
474				} else {
475					clones.push(clones.length / 2);
476					this.$stage.append(items[clones[clones.length - 1]].clone().addClass('cloned'));
477					clones.push(items.length - 1 - (clones.length - 1) / 2);
478					this.$stage.prepend(items[clones[clones.length - 1]].clone().addClass('cloned'));
479				}
480			}
481		}
482	}, {
483		filter: [ 'width', 'items', 'settings' ],
484		run: function() {
485			var rtl = (this.settings.rtl ? 1 : -1),
486				width = (this.width() / this.settings.items).toFixed(3),
487				coordinate = 0, merge, i, n;
488
489			this._coordinates = [];
490			for (i = 0, n = this._clones.length + this._items.length; i < n; i++) {
491				merge = this._mergers[this.relative(i)];
492				merge = (this.settings.mergeFit && Math.min(merge, this.settings.items)) || merge;
493				coordinate += (this.settings.autoWidth ? this._items[this.relative(i)].width() + this.settings.margin : width * merge) * rtl;
494
495				this._coordinates.push(coordinate);
496			}
497		}
498	}, {
499		filter: [ 'width', 'items', 'settings' ],
500		run: function() {
501			var i, n, width = (this.width() / this.settings.items).toFixed(3), css = {
502				'width': Math.abs(this._coordinates[this._coordinates.length - 1]) + this.settings.stagePadding * 2,
503				'padding-left': this.settings.stagePadding || '',
504				'padding-right': this.settings.stagePadding || ''
505			};
506
507			this.$stage.css(css);
508
509			css = { 'width': this.settings.autoWidth ? 'auto' : width - this.settings.margin };
510			css[this.settings.rtl ? 'margin-left' : 'margin-right'] = this.settings.margin;
511
512			if (!this.settings.autoWidth && $.grep(this._mergers, function(v) { return v > 1 }).length > 0) {
513				for (i = 0, n = this._coordinates.length; i < n; i++) {
514					css.width = Math.abs(this._coordinates[i]) - Math.abs(this._coordinates[i - 1] || 0) - this.settings.margin;
515					this.$stage.children().eq(i).css(css);
516				}
517			} else {
518				this.$stage.children().css(css);
519			}
520		}
521	}, {
522		filter: [ 'width', 'items', 'settings' ],
523		run: function(cache) {
524			cache.current && this.reset(this.$stage.children().index(cache.current));
525		}
526	}, {
527		filter: [ 'position' ],
528		run: function() {
529			this.animate(this.coordinates(this._current));
530		}
531	}, {
532		filter: [ 'width', 'position', 'items', 'settings' ],
533		run: function() {
534			var rtl = this.settings.rtl ? 1 : -1,
535				padding = this.settings.stagePadding * 2,
536				begin = this.coordinates(this.current()) + padding,
537				end = begin + this.width() * rtl,
538				inner, outer, matches = [], i, n;
539
540			for (i = 0, n = this._coordinates.length; i < n; i++) {
541				inner = this._coordinates[i - 1] || 0;
542				outer = Math.abs(this._coordinates[i]) + padding * rtl;
543
544				if ((this.op(inner, '<=', begin) && (this.op(inner, '>', end)))
545					|| (this.op(outer, '<', begin) && this.op(outer, '>', end))) {
546					matches.push(i);
547				}
548			}
549
550			this.$stage.children('.' + this.settings.activeClass).removeClass(this.settings.activeClass);
551			this.$stage.children(':eq(' + matches.join('), :eq(') + ')').addClass(this.settings.activeClass);
552
553			if (this.settings.center) {
554				this.$stage.children('.' + this.settings.centerClass).removeClass(this.settings.centerClass);
555				this.$stage.children().eq(this.current()).addClass(this.settings.centerClass);
556			}
557		}
558	} ];
559
560	/**
561	 * Initializes the carousel.
562	 * @protected
563	 */
564	Owl.prototype.initialize = function() {
565		this.trigger('initialize');
566
567		this.$element
568			.addClass(this.settings.baseClass)
569			.addClass(this.settings.themeClass)
570			.toggleClass('owl-rtl', this.settings.rtl);
571
572		// check support
573		this.browserSupport();
574
575		if (this.settings.autoWidth && this.state.imagesLoaded !== true) {
576			var imgs, nestedSelector, width;
577			imgs = this.$element.find('img');
578			nestedSelector = this.settings.nestedItemSelector ? '.' + this.settings.nestedItemSelector : undefined;
579			width = this.$element.children(nestedSelector).width();
580
581			if (imgs.length && width <= 0) {
582				this.preloadAutoWidthImages(imgs);
583				return false;
584			}
585		}
586
587		this.$element.addClass('owl-loading');
588
589		// create stage
590		this.$stage = $('<' + this.settings.stageElement + ' class="owl-stage"/>')
591			.wrap('<div class="owl-stage-outer">');
592
593		// append stage
594		this.$element.append(this.$stage.parent());
595
596		// append content
597		this.replace(this.$element.children().not(this.$stage.parent()));
598
599		// set view width
600		this._width = this.$element.width();
601
602		// update view
603		this.refresh();
604
605		this.$element.removeClass('owl-loading').addClass('owl-loaded');
606
607		// attach generic events
608		this.eventsCall();
609
610		// attach generic events
611		this.internalEvents();
612
613		// attach custom control events
614		this.addTriggerableEvents();
615
616		this.trigger('initialized');
617	};
618
619	/**
620	 * Setups the current settings.
621	 * @todo Remove responsive classes. Why should adaptive designs be brought into IE8?
622	 * @todo Support for media queries by using `matchMedia` would be nice.
623	 * @public
624	 */
625	Owl.prototype.setup = function() {
626		var viewport = this.viewport(),
627			overwrites = this.options.responsive,
628			match = -1,
629			settings = null;
630
631		if (!overwrites) {
632			settings = $.extend({}, this.options);
633		} else {
634			$.each(overwrites, function(breakpoint) {
635				if (breakpoint <= viewport && breakpoint > match) {
636					match = Number(breakpoint);
637				}
638			});
639
640			settings = $.extend({}, this.options, overwrites[match]);
641			delete settings.responsive;
642
643			// responsive class
644			if (settings.responsiveClass) {
645				this.$element.attr('class', function(i, c) {
646					return c.replace(/\b owl-responsive-\S+/g, '');
647				}).addClass('owl-responsive-' + match);
648			}
649		}
650
651		if (this.settings === null || this._breakpoint !== match) {
652			this.trigger('change', { property: { name: 'settings', value: settings } });
653			this._breakpoint = match;
654			this.settings = settings;
655			this.invalidate('settings');
656			this.trigger('changed', { property: { name: 'settings', value: this.settings } });
657		}
658	};
659
660	/**
661	 * Updates option logic if necessery.
662	 * @protected
663	 */
664	Owl.prototype.optionsLogic = function() {
665		// Toggle Center class
666		this.$element.toggleClass('owl-center', this.settings.center);
667
668		// if items number is less than in body
669		if (this.settings.loop && this._items.length < this.settings.items) {
670			this.settings.loop = false;
671		}
672
673		if (this.settings.autoWidth) {
674			this.settings.stagePadding = false;
675			this.settings.merge = false;
676		}
677	};
678
679	/**
680	 * Prepares an item before add.
681	 * @todo Rename event parameter `content` to `item`.
682	 * @protected
683	 * @returns {jQuery|HTMLElement} - The item container.
684	 */
685	Owl.prototype.prepare = function(item) {
686		var event = this.trigger('prepare', { content: item });
687
688		if (!event.data) {
689			event.data = $('<' + this.settings.itemElement + '/>')
690				.addClass(this.settings.itemClass).append(item)
691		}
692
693		this.trigger('prepared', { content: event.data });
694
695		return event.data;
696	};
697
698	/**
699	 * Updates the view.
700	 * @public
701	 */
702	Owl.prototype.update = function() {
703		var i = 0,
704			n = this._pipe.length,
705			filter = $.proxy(function(p) { return this[p] }, this._invalidated),
706			cache = {};
707
708		while (i < n) {
709			if (this._invalidated.all || $.grep(this._pipe[i].filter, filter).length > 0) {
710				this._pipe[i].run(cache);
711			}
712			i++;
713		}
714
715		this._invalidated = {};
716	};
717
718	/**
719	 * Gets the width of the view.
720	 * @public
721	 * @param {Owl.Width} [dimension=Owl.Width.Default] - The dimension to return.
722	 * @returns {Number} - The width of the view in pixel.
723	 */
724	Owl.prototype.width = function(dimension) {
725		dimension = dimension || Owl.Width.Default;
726		switch (dimension) {
727			case Owl.Width.Inner:
728			case Owl.Width.Outer:
729				return this._width;
730			default:
731				return this._width - this.settings.stagePadding * 2 + this.settings.margin;
732		}
733	};
734
735	/**
736	 * Refreshes the carousel primarily for adaptive purposes.
737	 * @public
738	 */
739	Owl.prototype.refresh = function() {
740		if (this._items.length === 0) {
741			return false;
742		}
743
744		var start = new Date().getTime();
745
746		this.trigger('refresh');
747
748		this.setup();
749
750		this.optionsLogic();
751
752		// hide and show methods helps here to set a proper widths,
753		// this prevents scrollbar to be calculated in stage width
754		this.$stage.addClass('owl-refresh');
755
756		this.update();
757
758		this.$stage.removeClass('owl-refresh');
759
760		this.state.orientation = window.orientation;
761
762		this.watchVisibility();
763
764		this.trigger('refreshed');
765	};
766
767	/**
768	 * Save internal event references and add event based functions.
769	 * @protected
770	 */
771	Owl.prototype.eventsCall = function() {
772		// Save events references
773		this.e._onDragStart = $.proxy(function(e) {
774			this.onDragStart(e);
775		}, this);
776		this.e._onDragMove = $.proxy(function(e) {
777			this.onDragMove(e);
778		}, this);
779		this.e._onDragEnd = $.proxy(function(e) {
780			this.onDragEnd(e);
781		}, this);
782		this.e._onResize = $.proxy(function(e) {
783			this.onResize(e);
784		}, this);
785		this.e._transitionEnd = $.proxy(function(e) {
786			this.transitionEnd(e);
787		}, this);
788		this.e._preventClick = $.proxy(function(e) {
789			this.preventClick(e);
790		}, this);
791	};
792
793	/**
794	 * Checks window `resize` event.
795	 * @protected
796	 */
797	Owl.prototype.onThrottledResize = function() {
798		window.clearTimeout(this.resizeTimer);
799		this.resizeTimer = window.setTimeout(this.e._onResize, this.settings.responsiveRefreshRate);
800	};
801
802	/**
803	 * Checks window `resize` event.
804	 * @protected
805	 */
806	Owl.prototype.onResize = function() {
807		if (!this._items.length) {
808			return false;
809		}
810
811		if (this._width === this.$element.width()) {
812			return false;
813		}
814
815		if (this.trigger('resize').isDefaultPrevented()) {
816			return false;
817		}
818
819		this._width = this.$element.width();
820
821		this.invalidate('width');
822
823		this.refresh();
824
825		this.trigger('resized');
826	};
827
828	/**
829	 * Checks for touch/mouse drag event type and add run event handlers.
830	 * @protected
831	 */
832	Owl.prototype.eventsRouter = function(event) {
833		var type = event.type;
834
835		if (type === "mousedown" || type === "touchstart") {
836			this.onDragStart(event);
837		} else if (type === "mousemove" || type === "touchmove") {
838			this.onDragMove(event);
839		} else if (type === "mouseup" || type === "touchend") {
840			this.onDragEnd(event);
841		} else if (type === "touchcancel") {
842			this.onDragEnd(event);
843		}
844	};
845
846	/**
847	 * Checks for touch/mouse drag options and add necessery event handlers.
848	 * @protected
849	 */
850	Owl.prototype.internalEvents = function() {
851		var isTouch = isTouchSupport(),
852			isTouchIE = isTouchSupportIE();
853
854		if (this.settings.mouseDrag){
855			this.$stage.on('mousedown', $.proxy(function(event) { this.eventsRouter(event) }, this));
856			this.$stage.on('dragstart', function() { return false });
857			this.$stage.get(0).onselectstart = function() { return false };
858		} else {
859			this.$element.addClass('owl-text-select-on');
860		}
861
862		if (this.settings.touchDrag && !isTouchIE){
863			this.$stage.on('touchstart touchcancel', $.proxy(function(event) { this.eventsRouter(event) }, this));
864		}
865
866		// catch transitionEnd event
867		if (this.transitionEndVendor) {
868			this.on(this.$stage.get(0), this.transitionEndVendor, this.e._transitionEnd, false);
869		}
870
871		// responsive
872		if (this.settings.responsive !== false) {
873			this.on(window, 'resize', $.proxy(this.onThrottledResize, this));
874		}
875	};
876
877	/**
878	 * Handles touchstart/mousedown event.
879	 * @protected
880	 * @param {Event} event - The event arguments.
881	 */
882	Owl.prototype.onDragStart = function(event) {
883		var ev, isTouchEvent, pageX, pageY, animatedPos;
884
885		ev = event.originalEvent || event || window.event;
886
887		// prevent right click
888		if (ev.which === 3 || this.state.isTouch) {
889			return false;
890		}
891
892		if (ev.type === 'mousedown') {
893			this.$stage.addClass('owl-grab');
894		}
895
896		this.trigger('drag');
897		this.drag.startTime = new Date().getTime();
898		this.speed(0);
899		this.state.isTouch = true;
900		this.state.isScrolling = false;
901		this.state.isSwiping = false;
902		this.drag.distance = 0;
903
904		pageX = getTouches(ev).x;
905		pageY = getTouches(ev).y;
906
907		// get stage position left
908		this.drag.offsetX = this.$stage.position().left;
909		this.drag.offsetY = this.$stage.position().top;
910
911		if (this.settings.rtl) {
912			this.drag.offsetX = this.$stage.position().left + this.$stage.width() - this.width()
913				+ this.settings.margin;
914		}
915
916		// catch position // ie to fix
917		if (this.state.inMotion && this.support3d) {
918			animatedPos = this.getTransformProperty();
919			this.drag.offsetX = animatedPos;
920			this.animate(animatedPos);
921			this.state.inMotion = true;
922		} else if (this.state.inMotion && !this.support3d) {
923			this.state.inMotion = false;
924			return false;
925		}
926
927		this.drag.startX = pageX - this.drag.offsetX;
928		this.drag.startY = pageY - this.drag.offsetY;
929
930		this.drag.start = pageX - this.drag.startX;
931		this.drag.targetEl = ev.target || ev.srcElement;
932		this.drag.updatedX = this.drag.start;
933
934		// to do/check
935		// prevent links and images dragging;
936		if (this.drag.targetEl.tagName === "IMG" || this.drag.targetEl.tagName === "A") {
937			this.drag.targetEl.draggable = false;
938		}
939
940		$(document).on('mousemove.owl.dragEvents mouseup.owl.dragEvents touchmove.owl.dragEvents touchend.owl.dragEvents', $.proxy(function(event) {this.eventsRouter(event)},this));
941	};
942
943	/**
944	 * Handles the touchmove/mousemove events.
945	 * @todo Simplify
946	 * @protected
947	 * @param {Event} event - The event arguments.
948	 */
949	Owl.prototype.onDragMove = function(event) {
950		var ev, isTouchEvent, pageX, pageY, minValue, maxValue, pull;
951
952		if (!this.state.isTouch) {
953			return;
954		}
955
956		if (this.state.isScrolling) {
957			return;
958		}
959
960		ev = event.originalEvent || event || window.event;
961
962		pageX = getTouches(ev).x;
963		pageY = getTouches(ev).y;
964
965		// Drag Direction
966		this.drag.currentX = pageX - this.drag.startX;
967		this.drag.currentY = pageY - this.drag.startY;
968		this.drag.distance = this.drag.currentX - this.drag.offsetX;
969
970		// Check move direction
971		if (this.drag.distance < 0) {
972			this.state.direction = this.settings.rtl ? 'right' : 'left';
973		} else if (this.drag.distance > 0) {
974			this.state.direction = this.settings.rtl ? 'left' : 'right';
975		}
976		// Loop
977		if (this.settings.loop) {
978			if (this.op(this.drag.currentX, '>', this.coordinates(this.minimum())) && this.state.direction === 'right') {
979				this.drag.currentX -= (this.settings.center && this.coordinates(0)) - this.coordinates(this._items.length);
980			} else if (this.op(this.drag.currentX, '<', this.coordinates(this.maximum())) && this.state.direction === 'left') {
981				this.drag.currentX += (this.settings.center && this.coordinates(0)) - this.coordinates(this._items.length);
982			}
983		} else {
984			// pull
985			minValue = this.settings.rtl ? this.coordinates(this.maximum()) : this.coordinates(this.minimum());
986			maxValue = this.settings.rtl ? this.coordinates(this.minimum()) : this.coordinates(this.maximum());
987			pull = this.settings.pullDrag ? this.drag.distance / 5 : 0;
988			this.drag.currentX = Math.max(Math.min(this.drag.currentX, minValue + pull), maxValue + pull);
989		}
990
991		// Lock browser if swiping horizontal
992
993		if ((this.drag.distance > 8 || this.drag.distance < -8)) {
994			if (ev.preventDefault !== undefined) {
995				ev.preventDefault();
996			} else {
997				ev.returnValue = false;
998			}
999			this.state.isSwiping = true;
1000		}
1001
1002		this.drag.updatedX = this.drag.currentX;
1003
1004		// Lock Owl if scrolling
1005		if ((this.drag.currentY > 16 || this.drag.currentY < -16) && this.state.isSwiping === false) {
1006			this.state.isScrolling = true;
1007			this.drag.updatedX = this.drag.start;
1008		}
1009
1010		this.animate(this.drag.updatedX);
1011	};
1012
1013	/**
1014	 * Handles the touchend/mouseup events.
1015	 * @protected
1016	 */
1017	Owl.prototype.onDragEnd = function(event) {
1018		var compareTimes, distanceAbs, closest;
1019
1020		if (!this.state.isTouch) {
1021			return;
1022		}
1023
1024		if (event.type === 'mouseup') {
1025			this.$stage.removeClass('owl-grab');
1026		}
1027
1028		this.trigger('dragged');
1029
1030		// prevent links and images dragging;
1031		this.drag.targetEl.removeAttribute("draggable");
1032
1033		// remove drag event listeners
1034
1035		this.state.isTouch = false;
1036		this.state.isScrolling = false;
1037		this.state.isSwiping = false;
1038
1039		// to check
1040		if (this.drag.distance === 0 && this.state.inMotion !== true) {
1041			this.state.inMotion = false;
1042			return false;
1043		}
1044
1045		// prevent clicks while scrolling
1046
1047		this.drag.endTime = new Date().getTime();
1048		compareTimes = this.drag.endTime - this.drag.startTime;
1049		distanceAbs = Math.abs(this.drag.distance);
1050
1051		// to test
1052		if (distanceAbs > 3 || compareTimes > 300) {
1053			this.removeClick(this.drag.targetEl);
1054		}
1055
1056		closest = this.closest(this.drag.updatedX);
1057
1058		this.speed(this.settings.dragEndSpeed || this.settings.smartSpeed);
1059		this.current(closest);
1060		this.invalidate('position');
1061		this.update();
1062
1063		// if pullDrag is off then fire transitionEnd event manually when stick
1064		// to border
1065		if (!this.settings.pullDrag && this.drag.updatedX === this.coordinates(closest)) {
1066			this.transitionEnd();
1067		}
1068
1069		this.drag.distance = 0;
1070
1071		$(document).off('.owl.dragEvents');
1072	};
1073
1074	/**
1075	 * Attaches `preventClick` to disable link while swipping.
1076	 * @protected
1077	 * @param {HTMLElement} [target] - The target of the `click` event.
1078	 */
1079	Owl.prototype.removeClick = function(target) {
1080		this.drag.targetEl = target;
1081		$(target).on('click.preventClick', this.e._preventClick);
1082		// to make sure click is removed:
1083		window.setTimeout(function() {
1084			$(target).off('click.preventClick');
1085		}, 300);
1086	};
1087
1088	/**
1089	 * Suppresses click event.
1090	 * @protected
1091	 * @param {Event} ev - The event arguments.
1092	 */
1093	Owl.prototype.preventClick = function(ev) {
1094		if (ev.preventDefault) {
1095			ev.preventDefault();
1096		} else {
1097			ev.returnValue = false;
1098		}
1099		if (ev.stopPropagation) {
1100			ev.stopPropagation();
1101		}
1102		$(ev.target).off('click.preventClick');
1103	};
1104
1105	/**
1106	 * Catches stage position while animate (only CSS3).
1107	 * @protected
1108	 * @returns
1109	 */
1110	Owl.prototype.getTransformProperty = function() {
1111		var transform, matrix3d;
1112
1113		transform = window.getComputedStyle(this.$stage.get(0), null).getPropertyValue(this.vendorName + 'transform');
1114		// var transform = this.$stage.css(this.vendorName + 'transform')
1115		transform = transform.replace(/matrix(3d)?\(|\)/g, '').split(',');
1116		matrix3d = transform.length === 16;
1117
1118		return matrix3d !== true ? transform[4] : transform[12];
1119	};
1120
1121	/**
1122	 * Gets absolute position of the closest item for a coordinate.
1123	 * @todo Setting `freeDrag` makes `closest` not reusable. See #165.
1124	 * @protected
1125	 * @param {Number} coordinate - The coordinate in pixel.
1126	 * @return {Number} - The absolute position of the closest item.
1127	 */
1128	Owl.prototype.closest = function(coordinate) {
1129		var position = -1, pull = 30, width = this.width(), coordinates = this.coordinates();
1130
1131		if (!this.settings.freeDrag) {
1132			// check closest item
1133			$.each(coordinates, $.proxy(function(index, value) {
1134				if (coordinate > value - pull && coordinate < value + pull) {
1135					position = index;
1136				} else if (this.op(coordinate, '<', value)
1137					&& this.op(coordinate, '>', coordinates[index + 1] || value - width)) {
1138					position = this.state.direction === 'left' ? index + 1 : index;
1139				}
1140				return position === -1;
1141			}, this));
1142		}
1143
1144		if (!this.settings.loop) {
1145			// non loop boundries
1146			if (this.op(coordinate, '>', coordinates[this.minimum()])) {
1147				position = coordinate = this.minimum();
1148			} else if (this.op(coordinate, '<', coordinates[this.maximum()])) {
1149				position = coordinate = this.maximum();
1150			}
1151		}
1152
1153		return position;
1154	};
1155
1156	/**
1157	 * Animates the stage.
1158	 * @public
1159	 * @param {Number} coordinate - The coordinate in pixels.
1160	 */
1161	Owl.prototype.animate = function(coordinate) {
1162		this.trigger('translate');
1163		this.state.inMotion = this.speed() > 0;
1164
1165		if (this.support3d) {
1166			this.$stage.css({
1167				transform: 'translate3d(' + coordinate + 'px' + ',0px, 0px)',
1168				transition: (this.speed() / 1000) + 's'
1169			});
1170		} else if (this.state.isTouch) {
1171			this.$stage.css({
1172				left: coordinate + 'px'
1173			});
1174		} else {
1175			this.$stage.animate({
1176				left: coordinate
1177			}, this.speed() / 1000, this.settings.fallbackEasing, $.proxy(function() {
1178				if (this.state.inMotion) {
1179					this.transitionEnd();
1180				}
1181			}, this));
1182		}
1183	};
1184
1185	/**
1186	 * Sets the absolute position of the current item.
1187	 * @public
1188	 * @param {Number} [position] - The new absolute position or nothing to leave it unchanged.
1189	 * @returns {Number} - The absolute position of the current item.
1190	 */
1191	Owl.prototype.current = function(position) {
1192		if (position === undefined) {
1193			return this._current;
1194		}
1195
1196		if (this._items.length === 0) {
1197			return undefined;
1198		}
1199
1200		position = this.normalize(position);
1201
1202		if (this._current !== position) {
1203			var event = this.trigger('change', { property: { name: 'position', value: position } });
1204
1205			if (event.data !== undefined) {
1206				position = this.normalize(event.data);
1207			}
1208
1209			this._current = position;
1210
1211			this.invalidate('position');
1212
1213			this.trigger('changed', { property: { name: 'position', value: this._current } });
1214		}
1215
1216		return this._current;
1217	};
1218
1219	/**
1220	 * Invalidates the given part of the update routine.
1221	 * @param {String} part - The part to invalidate.
1222	 */
1223	Owl.prototype.invalidate = function(part) {
1224		this._invalidated[part] = true;
1225	}
1226
1227	/**
1228	 * Resets the absolute position of the current item.
1229	 * @public
1230	 * @param {Number} position - The absolute position of the new item.
1231	 */
1232	Owl.prototype.reset = function(position) {
1233		position = this.normalize(position);
1234
1235		if (position === undefined) {
1236			return;
1237		}
1238
1239		this._speed = 0;
1240		this._current = position;
1241
1242		this.suppress([ 'translate', 'translated' ]);
1243
1244		this.animate(this.coordinates(position));
1245
1246		this.release([ 'translate', 'translated' ]);
1247	};
1248
1249	/**
1250	 * Normalizes an absolute or a relative position for an item.
1251	 * @public
1252	 * @param {Number} position - The absolute or relative position to normalize.
1253	 * @param {Boolean} [relative=false] - Whether the given position is relative or not.
1254	 * @returns {Number} - The normalized position.
1255	 */
1256	Owl.prototype.normalize = function(position, relative) {
1257		var n = (relative ? this._items.length : this._items.length + this._clones.length);
1258
1259		if (!$.isNumeric(position) || n < 1) {
1260			return undefined;
1261		}
1262
1263		if (this._clones.length) {
1264			position = ((position % n) + n) % n;
1265		} else {
1266			position = Math.max(this.minimum(relative), Math.min(this.maximum(relative), position));
1267		}
1268
1269		return position;
1270	};
1271
1272	/**
1273	 * Converts an absolute position for an item into a relative position.
1274	 * @public
1275	 * @param {Number} position - The absolute position to convert.
1276	 * @returns {Number} - The converted position.
1277	 */
1278	Owl.prototype.relative = function(position) {
1279		position = this.normalize(position);
1280		position = position - this._clones.length / 2;
1281		return this.normalize(position, true);
1282	};
1283
1284	/**
1285	 * Gets the maximum position for an item.
1286	 * @public
1287	 * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position.
1288	 * @returns {Number}
1289	 */
1290	Owl.prototype.maximum = function(relative) {
1291		var maximum, width, i = 0, coordinate,
1292			settings = this.settings;
1293
1294		if (relative) {
1295			return this._items.length - 1;
1296		}
1297
1298		if (!settings.loop && settings.center) {
1299			maximum = this._items.length - 1;
1300		}
1300 else if (!settings.loop && !settings.center) {
1301			maximum = this._items.length - settings.items;
1302		} else if (settings.loop || settings.center) {
1303			maximum = this._items.length + settings.items;
1304		} else if (settings.autoWidth || settings.merge) {
1305			revert = settings.rtl ? 1 : -1;
1306			width = this.$stage.width() - this.$element.width();
1307			while (coordinate = this.coordinates(i)) {
1308				if (coordinate * revert >= width) {
1309					break;
1310				}
1311				maximum = ++i;
1312			}
1313		} else {
1314			throw 'Can not detect maximum absolute position.'
1315		}
1316
1317		return maximum;
1318	};
1319
1320	/**
1321	 * Gets the minimum position for an item.
1322	 * @public
1323	 * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position.
1324	 * @returns {Number}
1325	 */
1326	Owl.prototype.minimum = function(relative) {
1327		if (relative) {
1328			return 0;
1329		}
1330
1331		return this._clones.length / 2;
1332	};
1333
1334	/**
1335	 * Gets an item at the specified relative position.
1336	 * @public
1337	 * @param {Number} [position] - The relative position of the item.
1338	 * @return {jQuery|Array.<jQuery>} - The item at the given position or all items if no position was given.
1339	 */
1340	Owl.prototype.items = function(position) {
1341		if (position === undefined) {
1342			return this._items.slice();
1343		}
1344
1345		position = this.normalize(position, true);
1346		return this._items[position];
1347	};
1348
1349	/**
1350	 * Gets an item at the specified relative position.
1351	 * @public
1352	 * @param {Number} [position] - The relative position of the item.
1353	 * @return {jQuery|Array.<jQuery>} - The item at the given position or all items if no position was given.
1354	 */
1355	Owl.prototype.mergers = function(position) {
1356		if (position === undefined) {
1357			return this._mergers.slice();
1358		}
1359
1360		position = this.normalize(position, true);
1361		return this._mergers[position];
1362	};
1363
1364	/**
1365	 * Gets the absolute positions of clones for an item.
1366	 * @public
1367	 * @param {Number} [position] - The relative position of the item.
1368	 * @returns {Array.<Number>} - The absolute positions of clones for the item or all if no position was given.
1369	 */
1370	Owl.prototype.clones = function(position) {
1371		var odd = this._clones.length / 2,
1372			even = odd + this._items.length,
1373			map = function(index) { return index % 2 === 0 ? even + index / 2 : odd - (index + 1) / 2 };
1374
1375		if (position === undefined) {
1376			return $.map(this._clones, function(v, i) { return map(i) });
1377		}
1378
1379		return $.map(this._clones, function(v, i) { return v === position ? map(i) : null });
1380	};
1381
1382	/**
1383	 * Sets the current animation speed.
1384	 * @public
1385	 * @param {Number} [speed] - The animation speed in milliseconds or nothing to leave it unchanged.
1386	 * @returns {Number} - The current animation speed in milliseconds.
1387	 */
1388	Owl.prototype.speed = function(speed) {
1389		if (speed !== undefined) {
1390			this._speed = speed;
1391		}
1392
1393		return this._speed;
1394	};
1395
1396	/**
1397	 * Gets the coordinate of an item.
1398	 * @todo The name of this method is missleanding.
1399	 * @public
1400	 * @param {Number} position - The absolute position of the item within `minimum()` and `maximum()`.
1401	 * @returns {Number|Array.<Number>} - The coordinate of the item in pixel or all coordinates.
1402	 */
1403	Owl.prototype.coordinates = function(position) {
1404		var coordinate = null;
1405
1406		if (position === undefined) {
1407			return $.map(this._coordinates, $.proxy(function(coordinate, index) {
1408				return this.coordinates(index);
1409			}, this));
1410		}
1411
1412		if (this.settings.center) {
1413			coordinate = this._coordinates[position];
1414			coordinate += (this.width() - coordinate + (this._coordinates[position - 1] || 0)) / 2 * (this.settings.rtl ? -1 : 1);
1415		} else {
1416			coordinate = this._coordinates[position - 1] || 0;
1417		}
1418
1419		return coordinate;
1420	};
1421
1422	/**
1423	 * Calculates the speed for a translation.
1424	 * @protected
1425	 * @param {Number} from - The absolute position of the start item.
1426	 * @param {Number} to - The absolute position of the target item.
1427	 * @param {Number} [factor=undefined] - The time factor in milliseconds.
1428	 * @returns {Number} - The time in milliseconds for the translation.
1429	 */
1430	Owl.prototype.duration = function(from, to, factor) {
1431		return Math.min(Math.max(Math.abs(to - from), 1), 6) * Math.abs((factor || this.settings.smartSpeed));
1432	};
1433
1434	/**
1435	 * Slides to the specified item.
1436	 * @public
1437	 * @param {Number} position - The position of the item.
1438	 * @param {Number}
1438 [speed] - The time in milliseconds for the transition.
1439	 */
1440	Owl.prototype.to = function(position, speed) {
1441		if (this.settings.loop) {
1442			var distance = position - this.relative(this.current()),
1443				revert = this.current(),
1444				before = this.current(),
1445				after = this.current() + distance,
1446				direction = before - after < 0 ? true : false,
1447				items = this._clones.length + this._items.length;
1448
1449			if (after < this.settings.items && direction === false) {
1450				revert = before + this._items.length;
1451				this.reset(revert);
1452			} else if (after >= items - this.settings.items && direction === true) {
1453				revert = before - this._items.length;
1454				this.reset(revert);
1455			}
1456			window.clearTimeout(this.e._goToLoop);
1457			this.e._goToLoop = window.setTimeout($.proxy(function() {
1458				this.speed(this.duration(this.current(), revert + distance, speed));
1459				this.current(revert + distance);
1460				this.update();
1461			}, this), 30);
1462		} else {
1463			this.speed(this.duration(this.current(), position, speed));
1464			this.current(position);
1465			this.update();
1466		}
1467	};
1468
1469	/**
1470	 * Slides to the next item.
1471	 * @public
1472	 * @param {Number} [speed] - The time in milliseconds for the transition.
1473	 */
1474	Owl.prototype.next = function(speed) {
1475		speed = speed || false;
1476		this.to(this.relative(this.current()) + 1, speed);
1477	};
1478
1479	/**
1480	 * Slides to the previous item.
1481	 * @public
1482	 * @param {Number} [speed] - The time in milliseconds for the transition.
1483	 */
1484	Owl.prototype.prev = function(speed) {
1485		speed = speed || false;
1486		this.to(this.relative(this.current()) - 1, speed);
1487	};
1488
1489	/**
1490	 * Handles the end of an animation.
1491	 * @protected
1492	 * @param {Event} event - The event arguments.
1493	 */
1494	Owl.prototype.transitionEnd = function(event) {
1495
1496		// if css2 animation then event object is undefined
1497		if (event !== undefined) {
1498			event.stopPropagation();
1499
1500			// Catch only owl-stage transitionEnd event
1501			if ((event.target || event.srcElement || event.originalTarget) !== this.$stage.get(0)) {
1502				return false;
1503			}
1504		}
1505
1506		this.state.inMotion = false;
1507		this.trigger('translated');
1508	};
1509
1510	/**
1511	 * Gets viewport width.
1512	 * @protected
1513	 * @return {Number} - The width in pixel.
1514	 */
1515	Owl.prototype.viewport = function() {
1516		var width;
1517		if (this.options.responsiveBaseElement !== window) {
1518			width = $(this.options.responsiveBaseElement).width();
1519		} else if (window.innerWidth) {
1520			width = window.innerWidth;
1521		} else if (document.documentElement && document.documentElement.clientWidth) {
1522			width = document.documentElement.clientWidth;
1523		} else {
1524			throw 'Can not detect viewport width.';
1525		}
1526		return width;
1527	};
1528
1529	/**
1530	 * Replaces the current content.
1531	 * @public
1532	 * @param {HTMLElement|jQuery|String} content - The new content.
1533	 */
1534	Owl.prototype.replace = function(content) {
1535		this.$stage.empty();
1536		this._items = [];
1537
1538		if (content) {
1539			content = (content instanceof jQuery) ? content : $(content);
1540		}
1541
1542		if (this.settings.nestedItemSelector) {
1543			content = content.find('.' + this.settings.nestedItemSelector);
1544		}
1545
1546		content.filter(function() {
1547			return this.nodeType === 1;
1548		}).each($.proxy(function(index, item) {
1549			item = this.prepare(item);
1550			this.$stage.append(item);
1551			this._items.push(item);
1552			this._mergers.push(item.find('[data-merge]').andSelf('[data-merge]').attr('data-merge') * 1 || 1);
1553		}, this));
1554
1555		this.reset($.isNumeric(this.settings.startPosition) ? this.settings.startPosition : 0);
1556
1557		this.invalidate('items');
1558	};
1559
1560	/**
1561	 * Adds an item.
1562	 * @todo Use `item` instead of `content` for the event arguments.
1563	 * @public
1564	 * @param {HTMLElement|jQuery|String} content - The item content to add.
1565	 * @param {Number} [position] - The relative position at which to insert the item otherwise the item will be added to the end.
1566	 */
1567	Owl.prototype.add = function(content, position) {
1568		position = position === undefined ? this._items.length : this.normalize(position, true);
1569
1570		this.trigger('add', { content: content, position: position });
1571
1572		if (this._items.length === 0 || position === this._items.length) {
1573			this.$stage.append(content);
1574			this._items.push(content);
1575			this._mergers.push(content.find('[data-merge]').andSelf('[data-merge]').attr('data-merge') * 1 || 1);
1576		} else {
1577			this._items[position].before(content);
1578			this._items.splice(position, 0, content);
1579			this._mergers.splice(position, 0, content.find('[data-merge]').andSelf('[data-merge]').attr('data-merge') * 1 || 1);
1580		}
1581
1582		this.invalidate('items');
1583
1584		this.trigger('added', { content: content, position: position });
1585	};
1586
1587	/**
1588	 * Removes an item by its position.
1589	 * @todo Use `item` instead of `content` for the event arguments.
1590	 * @public
1591	 * @param {Number} position - The relative position of the item to remove.
1592	 */
1593	Owl.prototype.remove = function(position) {
1594		position = this.normalize(position, true);
1595
1596		if (position === undefined) {
1597			return;
1598		}
1599
1600		this.trigger('remove', { content: this._items[position], position: position });
1601
1602		this._items[position].remove();
1603		this._items.splice(position, 1);
1604		this._mergers.splice(position, 1);
1605
1606		this.invalidate('items');
1607
1608		this.trigger('removed', { content: null, position: position });
1609	};
1610
1611	/**
1612	 * Adds triggerable events.
1613	 * @protected
1614	 */
1615	Owl.prototype.addTriggerableEvents = function() {
1616		var handler = $.proxy(function(callback, event) {
1617			return $.proxy(function(e) {
1618				if (e.relatedTarget !== this) {
1619					this.suppress([ event ]);
1620					callback.apply(this, [].slice.call(arguments, 1));
1621					this.release([ event ]);
1622				}
1623			}, this);
1624		}, this);
1625
1626		$.each({
1627			'next': this.next,
1628			'prev': this.prev,
1629			'to': this.to,
1630			'destroy': this.destroy,
1631			'refresh': this.refresh,
1632			'replace': this.replace,
1633			'add': this.add,
1634			'remove': this.remove
1635		}, $.proxy(function(event, callback) {
1636			this.$element.on(event + '.owl.carousel', handler(callback, event + '.owl.carousel'));
1637		}, this));
1638
1639	};
1640
1641	/**
1642	 * Watches the visibility of the carousel element.
1643	 * @protected
1644	 */
1645	Owl.prototype.watchVisibility = function() {
1646
1647		// test on zepto
1648		if (!isElVisible(this.$element.get(0))) {
1649			this.$element.addClass('owl-hidden');
1650			window.clearInterval(this.e._checkVisibile);
1651			this.e._checkVisibile = window.setInterval($.proxy(checkVisible, this), 500);
1652		}
1653
1654		function isElVisible(el) {
1655			return el.offsetWidth > 0 && el.offsetHeight > 0;
1656		}
1657
1658		function checkVisible() {
1659			if (isElVisible(this.$element.get(0))) {
1660				this.$element.removeClass('owl-hidden');
1661				this.refresh();
1662				window.clearInterval(this.e._checkVisibile);
1663			}
1664		}
1665	};
1666
1667	/**
1668	 * Preloads images with auto width.
1669	 * @protected
1670	 * @todo Still to test
1671	 */
1672	Owl.prototype.preloadAutoWidthImages = function(imgs) {
1673		var loaded, that, $el, img;
1674
1675		loaded = 0;
1676		that = this;
1677		imgs.each(function(i, el) {
1678			$el = $(el);
1679			img = new Image();
1680
1681			img.onload = function() {
1682				loaded++;
1683				$el.attr('src', img.src);
1684				$el.css('opacity', 1);
1685				if (loaded >= imgs.length) {
1686					that.state.imagesLoaded = true;
1687					that.initialize();
1688				}
1689			};
1690
1691			img.src = $el.attr('src') || $el.attr('data-src') || $el.attr('data-src-retina');
1692		});
1693	};
1694
1695	/**
1696	 * Destroys the carousel.
1697	 * @public
1698	 */
1699	Owl.prototype.destroy = function() {
1700
1701		if (this.$element.hasClass(this.settings.themeClass)) {
1702			this.$element.removeClass(this.settings.themeClass);
1703		}
1704
1705		if (this.settings.responsive !== false) {
1706			$(window).off('resize.owl.carousel');
1707		}
1708
1709		if (this.transitionEndVendor) {
1710			this.off(this.$stage.get(0), this.transitionEndVendor, this.e._transitionEnd);
1711		}
1712
1713		for ( var i in this._plugins) {
1714			this._plugins[i].destroy();
1715		}
1716
1717		if (this.settings.mouseDrag || this.settings.touchDrag) {
1718			this.$stage.off('mousedown touchstart touchcancel');
1719			$(document).off('.owl.dragEvents');
1720			this.$stage.get(0).onselectstart = function() {};
1721			this.$stage.off('dragstart', function() { return false });
1722		}
1723
1724		// remove event handlers in the ".owl.carousel" namespace
1725		this.$element.off('.owl');
1726
1727		this.$stage.children('.cloned').remove();
1728		this.e = null;
1729		this.$element.removeData('owlCarousel');
1730
1731		this.$stage.children().contents().unwrap();
1732		this.$stage.children().unwrap();
1733		this.$stage.unwrap();
1734	};
1735
1736	/**
1737	 * Operators to calculate right-to-left and left-to-right.
1738	 * @protected
1739	 * @param {Number} [a] - The left side operand.
1740	 * @param {String} [o] - The operator.
1741	 * @param {Number} [b] - The right side operand.
1742	 */
1743	Owl.prototype.op = function(a, o, b) {
1744		var rtl = this.settings.rtl;
1745		switch (o) {
1746			case '<':
1747				return rtl ? a > b : a < b;
1748			case '>':
1749				return rtl ? a < b : a > b;
1750			case '>=':
1751				return rtl ? a <= b : a >= b;
1752			case '<=':
1753				return rtl ? a >= b : a <= b;
1754			default:
1755				break;
1756		}
1757	};
1758
1759	/**
1760	 * Attaches to an internal event.
1761	 * @protected
1762	 * @param {HTMLElement} element - The event source.
1763	 * @param {String} event - The event name.
1764	 * @param {Function} listener - The event handler to attach.
1765	 * @param {Boolean} capture - Wether the event should be handled at the capturing p
1765hase or not.
1766	 */
1767	Owl.prototype.on = function(element, event, listener, capture) {
1768		if (element.addEventListener) {
1769			element.addEventListener(event, listener, capture);
1770		} else if (element.attachEvent) {
1771			element.attachEvent('on' + event, listener);
1772		}
1773	};
1774
1775	/**
1776	 * Detaches from an internal event.
1777	 * @protected
1778	 * @param {HTMLElement} element - The event source.
1779	 * @param {String} event - The event name.
1780	 * @param {Function} listener - The attached event handler to detach.
1781	 * @param {Boolean} capture - Wether the attached event handler was registered as a capturing listener or not.
1782	 */
1783	Owl.prototype.off = function(element, event, listener, capture) {
1784		if (element.removeEventListener) {
1785			element.removeEventListener(event, listener, capture);
1786		} else if (element.detachEvent) {
1787			element.detachEvent('on' + event, listener);
1788		}
1789	};
1790
1791	/**
1792	 * Triggers an public event.
1793	 * @protected
1794	 * @param {String} name - The event name.
1795	 * @param {*} [data=null] - The event data.
1796	 * @param {String} [namespace=.owl.carousel] - The event namespace.
1797	 * @returns {Event} - The event arguments.
1798	 */
1799	Owl.prototype.trigger = function(name, data, namespace) {
1800		var status = {
1801			item: { count: this._items.length, index: this.current() }
1802		}, handler = $.camelCase(
1803			$.grep([ 'on', name, namespace ], function(v) { return v })
1804				.join('-').toLowerCase()
1805		), event = $.Event(
1806			[ name, 'owl', namespace || 'carousel' ].join('.').toLowerCase(),
1807			$.extend({ relatedTarget: this }, status, data)
1808		);
1809
1810		if (!this._supress[name]) {
1811			$.each(this._plugins, function(name, plugin) {
1812				if (plugin.onTrigger) {
1813					plugin.onTrigger(event);
1814				}
1815			});
1816
1817			this.$element.trigger(event);
1818
1819			if (this.settings && typeof this.settings[handler] === 'function') {
1820				this.settings[handler].apply(this, event);
1821			}
1822		}
1823
1824		return event;
1825	};
1826
1827	/**
1828	 * Suppresses events.
1829	 * @protected
1830	 * @param {Array.<String>} events - The events to suppress.
1831	 */
1832	Owl.prototype.suppress = function(events) {
1833		$.each(events, $.proxy(function(index, event) {
1834			this._supress[event] = true;
1835		}, this));
1836	}
1837
1838	/**
1839	 * Releases suppressed events.
1840	 * @protected
1841	 * @param {Array.<String>} events - The events to release.
1842	 */
1843	Owl.prototype.release = function(events) {
1844		$.each(events, $.proxy(function(index, event) {
1845			delete this._supress[event];
1846		}, this));
1847	}
1848
1849	/**
1850	 * Checks the availability of some browser features.
1851	 * @protected
1852	 */
1853	Owl.prototype.browserSupport = function() {
1854		this.support3d = isPerspective();
1855
1856		if (this.support3d) {
1857			this.transformVendor = isTransform();
1858
1859			// take transitionend event name by detecting transition
1860			var endVendors = [ 'transitionend', 'webkitTransitionEnd', 'transitionend', 'oTransitionEnd' ];
1861			this.transitionEndVendor = endVendors[isTransition()];
1862
1863			// take vendor name from transform name
1864			this.vendorName = this.transformVendor.replace(/Transform/i, '');
1865			this.vendorName = this.vendorName !== '' ? '-' + this.vendorName.toLowerCase() + '-' : '';
1866		}
1867
1868		this.state.orientation = window.orientation;
1869	};
1870
1871	/**
1872	 * Get touch/drag coordinats.
1873	 * @private
1874	 * @param {event} - mousedown/touchstart event
1875	 * @returns {object} - Contains X and Y of current mouse/touch position
1876	 */
1877
1878	function getTouches(event) {
1879		if (event.touches !== undefined) {
1880			return {
1881				x: event.touches[0].pageX,
1882				y: event.touches[0].pageY
1883			};
1884		}
1885
1886		if (event.touches === undefined) {
1887			if (event.pageX !== undefined) {
1888				return {
1889					x: event.pageX,
1890					y: event.pageY
1891				};
1892			}
1893
1894		if (event.pageX === undefined) {
1895			return {
1896					x: event.clientX,
1897					y: event.clientY
1898				};
1899			}
1900		}
1901	}
1902
1903	/**
1904	 * Checks for CSS support.
1905	 * @private
1906	 * @param {Array} array - The CSS properties to check for.
1907	 * @returns {Array} - Contains the supported CSS property name and its index or `false`.
1908	 */
1909	function isStyleSupported(array) {
1910		var p, s, fake = document.createElement('div'), list = array;
1911		for (p in list) {
1912			s = list[p];
1913			if (typeof fake.style[s] !== 'undefined') {
1914				fake = null;
1915				return [ s, p ];
1916			}
1917		}
1918		return [ false ];
1919	}
1920
1921	/**
1922	 * Checks for CSS transition support.
1923	 * @private
1924	 * @todo Realy bad design
1925	 * @returns {Number}
1926	 */
1927	function isTransition() {
1928		return isStyleSupported([ 'transition', 'WebkitTransition', 'MozTransition', 'OTransition' ])[1];
1929	}
1930
1931	/**
1932	 * Checks for CSS transform support.
1933	 * @private
1934	 * @returns {String} The supported property name or false.
1935	 */
1936	function isTransform() {
1937		return isStyleSupported([ 'transform', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ])[0];
1938	}
1939
1940	/**
1941	 * Checks for CSS perspective support.
1942	 * @private
1943	 * @returns {String} The supported property name or false.
1944	 */
1945	function isPerspective() {
1946		return isStyleSupported([ 'perspective', 'webkitPerspective', 'MozPerspective', 'OPerspective', 'MsPerspective' ])[0];
1947	}
1948
1949	/**
1950	 * Checks wether touch is supported or not.
1951	 * @private
1952	 * @returns {Boolean}
1953	 */
1954	function isTouchSupport() {
1955		return 'ontouchstart' in window || !!(navigator.msMaxTouchPoints);
1956	}
1957
1958	/**
1959	 * Checks wether touch is supported or not for IE.
1960	 * @private
1961	 * @returns {Boolean}
1962	 */
1963	function isTouchSupportIE() {
1964		return window.navigator.msPointerEnabled;
1965	}
1966
1967	/**
1968	 * The jQuery Plugin for the Owl Carousel
1969	 * @public
1970	 */
1971	$.fn.owlCarousel = function(options) {
1972		return this.each(function() {
1973			if (!$(this).data('owlCarousel')) {
1974				$(this).data('owlCarousel', new Owl(this, options));
1975			}
1976		});
1977	};
1978
1979	/**
1980	 * The constructor for the jQuery Plugin
1981	 * @public
1982	 */
1983	$.fn.owlCarousel.Constructor = Owl;
1984
1985})(window.Zepto || window.jQuery, window, document);
1986
1987/**
1988 * Lazy Plugin
1989 * @version 2.0.0
1990 * @author Bartosz Wojciechowski
1991 * @license The MIT License (MIT)
1992 */
1993;(function($, window, document, undefined) {
1994
1995	/**
1996	 * Creates the lazy plugin.
1997	 * @class The Lazy Plugin
1998	 * @param {Owl} carousel - The Owl Carousel
1999	 */
2000	var Lazy = function(carousel) {
2001
2002		/**
2003		 * Reference to the core.
2004		 * @protected
2005		 * @type {Owl}
2006		 */
2007		this._core = carousel;
2008
2009		/**
2010		 * Already loaded items.
2011		 * @protected
2012		 * @type {Array.<jQuery>}
2013		 */
2014		this._loaded = [];
2015
2016		/**
2017		 * Event handlers.
2018		 * @protected
2019		 * @type {Object}
2020		 */
2021		this._handlers = {
2022			'initialized.owl.carousel change.owl.carousel': $.proxy(function(e) {
2023				if (!e.namespace) {
2024					return;
2025				}
2026
2027				if (!this._core.settings || !this._core.settings.lazyLoad) {
2028					return;
2029				}
2030
2031				if ((e.property && e.property.name == 'position') || e.type == 'initialized') {
2032					var settings = this._core.settings,
2033						n = (settings.center && Math.ceil(settings.items / 2) || settings.items),
2034						i = ((settings.center && n * -1) || 0),
2035						position = ((e.property && e.property.value) || this._core.current()) + i,
2036						clones = this._core.clones().length,
2037						load = $.proxy(function(i, v) { this.load(v) }, this);
2038
2039					while (i++ < n) {
2040						this.load(clones / 2 + this._core.relative(position));
2041						clones && $.each(this._core.clones(this._core.relative(position++)), load
2041);
2042					}
2043				}
2044			}, this)
2045		};
2046
2047		// set the default options
2048		this._core.options = $.extend({}, Lazy.Defaults, this._core.options);
2049
2050		// register event handler
2051		this._core.$element.on(this._handlers);
2052	}
2053
2054	/**
2055	 * Default options.
2056	 * @public
2057	 */
2058	Lazy.Defaults = {
2059		lazyLoad: false
2060	}
2061
2062	/**
2063	 * Loads all resources of an item at the specified position.
2064	 * @param {Number} position - The absolute position of the item.
2065	 * @protected
2066	 */
2067	Lazy.prototype.load = function(position) {
2068		var $item = this._core.$stage.children().eq(position),
2069			$elements = $item && $item.find('.owl-lazy');
2070
2071		if (!$elements || $.inArray($item.get(0), this._loaded) > -1) {
2072			return;
2073		}
2074
2075		$elements.each($.proxy(function(index, element) {
2076			var $element = $(element), image,
2077				url = (window.devicePixelRatio > 1 && $element.attr('data-src-retina')) || $element.attr('data-src');
2078
2079			this._core.trigger('load', { element: $element, url: url }, 'lazy');
2080
2081			if ($element.is('img')) {
2082				$element.one('load.owl.lazy', $.proxy(function() {
2083					$element.css('opacity', 1);
2084					this._core.trigger('loaded', { element: $element, url: url }, 'lazy');
2085				}, this)).attr('src', url);
2086			} else {
2087				image = new Image();
2088				image.onload = $.proxy(function() {
2089					$element.css({
2090						'background-image': 'url(' + url + ')',
2091						'opacity': '1'
2092					});
2093					this._core.trigger('loaded', { element: $element, url: url }, 'lazy');
2094				}, this);
2095				image.src = url;
2096			}
2097		}, this));
2098
2099		this._loaded.push($item.get(0));
2100	}
2101
2102	/**
2103	 * Destroys the plugin.
2104	 * @public
2105	 */
2106	Lazy.prototype.destroy = function() {
2107		var handler, property;
2108
2109		for (handler in this.handlers) {
2110			this._core.$element.off(handler, this.handlers[handler]);
2111		}
2112		for (property in Object.getOwnPropertyNames(this)) {
2113			typeof this[property] != 'function' && (this[property] = null);
2114		}
2115	}
2116
2117	$.fn.owlCarousel.Constructor.Plugins.Lazy = Lazy;
2118
2119})(window.Zepto || window.jQuery, window, document);
2120
2121/**
2122 * AutoHeight Plugin
2123 * @version 2.0.0
2124 * @author Bartosz Wojciechowski
2125 * @license The MIT License (MIT)
2126 */
2127;(function($, window, document, undefined) {
2128
2129	/**
2130	 * Creates the auto height plugin.
2131	 * @class The Auto Height Plugin
2132	 * @param {Owl} carousel - The Owl Carousel
2133	 */
2134	var AutoHeight = function(carousel) {
2135		/**
2136		 * Reference to the core.
2137		 * @protected
2138		 * @type {Owl}
2139		 */
2140		this._core = carousel;
2141
2142		/**
2143		 * All event handlers.
2144		 * @protected
2145		 * @type {Object}
2146		 */
2147		this._handlers = {
2148			'initialized.owl.carousel': $.proxy(function() {
2149				if (this._core.settings.autoHeight) {
2150					this.update();
2151				}
2152			}, this),
2153			'changed.owl.carousel': $.proxy(function(e) {
2154				if (this._core.settings.autoHeight && e.property.name == 'position'){
2155					this.update();
2156				}
2157			}, this),
2158			'loaded.owl.lazy': $.proxy(function(e) {
2159				if (this._core.settings.autoHeight && e.element.closest('.' + this._core.settings.itemClass)
2160					=== this._core.$stage.children().eq(this._core.current())) {
2161					this.update();
2162				}
2163			}, this)
2164		};
2165
2166		// set default options
2167		this._core.options = $.extend({}, AutoHeight.Defaults, this._core.options);
2168
2169		// register event handlers
2170		this._core.$element.on(this._handlers);
2171	};
2172
2173	/**
2174	 * Default options.
2175	 * @public
2176	 */
2177	AutoHeight.Defaults = {
2178		autoHeight: false,
2179		autoHeightClass: 'owl-height'
2180	};
2181
2182	/**
2183	 * Updates the view.
2184	 */
2185	AutoHeight.prototype.update = function() {
2186		this._core.$stage.parent()
2187			.height(this._core.$stage.children().eq(this._core.current()).height())
2188			.addClass(this._core.settings.autoHeightClass);
2189	};
2190
2191	AutoHeight.prototype.destroy = function() {
2192		var handler, property;
2193
2194		for (handler in this._handlers) {
2195			this._core.$element.off(handler, this._handlers[handler]);
2196		}
2197		for (property in Object.getOwnPropertyNames(this)) {
2198			typeof this[property] != 'function' && (this[property] = null);
2199		}
2200	};
2201
2202	$.fn.owlCarousel.Constructor.Plugins.AutoHeight = AutoHeight;
2203
2204})(window.Zepto || window.jQuery, window, document);
2205
2206/**
2207 * Video Plugin
2208 * @version 2.0.0
2209 * @author Bartosz Wojciechowski
2210 * @license The MIT License (MIT)
2211 */
2212;(function($, window, document, undefined) {
2213
2214	/**
2215	 * Creates the video plugin.
2216	 * @class The Video Plugin
2217	 * @param {Owl} carousel - The Owl Carousel
2218	 */
2219	var Video = function(carousel) {
2220		/**
2221		 * Reference to the core.
2222		 * @protected
2223		 * @type {Owl}
2224		 */
2225		this._core = carousel;
2226
2227		/**
2228		 * Cache all video URLs.
2229		 * @protected
2230		 * @type {Object}
2231		 */
2232		this._videos = {};
2233
2234		/**
2235		 * Current playing item.
2236		 * @protected
2237		 * @type {jQuery}
2238		 */
2239		this._playing = null;
2240
2241		/**
2242		 * Whether this is in fullscreen or not.
2243		 * @protected
2244		 * @type {Boolean}
2245		 */
2246		this._fullscreen = false;
2247
2248		/**
2249		 * All event handlers.
2250		 * @protected
2251		 * @type {Object}
2252		 */
2253		this._handlers = {
2254			'resize.owl.carousel': $.proxy(function(e) {
2255				if (this._core.settings.video && !this.isInFullScreen()) {
2256					e.preventDefault();
2257				}
2258			}, this),
2259			'refresh.owl.carousel changed.owl.carousel': $.proxy(function(e) {
2260				if (this._playing) {
2261					this.stop();
2262				}
2263			}, this),
2264			'prepared.owl.carousel': $.proxy(function(e) {
2265				var $element = $(e.content).find('.owl-video');
2266				if ($element.length) {
2267					$element.css('display', 'none');
2268					this.fetch($element, $(e.content));
2269				}
2270			}, this)
2271		};
2272
2273		// set default options
2274		this._core.options = $.extend({}, Video.Defaults, this._core.options);
2275
2276		// register event handlers
2277		this._core.$element.on(this._handlers);
2278
2279		this._core.$element.on('click.owl.video', '.owl-video-play-icon', $.proxy(function(e) {
2280			this.play(e);
2281		}, this));
2282	};
2283
2284	/**
2285	 * Default options.
2286	 * @public
2287	 */
2288	Video.Defaults = {
2289		video: false,
2290		videoHeight: false,
2291		videoWidth: false
2292	};
2293
2294	/**
2295	 * Gets the video ID and the type (YouTube/Vimeo only).
2296	 * @protected
2297	 * @param {jQuery} target - The target containing the video data.
2298	 * @param {jQuery} item - The item containing the video.
2299	 */
2300	Video.prototype.fetch = function(target, item) {
2301
2302		var type = target.attr('data-vimeo-id') ? 'vimeo' : 'youtube',
2303			id = target.attr('data-vimeo-id') || target.attr('data-youtube-id'),
2304			width = target.attr('data-width') || this._core.settings.videoWidth,
2305			height = target.attr('data-height') || this._core.settings.videoHeight,
2306			url = target.attr('href');
2307
2308		if (url) {
2309			id = url.match(/(http:|https:|)\/\/(player.|www.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com))\/(video\/|embed\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/);
2310
2311			if (id[3].indexOf('youtu') > -1) {
2312				type = 'youtube';
2313			} else if (id[3].indexOf('vimeo') > -1) {
2314				type = 'vimeo';
2315			} else {
2316				throw new Error('Video URL not supported.');
2317			}
2318			id = id[6];
2319		} else {
2320			throw new Error('Missing video URL.');
2321		}
2322
2323		this._videos[url] = {
2324			type: type,
2325			id: id,
2326			width: width,
2327			height: height
2328		};
2329
2330		item.attr('data-video', url);
2331
2332		this.thumbnail(target, this._videos[url]);
2333	};
2334
2335	/**
2336	 * Creates video thumbnail.
2337	 * @protected
2338	 * @param {jQuery} target - The target containing the video data.
2339	 * @param {Object} info - The video info object.
2340	 * @see `fetch`
2341	 */
2342	Video.prototype.thumbnail = function(target, video) {
2343
2344		var tnLink,
2345			icon,
2346			path,
2347			dimensions = video.width && video.height ? 'style="width:' + video.width + 'px;height:' + video.height + 'px;"' : '',
2348			customTn = target.find('img'),
2349			srcType = 'src',
2350			lazyClass = '',
2351			settings = this._core.settings,
2352			create = function(path) {
2353				icon = '<div class="owl-video-play-icon"></div>';
2354
2355				if (settings.lazyLoad) {
2356					tnLink = '<div class="owl-video-tn ' + lazyClass + '" ' + srcType + '="' + path + '"></div>';
2357				} else {
2358					tnLink = '<div class="owl-video-tn" style="opacity:1;background-image:url(' + path + ')"></div>';
2359				}
2360				target.after(tnLink);
2361				target.after(icon);
2362			};
2363
2364		// wrap video content into owl-video-wrapper div
2365		target.wrap('<div class="owl-video-wrapper"' + dimensions + '></div>');
2366
2367		if (this._core.settings.lazyLoad) {
2368			srcType = 'data-src';
2369			lazyClass = 'owl-lazy';
2370		}
2371
2372		// custom thumbnail
2373		if (customTn.length) {
2374			create(customTn.attr(srcType));
2375			customTn.remove();
2376			return false;
2377		}
2378
2379		if (video.type === 'youtube') {
2380			path = "http://img.youtube.com/vi/" + video.id + "/hqdefault.jpg";
2381			create(path);
2382		} else if (video.type === 'vimeo') {
2383			$.ajax({
2384				type: 'GET',
2385				url: 'http://vimeo.com/api/v2/video/' + video.id + '.json',
2386				jsonp: 'callback',
2387				dataType: 'jsonp',
2388				success: function(data) {
2389					path = data[0].thumbnail_large;
2390					create(path);
2391				}
2392			});
2393		}
2394	};
2395
2396	/**
2397	 * Stops the current video.
2398	 * @public
2399	 */
2400	Video.prototype.stop = function() {
2401		this._core.trigger('stop', null, 'video');
2402		this._playing.find('.owl-video-frame').remove();
2403		this._playing.removeClass('owl-video-playing');
2404		this._playing = null;
2405	};
2406
2407	/**
2408	 * Starts the current video.
2409	 * @public
2410	 * @param {Event} ev - The event arguments.
2411	 */
2412	Video.prototype.play = function(ev) {
2413		this._core.trigger('play', null, 'video');
2414
2415		if (this._playing) {
2416			this.stop();
2417		}
2418
2419		var target = $(ev.target || ev.srcElement),
2420			item = target.closest('.' + this._core.settings.itemClass),
2421			video = this._videos[item.attr('data-video')],
2422			width = video.width || '100%',
2423			height = video.height || this._core.$stage.height(),
2424			html, wrap;
2425
2426		if (video.type === 'youtube') {
2427			html = '<iframe width="' + width + '" height="' + height + '" src="http://www.youtube.com/embed/'
2428				+ video.id + '?autoplay=1&v=' + video.id + '" frameborder="0" allowfullscreen></iframe>';
2429		} else if (video.type === 'vimeo') {
2430			html = '<iframe src="http://player.vimeo.com/video/' + video.id + '?autoplay=1" width="' + width
2431				+ '" height="' + height
2432				+ '" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
2433		}
2434
2435		item.addClass('owl-video-playing');
2436		this._playing = item;
2437
2438		wrap = $('<div style="height:' + height + 'px; width:' + width + 'px" class="owl-video-frame">'
2439			+ html + '</div>');
2440		target.after(wrap);
2441	};
2442
2443	/**
2444	 * Checks whether an video is currently in full screen mode or not.
2445	 * @todo Bad style because looks like a readonly method but changes members.
2446	 * @protected
2447	 * @returns {Boolean}
2448	 */
2449	Video.prototype.isInFullScreen = function() {
2450
2451		// if Vimeo Fullscreen mode
2452		var element = document.fullscreenElement || document.mozFullScreenElement
2453			|| document.webkitFullscreenElement;
2454
2455		if (element && $(element).parent().hasClass('owl-video-frame')) {
2456			this._core.speed(0);
2457			this._fullscreen = true;
2458		}
2459
2460		if (element && this._fullscreen && this._playing) {
2461			return false;
2462		}
2463
2464		// comming back from fullscreen
2465		if (this._fullscreen) {
2466			this._fullscreen = false;
2467			return false;
2468		}
2469
2470		// check full screen mode and window orientation
2471		if (this._playing) {
2472			if (this._core.state.orientation !== window.orientation) {
2473				this._core.state.orientation = window.orientation;
2474				return false;
2475			}
2476		}
2477
2478		return true;
2479	};
2480
2481	/**
2482	 * Destroys the plugin.
2483	 */
2484	Video.prototype.destroy = function() {
2485		var handler, property;
2486
2487		this._core.$element.off('click.owl.video');
2488
2489		for (handler in this._handlers) {
2490			this._core.$element.off(handler, this._handlers[handler]);
2491		}
2492		for (property in Object.getOwnPropertyNames(this)) {
2493			typeof this[property] != 'function' && (this[property] = null);
2494		}
2495	};
2496
2497	$.fn.owlCarousel.Constructor.Plugins.Video = Video;
2498
2499})(window.Zepto || window.jQuery, window, document);
2500
2501/**
2502 * Animate Plugin
2503 * @version 2.0.0
2504 * @author Bartosz Wojciechowski
2505 * @license The MIT License (MIT)
2506 */
2507;(function($, window, document, undefined) {
2508
2509	/**
2510	 * Creates the animate plugin.
2511	 * @class The Navigation Plugin
2512	 * @param {Owl} scope - The Owl Carousel
2513	 */
2514	var Animate = function(scope) {
2515		this.core = scope;
2516		this.core.options = $.extend({}, Animate.Defaults, this.core.options);
2517		this.swapping = true;
2518		this.previous = undefined;
2519		this.next = undefined;
2520
2521		this.handlers = {
2522			'change.owl.carousel': $.proxy(function(e) {
2523				if (e.property.name == 'position') {
2524					this.previous = this.core.current();
2525					this.next = e.property.value;
2526				}
2527			}, this),
2528			'drag.owl.carousel dragged.owl.carousel translated.owl.carousel': $.proxy(function(e) {
2529				this.swapping = e.type == 'translated';
2530			}, this),
2531			'translate.owl.carousel': $.proxy(function(e) {
2532				if (this.swapping && (this.core.options.animateOut || this.core.options.animateIn)) {
2533					this.swap();
2534				}
2535			}, this)
2536		};
2537
2538		this.core.$element.on(this.handlers);
2539	};
2540
2541	/**
2542	 * Default options.
2543	 * @public
2544	 */
2545	Animate.Defaults = {
2546		animateOut: false,
2547		animateIn: false
2548	};
2549
2550	/**
2551	 * Toggles the animation classes whenever an translations starts.
2552	 * @protected
2553	 * @returns {Boolean|undefined}
2554	 */
2555	Animate.prototype.swap = function() {
2556
2557		if (this.core.settings.items !== 1 || !this.core.support3d) {
2558			return;
2559		}
2560
2561		this.core.speed(0);
2562
2563		var left,
2564			clear = $.proxy(this.clear, this),
2565			previous = this.core.$stage.children().eq(this.previous),
2566			next = this.core.$stage.children().eq(this.next),
2567			incoming = this.core.settings.animateIn,
2568			outgoing = this.core.settings.animateOut;
2569
2570		if (this.core.current() === this.previous) {
2571			return;
2572		}
2573
2574		if (outgoing) {
2575			left = this.core.coordinates(this.previous) - this.core.coordinates(this.next);
2576			previous.css( { 'left': left + 'px' } )
2577				.addClass('animated owl-animated-out')
2578				.addClass(outgoing)
2579				.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', clear);
2580		}
2581
2582		if (incoming) {
2583			next.addClass('animated owl-animated-in')
2584				.addClass(incoming)
2585				.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', clear);
2586		}
2587	};
2588
2589	Animate.prototype.clear = function(e) {
2590		$(e.target).css( { 'left': '' } )
2591			.removeClass('animated owl-animated-out owl-animated-in')
2592			.removeClass(this.core.settings.animateIn)
2593			.removeClass(this.core.settings.animateOut);
2594		this.core.transitionEnd();
2595	}
2596
2597	/**
2598	 * Destroys the plugin.
2599	 * @public
2600	 */
2601	Animate.prototype.destroy = function() {
2602		var handler, property;
2603
2604		for (handler in this.handlers) {
2605			this.core.$element.off(handler, this.handlers[handler]);
2606		}
2607		for (property in Object.getOwnPropertyNames(this)) {
2608			typeof this[property] != 'function' && (this[property] = null);
2609		}
2610	};
2611
2612	$.fn.owlCarousel.Constructor.Plugins.Animate = Animate;
2613
2614})(window.Zepto || window.jQuery, window, document);
2615
2616/**
2617 * Autoplay Plugin
2618 * @version 2.0.0
2619 * @author Bartosz Wojciechowski
2620 * @license The MIT License (MIT)
2621 */
2622;(function($, window, document, undefined) {
2623
2624	/**
2625	 * Creates the autoplay plugin.
2626	 * @class The Autoplay Plugin
2627	 * @param {Owl} scope - The Owl Carousel
2628	 */
2629	var Autoplay = function(scope) {
2630		this.core = scope;
2631		this.core.options = $.extend({}, Autoplay.Defaults, this.core.options);
2632
2633		this.handlers = {
2634			'translated.owl.carousel refreshed.owl.carousel': $.proxy(function() {
2635				this.autoplay();
2636			}, this),
2637			'play.owl.autoplay': $.proxy(function(e, t, s) {
2638				this.play(t, s);
2639			}, this),
2640			'stop.owl.autoplay': $.proxy(function() {
2641				this.stop();
2642			}, this),
2643			'mouseover.owl.autoplay': $.proxy(function() {
2644				if (this.core.settings.autoplayHoverPause) {
2645					this.pause();
2646				}
2647			}, this),
2648			'mouseleave.owl.autoplay': $.proxy(function() {
2649				if (this.core.settings.autoplayHoverPause) {
2650					this.autoplay();
2651				}
2652			}, this)
2653		};
2654
2655		this.core.$element.on(this.handlers);
2656	};
2657
2658	/**
2659	 * Default options.
2660	 * @public
2661	 */
2662	Autoplay.Defaults = {
2663		autoplay: false,
2664		autoplayTimeout: 5000,
2665		autoplayHoverPause: false,
2666		autoplaySpeed: false
2667	};
2668
2669	/**
2670	 * @protected
2671	 * @todo Must be documented.
2672	 */
2673	Autoplay.prototype.autoplay = function() {
2674		if (this.core.settings.autoplay && !this.core.state.videoPlay) {
2675			window.clearInterval(this.interval);
2676
2677			this.interval = window.setInterval($.proxy(function() {
2678				this.play();
2679			}, this), this.core.settings.autoplayTimeout);
2680		} else {
2681			window.clearInterval(this.interval);
2682		}
2683	};
2684
2685	/**
2686	 * Starts the autoplay.
2687	 * @public
2688	 * @param {Number} [timeout] - ...
2689	 * @param {Number} [speed] - ...
2690	 * @returns {Boolean|undefined} - ...
2691	 * @todo Must be documented.
2692	 */
2693	Autoplay.prototype.play = function(timeout, speed) {
2694		// if tab is inactive - doesnt work in <IE10
2695		if (document.hidden === true) {
2696			return;
2697		}
2698
2699		if (this.core.state.isTouch || this.core.state.isScrolling
2700			|| this.core.state.isSwiping || this.core.state.inMotion) {
2701			return;
2702		}
2703
2704		if (this.core.settings.autoplay === false) {
2705			window.clearInterval(this.interval);
2706			return;
2707		}
2708
2709		this.core.next(this.core.settings.autoplaySpeed);
2710	};
2711
2712	/**
2713	 * Stops the autoplay.
2714	 * @public
2715	 */
2716	Autoplay.prototype.stop = function() {
2717		window.clearInterval(this.interval);
2718	};
2719
2720	/**
2721	 * Pauses the autoplay.
2722	 * @public
2723	 */
2724	Autoplay.prototype.pause = function() {
2725		window.clearInterval(this.interval);
2726	};
2727
2728	/**
2729	 * Destroys the plugin.
2730	 */
2731	Autoplay.prototype.destroy = function() {
2732		var handler, property;
2733
2734		window.clearInterval(this.interval);
2735
2736		for (handler in this.handlers) {
2737			this.core.$element.off(handler, this.handlers[handler]);
2738		}
2739		for (property in Object.getOwnPropertyNames(this)) {
2740			typeof this[property] != 'function' && (this[property] = null);
2741		}
2742	};
2743
2744	$.fn.owlCarousel.Constructor.Plugins.autoplay = Autoplay;
2745
2746})(window.Zepto || window.jQuery, window, document);
2747
2748/**
2749 * Navigation Plugin
2750 * @version 2.0.0
2751 * @author Artus Kolanowski
2752 * @license The MIT License (MIT)
2753 */
2754;(function($, window, document, undefined) {
2755	'use strict';
2756
2757	/**
2758	 * Creates the navigation plugin.
2759	 * @class The Navigation Plugin
2760	 * @param {Owl} carousel - The Owl Carousel.
2761	 */
2762	var Navigation = function(carousel) {
2763		/**
2764		 * Reference to the core.
2765		 * @protected
2766		 * @type {Owl}
2767		 */
2768		this._core = carousel;
2769
2770		/**
2771		 * Indicates whether the plugin is initialized or not.
2772		 * @protected
2773		 * @type {Boolean}
2774		 */
2775		this._initialized = false;
2776
2777		/**
2778		 * The current paging indexes.
2779		 * @protected
2780		 * @type {Array}
2781		 */
2782		this._pages = [];
2783
2784		/**
2785		 * All DOM elements of the user interface.
2786		 * @protected
2787		 * @type {Object}
2788		 */
2789		this._controls = {};
2790
2791		/**
2792		 * Markup for an indicator.
2793		 * @protected
2794		 * @type {Array.<String>}
2795		 */
2796		this._templates = [];
2797
2798		/**
2799		 * The carousel element.
2800		 * @type {jQuery}
2801		 */
2802		this.$element = this._core.$element;
2803
2804		/**
2805		 * Overridden methods of the carousel.
2806		 * @protected
2807		 * @type {Object}
2808		 */
2809		this._overrides = {
2810			next: this._core.next,
2811			prev: this._core.prev,
2812			to: this._core.to
2813		};
2814
2815		/**
2816		 * All event handlers.
2817		 * @protected
2818		 * @type {Object}
2819		 */
2820		this._handlers = {
2821			'prepared.owl.carousel': $.proxy(function(e) {
2822				if (this._core.settings.dotsData) {
2823					this._templates.push($(e.content).find('[data-dot]').andSelf('[data-dot]').attr('data-dot'));
2824				}
2825			}, this),
2826			'add.owl.carousel': $.proxy(function(e) {
2827				if (this._core.settings.dotsData) {
2828					this._templates.splice(e.position, 0, $(e.content).find('[data-dot]').andSelf('[data-dot]').attr('data-dot'));
2829				}
2830			}, this),
2831			'remove.owl.carousel prepared.owl.carousel': $.proxy(function(e) {
2832				if (this._core.settings.dotsData) {
2833					this._templates.splice(e.position, 1);
2834				}
2835			}, this),
2836			'change.owl.carousel': $.proxy(function(e) {
2837				if (e.property.name == 'position') {
2838					if (!this._core.state.revert && !this._core.settings.loop && this._core.settings.navRewind) {
2839						var current = this._core.current(),
2840							maximum = this._core.maximum(),
2841							minimum = this._core.minimum();
2842						e.data = e.property.value > maximum
2843							? current >= maximum ? minimum : maximum
2844							: e.property.value < minimum ? maximum : e.property.value;
2845					}
2846				}
2847			}, this),
2848			'changed.owl.carousel': $.proxy(function(e) {
2849				if (e.property.name == 'position') {
2850					this.draw();
2851				}
2852			}, this),
2853			'refreshed.owl.carousel': $.proxy(function() {
2854				if (!this._initialized) {
2855					this.initialize();
2856					this._initialized = true;
2857				}
2858				this._core.trigger('refresh', null, 'navigation');
2859				this.update();
2860				this.draw();
2861				this._core.trigger('refreshed', null, 'navigation');
2862			}, this)
2863		};
2864
2865		// set default options
2866		this._core.options = $.extend({}, Navigation.Defaults, this._core.options);
2867
2868		// register event handlers
2869		this.$element.on(this._handlers);
2870	}
2871
2872	/**
2873	 * Default options.
2874	 * @public
2875	 * @todo Rename `slideBy` to `navBy`
2876	 */
2877	Navigation.Defaults = {
2878		nav: false,
2879		navRewind: true,
2880		navText: [ 'prev', 'next' ],
2881		navSpeed: false,
2882		navElement: 'div',
2883		navContainer: false,
2884		navContainerClass: 'owl-nav',
2885		navClass: [ 'owl-prev', 'owl-next' ],
2886		slideBy: 1,
2887		dotClass: 'owl-dot',
2888		dotsClass: 'owl-dots',
2889		dots: true,
2890		dotsEach: false,
2891		dotData: false,
2892		dotsSpeed: false,
2893		dotsContainer: false,
2894		controlsClass: 'owl-controls'
2895	}
2896
2897	/**
2898	 * Initializes the layout of the plugin and extends the carousel.
2899	 * @protected
2900	 */
2901	Navigation.prototype.initialize = function() {
2902		var $container, override,
2903			options = this._core.settings;
2904
2905		// create the indicator template
2906		if (!options.dotsData) {
2907			this._templates = [ $('<div>')
2908				.addClass(options.dotClass)
2909				.append($('<span>'))
2910				.prop('outerHTML') ];
2911		}
2912
2913		// create controls container if needed
2914		if (!options.navContainer || !options.dotsContainer) {
2915			this._controls.$container = $('<div>')
2916				.addClass(options.controlsClass)
2917				.appendTo(this.$element);
2918		}
2919
2920		// create DOM structure for absolute navigation
2921		this._controls.$indicators = options.dotsContainer ? $(options.dotsContainer)
2922			: $('<div>').hide().addClass(options.dotsClass).appendTo(this._controls.$container);
2923
2924		this._controls.$indicators.on('click', 'div', $.proxy(function(e) {
2925			var index = $(e.target).parent().is(this._controls.$indicators)
2926				? $(e.target).index() : $(e.target).parent().index();
2927
2928			e.preventDefault();
2929
2930			this.to(index, options.dotsSpeed);
2931		}, this));
2932
2933		// create DOM structure for relative navigation
2934		$container = options.navContainer ? $(options.navContainer)
2935			: $('<div>').addClass(options.navContainerClass).prependTo(this._controls.$container);
2936
2937		this._controls.$next = $('<' + options.navElement + '>');
2938		this._controls.$previous = this._controls.$next.clone();
2939
2940		this._controls.$previous
2941			.addClass(options.navClass[0])
2942			.html(options.navText[0])
2943			.hide()
2944			.prependTo($container)
2945			.on('click', $.proxy(function(e) {
2946				this.prev(options.navSpeed);
2947			}, this));
2948		this._controls.$next
2949			.addClass(options.navClass[1])
2950			.html(options.navText[1])
2951			.hide()
2952			.appendTo($container)
2953			.on('click', $.proxy(function(e) {
2954				this.next(options.navSpeed);
2955			}, this));
2956
2957		// override public methods of the carousel
2958		for (override in this._overrides) {
2959			this._core[override] = $.proxy(this[override], this);
2960		}
2961	}
2962
2963	/**
2964	 * Destroys the plugin.
2965	 * @protected
2966	 */
2967	Navigation.prototype.destroy = function() {
2968		var handler, control, property, override;
2969
2970		for (handler in this._handlers) {
2971			this.$element.off(handler, this._handlers[handler]);
2972		}
2973		for (control in this._controls) {
2974			this._controls[control].remove();
2975		}
2976		for (override in this.overides) {
2977			this._core[override] = this._overrides[override];
2978		}
2979		for (property in Object.getOwnPropertyNames(this)) {
2980			typeof this[property] != 'function' && (this[property] = null);
2981		}
2982	}
2983
2984	/**
2985	 * Updates the internal state.
2986	 * @protected
2987	 */
2988	Navigation.prototype.update = function() {
2989		var i, j, k,
2990			options = this._core.settings,
2991			lower = this._core.clones().length / 2,
2992			upper = lower + this._core.items().length,
2993			size = options.center || options.autoWidth || options.dotData
2994				? 1 : options.dotsEach || options.items;
2995
2996		if (options.slideBy !== 'page') {
2997			options.slideBy = Math.min(options.slideBy, options.items);
2998		}
2999
3000		if (options.dots || options.slideBy == 'page') {
3001			this._pages = [];
3002
3003			for (i = lower, j = 0, k = 0; i < upper; i++) {
3004				if (j >= size || j === 0) {
3005					this._pages.push({
3006						start: i - lower,
3007						end: i - lower + size - 1
3008					});
3009					j = 0, ++k;
3010				}
3011				j += this._core.mergers(this._core.relative(i));
3012			}
3013		}
3014	}
3015
3016	/**
3017	 * Draws the user interface.
3018	 * @todo The option `dotData` wont work.
3019	 * @protected
3020	 */
3021	Navigation.prototype.draw = function() {
3022		var difference, i, html = '',
3023			options = this._core.settings,
3024			$items = this._core.$stage.children(),
3025			index = this._core.relative(this._core.current());
3026
3027		if (options.nav && !options.loop && !options.navRewind) {
3028			this._controls.$previous.toggleClass('disabled', index <= 0);
3029			this._controls.$next.toggleClass('disabled', index >= this._core.maximum());
3030		}
3031
3032		this._controls.$previous.toggle(options.nav);
3033		this._controls.$next.toggle(options.nav);
3034
3035		if (options.dots) {
3036			difference = this._pages.length - this._controls.$indicators.children().length;
3037
3038			if (options.dotData && difference !== 0) {
3039				for (i = 0; i < this._controls.$indicators.children().length; i++) {
3040					html += this._templates[this._core.relative(i)];
3041				}
3042				this._controls.$indicators.html(html);
3043			} else if (difference > 0) {
3044				html = new Array(difference + 1).join(this._templates[0]);
3045				this._controls.$indicators.append(html);
3046			} else if (difference < 0) {
3047				this._controls.$indicators.children().slice(difference).remove();
3048			}
3049
3050			this._controls.$indicators.find('.active').removeClass('active');
3051			this._controls.$indicators.children().eq($.inArray(this.current(), this._pages)).addClass('active');
3052		}
3053
3054		this._controls.$indicators.toggle(options.dots);
3055	}
3056
3057	/**
3058	 * Extends event data.
3059	 * @protected
3060	 * @param {Event} event - The event object which gets thrown.
3061	 */
3062	Navigation.prototype.onTrigger = function(event) {
3063		var settings = this._core.settings;
3064
3065		event.page = {
3066			index: $.inArray(this.current(), this._pages),
3067			count: this._pages.length,
3068			size: settings && (settings.center || settings.autoWidth || settings.dotData
3069				? 1 : settings.dotsEach || settings.items)
3070		};
3071	}
3072
3073	/**
3074	 * Gets the current page position of the carousel.
3075	 * @protected
3076	 * @returns {Number}
3077	 */
3078	Navigation.prototype.current = function() {
3079		var index = this._core.relative(this._core.current());
3080		return $.grep(this._pages, function(o) {
3081			return o.start <= index && o.end >= index;
3082		}).pop();
3083	}
3084
3085	/**
3086	 * Gets the current succesor/predecessor position.
3087	 * @protected
3088	 * @returns {Number}
3089	 */
3090	Navigation.prototype.getPosition = function(successor) {
3091		var position, length,
3092			options = this._core.settings;
3093
3094		if (options.slideBy == 'page') {
3095			position = $.inArray(this.current(), this._pages);
3096			length = this._pages.length;
3097			successor ? ++position : --position;
3098			position = this._pages[((position % length) + length) % length].start;
3099		} else {
3100			position = this._core.relative(this._core.current());
3101			length = this._core.items().length;
3102			successor ? position += options.slideBy : position -= options.slideBy;
3103		}
3104		return position;
3105	}
3106
3107	/**
3108	 * Slides to the next item or page.
3109	 * @public
3110	 * @param {Number} [speed=false] - The time in milliseconds for the transition.
3111	 */
3112	Navigation.prototype.next = function(speed) {
3113		$.proxy(this._overrides.to, this._core)(this.getPosition(true), speed);
3114	}
3115
3116	/**
3117	 * Slides to the previous item or page.
3118	 * @public
3119	 * @param {Number} [speed=false] - The time in milliseconds for the transition.
3120	 */
3121	Navigation.prototype.prev = function(speed) {
3122		$.proxy(this._overrides.to, this._core)(this.getPosition(false), speed);
3123	}
3124
3125	/**
3126	 * Slides to the specified item or page.
3127	 * @public
3128	 * @param {Number} position - The position of the item or page.
3129	 * @param {Number} [speed] - The time in milliseconds for the transition.
3130	 * @param {Boolean} [standard=false] - Whether to use the standard behaviour or not.
3131	 */
3132	Navigation.prototype.to = function(position, speed, standard) {
3133		var length;
3134
3135		if (!standard) {
3136			length = this._pages.length;
3137			$.proxy(this._overrides.to, this._core)(this._pages[((position % length) + length) % length].start, speed);
3138		} else {
3139			$.proxy(this._overrides.to, this._core)(position, speed);
3140		}
3141	}
3142
3143	$.fn.owlCarousel.Constructor.Plugins.Navigation = Navigation;
3144
3145})(window.Zepto || window.jQuery, window, document);
3146
3147/**
3148 * Hash Plugin
3149 * @version 2.0.0
3150 * @author Artus Kolanowski
3151 * @license The MIT License (MIT)
3152 */
3153;(function($, window, document, undefined) {
3154	'use strict';
3155
3156	/**
3157	 * Creates the hash plugin.
3158	 * @class The Hash Plugin
3159	 * @param {Owl} carousel - The Owl Carousel
3160	 */
3161	var Hash = function(carousel) {
3162		/**
3163		 * Reference to the core.
3164		 * @protected
3165		 * @type {Owl}
3166		 */
3167		this._core = carousel;
3168
3169		/**
3170		 * Hash table for the hashes.
3171		 * @protected
3172		 * @type {Object}
3173		 */
3174		this._hashes = {};
3175
3176		/**
3177		 * The carousel element.
3178		 * @type {jQuery}
3179		 */
3180		this.$element = this._core.$element;
3181
3182		/**
3183		 * All event handlers.
3184		 * @protected
3185		 * @type {Object}
3186		 */
3187		this._handlers = {
3188			'initialized.owl.carousel': $.proxy(function() {
3189				if (this._core.settings.startPosition == 'URLHash') {
3190					$(window).trigger('hashchange.owl.navigation');
3191				}
3192			}, this),
3193			'prepared.owl.carousel': $.proxy(function(e) {
3194				var hash = $(e.content).find('[data-hash]').andSelf('[data-hash]').attr('data-hash');
3195				this._hashes[hash] = e.content;
3196			}, this)
3197		};
3198
3199		// set default options
3200		this._core.options = $.extend({}, Hash.Defaults, this._core.options);
3201
3202		// register the event handlers
3203		this.$element.on(this._handlers);
3204
3205		// register event listener for hash navigation
3206		$(window).on('hashchange.owl.navigation', $.proxy(function() {
3207			var hash = window.location.hash.substring(1),
3208				items = this._core.$stage.children(),
3209				position = this._hashes[hash] && items.index(this._hashes[hash]) || 0;
3210
3211			if (!hash) {
3212				return false;
3213			}
3214
3215			this._core.to(position, false, true);
3216		}, this));
3217	}
3218
3219	/**
3220	 * Default options.
3221	 * @public
3222	 */
3223	Hash.Defaults = {
3224		URLhashListener: false
3225	}
3226
3227	/**
3228	 * Destroys the plugin.
3229	 * @public
3230	 */
3231	Hash.prototype.destroy = function() {
3232		var handler, property;
3233
3234		$(window).off('hashchange.owl.navigation');
3235
3236		for (handler in this._handlers) {
3237			this._core.$element.off(handler, this._handlers[handler]);
3238		}
3239		for (property in Object.getOwnPropertyNames(this)) {
3240			typeof this[property] != 'function' && (this[property] = null);
3241		}
3242	}
3243
3244	$.fn.owlCarousel.Constructor.Plugins.Hash = Hash;
3245
3246})(window.Zepto || window.jQuery, window, document);
3247
3248/*! PhotoSwipe - v4.1.0 - 2015-07-23
3249* http://photoswipe.com
3250* Copyright (c) 2015 Dmitry Semenov; */
3251(function (root, factory) { 
3252	if (typeof define === 'function' && define.amd) {
3253		define(factory);
3254	} else if (typeof exports === 'object') {
3255		module.exports = factory();
3256	} else {
3257		root.PhotoSwipe = factory();
3258	}
3259})(this, function () {
3260
3261	'use strict';
3262	var PhotoSwipe = function(template, UiClass, items, options){
3263
3264/*>>framework-bridge*/
3265/**
3266 *
3267 * Set of generic functions used by gallery.
3268 * 
3269 * You're free to modify anything here as long as functionality is kept.
3270 * 
3271 */
3272var framework = {
3273	features: null,
3274	bind: function(target, type, listener, unbind) {
3275		var methodName = (unbind ? 'remove' : 'add') + 'EventListener';
3276		type = type.split(' ');
3277		for(var i = 0; i < type.length; i++) {
3278			if(type[i]) {
3279				target[methodName]( type[i], listener, false);
3280			}
3281		}
3282	},
3283	isArray: function(obj) {
3284		return (obj instanceof Array);
3285	},
3286	createEl: function(classes, tag) {
3287		var el = document.createElement(tag || 'div');
3288		if(classes) {
3289			el.className = classes;
3290		}
3291		return el;
3292	},
3293	getScrollY: function() {
3294		var yOffset = window.pageYOffset;
3295		return yOffset !== undefined ? yOffset : document.documentElement.scrollTop;
3296	},
3297	unbind: function(target, type, listener) {
3298		framework.bind(target,type,listener,true);
3299	},
3300	removeClass: function(el, className) {
3301		var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
3302		el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 
3303	},
3304	addClass: function(el, className) {
3305		if( !framework.hasClass(el,className) ) {
3306			el.className += (el.className ? ' ' : '') + className;
3307		}
3308	},
3309	hasClass: function(el, className) {
3310		return el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
3311	},
3312	getChildByClass: function(parentEl, childClassName) {
3313		var node = parentEl.firstChild;
3314		while(node) {
3315			if( framework.hasClass(node, childClassName) ) {
3316				return node;
3317			}
3318			node = node.nextSibling;
3319		}
3320	},
3321	arraySearch: function(array, value, key) {
3322		var i = array.length;
3323		while(i--) {
3324			if(array[i][key] === value) {
3325				return i;
3326			} 
3327		}
3328		return -1;
3329	},
3330	extend: function(o1, o2, preventOverwrite) {
3331		for (var prop in o2) {
3332			if (o2.hasOwnProperty(prop)) {
3333				if(preventOverwrite && o1.hasOwnProperty(prop)) {
3334					continue;
3335				}
3336				o1[prop] = o2[prop];
3337			}
3338		}
3339	},
3340	easing: {
3341		sine: {
3342			out: function(k) {
3343				return Math.sin(k * (Math.PI / 2));
3344			},
3345			inOut: function(k) {
3346				return - (Math.cos(Math.PI * k) - 1) / 2;
3347			}
3348		},
3349		cubic: {
3350			out: function(k) {
3351				return --k * k * k + 1;
3352			}
3353		}
3354		/*
3355			elastic: {
3356				out: function ( k ) {
3357
3358					var s, a = 0.1, p = 0.4;
3359					if ( k === 0 ) return 0;
3360					if ( k === 1 ) return 1;
3361					if ( !a || a < 1 ) { a = 1; s = p / 4; }
3362					else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
3363					return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
3364
3365				},
3366			},
3367			back: {
3368				out: function ( k ) {
3369					var s = 1.70158;
3370					return --k * k * ( ( s + 1 ) * k + s ) + 1;
3371				}
3372			}
3373		*/
3374	},
3375
3376	/**
3377	 * 
3378	 * @return {object}
3379	 * 
3380	 * {
3381	 *  raf : request animation frame function
3382	 *  caf : cancel animation frame function
3383	 *  transfrom : transform property key (with vendor), or null if not supported
3384	 *  oldIE : IE8 or below
3385	 * }
3386	 * 
3387	 */
3388	detectFeatures: function() {
3389		if(framework.features) {
3390			return framework.features;
3391		}
3392		var helperEl = framework.createEl(),
3393			helperStyle = helperEl.style,
3394			vendor = '',
3395			features = {};
3396
3397		// IE8 and below
3398		features.oldIE = document.all && !document.addEventListener;
3399
3400		features.touch = 'ontouchstart' in window;
3401
3402		if(window.requestAnimationFrame) {
3403			features.raf = window.requestAnimationFrame;
3404			features.caf = window.cancelAnimationFrame;
3405		}
3406
3407		features.pointerEvent = navigator.pointerEnabled || navigator.msPointerEnabled;
3408
3409		// fix false-positive detection of old Android in new IE
3410		// (IE11 ua string contains "Android 4.0")
3411		
3412		if(!features.pointerEvent) { 
3413
3414			var ua = navigator.userAgent;
3415
3416			// Detect if device is iPhone or iPod and if it's older than iOS 8
3417			// http://stackoverflow.com/a/14223920
3418			// 
3419			// This detection is made because of buggy top/bottom toolbars
3420			// that don't trigger window.resize event.
3421			// For more info refer to _isFixedPosition variable in core.js
3422
3423			if (/iP(hone|od)/.test(navigator.platform)) {
3424				var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
3425				if(v && v.length > 0) {
3426					v = parseInt(v[1], 10);
3427					if(v >= 1 && v < 8 ) {
3428						features.isOldIOSPhone = true;
3429					}
3430				}
3431			}
3432
3433			// Detect old Android (before KitKat)
3434			// due to bugs related to position:fixed
3435			// http://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript
3436			
3437			var match = ua.match(/Android\s([0-9\.]*)/);
3438			var androidversion =  match ? match[1] : 0;
3439			androidversion = parseFloat(androidversion);
3440			if(androidversion >= 1 ) {
3441				if(androidversion < 4.4) {
3442					features.isOldAndroid = true; // for fixed position bug & performance
3443				}
3444				features.androidVersion = androidversion; // for touchend bug
3445			}	
3446			features.isMobileOpera = /opera mini|opera mobi/i.test(ua);
3447
3448			// p.s. yes, yes, UA sniffing is bad, propose your solution for above bugs.
3449		}
3450		
3451		var styleChecks = ['transform', 'perspective', 'animationName'],
3452			vendors = ['', 'webkit','Moz','ms','O'],
3453			styleCheckItem,
3454			styleName;
3455
3456		for(var i = 0; i < 4; i++) {
3457			vendor = vendors[i];
3458
3459			for(var a = 0; a < 3; a++) {
3460				styleCheckItem = styleChecks[a];
3461
3462				// uppercase first letter of property name, if vendor is present
3463				styleName = vendor + (vendor ? 
3464										styleCheckItem.charAt(0).toUpperCase() + styleCheckItem.slice(1) : 
3465										styleCheckItem);
3466			
3467				if(!features[styleCheckItem] && styleName in helperStyle ) {
3468					features[styleCheckItem] = styleName;
3469				}
3470			}
3471
3472			if(vendor && !features.raf) {
3473				vendor = vendor.toLowerCase();
3474				features.raf = window[vendor+'RequestAnimationFrame'];
3475				if(features.raf) {
3476					features.caf = window[vendor+'CancelAnimationFrame'] || 
3477									window[vendor+'CancelRequestAnimationFrame'];
3478				}
3479			}
3480		}
3481			
3482		if(!features.raf) {
3483			var lastTime = 0;
3484			features.raf = function(fn) {
3485				var currTime = new Date().getTime();
3486				var timeToCall = Math.max(0, 16 - (currTime - lastTime));
3487				var id = window.setTimeout(function() { fn(currTime + timeToCall); }, timeToCall);
3488				lastTime = currTime + timeToCall;
3489				return id;
3490			};
3491			features.caf = function(id) { clearTimeout(id); };
3492		}
3493
3494		// Detect SVG support
3495		features.svg = !!document.createElementNS && 
3496						!!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect;
3497
3498		framework.features = features;
3499
3500		return features;
3501	}
3502};
3503
3504framework.detectFeatures();
3505
3506// Override addEventListener for old versions of IE
3507if(framework.features.oldIE) {
3508
3509	framework.bind = function(target, type, listener, unbind) {
3510		
3511		type = type.split(' ');
3512
3513		var methodName = (unbind ? 'detach' : 'attach') + 'Event',
3514			evName,
3515			_handleEv = function() {
3516				listener.handleEvent.call(listener);
3517			};
3518
3519		for(var i = 0; i < type.length; i++) {
3520			evName = type[i];
3521			if(evName) {
3522
3523				if(typeof listener === 'object' && listener.handleEvent) {
3524					if(!unbind) {
3525						listener['oldIE' + evName] = _handleEv;
3526					} else {
3527						if(!listener['oldIE' + evName]) {
3528							return false;
3529						}
3530					}
3531
3532					target[methodName]( 'on' + evName, listener['oldIE' + evName]);
3533				} else {
3534					target[methodName]( 'on' + evName, listener);
3535				}
3536
3537			}
3538		}
3539	};
3540	
3541}
3542
3543/*>>framework-bridge*/
3544
3545/*>>core*/
3546//function(template, UiClass, items, options)
3547
3548var self = this;
3549
3550/**
3551 * Static vars, don't change unless you know what you're doing.
3552 */
3553var DOUBLE_TAP_RADIUS = 25, 
3554	NUM_HOLDERS = 3;
3555
3556/**
3557 * Options
3558 */
3559var _options = {
3560	allowPanToNext:true,
3561	spacing: 0.12,
3562	bgOpacity: 1,
3563	mouseUsed: false,
3564	loop: true,
3565	pinchToClose: true,
3566	closeOnScroll: true,
3567	closeOnVerticalDrag: true,
3568	verticalDragRange: 0.75,
3569	hideAnimationDuration: 333,
3570	showAnimationDuration: 333,
3571	showHideOpacity: false,
3572	focus: true,
3573	escKey: true,
3574	arrowKeys: true,
3575	mainScrollEndFriction: 0.35,
3576	panEndFriction: 0.35,
3577	isClickableElement: function(el) {
3578        return el.tagName === 'A';
3579    },
3580    getDoubleTapZoom: function(isMouseClick, item) {
3581    	if(isMouseClick) {
3582    		return 1;
3583    	} else {
3584    		return item.initialZoomLevel < 0.7 ? 1 : 1.33;
3585    	}
3586    },
3587    maxSpreadZoom: 1.33,
3588	modal: true,
3589
3590	// not fully implemented yet
3591	scaleMode: 'fit' // TODO
3592};
3593framework.extend(_options, options);
3594
3595
3596/**
3597 * Private helper variables & functions
3598 */
3599
3600var _getEmptyPoint = function() { 
3601		return {x:0,y:0}; 
3602	};
3603
3604var _isOpen,
3605	_isDestroying,
3606	_closedByScroll,
3607	_currentItemIndex,
3608	_containerStyle,
3609	_containerShiftIndex,
3610	_currPanDist = _getEmptyPoint(),
3611	_startPanOffset = _getEmptyPoint(),
3612	_panOffset = _getEmptyPoint(),
3613	_upMoveEvents, // drag move, drag end & drag cancel events array
3614	_downEvents, // drag start events array
3615	_globalEventHandlers,
3616	_viewportSize = {},
3617	_currZoomLevel,
3618	_startZoomLevel,
3619	_translatePrefix,
3620	_translateSufix,
3621	_updateSizeInterval,
3622	_itemsNeedUpdate,
3623	_currPositionIndex = 0,
3624	_offset = {},
3625	_slideSize = _getEmptyPoint(), // size of slide area, including spacing
3626	_itemHolders,
3627	_prevItemIndex,
3628	_indexDiff = 0, // difference of indexes since last content update
3629	_dragStartEvent,
3630	_dragMoveEvent,
3631	_dragEndEvent,
3632	_dragCancelEvent,
3633	_transformKey,
3634	_pointerEventEnabled,
3635	_isFixedPosition = true,
3636	_likelyTouchDevice,
3637	_modules = [],
3638	_requestAF,
3639	_cancelAF,
3640	_initalClassName,
3641	_initalWindowScrollY,
3642	_oldIE,
3643	_currentWindowScrollY,
3644	_features,
3645	_windowVisibleSize = {},
3646	_renderMaxResolution = false,
3647
3648	// Registers PhotoSWipe module (History, Controller ...)
3649	_registerModule = function(name, module) {
3650		framework.extend(self, module.publicMethods);
3651		_modules.push(name);
3652	},
3653
3654	_getLoopedId = function(index) {
3655		var numSlides = _getNumItems();
3656		if(index > numSlides - 1) {
3657			return index - numSlides;
3658		} else  if(index < 0) {
3659			return numSlides + index;
3660		}
3661		return index;
3662	},
3663	
3664	// Micro bind/trigger
3665	_listeners = {},
3666	_listen = function(name, fn) {
3667		if(!_listeners[name]) {
3668			_listeners[name] = [];
3669		}
3670		return _listeners[name].push(fn);
3671	},
3672	_shout = function(name) {
3673		var listeners = _listeners[name];
3674
3675		if(listeners) {
3676			var args = Array.prototype.slice.call(arguments);
3677			args.shift();
3678
3679			for(var i = 0; i < listeners.length; i++) {
3680				listeners[i].apply(self, args);
3681			}
3682		}
3683	},
3684
3685	_getCurrentTime = function() {
3686		return new Date().getTime();
3687	},
3688	_applyBgOpacity = function(opacity) {
3689		_bgOpacity = opacity;
3690		self.bg.style.opacity = opacity * _options.bgOpacity;
3691	},
3692
3693	_applyZoomTransform = function(styleObj,x,y,zoom,item) {
3694		if(!_renderMaxResolution || (item && item !== self.currItem) ) {
3695			zoom = zoom / (item ? item.fitRatio : self.currItem.fitRatio);	
3696		}
3697			
3698		styleObj[_transformKey] = _translatePrefix + x + 'px, ' + y + 'px' + _translateSufix + ' scale(' + zoom + ')';
3699	},
3700	_applyCurrentZoomPan = function( allowRenderResolution ) {
3701		if(_currZoomElementStyle) {
3702
3703			if(allowRenderResolution) {
3704				if(_currZoomLevel > self.currItem.fitRatio) {
3705					if(!_renderMaxResolution) {
3706						_setImageSize(self.currItem, false, true);
3707						_renderMaxResolution = true;
3708					}
3709				} else {
3710					if(_renderMaxResolution) {
3711						_setImageSize(self.currItem);
3712						_renderMaxResolution = false;
3713					}
3714				}
3715			}
3716			
3717
3718			_applyZoomTransform(_currZoomElementStyle, _panOffset.x, _panOffset.y, _currZoomLevel);
3719		}
3720	},
3721	_applyZoomPanToItem = function(item) {
3722		if(item.container) {
3723
3724			_applyZoomTransform(item.container.style, 
3725								item.initialPosition.x, 
3726								item.initialPosition.y, 
3727								item.initialZoomLevel,
3728								item);
3729		}
3730	},
3731	_setTranslateX = function(x, elStyle) {
3732		elStyle[_transformKey] = _translatePrefix + x + 'px, 0px' + _translateSufix;
3733	},
3734	_moveMainScroll = function(x, dragging) {
3735
3736		if(!_options.loop && dragging) {
3737			var newSlideIndexOffset = _currentItemIndex + (_slideSize.x * _currPositionIndex - x) / _slideSize.x,
3738				delta = Math.round(x - _mainScrollPos.x);
3739
3740			if( (newSlideIndexOffset < 0 && delta > 0) || 
3741				(newSlideIndexOffset >= _getNumItems() - 1 && delta < 0) ) {
3742				x = _mainScrollPos.x + delta * _options.mainScrollEndFriction;
3743			} 
3744		}
3745		
3746		_mainScrollPos.x = x;
3747		_setTranslateX(x, _containerStyle);
3748	},
3749	_calculatePanOffset = function(axis, zoomLevel) {
3750		var m = _midZoomPoint[axis] - _offset[axis];
3751		return _startPanOffset[axis] + _currPanDist[axis] + m - m * ( zoomLevel / _startZoomLevel );
3752	},
3753	
3754	_equalizePoints = function(p1, p2) {
3755		p1.x = p2.x;
3756		p1.y = p2.y;
3757		if(p2.id) {
3758			p1.id = p2.id;
3759		}
3760	},
3761	_roundPoint = function(p) {
3762		p.x = Math.round(p.x);
3763		p.y = Math.round(p.y);
3764	},
3765
3766	_mouseMoveTimeout = null,
3767	_onFirstMouseMove = function() {
3768		// Wait until mouse move event is fired at least twice during 100ms
3769		// We do this, because some mobile browsers trigger it on touchstart
3770		if(_mouseMoveTimeout ) { 
3771			framework.unbind(document, 'mousemove', _onFirstMouseMove);
3772			framework.addClass(template, 'pswp--has_mouse');
3773			_options.mouseUsed = true;
3774			_shout('mouseUsed');
3775		}
3776		_mouseMoveTimeout = setTimeout(function() {
3777			_mouseMoveTimeout = null;
3778		}, 100);
3779	},
3780
3781	_bindEvents = function() {
3782		framework.bind(document, 'keydown', self);
3783
3784		if(_features.transform) {
3785			// don't bind click event in browsers that don't support transform (mostly IE8)
3786			framework.bind(self.scrollWrap, 'click', self);
3787		}
3788		
3789
3790		if(!_options.mouseUsed) {
3791			framework.bind(document, 'mousemove', _onFirstMouseMove);
3792		}
3793
3794		framework.bind(window, 'resize scroll', self);
3795
3796		_shout('bindEvents');
3797	},
3798
3799	_unbindEvents = function() {
3800		framework.unbind(window, 'resize', self);
3801		framework.unbind(window, 'scroll', _globalEventHandlers.scroll);
3802		framework.unbind(document, 'keydown', self);
3803		framework.unbind(document, 'mousemove', _onFirstMouseMove);
3804
3805		if(_features.transform) {
3806			framework.unbind(self.scrollWrap, 'click', self);
3807		}
3808
3809		if(_isDragging) {
3810			framework.unbind(window, _upMoveEvents, self);
3811		}
3812
3813		_shout('unbindEvents');
3814	},
3815	
3816	_calculatePanBounds = function(zoomLevel, update) {
3817		var bounds = _calculateItemSize( self.currItem, _viewportSize, zoomLevel );
3818		if(update) {
3819			_currPanBounds = bounds;
3820		}
3821		return bounds;
3822	},
3823	
3824	_getMinZoomLevel = function(item) {
3825		if(!item) {
3826			item = self.currItem;
3827		}
3828		return item.initialZoomLevel;
3829	},
3830	_getMaxZoomLevel = function(item) {
3831		if(!item) {
3832			item = self.currItem;
3833		}
3834		return item.w > 0 ? _options.maxSpreadZoom : 1;
3835	},
3836
3837	// Return true if offset is out of the bounds
3838	_modifyDestPanOffset = function(axis, destPanBounds, destPanOffset, destZoomLevel) {
3839		if(destZoomLevel === self.currItem.initialZoomLevel) {
3840			destPanOffset[axis] = self.currItem.initialPosition[axis];
3841			return true;
3842		} else {
3843			destPanOffset[axis] = _calculatePanOffset(axis, destZoomLevel); 
3844
3845			if(destPanOffset[axis] > destPanBounds.min[axis]) {
3846				destPanOffset[axis] = destPanBounds.min[axis];
3847				return true;
3848			} else if(destPanOffset[axis] < destPanBounds.max[axis] ) {
3849				destPanOffset[axis] = destPanBounds.max[axis];
3850				return true;
3851			}
3852		}
3853		return false;
3854	},
3855
3856	_setupTransforms = function() {
3857
3858		if(_transformKey) {
3859			// setup 3d transforms
3860			var allow3dTransform = _features.perspective && !_likelyTouchDevice;
3861			_translatePrefix = 'translate' + (allow3dTransform ? '3d(' : '(');
3862			_translateSufix = _features.perspective ? ', 0px)' : ')';	
3863			return;
3864		}
3865
3866		// Override zoom/pan/move functions in case old browser is used (most likely IE)
3867		// (so they use left/top/width/height, instead of CSS transform)
3868	
3869		_transformKey = 'left';
3870		framework.addClass(template, 'pswp--ie');
3871
3872		_setTranslateX = function(x, elStyle) {
3873			elStyle.left = x + 'px';
3874		};
3875		_applyZoomPanToItem = function(item) {
3876
3877			var zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
3878				s = item.container.style,
3879				w = zoomRatio * item.w,
3880				h = zoomRatio * item.h;
3881
3882			s.width = w + 'px';
3883			s.height = h + 'px';
3884			s.left = item.initialPosition.x + 'px';
3885			s.top = item.initialPosition.y + 'px';
3886
3887		};
3888		_applyCurrentZoomPan = function() {
3889			if(_currZoomElementStyle) {
3890
3891				var s = _currZoomElementStyle,
3892					item = self.currItem,
3893					zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
3894					w = zoomRatio * item.w,
3895					h = zoomRatio * item.h;
3896
3897				s.width = w + 'px';
3898				s.height = h + 'px';
3899
3900
3901				s.left = _panOffset.x + 'px';
3902				s.top = _panOffset.y + 'px';
3903			}
3904			
3905		};
3906	},
3907
3908	_onKeyDown = function(e) {
3909		var keydownAction = '';
3910		if(_options.escKey && e.keyCode === 27) { 
3911			keydownAction = 'close';
3912		} else if(_options.arrowKeys) {
3913			if(e.keyCode === 37) {
3914				keydownAction = 'prev';
3915			} else if(e.keyCode === 39) { 
3916				keydownAction = 'next';
3917			}
3918		}
3919
3920		if(keydownAction) {
3921			// don't do anything if special key pressed to prevent from overriding default browser actions
3922			// e.g. in Chrome on Mac cmd+arrow-left returns to previous page
3923			if( !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey ) {
3924				if(e.preventDefault) {
3925					e.preventDefault();
3926				} else {
3927					e.returnValue = false;
3928				} 
3929				self[keydownAction]();
3930			}
3931		}
3932	},
3933
3934	_onGlobalClick = function(e) {
3935		if(!e) {
3936			return;
3937		}
3938
3939		// don't allow click event to pass through when triggering after drag or some other gesture
3940		if(_moved || _zoomStarted || _mainScrollAnimating || _verticalDragInitiated) {
3941			e.preventDefault();
3942			e.stopPropagation();
3943		}
3944	},
3945
3946	_updatePageScrollOffset = function() {
3947		self.setScrollOffset(0, framework.getScrollY());		
3948	};
3949	
3950
3951
3952	
3953
3954
3955
3956// Micro animation engine
3957var _animations = {},
3958	_numAnimations = 0,
3959	_stopAnimation = function(name) {
3960		if(_animations[name]) {
3961			if(_animations[name].raf) {
3962				_cancelAF( _animations[name].raf );
3963			}
3964			_numAnimations--;
3965			delete _animations[name];
3966		}
3967	},
3968	_registerStartAnimation = function(name) {
3969		if(_animations[name]) {
3970			_stopAnimation(name);
3971		}
3972		if(!_animations[name]) {
3973			_numAnimations++;
3974			_animations[name] = {};
3975		}
3976	},
3977	_stopAllAnimations = function() {
3978		for (var prop in _animations) {
3979
3980			if( _animations.hasOwnProperty( prop ) ) {
3981				_stopAnimation(prop);
3982			} 
3983			
3984		}
3985	},
3986	_animateProp = function(name, b, endProp, d, easingFn, onUpdate, onComplete) {
3987		var startAnimTime = _getCurrentTime(), t;
3988		_registerStartAnimation(name);
3989
3990		var animloop = function(){
3991			if ( _animations[name] ) {
3992				
3993				t = _getCurrentTime() - startAnimTime; // time diff
3994				//b - beginning (start prop)
3995				//d - anim duration
3996
3997				if ( t >= d ) {
3998					_stopAnimation(name);
3999					onUpdate(endProp);
4000					if(onComplete) {
4001						onComplete();
4002					}
4003					return;
4004				}
4005				onUpdate( (endProp - b) * easingFn(t/d) + b );
4006
4007				_animations[name].raf = _requestAF(animloop);
4008			}
4009		};
4010		animloop();
4011	};
4012	
4013
4014
4015var publicMethods = {
4016
4017	// make a few local variables and functions public
4018	shout: _shout,
4019	listen: _listen,
4020	viewportSize: _viewportSize,
4021	options: _options,
4022
4023	isMainScrollAnimating: function() {
4024		return _mainScrollAnimating;
4025	},
4026	getZoomLevel: function() {
4027		return _currZoomLevel;
4028	},
4029	getCurrentIndex: function() {
4030		return _currentItemIndex;
4031	},
4032	isDragging: function() {
4033		return _isDragging;
4034	},	
4035	isZooming: function() {
4036		return _isZooming;
4037	},
4038	setScrollOffset: function(x,y) {
4039		_offset.x = x;
4040		_currentWindowScrollY = _offset.y = y;
4041		_shout('updateScrollOffset', _offset);
4042	},
4043	applyZoomPan: function(zoomLevel,panX,panY,allowRenderResolution) {
4044		_panOffset.x = panX;
4045		_panOffset.y = panY;
4046		_currZoomLevel = zoomLevel;
4047		_applyCurrentZoomPan( allowRenderResolution );
4048	},
4049
4050	init: function() {
4051
4052		if(_isOpen || _isDestroying) {
4053			return;
4054		}
4055
4056		var i;
4057
4058		self.framework = framework; // basic functionality
4059		self.template = template; // root DOM element of PhotoSwipe
4060		self.bg = framework.getChildByClass(template, 'pswp__bg');
4061
4062		_initalClassName = template.className;
4063		_isOpen = true;
4064				
4065		_features = framework.detectFeatures();
4066		_requestAF = _features.raf;
4067		_cancelAF = _features.caf;
4068		_transformKey = _features.transform;
4069		_oldIE = _features.oldIE;
4070		
4071		self.scrollWrap = framework.getChildByClass(template, 'pswp__scroll-wrap');
4072		self.container = framework.getChildByClass(self.scrollWrap, 'pswp__container');
4073
4074		_containerStyle = self.container.style; // for fast access
4075
4076		// Objects that hold slides (there are only 3 in DOM)
4077		self.itemHolders = _itemHolders = [
4078			{el:self.container.children[0] , wrap:0, index: -1},
4079			{el:self.container.children[1] , wrap:0, index: -1},
4080			{el:self.container.children[2] , wrap:0, index: -1}
4081		];
4082
4083		// hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
4084		_itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'none';
4085
4086		_setupTransforms();
4087
4088		// Setup global events
4089		_globalEventHandlers = {
4090			resize: self.updateSize,
4091			scroll: _updatePageScrollOffset,
4092			keydown: _onKeyDown,
4093			click: _onGlobalClick
4094		};
4095
4096		// disable show/hide effects on old browsers that don't support CSS animations or transforms, 
4097		// old IOS, Android and Opera mobile. Blackberry seems to work fine, even older models.
4098		var oldPhone = _features.isOldIOSPhone || _features.isOldAndroid || _features.isMobileOpera;
4099		if(!_features.animationName || !_features.transform || oldPhone) {
4100			_options.showAnimationDuration = _options.hideAnimationDuration = 0;
4101		}
4102
4103		// init modules
4104		for(i = 0; i < _modules.length; i++) {
4105			self['init' + _modules[i]]();
4106		}
4107		
4108		// init
4109		if(UiClass) {
4110			var ui = self.ui = new UiClass(self, framework);
4111			ui.init();
4112		}
4113
4114		_shout('firstUpdate');
4115		_currentItemIndex = _currentItemIndex || _options.index || 0;
4116		// validate index
4117		if( isNaN(_currentItemIndex) || _currentItemIndex < 0 || _currentItemIndex >= _getNumItems() ) {
4118			_currentItemIndex = 0;
4119		}
4120		self.currItem = _getItemAt( _currentItemIndex );
4121
4122		
4123		if(_features.isOldIOSPhone || _features.isOldAndroid) {
4124			_isFixedPosition = false;
4125		}
4126		
4127		template.setAttribute('aria-hidden', 'false');
4128		if(_options.modal) {
4129			if(!_isFixedPosition) {
4130				template.style.position = 'absolute';
4131				template.style.top = framework.getScrollY() + 'px';
4132			} else {
4133				template.style.position = 'fixed';
4134			}
4135		}
4136
4137		if(_currentWindowScrollY === undefined) {
4138			_shout('initialLayout');
4139			_currentWindowScrollY = _initalWindowScrollY = framework.getScrollY();
4140		}
4141		
4142		// add classes to root element of PhotoSwipe
4143		var rootClasses = 'pswp--open ';
4144		if(_options.mainClass) {
4145			rootClasses += _options.mainClass + ' ';
4146		}
4147		if(_options.showHideOpacity) {
4148			rootClasses += 'pswp--animate_opacity ';
4149		}
4150		rootClasses += _likelyTouchDevice ? 'pswp--touch' : 'pswp--notouch';
4151		rootClasses += _features.animationName ? ' pswp--css_animation' : '';
4152		rootClasses += _features.svg ? ' pswp--svg' : '';
4153		framework.addClass(template, rootClasses);
4154
4155		self.updateSize();
4156
4157		// initial update
4158		_containerShiftIndex = -1;
4159		_indexDiff = null;
4160		for(i = 0; i < NUM_HOLDERS; i++) {
4161			_setTranslateX( (i+_containerShiftIndex) * _slideSize.x, _itemHolders[i].el.style);
4162		}
4163
4164		if(!_oldIE) {
4165			framework.bind(self.scrollWrap, _downEvents, self); // no dragging for old IE
4166		}	
4167
4168		_listen('initialZoomInEnd', function() {
4169			self.setContent(_itemHolders[0], _currentItemIndex-1);
4170			self.setContent(_itemHolders[2], _currentItemIndex+1);
4171
4172			_itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'block';
4173
4174			if(_options.focus) {
4175				// focus causes layout, 
4176				// which causes lag during the animation, 
4177				// that's why we delay it untill the initial zoom transition ends
4178				template.focus();
4179			}
4180			 
4181
4182			_bindEvents();
4183		});
4184
4185		// set content for center slide (first time)
4186		self.setContent(_itemHolders[1], _currentItemIndex);
4187		
4188		self.updateCurrItem();
4189
4190		_shout('afterInit');
4191
4192		if(!_isFixedPosition) {
4193
4194			// On all versions of iOS lower than 8.0, we check size of viewport every second.
4195			// 
4196			// This is done to detect when Safari top & bottom bars appear, 
4197			// as this action doesn't trigger any events (like resize). 
4198			// 
4199			// On iOS8 they fixed this.
4200			// 
4201			// 10 Nov 2014: iOS 7 usage ~40%. iOS 8 usage 56%.
4202			
4203			_updateSizeInterval = setInterval(function() {
4204				if(!_numAnimations && !_isDragging && !_isZooming && (_currZoomLevel === self.currItem.initialZoomLevel)  ) {
4205					self.updateSize();
4206				}
4207			}, 1000);
4208		}
4209
4210		framework.addClass(template, 'pswp--visible');
4211	},
4212
4213	// Close the gallery, then destroy it
4214	close: function() {
4215		if(!_isOpen) {
4216			return;
4217		}
4218
4219		_isOpen = false;
4220		_isDestroying = true;
4221		_shout('close');
4222		_unbindEvents();
4223
4224		_showOrHide(self.currItem, null, true, self.destroy);
4225	},
4226
4227	// destroys the gallery (unbinds events, cleans up intervals and timeouts to avoid memory leaks)
4228	destroy: function() {
4229		_shout('destroy');
4230
4231		if(_showOrHideTimeout) {
4232			clearTimeout(_showOrHideTimeout);
4233		}
4234		
4235		template.setAttribute('aria-hidden', 'true');
4236		template.className = _initalClassName;
4237
4238		if(_updateSizeInterval) {
4239			clearInterval(_updateSizeInterval);
4240		}
4241
4242		framework.unbind(self.scrollWrap, _downEvents, self);
4243
4244		// we unbind scroll event at the end, as closing animation may depend on it
4245		framework.unbind(window, 'scroll', self);
4246
4247		_stopDragUpdateLoop();
4248
4249		_stopAllAnimations();
4250
4251		_listeners = null;
4252	},
4253
4254	/**
4255	 * Pan image to position
4256	 * @param {Number} x     
4257	 * @param {Number} y     
4258	 * @param {Boolean} force Will ignore bounds if set to true.
4259	 */
4260	panTo: function(x,y,force) {
4261		if(!force) {
4262			if(x > _currPanBounds.min.x) {
4263				x = _currPanBounds.min.x;
4264			} else if(x < _currPanBounds.max.x) {
4265				x = _currPanBounds.max.x;
4266			}
4267
4268			if(y > _currPanBounds.min.y) {
4269				y = _currPanBounds.min.y;
4270			} else if(y < _currPanBounds.max.y) {
4271				y = _currPanBounds.max.y;
4272			}
4273		}
4274		
4275		_panOffset.x = x;
4276		_panOffset.y = y;
4277		_applyCurrentZoomPan();
4278	},
4279	
4280	handleEvent: function (e) {
4281		e = e || window.event;
4282		if(_globalEventHandlers[e.type]) {
4283			_globalEventHandlers[e.type](e);
4284		}
4285	},
4286
4287
4288	goTo: function(index) {
4289
4290		index = _getLoopedId(index);
4291
4292		var diff = index - _currentItemIndex;
4293		_indexDiff = diff;
4294
4295		_currentItemIndex = index;
4296		self.currItem = _getItemAt( _currentItemIndex );
4297		_currPositionIndex -= diff;
4298		
4299		_moveMainScroll(_slideSize.x * _currPositionIndex);
4300		
4301
4302		_stopAllAnimations();
4303		_mainScrollAnimating = false;
4304
4305		self.updateCurrItem();
4306	},
4307	next: function() {
4308		self.goTo( _currentItemIndex + 1);
4309	},
4310	prev: function() {
4311		self.goTo( _currentItemIndex - 1);
4312	},
4313
4314	// update current zoom/pan objects
4315	updateCurrZoomItem: function(emulateSetContent) {
4316		if(emulateSetContent) {
4317			_shout('beforeChange', 0);
4318		}
4319
4320		// itemHolder[1] is middle (current) item
4321		if(_itemHolders[1].el.children.length) {
4322			var zoomElement = _itemHolders[1].el.children[0];
4323			if( framework.hasClass(zoomElement, 'pswp__zoom-wrap') ) {
4324				_currZoomElementStyle = zoomElement.style;
4325			} else {
4326				_currZoomElementStyle = null;
4327			}
4328		} else {
4329			_currZoomElementStyle = null;
4330		}
4331		
4332		_currPanBounds = self.currItem.bounds;	
4333		_startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
4334
4335		_panOffset.x = _currPanBounds.center.x;
4336		_panOffset.y = _currPanBounds.center.y;
4337
4338		if(emulateSetContent) {
4339			_shout('afterChange');
4340		}
4341	},
4342
4343
4344	invalidateCurrItems: function() {
4345		_itemsNeedUpdate = true;
4346		for(var i = 0; i < NUM_HOLDERS; i++) {
4347			if( _itemHolders[i].item ) {
4348				_itemHolders[i].item.needsUpdate = true;
4349			}
4350		}
4351	},
4352
4353	updateCurrItem: function(beforeAnimation) {
4354
4355		if(_indexDiff === 0) {
4356			return;
4357		}
4358
4359		var diffAbs = Math.abs(_indexDiff),
4360			tempHolder;
4361
4362		if(beforeAnimation && diffAbs < 2) {
4363			return;
4364		}
4365
4366
4367		self.currItem = _getItemAt( _currentItemIndex );
4368		_renderMaxResolution = false;
4369		
4370		_shout('beforeChange', _indexDiff);
4371
4372		if(diffAbs >= NUM_HOLDERS) {
4373			_containerShiftIndex += _indexDiff + (_indexDiff > 0 ? -NUM_HOLDERS : NUM_HOLDERS);
4374			diffAbs = NUM_HOLDERS;
4375		}
4376		for(var i = 0; i < diffAbs; i++) {
4377			if(_indexDiff > 0) {
4378				tempHolder = _itemHolders.shift();
4379				_itemHolders[NUM_HOLDERS-1] = tempHolder; // move first to last
4380
4381				_containerShiftIndex++;
4382				_setTranslateX( (_containerShiftIndex+2) * _slideSize.x, tempHolder.el.style);
4383				self.setContent(tempHolder, _currentItemIndex - diffAbs + i + 1 + 1);
4384			} else {
4385				tempHolder = _itemHolders.pop();
4386				_itemHolders.unshift( tempHolder ); // move last to first
4387
4388				_containerShiftIndex--;
4389				_setTranslateX( _containerShiftIndex * _slideSize.x, tempHolder.el.style);
4390				self.setContent(tempHolder, _currentItemIndex + diffAbs - i - 1 - 1);
4391			}
4392			
4393		}
4394
4395		// reset zoom/pan on previous item
4396		if(_currZoomElementStyle && Math.abs(_indexDiff) === 1) {
4397
4398			var prevItem = _getItemAt(_prevItemIndex);
4399			if(prevItem.initialZoomLevel !== _currZoomLevel) {
4400				_calculateItemSize(prevItem , _viewportSize );
4401				_setImageSize(prevItem);
4402				_applyZoomPanToItem( prevItem ); 				
4403			}
4404
4405		}
4406
4407		// reset diff after update
4408		_indexDiff = 0;
4409
4410		self.updateCurrZoomItem();
4411
4412		_prevItemIndex = _currentItemIndex;
4413
4414		_shout('afterChange');
4415		
4416	},
4417
4418
4419
4420	updateSize: function(force) {
4421		
4422		if(!_isFixedPosition && _options.modal) {
4423			var windowScrollY = framework.getScrollY();
4424			if(_currentWindowScrollY !== windowScrollY) {
4425				template.style.top = windowScrollY + 'px';
4426				_currentWindowScrollY = windowScrollY;
4427			}
4428			if(!force && _windowVisibleSize.x === window.innerWidth && _windowVisibleSize.y === window.innerHeight) {
4429				return;
4430			}
4431			_windowVisibleSize.x = window.innerWidth;
4432			_windowVisibleSize.y = window.innerHeight;
4433
4434			//template.style.width = _windowVisibleSize.x + 'px';
4435			template.style.height = _windowVisibleSize.y + 'px';
4436		}
4437
4438
4439
4440		_viewportSize.x = self.scrollWrap.clientWidth;
4441		_viewportSize.y = self.scrollWrap.clientHeight;
4442
4443		_updatePageScrollOffset();
4444
4445		_slideSize.x = _viewportSize.x + Math.round(_viewportSize.x * _options.spacing);
4446		_slideSize.y = _viewportSize.y;
4447
4448		_moveMainScroll(_slideSize.x * _currPositionIndex);
4449
4450		_shout('beforeResize'); // even may be used for example to switch image sources
4451
4452
4453		// don't re-calculate size on inital size update
4454		if(_containerShiftIndex !== undefined) {
4455
4456			var holder,
4457				item,
4458				hIndex;
4459
4460			for(var i = 0; i < NUM_HOLDERS; i++) {
4461				holder = _itemHolders[i];
4462				_setTranslateX( (i+_containerShiftIndex) * _slideSize.x, holder.el.style);
4463
4464				hIndex = _currentItemIndex+i-1;
4465
4466				if(_options.loop && _getNumItems() > 2) {
4467					hIndex = _getLoopedId(hIndex);
4468				}
4469
4470				// update zoom level on items and refresh source (if needsUpdate)
4471				item = _getItemAt( hIndex );
4472
4473				// re-render gallery item if `needsUpdate`,
4474				// or doesn't have `bounds` (entirely new slide object)
4475				if( item && (_itemsNeedUpdate || item.needsUpdate || !item.bounds) ) {
4476
4477					self.cleanSlide( item );
4478					
4479					self.setContent( holder, hIndex );
4480
4481					// if "center" slide
4482					if(i === 1) {
4483						self.currItem = item;
4484						self.updateCurrZoomItem(true);
4485					}
4486
4487					item.needsUpdate = false;
4488
4489				} else if(holder.index === -1 && hIndex >= 0) {
4490					// add content first time
4491					self.setContent( holder, hIndex );
4492				}
4493				if(item && item.container) {
4494					_calculateItemSize(item, _viewportSize);
4495					_setImageSize(item);
4496					_applyZoomPanToItem( item );
4497				}
4498				
4499			}
4500			_itemsNeedUpdate = false;
4501		}	
4502
4503		_startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
4504		_currPanBounds = self.currItem.bounds;
4505
4506		if(_currPanBounds) {
4507			_panOffset.x = _currPanBounds.center.x;
4508			_panOffset.y = _currPanBounds.center.y;
4509			_applyCurrentZoomPan( true );
4510		}
4511		
4512		_shout('resize');
4513	},
4514	
4515	// Zoom current item to
4516	zoomTo: function(destZoomLevel, centerPoint, speed, easingFn, updateFn) {
4517		/*
4518			if(destZoomLevel === 'fit') {
4519				destZoomLevel = self.currItem.fitRatio;
4520			} else if(destZoomLevel === 'fill') {
4521				destZoomLevel = self.currItem.fillRatio;
4522			}
4523		*/
4524
4525		if(centerPoint) {
4526			_startZoomLevel = _currZoomLevel;
4527			_midZoomPoint.x = Math.abs(centerPoint.x) - _panOffset.x ;
4528			_midZoomPoint.y = Math.abs(centerPoint.y) - _panOffset.y ;
4529			_equalizePoints(_startPanOffset, _panOffset);
4530		}
4531
4532		var destPanBounds = _calculatePanBounds(destZoomLevel, false),
4533			destPanOffset = {};
4534
4535		_modifyDestPanOffset('x', destPanBounds, destPanOffset, destZoomLevel);
4536		_modifyDestPanOffset('y', destPanBounds, destPanOffset, destZoomLevel);
4537
4538		var initialZoomLevel = _currZoomLevel;
4539		var initialPanOffset = {
4540			x: _panOffset.x,
4541			y: _panOffset.y
4542		};
4543
4544		_roundPoint(destPanOffset);
4545
4546		var onUpdate = function(now) {
4547			if(now === 1) {
4548				_currZoomLevel = destZoomLevel;
4549				_panOffset.x = destPanOffset.x;
4550				_panOffset.y = destPanOffset.y;
4551			} else {
4552				_currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
4553				_panOffset.x = (destPanOffset.x - initialPanOffset.x) * now + initialPanOffset.x;
4554				_panOffset.y = (destPanOffset.y - initialPanOffset.y) * now + initialPanOffset.y;
4555			}
4556
4557			if(updateFn) {
4558				updateFn(now);
4559			}
4560
4561			_applyCurrentZoomPan( now === 1 );
4562		};
4563
4564		if(speed) {
4565			_animateProp('customZoomTo', 0, 1, speed, easingFn || framework.easing.sine.inOut, onUpdate);
4566		} else {
4567			onUpdate(1);
4568		}
4569	}
4570
4571
4572};
4573
4574
4575/*>>core*/
4576
4577/*>>gestures*/
4578/**
4579 * Mouse/touch/pointer event handlers.
4580 * 
4581 * separated from @core.js for readability
4582 */
4583
4584var MIN_SWIPE_DISTANCE = 30,
4585	DIRECTION_CHECK_OFFSET = 10; // amount of pixels to drag to determine direction of swipe
4586
4587var _gestureStartTime,
4588	_gestureCheckSpeedTime,
4589
4590	// pool of objects that are used during dragging of zooming
4591	p = {}, // first point
4592	p2 = {}, // second point (for zoom gesture)
4593	delta = {},
4594	_currPoint = {},
4595	_startPoint = {},
4596	_currPointers = [],
4597	_startMainScrollPos = {},
4598	_releaseAnimData,
4599	_posPoints = [], // array of points during dragging, used to determine type of gesture
4600	_tempPoint = {},
4601
4602	_isZoomingIn,
4603	_verticalDragInitiated,
4604	_oldAndroidTouchEndTimeout,
4605	_currZoomedItemIndex = 0,
4606	_centerPoint = _getEmptyPoint(),
4607	_lastReleaseTime = 0,
4608	_isDragging, // at least one pointer is down
4609	_isMultitouch, // at least two _pointers are down
4610	_zoomStarted, // zoom level changed during zoom gesture
4611	_moved,
4612	_dragAnimFrame,
4613	_mainScrollShifted,
4614	_currentPoints, // array of current touch points
4615	_isZooming,
4616	_currPointsDistance,
4617	_startPointsDistance,
4618	_currPanBounds,
4619	_mainScrollPos = _getEmptyPoint(),
4620	_currZoomElementStyle,
4621	_mainScrollAnimating, // true, if animation after swipe gesture is running
4622	_midZoomPoint = _getEmptyPoint(),
4623	_currCenterPoint = _getEmptyPoint(),
4624	_direction,
4625	_isFirstMove,
4626	_opacityChanged,
4627	_bgOpacity,
4628	_wasOverInitialZoom,
4629
4630	_isEqualPoints = function(p1, p2) {
4631		return p1.x === p2.x && p1.y === p2.y;
4632	},
4633	_isNearbyPoints = function(touch0, touch1) {
4634		return Math.abs(touch0.x - touch1.x) < DOUBLE_TAP_RADIUS && Math.abs(touch0.y - touch1.y) < DOUBLE_TAP_RADIUS;
4635	},
4636	_calculatePointsDistance = function(p1, p2) {
4637		_tempPoint.x = Math.abs( p1.x - p2.x );
4638		_tempPoint.y = Math.abs( p1.y - p2.y );
4639		return Math.sqrt(_tempPoint.x * _tempPoint.x + _tempPoint.y * _tempPoint.y);
4640	},
4641	_stopDragUpdateLoop = function() {
4642		if(_dragAnimFrame) {
4643			_cancelAF(_dragAnimFrame);
4644			_dragAnimFrame = null;
4645		}
4646	},
4647	_dragUpdateLoop = function() {
4648		if(_isDragging) {
4649			_dragAnimFrame = _requestAF(_dragUpdateLoop);
4650			_renderMovement();
4651		}
4652	},
4653	_canPan = function() {
4654		return !(_options.scaleMode === 'fit' && _currZoomLevel ===  self.currItem.initialZoomLevel);
4655	},
4656	
4657	// find the closest parent DOM element
4658	_closestElement = function(el, fn) {
4659	  	if(!el) {
4660	  		return false;
4661	  	}
4662
4663	  	// don't search elements above pswp__scroll-wrap
4664	  	if(el.className && el.className.indexOf('pswp__scroll-wrap') > -1 ) {
4665	  		return false;
4666	  	}
4667
4668	  	if( fn(el) ) {
4669	  		return el;
4670	  	}
4671
4672	  	return _closestElement(el.parentNode, fn);
4673	},
4674
4675	_preventObj = {},
4676	_preventDefaultEventBehaviour = function(e, isDown) {
4677	    _preventObj.prevent = !_closestElement(e.target, _options.isClickableElement);
4678
4679		_shout('preventDragEvent', e, isDown, _preventObj);
4680		return _preventObj.prevent;
4681
4682	},
4683	_convertTouchToPoint = function(touch, p) {
4684		p.x = touch.pageX;
4685		p.y = touch.pageY;
4686		p.id = touch.identifier;
4687		return p;
4688	},
4689	_findCenterOfPoints = function(p1, p2, pCenter) {
4690		pCenter.x = (p1.x + p2.x) * 0.5;
4691		pCenter.y = (p1.y + p2.y) * 0.5;
4692	},
4693	_pushPosPoint = function(time, x, y) {
4694		if(time - _gestureCheckSpeedTime > 50) {
4695			var o = _posPoints.length > 2 ? _posPoints.shift() : {};
4696			o.x = x;
4697			o.y = y; 
4698			_posPoints.push(o);
4699			_gestureCheckSpeedTime = time;
4700		}
4701	},
4702
4703	_calculateVerticalDragOpacityRatio = function() {
4704		var yOffset = _panOffset.y - self.currItem.initialPosition.y; // difference between initial and current position
4705		return 1 -  Math.abs( yOffset / (_viewportSize.y / 2)  );
4706	},
4707
4708	
4709	// points pool, reused during touch events
4710	_ePoint1 = {},
4711	_ePoint2 = {},
4712	_tempPointsArr = [],
4713	_tempCounter,
4714	_getTouchPoints = function(e) {
4715		// clean up previous points, without recreating array
4716		while(_tempPointsArr.length > 0) {
4717			_tempPointsArr.pop();
4718		}
4719
4720		if(!_pointerEventEnabled) {
4721			if(e.type.indexOf('touch') > -1) {
4722
4723				if(e.touches && e.touches.length > 0) {
4724					_tempPointsArr[0] = _convertTouchToPoint(e.touches[0], _ePoint1);
4725					if(e.touches.length > 1) {
4726						_tempPointsArr[1] = _convertTouchToPoint(e.touches[1], _ePoint2);
4727					}
4728				}
4729				
4730			} else {
4731				_ePoint1.x = e.pageX;
4732				_ePoint1.y = e.pageY;
4733				_ePoint1.id = '';
4734				_tempPointsArr[0] = _ePoint1;//_ePoint1;
4735			}
4736		} else {
4737			_tempCounter = 0;
4738			// we can use forEach, as pointer events are supported only in modern browsers
4739			_currPointers.forEach(function(p) {
4740				if(_tempCounter === 0) {
4741					_tempPointsArr[0] = p;
4742				} else if(_tempCounter === 1) {
4743					_tempPointsArr[1] = p;
4744				}
4745				_tempCounter++;
4746
4747			});
4748		}
4749		return _tempPointsArr;
4750	},
4751
4752	_panOrMoveMainScroll = function(axis, delta) {
4753
4754		var panFriction,
4755			overDiff = 0,
4756			newOffset = _panOffset[axis] + delta[axis],
4757			startOverDiff,
4758			dir = delta[axis] > 0,
4759			newMainScrollPosition = _mainScrollPos.x + delta.x,
4760			mainScrollDiff = _mainScrollPos.x - _startMainScrollPos.x,
4761			newPanPos,
4762			newMainScrollPos;
4763
4764		// calculate fdistance over the bounds and friction
4765		if(newOffset > _currPanBounds.min[axis] || newOffset < _currPanBounds.max[axis]) {
4766			panFriction = _options.panEndFriction;
4767			// Linear increasing of friction, so at 1/4 of viewport it's at max value. 
4768			// Looks not as nice as was expected. Left for history.
4769			// panFriction = (1 - (_panOffset[axis] + delta[axis] + panBounds.min[axis]) / (_viewportSize[axis] / 4) );
4770		} else {
4771			panFriction = 1;
4772		}
4773		
4774		newOffset = _panOffset[axis] + delta[axis] * panFriction;
4775
4776		// move main scroll or start panning
4777		if(_options.allowPanToNext || _currZoomLevel === self.currItem.initialZoomLevel) {
4778
4779
4780			if(!_currZoomElementStyle) {
4781				
4782				newMainScrollPos = newMainScrollPosition;
4783
4784			} else if(_direction === 'h' && axis === 'x' && !_zoomStarted ) {
4785				
4786				if(dir) {
4787					if(newOffset > _currPanBounds.min[axis]) {
4788						panFriction = _options.panEndFriction;
4789						overDiff = _currPanBounds.min[axis] - newOffset;
4790						startOverDiff = _currPanBounds.min[axis] - _startPanOffset[axis];
4791					}
4792					
4793					// drag right
4794					if( (startOverDiff <= 0 || mainScrollDiff < 0) && _getNumItems() > 1 ) {
4795						newMainScrollPos = newMainScrollPosition;
4796						if(mainScrollDiff < 0 && newMainScrollPosition > _startMainScrollPos.x) {
4797							newMainScrollPos = _startMainScrollPos.x;
4798						}
4799					} else {
4800						if(_currPanBounds.min.x !== _currPanBounds.max.x) {
4801							newPanPos = newOffset;
4802						}
4803						
4804					}
4805
4806				} else {
4807
4808					if(newOffset < _currPanBounds.max[axis] ) {
4809						panFriction =_options.panEndFriction;
4810						overDiff = newOffset - _currPanBounds.max[axis];
4811						startOverDiff = _startPanOffset[axis] - _currPanBounds.max[axis];
4812					}
4813
4814					if( (startOverDiff <= 0 || mainScrollDiff > 0) && _getNumItems() > 1 ) {
4815						newMainScrollPos = newMainScrollPosition;
4816
4817						if(mainScrollDiff > 0 && newMainScrollPosition < _startMainScrollPos.x) {
4818							newMainScrollPos = _startMainScrollPos.x;
4819						}
4820
4821					} else {
4822						if(_currPanBounds.min.x !== _currPanBounds.max.x) {
4823							newPanPos = newOffset;
4824						}
4825					}
4826
4827				}
4828
4829
4830				//
4831			}
4832
4833			if(axis === 'x') {
4834
4835				if(newMainScrollPos !== undefined) {
4836					_moveMainScroll(newMainScrollPos, true);
4837					if(newMainScrollPos === _startMainScrollPos.x) {
4838						_mainScrollShifted = false;
4839					} else {
4840						_mainScrollShifted = true;
4841					}
4842				}
4843
4844				if(_currPanBounds.min.x !== _currPanBounds.max.x) {
4845					if(newPanPos !== undefined) {
4846						_panOffset.x = newPanPos;
4847					} else if(!_mainScrollShifted) {
4848						_panOffset.x += delta.x * panFriction;
4849					}
4850				}
4851
4852				return newMainScrollPos !== undefined;
4853			}
4854
4855		}
4856
4857		if(!_mainScrollAnimating) {
4858			
4859			if(!_mainScrollShifted) {
4860				if(_currZoomLevel > self.currItem.fitRatio) {
4861					_panOffset[axis] += delta[axis] * panFriction;
4862				
4863				}
4864			}
4865
4866			
4867		}
4868		
4869	},
4870
4871	// Pointerdown/touchstart/mousedown handler
4872	_onDragStart = function(e) {
4873
4874		// Allow dragging only via left mouse button.
4875		// As this handler is not added in IE8 - we ignore e.which
4876		// 
4877		// http://www.quirksmode.org/js/events_properties.html
4878		// https://developer.mozilla.org/en-US/docs/Web/API/event.button
4879		if(e.type === 'mousedown' && e.button > 0  ) {
4880			return;
4881		}
4882
4883		if(_initialZoomRunning) {
4884			e.preventDefault();
4885			return;
4886		}
4887
4888		if(_oldAndroidTouchEndTimeout && e.type === 'mousedown') {
4889			return;
4890		}
4891
4892		if(_preventDefaultEventBehaviour(e, true)) {
4893			e.preventDefault();
4894		}
4895
4896
4897
4898		_shout('pointerDown');
4899
4900		if(_pointerEventEnabled) {
4901			var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
4902			if(pointerIndex < 0) {
4903				pointerIndex = _currPointers.length;
4904			}
4905			_currPointers[pointerIndex] = {x:e.pageX, y:e.pageY, id: e.pointerId};
4906		}
4907		
4908
4909
4910		var startPointsList = _getTouchPoints(e),
4911			numPoints = startPointsList.length;
4912
4913		_currentPoints = null;
4914
4915		_stopAllAnimations();
4916
4917		// init drag
4918		if(!_isDragging || numPoints === 1) {
4919
4920			
4921
4922			_isDragging = _isFirstMove = true;
4923			framework.bind(window, _upMoveEvents, self);
4924
4925			_isZoomingIn = 
4926				_wasOverInitialZoom = 
4927				_opacityChanged = 
4928				_verticalDragInitiated = 
4929				_mainScrollShifted = 
4930				_moved = 
4931				_isMultitouch = 
4932				_zoomStarted = false;
4933
4934			_direction = null;
4935
4936			_shout('firstTouchStart', startPointsList);
4937
4938			_equalizePoints(_startPanOffset, _panOffset);
4939
4940			_currPanDist.x = _currPanDist.y = 0;
4941			_equalizePoints(_currPoint, startPointsList[0]);
4942			_equalizePoints(_startPoint, _currPoint);
4943
4944			//_equalizePoints(_startMainScrollPos, _mainScrollPos);
4945			_startMainScrollPos.x = _slideSize.x * _currPositionIndex;
4946
4947			_posPoints = [{
4948				x: _currPoint.x,
4949				y: _currPoint.y
4950			}];
4951
4952			_gestureCheckSpeedTime = _gestureStartTime = _getCurrentTime();
4953
4954			//_mainScrollAnimationEnd(true);
4955			_calculatePanBounds( _currZoomLevel, true );
4956			
4957			// Start rendering
4958			_stopDragUpdateLoop();
4959			_dragUpdateLoop();
4960			
4961		}
4962
4963		// init zoom
4964		if(!_isZooming && numPoints > 1 && !_mainScrollAnimating && !_mainScrollShifted) {
4965			_startZoomLevel = _currZoomLevel;
4966			_zoomStarted = false; // true if zoom changed at least once
4967
4968			_isZooming = _isMultitouch = true;
4969			_currPanDist.y = _currPanDist.x = 0;
4970
4971			_equalizePoints(_startPanOffset, _panOffset);
4972
4973			_equalizePoints(p, startPointsList[0]);
4974			_equalizePoints(p2, startPointsList[1]);
4975
4976			_findCenterOfPoints(p, p2, _currCenterPoint);
4977
4978			_midZoomPoint.x = Math.abs(_currCenterPoint.x) - _panOffset.x;
4979			_midZoomPoint.y = Math.abs(_currCenterPoint.y) - _panOffset.y;
4980			_currPointsDistance = _startPointsDistance = _calculatePointsDistance(p, p2);
4981		}
4982
4983
4984	},
4985
4986	// Pointermove/touchmove/mousemove handler
4987	_onDragMove = function(e) {
4988
4989		e.preventDefault();
4990
4991		if(_pointerEventEnabled) {
4992			var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
4993			if(pointerIndex > -1) {
4994				var p = _currPointers[pointerIndex];
4995				p.x = e.pageX;
4996				p.y = e.pageY; 
4997			}
4998		}
4999
5000		if(_isDragging) {
5001			var touchesList = _getTouchPoints(e);
5002			if(!_direction && !_moved && !_isZooming) {
5003
5004				if(_mainScrollPos.x !== _slideSize.x * _currPositionIndex) {
5005					// if main scroll position is shifted – direction is always horizontal
5006					_direction = 'h';
5007				} else {
5008					var diff = Math.abs(touchesList[0].x - _currPoint.x) - Math.abs(touchesList[0].y - _currPoint.y);
5009					// check the direction of movement
5010					if(Math.abs(diff) >= DIRECTION_CHECK_OFFSET) {
5011						_direction = diff > 0 ? 'h' : 'v';
5012						_currentPoints = touchesList;
5013					}
5014				}
5015				
5016			} else {
5017				_currentPoints = touchesList;
5018			}
5019		}	
5020	},
5021	// 
5022	_renderMovement =  function() {
5023
5024		if(!_currentPoints) {
5025			return;
5026		}
5027
5028		var numPoints = _currentPoints.length;
5029
5030		if(numPoints === 0) {
5031			return;
5032		}
5033
5034		_equalizePoints(p, _currentPoints[0]);
5035
5036		delta.x = p.x - _currPoint.x;
5037		delta.y = p.y - _currPoint.y;
5038
5039		if(_isZooming && numPoints > 1) {
5040			// Handle behaviour for more than 1 point
5041
5042			_currPoint.x = p.x;
5043			_currPoint.y = p.y;
5044		
5045			// check if one of two points changed
5046			if( !delta.x && !delta.y && _isEqualPoints(_currentPoints[1], p2) ) {
5047				return;
5048			}
5049
5050			_equalizePoints(p2, _currentPoints[1]);
5051
5052
5053			if(!_zoomStarted) {
5054				_zoomStarted = true;
5055				_shout('zoomGestureStarted');
5056			}
5057			
5058			// Distance between two points
5059			var pointsDistance = _calculatePointsDistance(p,p2);
5060
5061			var zoomLevel = _calculateZoomLevel(pointsDistance);
5062
5063			// slightly over the of initial zoom level
5064			if(zoomLevel > self.currItem.initialZoomLevel + self.currItem.initialZoomLevel / 15) {
5065				_wasOverInitialZoom = true;
5066			}
5067
5068			// Apply the friction if zoom level is out of the bounds
5069			var zoomFriction = 1,
5070				minZoomLevel = _getMinZoomLevel(),
5071				maxZoomLevel = _getMaxZoomLevel();
5072
5073			if ( zoomLevel < minZoomLevel ) {
5074				
5075				if(_options.pinchToClose && !_wasOverInitialZoom && _startZoomLevel <= self.currItem.initialZoomLevel) {
5076					// fade out background if zooming out
5077					var minusDiff = minZoomLevel - zoomLevel;
5078					var percent = 1 - minusDiff / (minZoomLevel / 1.2);
5079
5080					_applyBgOpacity(percent);
5081					_shout('onPinchClose', percent);
5082					_opacityChanged = true;
5083				} else {
5084					zoomFriction = (minZoomLevel - zoomLevel) / minZoomLevel;
5085					if(zoomFriction > 1) {
5086						zoomFriction = 1;
5087					}
5088					zoomLevel = minZoomLevel - zoomFriction * (minZoomLevel / 3);
5089				}
5090				
5091			} else if ( zoomLevel > maxZoomLevel ) {
5092				// 1.5 - extra zoom level above the max. E.g. if max is x6, real max 6 + 1.5 = 7.5
5093				zoomFriction = (zoomLevel - maxZoomLevel) / ( minZoomLevel * 6 );
5094				if(zoomFriction > 1) {
5095					zoomFriction = 1;
5096				}
5097				zoomLevel = maxZoomLevel + zoomFriction * minZoomLevel;
5098			}
5099
5100			if(zoomFriction < 0) {
5101				zoomFriction = 0;
5102			}
5103
5104			// distance between touch points after friction is applied
5105			_currPointsDistance = pointsDistance;
5106
5107			// _centerPoint - The point in the middle of two pointers
5108			_findCenterOfPoints(p, p2, _centerPoint);
5109		
5110			// paning with two pointers pressed
5111			_currPanDist.x += _centerPoint.x - _currCenterPoint.x;
5112			_currPanDist.y += _centerPoint.y - _currCenterPoint.y;
5113			_equalizePoints(_currCenterPoint, _centerPoint);
5114
5115			_panOffset.x = _calculatePanOffset('x', zoomLevel);
5116			_panOffset.y = _calculatePanOffset('y', zoomLevel);
5117
5118			_isZoomingIn = zoomLevel > _currZoomLevel;
5119			_currZoomLevel = zoomLevel;
5120			_applyCurrentZoomPan();
5121
5122		} else {
5123
5124			// handle behaviour for one point (dragging or panning)
5125
5126			if(!_direction) {
5127				return;
5128			}
5129
5130			if(_isFirstMove) {
5131				_isFirstMove = false;
5132
5133				// subtract drag distance that was used during the detection direction  
5134
5135				if( Math.abs(delta.x) >= DIRECTION_CHECK_OFFSET) {
5136					delta.x -= _currentPoints[0].x - _startPoint.x;
5137				}
5138				
5139				if( Math.abs(delta.y) >= DIRECTION_CHECK_OFFSET) {
5140					delta.y -= _currentPoints[0].y - _startPoint.y;
5141				}
5142			}
5143
5144			_currPoint.x = p.x;
5145			_currPoint.y = p.y;
5146
5147			// do nothing if pointers position hasn't changed
5148			if(delta.x === 0 && delta.y === 0) {
5149				return;
5150			}
5151
5152			if(_direction === 'v' && _options.closeOnVerticalDrag) {
5153				if(!_canPan()) {
5154					_currPanDist.y += delta.y;
5155					_panOffset.y += delta.y;
5156
5157					var opacityRatio = _calculateVerticalDragOpacityRatio();
5158
5159					_verticalDragInitiated = true;
5160					_shout('onVerticalDrag', opacityRatio);
5161
5162					_applyBgOpacity(opacityRatio);
5163					_applyCurrentZoomPan();
5164					return ;
5165				}
5166			}
5167
5168			_pushPosPoint(_getCurrentTime(), p.x, p.y);
5169
5170			_moved = true;
5171			_currPanBounds = self.currItem.bounds;
5172			
5173			var mainScrollChanged = _panOrMoveMainScroll('x', delta);
5174			if(!mainScrollChanged) {
5175				_panOrMoveMainScroll('y', delta);
5176
5177				_roundPoint(_panOffset);
5178				_applyCurrentZoomPan();
5179			}
5180
5181		}
5182
5183	},
5184	
5185	// Pointerup/pointercancel/touchend/touchcancel/mouseup event handler
5186	_onDragRelease = function(e) {
5187
5188		if(_features.isOldAndroid ) {
5189
5190			if(_oldAndroidTouchEndTimeout && e.type === 'mouseup') {
5191				return;
5192			}
5193
5194			// on Android (v4.1, 4.2, 4.3 & possibly older) 
5195			// ghost mousedown/up event isn't preventable via e.preventDefault,
5196			// which causes fake mousedown event
5197			// so we block mousedown/up for 600ms
5198			if( e.type.indexOf('touch') > -1 ) {
5199				clearTimeout(_oldAndroidTouchEndTimeout);
5200				_oldAndroidTouchEndTimeout = setTimeout(function() {
5201					_oldAndroidTouchEndTimeout = 0;
5202				}, 600);
5203			}
5204			
5205		}
5206
5207		_shout('pointerUp');
5208
5209		if(_preventDefaultEventBehaviour(e, false)) {
5210			e.preventDefault();
5211		}
5212
5213		var releasePoint;
5214
5215		if(_pointerEventEnabled) {
5216			var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
5217			
5218			if(pointerIndex > -1) {
5219				releasePoint = _currPointers.splice(pointerIndex, 1)[0];
5220
5221				if(navigator.pointerEnabled) {
5222					releasePoint.type = e.pointerType || 'mouse';
5223				} else {
5224					var MSPOINTER_TYPES = {
5225						4: 'mouse', // event.MSPOINTER_TYPE_MOUSE
5226						2: 'touch', // event.MSPOINTER_TYPE_TOUCH 
5227						3: 'pen' // event.MSPOINTER_TYPE_PEN
5228					};
5229					releasePoint.type = MSPOINTER_TYPES[e.pointerType];
5230
5231					if(!releasePoint.type) {
5232						releasePoint.type = e.pointerType || 'mouse';
5233					}
5234				}
5235
5236			}
5237		}
5238
5239		var touchList = _getTouchPoints(e),
5240			gestureType,
5241			numPoints = touchList.length;
5242
5243		if(e.type === 'mouseup') {
5244			numPoints = 0;
5245		}
5246
5247		// Do nothing if there were 3 touch points or more
5248		if(numPoints === 2) {
5249			_currentPoints = null;
5250			return true;
5251		}
5252
5253		// if second pointer released
5254		if(numPoints === 1) {
5255			_equalizePoints(_startPoint, touchList[0]);
5256		}				
5257
5258
5259		// pointer hasn't moved, send "tap release" point
5260		if(numPoints === 0 && !_direction && !_mainScrollAnimating) {
5261			if(!releasePoint) {
5262				if(e.type === 'mouseup') {
5263					releasePoint = {x: e.pageX, y: e.pageY, type:'mouse'};
5264				} else if(e.changedTouches && e.changedTouches[0]) {
5265					releasePoint = {x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY, type:'touch'};
5266				}		
5267			}
5268
5269			_shout('touchRelease', e, releasePoint);
5270		}
5271
5272		// Difference in time between releasing of two last touch points (zoom gesture)
5273		var releaseTimeDiff = -1;
5274
5275		// Gesture completed, no pointers left
5276		if(numPoints === 0) {
5277			_isDragging = false;
5278			framework.unbind(window, _upMoveEvents, self);
5279
5280			_stopDragUpdateLoop();
5281
5282			if(_isZooming) {
5283				// Two points released at the same time
5284				releaseTimeDiff = 0;
5285			} else if(_lastReleaseTime !== -1) {
5286				releaseTimeDiff = _getCurrentTime() - _lastReleaseTime;
5287			}
5288		}
5289		_lastReleaseTime = numPoints === 1 ? _getCurrentTime() : -1;
5290		
5291		if(releaseTimeDiff !== -1 && releaseTimeDiff < 150) {
5292			gestureType = 'zoom';
5293		} else {
5294			gestureType = 'swipe';
5295		}
5296
5297		if(_isZooming && numPoints < 2) {
5298			_isZooming = false;
5299
5300			// Only second point released
5301			if(numPoints === 1) {
5302				gestureType = 'zoomPointerUp';
5303			}
5304			_shout('zoomGestureEnded');
5305		}
5306
5307		_currentPoints = null;
5308		if(!_moved && !_zoomStarted && !_mainScrollAnimating && !_verticalDragInitiated) {
5309			// nothing to animate
5310			return;
5311		}
5312	
5313		_stopAllAnimations();
5314
5315		
5316		if(!_releaseAnimData) {
5317			_releaseAnimData = _initDragReleaseAnimationData();
5318		}
5319		
5320		_releaseAnimData.calculateSwipeSpeed('x');
5321
5322
5323		if(_verticalDragInitiated) {
5324
5325			var opacityRatio = _calculateVerticalDragOpacityRatio();
5326
5327			if(opacityRatio < _options.verticalDragRange) {
5328				self.close();
5329			} else {
5330				var initalPanY = _panOffset.y,
5331					initialBgOpacity = _bgOpacity;
5332
5333				_animateProp('verticalDrag', 0, 1, 300, framework.easing.cubic.out, function(now) {
5334					
5335					_panOffset.y = (self.currItem.initialPosition.y - initalPanY) * now + initalPanY;
5336
5337					_applyBgOpacity(  (1 - initialBgOpacity) * now + initialBgOpacity );
5338					_applyCurrentZoomPan();
5339				});
5340
5341				_shout('onVerticalDrag', 1);
5342			}
5343
5344			return;
5345		}
5346
5347
5348		// main scroll 
5349		if(  (_mainScrollShifted || _mainScrollAnimating) && numPoints === 0) {
5350			var itemChanged = _finishSwipeMainScrollGesture(gestureType, _releaseAnimData);
5351			if(itemChanged) {
5352				return;
5353			}
5354			gestureType = 'zoomPointerUp';
5355		}
5356
5357		// prevent zoom/pan animation when main scroll animation runs
5358		if(_mainScrollAnimating) {
5359			return;
5360		}
5361		
5362		// Complete simple zoom gesture (reset zoom level if it's out of the bounds)  
5363		if(gestureType !== 'swipe') {
5364			_completeZoomGesture();
5365			return;
5366		}
5367	
5368		// Complete pan gesture if main scroll is not shifted, and it's possible to pan current image
5369		if(!_mainScrollShifted && _currZoomLevel > self.currItem.fitRatio) {
5370			_completePanGesture(_releaseAnimData);
5371		}
5372	},
5373
5374
5375	// Returns object with data about gesture
5376	// It's created only once and then reused
5377	_initDragReleaseAnimationData  = function() {
5378		// temp local vars
5379		var lastFlickDuration,
5380			tempReleasePos;
5381
5382		// s = this
5383		var s = {
5384			lastFlickOffset: {},
5385			lastFlickDist: {},
5386			lastFlickSpeed: {},
5387			slowDownRatio:  {},
5388			slowDownRatioReverse:  {},
5389			speedDecelerationRatio:  {},
5390			speedDecelerationRatioAbs:  {},
5391			distanceOffset:  {},
5392			backAnimDestination: {},
5393			backAnimStarted: {},
5394			calculateSwipeSpeed: function(axis) {
5395				
5396
5397				if( _posPoints.length > 1) {
5398					lastFlickDuration = _getCurrentTime() - _gestureCheckSpeedTime + 50;
5399					tempReleasePos = _posPoints[_posPoints.length-2][axis];
5400				} else {
5401					lastFlickDuration = _getCurrentTime() - _gestureStartTime; // total gesture duration
5402					tempReleasePos = _startPoint[axis];
5403				}
5404				s.lastFlickOffset[axis] = _currPoint[axis] - tempReleasePos;
5405				s.lastFlickDist[axis] = Math.abs(s.lastFlickOffset[axis]);
5406				if(s.lastFlickDist[axis] > 20) {
5407					s.lastFlickSpeed[axis] = s.lastFlickOffset[axis] / lastFlickDuration;
5408				} else {
5409					s.lastFlickSpeed[axis] = 0;
5410				}
5411				if( Math.abs(s.lastFlickSpeed[axis]) < 0.1 ) {
5412					s.lastFlickSpeed[axis] = 0;
5413				}
5414				
5415				s.slowDownRatio[axis] = 0.95;
5416				s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
5417				s.speedDecelerationRatio[axis] = 1;
5418			},
5419
5420			calculateOverBoundsAnimOffset: function(axis, speed) {
5421				if(!s.backAnimStarted[axis]) {
5422
5423					if(_panOffset[axis] > _currPanBounds.min[axis]) {
5424						s.backAnimDestination[axis] = _currPanBounds.min[axis];
5425						
5426					} else if(_panOffset[axis] < _currPanBounds.max[axis]) {
5427						s.backAnimDestination[axis] = _currPanBounds.max[axis];
5428					}
5429
5430					if(s.backAnimDestination[axis] !== undefined) {
5431						s.slowDownRatio[axis] = 0.7;
5432						s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
5433						if(s.speedDecelerationRatioAbs[axis] < 0.05) {
5434
5435							s.lastFlickSpeed[axis] = 0;
5436							s.backAnimStarted[axis] = true;
5437
5438							_animateProp('bounceZoomPan'+axis,_panOffset[axis], 
5439								s.backAnimDestination[axis], 
5440								speed || 300, 
5441								framework.easing.sine.out, 
5442								function(pos) {
5443									_panOffset[axis] = pos;
5444									_applyCurrentZoomPan();
5445								}
5446							);
5447
5448						}
5449					}
5450				}
5451			},
5452
5453			// Reduces the speed by slowDownRatio (per 10ms)
5454			calculateAnimOffset: function(axis) {
5455				if(!s.backAnimStarted[axis]) {
5456					s.speedDecelerationRatio[axis] = s.speedDecelerationRatio[axis] * (s.slowDownRatio[axis] + 
5457												s.slowDownRatioReverse[axis] - 
5458												s.slowDownRatioReverse[axis] * s.timeDiff / 10);
5459
5460					s.speedDecelerationRatioAbs[axis] = Math.abs(s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis]);
5461					s.distanceOffset[axis] = s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis] * s.timeDiff;
5462					_panOffset[axis] += s.distanceOffset[axis];
5463
5464				}
5465			},
5466
5467			panAnimLoop: function() {
5468				if ( _animations.zoomPan ) {
5469					_animations.zoomPan.raf = _requestAF(s.panAnimLoop);
5470
5471					s.now = _getCurrentTime();
5472					s.timeDiff = s.now - s.lastNow;
5473					s.lastNow = s.now;
5474					
5475					s.calculateAnimOffset('x');
5476					s.calculateAnimOffset('y');
5477
5478					_applyCurrentZoomPan();
5479					
5480					s.calculateOverBoundsAnimOffset('x');
5481					s.calculateOverBoundsAnimOffset('y');
5482
5483
5484					if (s.speedDecelerationRatioAbs.x < 0.05 && s.speedDecelerationRatioAbs.y < 0.05) {
5485
5486						// round pan position
5487						_panOffset.x = Math.round(_panOffset.x);
5488						_panOffset.y = Math.round(_panOffset.y);
5489						_applyCurrentZoomPan();
5490						
5491						_stopAnimation('zoomPan');
5492						return;
5493					}
5494				}
5495
5496			}
5497		};
5498		return s;
5499	},
5500
5501	_completePanGesture = function(animData) {
5502		// calculate swipe speed for Y axis (paanning)
5503		animData.calculateSwipeSpeed('y');
5504
5505		_currPanBounds = self.currItem.bounds;
5506		
5507		animData.backAnimDestination = {};
5508		animData.backAnimStarted = {};
5509
5510		// Avoid acceleration animation if speed is too low
5511		if(Math.abs(animData.lastFlickSpeed.x) <= 0.05 && Math.abs(animData.lastFlickSpeed.y) <= 0.05 ) {
5512			animData.speedDecelerationRatioAbs.x = animData.speedDecelerationRatioAbs.y = 0;
5513
5514			// Run pan drag release animation. E.g. if you drag image and release finger without momentum.
5515			animData.calculateOverBoundsAnimOffset('x');
5516			animData.calculateOverBoundsAnimOffset('y');
5517			return true;
5518		}
5519
5520		// Animation loop that controls the acceleration after pan gesture ends
5521		_registerStartAnimation('zoomPan');
5522		animData.lastNow = _getCurrentTime();
5523		animData.panAnimLoop();
5524	},
5525
5526
5527	_finishSwipeMainScrollGesture = function(gestureType, _releaseAnimData) {
5528		var itemChanged;
5529		if(!_mainScrollAnimating) {
5530			_currZoomedItemIndex = _currentItemIndex;
5531		}
5532
5533
5534		
5535		var itemsDiff;
5536
5537		if(gestureType === 'swipe') {
5538			var totalShiftDist = _currPoint.x - _startPoint.x,
5539				isFastLastFlick = _releaseAnimData.lastFlickDist.x < 10;
5540
5541			// if container is shifted for more than MIN_SWIPE_DISTANCE, 
5542			// and last flick gesture was in right direction
5543			if(totalShiftDist > MIN_SWIPE_DISTANCE && 
5544				(isFastLastFlick || _releaseAnimData.lastFlickOffset.x > 20) ) {
5545				// go to prev item
5546				itemsDiff = -1;
5547			} else if(totalShiftDist < -MIN_SWIPE_DISTANCE && 
5548				(isFastLastFlick || _releaseAnimData.lastFlickOffset.x < -20) ) {
5549				// go to next item
5550				itemsDiff = 1;
5551			}
5552		}
5553
5554		var nextCircle;
5555
5556		if(itemsDiff) {
5557			
5558			_currentItemIndex += itemsDiff;
5559
5560			if(_currentItemIndex < 0) {
5561				_currentItemIndex = _options.loop ? _getNumItems()-1 : 0;
5562				nextCircle = true;
5563			} else if(_currentItemIndex >= _getNumItems()) {
5564				_currentItemIndex = _options.loop ? 0 : _getNumItems()-1;
5565				nextCircle = true;
5566			}
5567
5568			if(!nextCircle || _options.loop) {
5569				_indexDiff += itemsDiff;
5570				_currPositionIndex -= itemsDiff;
5571				itemChanged = true;
5572			}
5573			
5574
5575			
5576		}
5577
5578		var animateToX = _slideSize.x * _currPositionIndex;
5579		var animateToDist = Math.abs( animateToX - _mainScrollPos.x );
5580		var finishAnimDuration;
5581
5582
5583		if(!itemChanged && animateToX > _mainScrollPos.x !== _releaseAnimData.lastFlickSpeed.x > 0) {
5584			// "return to current" duration, e.g. when dragging from slide 0 to -1
5585			finishAnimDuration = 333; 
5586		} else {
5587			finishAnimDuration = Math.abs(_releaseAnimData.lastFlickSpeed.x) > 0 ? 
5588									animateToDist / Math.abs(_releaseAnimData.lastFlickSpeed.x) : 
5589									333;
5590
5591			finishAnimDuration = Math.min(finishAnimDuration, 400);
5592			finishAnimDuration = Math.max(finishAnimDuration, 250);
5593		}
5594
5595		if(_currZoomedItemIndex === _currentItemIndex) {
5596			itemChanged = false;
5597		}
5598		
5599		_mainScrollAnimating = true;
5600		
5601		_shout('mainScrollAnimStart');
5602
5603		_animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out, 
5604			_moveMainScroll,
5605			function() {
5606				_stopAllAnimations();
5607				_mainScrollAnimating = false;
5608				_currZoomedItemIndex = -1;
5609				
5610				if(itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
5611					self.updateCurrItem();
5612				}
5613				
5614				_shout('mainScrollAnimComplete');
5615			}
5616		);
5617
5618		if(itemChanged) {
5619			self.updateCurrItem(true);
5620		}
5621
5622		return itemChanged;
5623	},
5624
5625	_calculateZoomLevel = function(touchesDistance) {
5626		return  1 / _startPointsDistance * touchesDistance * _startZoomLevel;
5627	},
5628
5629	// Resets zoom if it's out of bounds
5630	_completeZoomGesture = function() {
5631		var destZoomLevel = _currZoomLevel,
5632			minZoomLevel = _getMinZoomLevel(),
5633			maxZoomLevel = _getMaxZoomLevel();
5634
5635		if ( _currZoomLevel < minZoomLevel ) {
5636			destZoomLevel = minZoomLevel;
5637		} else if ( _currZoomLevel > maxZoomLevel ) {
5638			destZoomLevel = maxZoomLevel;
5639		}
5640
5641		var destOpacity = 1,
5642			onUpdate,
5643			initialOpacity = _bgOpacity;
5644
5645		if(_opacityChanged && !_isZoomingIn && !_wasOverInitialZoom && _currZoomLevel < minZoomLevel) {
5646			//_closedByScroll = true;
5647			self.close();
5648			return true;
5649		}
5650
5651		if(_opacityChanged) {
5652			onUpdate = function(now) {
5653				_applyBgOpacity(  (destOpacity - initialOpacity) * now + initialOpacity );
5654			};
5655		}
5656
5657		self.zoomTo(destZoomLevel, 0, 200,  framework.easing.cubic.out, onUpdate);
5658		return true;
5659	};
5660
5661
5662_registerModule('Gestures', {
5663	publicMethods: {
5664
5665		initGestures: function() {
5666
5667			// helper function that builds touch/pointer/mouse events
5668			var addEventNames = function(pref, down, move, up, cancel) {
5669				_dragStartEvent = pref + down;
5670				_dragMoveEvent = pref + move;
5671				_dragEndEvent = pref + up;
5672				if(cancel) {
5673					_dragCancelEvent = pref + cancel;
5674				} else {
5675					_dragCancelEvent = '';
5676				}
5677			};
5678
5679			_pointerEventEnabled = _features.pointerEvent;
5680			if(_pointerEventEnabled && _features.touch) {
5681				// we don't need touch events, if browser supports pointer events
5682				_features.touch = false;
5683			}
5684
5685			if(_pointerEventEnabled) {
5686				if(navigator.pointerEnabled) {
5687					addEventNames('pointer', 'down', 'move', 'up', 'cancel');
5688				} else {
5689					// IE10 pointer events are case-sensitive
5690					addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
5691				}
5692			} else if(_features.touch) {
5693				addEventNames('touch', 'start', 'move', 'end', 'cancel');
5694				_likelyTouchDevice = true;
5695			} else {
5696				addEventNames('mouse', 'down', 'move', 'up');	
5697			}
5698
5699			_upMoveEvents = _dragMoveEvent + ' ' + _dragEndEvent  + ' ' +  _dragCancelEvent;
5700			_downEvents = _dragStartEvent;
5701
5702			if(_pointerEventEnabled && !_likelyTouchDevice) {
5703				_likelyTouchDevice = (navigator.maxTouchPoints > 1) || (navigator.msMaxTouchPoints > 1);
5704			}
5705			// make variable public
5706			self.likelyTouchDevice = _likelyTouchDevice; 
5707			
5708			_globalEventHandlers[_dragStartEvent] = _onDragStart;
5709			_globalEventHandlers[_dragMoveEvent] = _onDragMove;
5710			_globalEventHandlers[_dragEndEvent] = _onDragRelease; // the Kraken
5711
5712			if(_dragCancelEvent) {
5713				_globalEventHandlers[_dragCancelEvent] = _globalEventHandlers[_dragEndEvent];
5714			}
5715
5716			// Bind mouse events on device with detected hardware touch support, in case it supports multiple types of input.
5717			if(_features.touch) {
5718				_downEvents += ' mousedown';
5719				_upMoveEvents += ' mousemove mouseup';
5720				_globalEventHandlers.mousedown = _globalEventHandlers[_dragStartEvent];
5721				_globalEventHandlers.mousemove = _globalEventHandlers[_dragMoveEvent];
5722				_globalEventHandlers.mouseup = _globalEventHandlers[_dragEndEvent];
5723			}
5724
5725			if(!_likelyTouchDevice) {
5726				// don't allow pan to next slide from zoomed state on Desktop
5727				_options.allowPanToNext = false;
5728			}
5729		}
5730
5731	}
5732});
5733
5734
5735/*>>gestures*/
5736
5737/*>>show-hide-transition*/
5738/**
5739 * show-hide-transition.js:
5740 *
5741 * Manages initial opening or closing transition.
5742 *
5743 * If you're not planning to use transition for gallery at all,
5744 * you may set options hideAnimationDuration and showAnimationDuration to 0,
5745 * and just delete startAnimation function.
5746 * 
5747 */
5748
5749
5750var _showOrHideTimeout,
5751	_showOrHide = function(item, img, out, completeFn) {
5752
5753		if(_showOrHideTimeout) {
5754			clearTimeout(_showOrHideTimeout);
5755		}
5756
5757		_initialZoomRunning = true;
5758		_initialContentSet = true;
5759		
5760		// dimensions of small thumbnail {x:,y:,w:}.
5761		// Height is optional, as calculated based on large image.
5762		var thumbBounds; 
5763		if(item.initialLayout) {
5764			thumbBounds = item.initialLayout;
5765			item.initialLayout = null;
5766		} else {
5767			thumbBounds = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
5768		}
5769
5770		var duration = out ? _options.hideAnimationDuration : _options.showAnimationDuration;
5771
5772		var onComplete = function() {
5773			_stopAnimation('initialZoom');
5774			if(!out) {
5775				_applyBgOpacity(1);
5776				if(img) {
5777					img.style.display = 'block';
5778				}
5779				framework.addClass(template, 'pswp--animated-in');
5780				_shout('initialZoom' + (out ? 'OutEnd' : 'InEnd'));
5781			} else {
5782				self.template.removeAttribute('style');
5783				self.bg.removeAttribute('style');
5784			}
5785
5786			if(completeFn) {
5787				completeFn();
5788			}
5789			_initialZoomRunning = false;
5790		};
5791
5792		// if bounds aren't provided, just open gallery without animation
5793		if(!duration || !thumbBounds || thumbBounds.x === undefined) {
5794
5795			_shout('initialZoom' + (out ? 'Out' : 'In') );
5796
5797			_currZoomLevel = item.initialZoomLevel;
5798			_equalizePoints(_panOffset,  item.initialPosition );
5799			_applyCurrentZoomPan();
5800
5801			template.style.opacity = out ? 0 : 1;
5802			_applyBgOpacity(1);
5803
5804			if(duration) {
5805				setTimeout(function() {
5806					onComplete();
5807				}, duration);
5808			} else {
5809				onComplete();
5810			}
5811
5812			return;
5813		}
5814
5815		var startAnimation = function() {
5816			var closeWithRaf = _closedByScroll,
5817				fadeEverything = !self.currItem.src || self.currItem.loadError || _options.showHideOpacity;
5818			
5819			// apply hw-acceleration to image
5820			if(item.miniImg) {
5821				item.miniImg.style.webkitBackfaceVisibility = 'hidden';
5822			}
5823
5824			if(!out) {
5825				_currZoomLevel = thumbBounds.w / item.w;
5826				_panOffset.x = thumbBounds.x;
5827				_panOffset.y = thumbBounds.y - _initalWindowScrollY;
5828
5829				self[fadeEverything ? 'template' : 'bg'].style.opacity = 0.001;
5830				_applyCurrentZoomPan();
5831			}
5832
5833			_registerStartAnimation('initialZoom');
5834			
5835			if(out && !closeWithRaf) {
5836				framework.removeClass(template, 'pswp--animated-in');
5837			}
5838
5839			if(fadeEverything) {
5840				if(out) {
5841					framework[ (closeWithRaf ? 'remove' : 'add') + 'Class' ](template, 'pswp--animate_opacity');
5842				} else {
5843					setTimeout(function() {
5844						framework.addClass(template, 'pswp--animate_opacity');
5845					}, 30);
5846				}
5847			}
5848
5849			_showOrHideTimeout = setTimeout(function() {
5850
5851				_shout('initialZoom' + (out ? 'Out' : 'In') );
5852				
5853
5854				if(!out) {
5855
5856					// "in" animation always uses CSS transitions (instead of rAF).
5857					// CSS transition work faster here, 
5858					// as developer may also want to animate other things, 
5859					// like ui on top of sliding area, which can be animated just via CSS
5860					
5861					_currZoomLevel = item.initialZoomLevel;
5862					_equalizePoints(_panOffset,  item.initialPosition );
5863					_applyCurrentZoomPan();
5864					_applyBgOpacity(1);
5865
5866					if(fadeEverything) {
5867						template.style.opacity = 1;
5868					} else {
5869						_applyBgOpacity(1);
5870					}
5871
5872					_showOrHideTimeout = setTimeout(onComplete, duration + 20);
5873				} else {
5874
5875					// "out" animation uses rAF only when PhotoSwipe is closed by browser scroll, to recalculate position
5876					var destZoomLevel = thumbBounds.w / item.w,
5877						initialPanOffset = {
5878							x: _panOffset.x,
5879							y: _panOffset.y
5880						},
5881						initialZoomLevel = _currZoomLevel,
5882						initalBgOpacity = _bgOpacity,
5883						onUpdate = function(now) {
5884							
5885							if(now === 1) {
5886								_currZoomLevel = destZoomLevel;
5887								_panOffset.x = thumbBounds.x;
5888								_panOffset.y = thumbBounds.y  - _currentWindowScrollY;
5889							} else {
5890								_currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
5891								_panOffset.x = (thumbBounds.x - initialPanOffset.x) * now + initialPanOffset.x;
5892								_panOffset.y = (thumbBounds.y - _currentWindowScrollY - initialPanOffset.
5892y) * now + initialPanOffset.y;
5893							}
5894							
5895							_applyCurrentZoomPan();
5896							if(fadeEverything) {
5897								template.style.opacity = 1 - now;
5898							} else {
5899								_applyBgOpacity( initalBgOpacity - now * initalBgOpacity );
5900							}
5901						};
5902
5903					if(closeWithRaf) {
5904						_animateProp('initialZoom', 0, 1, duration, framework.easing.cubic.out, onUpdate, onComplete);
5905					} else {
5906						onUpdate(1);
5907						_showOrHideTimeout = setTimeout(onComplete, duration + 20);
5908					}
5909				}
5910			
5911			}, out ? 25 : 90); // Main purpose of this delay is to give browser time to paint and
5912					// create composite layers of PhotoSwipe UI parts (background, controls, caption, arrows).
5913					// Which avoids lag at the beginning of scale transition.
5914		};
5915		startAnimation();
5916
5917		
5918	};
5919
5920/*>>show-hide-transition*/
5921
5922/*>>items-controller*/
5923/**
5924*
5925* Controller manages gallery items, their dimensions, and their content.
5926* 
5927*/
5928
5929var _items,
5930	_tempPanAreaSize = {},
5931	_imagesToAppendPool = [],
5932	_initialContentSet,
5933	_initialZoomRunning,
5934	_controllerDefaultOptions = {
5935		index: 0,
5936		errorMsg: '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not be loaded.</div>',
5937		forceProgressiveLoading: false, // TODO
5938		preload: [1,1],
5939		getNumItemsFn: function() {
5940			return _items.length;
5941		}
5942	};
5943
5944
5945var _getItemAt,
5946	_getNumItems,
5947	_initialIsLoop,
5948	_getZeroBounds = function() {
5949		return {
5950			center:{x:0,y:0}, 
5951			max:{x:0,y:0}, 
5952			min:{x:0,y:0}
5953		};
5954	},
5955	_calculateSingleItemPanBounds = function(item, realPanElementW, realPanElementH ) {
5956		var bounds = item.bounds;
5957
5958		// position of element when it's centered
5959		bounds.center.x = Math.round((_tempPanAreaSize.x - realPanElementW) / 2);
5960		bounds.center.y = Math.round((_tempPanAreaSize.y - realPanElementH) / 2) + item.vGap.top;
5961
5962		// maximum pan position
5963		bounds.max.x = (realPanElementW > _tempPanAreaSize.x) ? 
5964							Math.round(_tempPanAreaSize.x - realPanElementW) : 
5965							bounds.center.x;
5966		
5967		bounds.max.y = (realPanElementH > _tempPanAreaSize.y) ? 
5968							Math.round(_tempPanAreaSize.y - realPanElementH) + item.vGap.top : 
5969							bounds.center.y;
5970		
5971		// minimum pan position
5972		bounds.min.x = (realPanElementW > _tempPanAreaSize.x) ? 0 : bounds.center.x;
5973		bounds.min.y = (realPanElementH > _tempPanAreaSize.y) ? item.vGap.top : bounds.center.y;
5974	},
5975	_calculateItemSize = function(item, viewportSize, zoomLevel) {
5976
5977		if (item.src && !item.loadError) {
5978			var isInitial = !zoomLevel;
5979			
5980			if(isInitial) {
5981				if(!item.vGap) {
5982					item.vGap = {top:0,bottom:0};
5983				}
5984				// allows overriding vertical margin for individual items
5985				_shout('parseVerticalMargin', item);
5986			}
5987
5988
5989			_tempPanAreaSize.x = viewportSize.x;
5990			_tempPanAreaSize.y = viewportSize.y - item.vGap.top - item.vGap.bottom;
5991
5992			if (isInitial) {
5993				var hRatio = _tempPanAreaSize.x / item.w;
5994				var vRatio = _tempPanAreaSize.y / item.h;
5995
5996				item.fitRatio = hRatio < vRatio ? hRatio : vRatio;
5997				//item.fillRatio = hRatio > vRatio ? hRatio : vRatio;
5998
5999				var scaleMode = _options.scaleMode;
6000
6001				if (scaleMode === 'orig') {
6002					zoomLevel = 1;
6003				} else if (scaleMode === 'fit') {
6004					zoomLevel = item.fitRatio;
6005				}
6006
6007				if (zoomLevel > 1) {
6008					zoomLevel = 1;
6009				}
6010
6011				item.initialZoomLevel = zoomLevel;
6012				
6013				if(!item.bounds) {
6014					// reuse bounds object
6015					item.bounds = _getZeroBounds(); 
6016				}
6017			}
6018
6019			if(!zoomLevel) {
6020				return;
6021			}
6022
6023			_calculateSingleItemPanBounds(item, item.w * zoomLevel, item.h * zoomLevel);
6024
6025			if (isInitial && zoomLevel === item.initialZoomLevel) {
6026				item.initialPosition = item.bounds.center;
6027			}
6028
6029			return item.bounds;
6030		} else {
6031			item.w = item.h = 0;
6032			item.initialZoomLevel = item.fitRatio = 1;
6033			item.bounds = _getZeroBounds();
6034			item.initialPosition = item.bounds.center;
6035
6036			// if it's not image, we return zero bounds (content is not zoomable)
6037			return item.bounds;
6038		}
6039		return false;
6040	},
6041
6042	
6043
6044
6045	_appendImage = function(index, item, baseDiv, img, preventAnimation, keepPlaceholder) {
6046		
6047
6048		if(item.loadError) {
6049			return;
6050		}
6051
6052		if(img) {
6053
6054			item.imageAppended = true;
6055			_setImageSize(item, img);
6056			
6057			baseDiv.appendChild(img);
6058
6059			if(keepPlaceholder) {
6060				setTimeout(function() {
6061					if(item && item.loaded && item.placeholder) {
6062						item.placeholder.style.display = 'none';
6063						item.placeholder = null;
6064					}
6065				}, 500);
6066			}
6067		}
6068	},
6069	
6070
6071
6072	_preloadImage = function(item) {
6073		item.loading = true;
6074		item.loaded = false;
6075		var img = item.img = framework.createEl('pswp__img', 'img');
6076		var onComplete = function() {
6077			item.loading = false;
6078			item.loaded = true;
6079
6080			if(item.loadComplete) {
6081				item.loadComplete(item);
6082			} else {
6083				item.img = null; // no need to store image object
6084			}
6085			img.onload = img.onerror = null;
6086			img = null;
6087		};
6088		img.onload = onComplete;
6089		img.onerror = function() {
6090			item.loadError = true;
6091			onComplete();
6092		};		
6093
6094		img.src = item.src;// + '?a=' + Math.random();
6095
6096		return img;
6097	},
6098	_checkForError = function(item, cleanUp) {
6099		if(item.src && item.loadError && item.container) {
6100
6101			if(cleanUp) {
6102				item.container.innerHTML = '';
6103			}
6104
6105			item.container.innerHTML = _options.errorMsg.replace('%url%',  item.src );
6106			return true;
6107			
6108		}
6109	},
6110	_setImageSize = function(item, img, maxRes) {
6111		if(!item.src) {
6112			return;
6113		}
6114
6115		if(!img) {
6116			img = item.container.lastChild;
6117		}
6118
6119		var w = maxRes ? item.w : Math.round(item.w * item.fitRatio),
6120			h = maxRes ? item.h : Math.round(item.h * item.fitRatio);
6121		
6122		if(item.placeholder && !item.loaded) {
6123			item.placeholder.style.width = w + 'px';
6124			item.placeholder.style.height = h + 'px';
6125		}
6126
6127		img.style.width = w + 'px';
6128		img.style.height = h + 'px';
6129	},
6130	_appendImagesPool = function() {
6131
6132		if(_imagesToAppendPool.length) {
6133			var poolItem;
6134
6135			for(var i = 0; i < _imagesToAppendPool.length; i++) {
6136				poolItem = _imagesToAppendPool[i];
6137				if( poolItem.holder.index === poolItem.index ) {
6138					_appendImage(poolItem.index, poolItem.item, poolItem.baseDiv, poolItem.img, false, poolItem.clearPlaceholder);
6139				}
6140			}
6141			_imagesToAppendPool = [];
6142		}
6143	};
6144	
6145
6146
6147_registerModule('Controller', {
6148
6149	publicMethods: {
6150
6151		lazyLoadItem: function(index) {
6152			index = _getLoopedId(index);
6153			var item = _getItemAt(index);
6154
6155			if(!item || ((item.loaded || item.loading) && !_itemsNeedUpdate)) {
6156				return;
6157			}
6158
6159			_shout('gettingData', index, item);
6160
6161			if (!item.src) {
6162				return;
6163			}
6164
6165			_preloadImage(item);
6166		},
6167		initController: function() {
6168			framework.extend(_options, _controllerDefaultOptions, true);
6169			self.items = _items = items;
6170			_getItemAt = self.getItemAt;
6171			_getNumItems = _options.getNumItemsFn; //self.getNumItems;
6172
6173
6174
6175			_initialIsLoop = _options.loop;
6176			if(_getNumItems() < 3) {
6177				_options.loop = false; // disable loop if less then 3 items
6178			}
6179
6180			_listen('beforeChange', function(diff) {
6181
6182				var p = _options.preload,
6183					isNext = diff === null ? true : (diff >= 0),
6184					preloadBefore = Math.min(p[0], _getNumItems() ),
6185					preloadAfter = Math.min(p[1], _getNumItems() ),
6186					i;
6187
6188
6189				for(i = 1; i <= (isNext ? preloadAfter : preloadBefore); i++) {
6190					self.lazyLoadItem(_currentItemIndex+i);
6191				}
6192				for(i = 1; i <= (isNext ? preloadBefore : preloadAfter); i++) {
6193					self.lazyLoadItem(_currentItemIndex-i);
6194				}
6195			});
6196
6197			_listen('initialLayout', function() {
6198				self.currItem.initialLayout = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
6199			});
6200
6201			_listen('mainScrollAnimComplete', _appendImagesPool);
6202			_listen('initialZoomInEnd', _appendImagesPool);
6203
6204
6205
6206			_listen('destroy', function() {
6207				var item;
6208				for(var i = 0; i < _items.length; i++) {
6209					item = _items[i];
6210					// remove reference to DOM elements, for GC
6211					if(item.container) {
6212						item.container = null; 
6213					}
6214					if(item.placeholder) {
6215						item.placeholder = null;
6216					}
6217					if(item.img) {
6218						item.img = null;
6219					}
6220					if(item.preloader) {
6221						item.preloader = null;
6222					}
6223					if(item.loadError) {
6224						item.loaded = item.loadError = false;
6225					}
6226				}
6227				_imagesToAppendPool = null;
6228			});
6229		},
6230
6231
6232		getItemAt: function(index) {
6233			if (index >= 0) {
6234				return _items[index] !== undefined ? _items[index] : false;
6235			}
6236			return false;
6237		},
6238
6239		allowProgressiveImg: function() {
6240			// 1. Progressive image loading isn't working on webkit/blink 
6241			//    when hw-acceleration (e.g. translateZ) is applied to IMG element.
6242			//    That's why in PhotoSwipe parent element gets zoom transform, not image itself.
6243			//    
6244			// 2. Progressive image loading sometimes blinks in webkit/blink when applying animation to parent element.
6245			//    That's why it's disabled on touch devices (mainly because of swipe transition)
6246			//    
6247			// 3. Progressive image loading sometimes doesn't work in IE (up to 11).
6248
6249			// Don't allow progressive loading on non-large touch devices
6250			return _options.forceProgressiveLoading || !_likelyTouchDevice || _options.mouseUsed || screen.width > 1200; 
6251			// 1200 - to eliminate touch devices with large screen (like Chromebook Pixel)
6252		},
6253
6254		setContent: function(holder, index) {
6255
6256			if(_options.loop) {
6257				index = _getLoopedId(index);
6258			}
6259
6260			var prevItem = self.getItemAt(holder.index);
6261			if(prevItem) {
6262				prevItem.container = null;
6263			}
6264	
6265			var item = self.getItemAt(index),
6266				img;
6267			
6268			if(!item) {
6269				holder.el.innerHTML = '';
6270				return;
6271			}
6272
6273			// allow to override data
6274			_shout('gettingData', index, item);
6275
6276			holder.index = index;
6277			holder.item = item;
6278
6279			// base container DIV is created only once for each of 3 holders
6280			var baseDiv = item.container = framework.createEl('pswp__zoom-wrap'); 
6281
6282			
6283
6284			if(!item.src && item.html) {
6285				if(item.html.tagName) {
6286					baseDiv.appendChild(item.html);
6287				} else {
6288					baseDiv.innerHTML = item.html;
6289				}
6290			}
6291
6292			_checkForError(item);
6293
6294			_calculateItemSize(item, _viewportSize);
6295			
6296			if(item.src && !item.loadError && !item.loaded) {
6297
6298				item.loadComplete = function(item) {
6299
6300					// gallery closed before image finished loading
6301					if(!_isOpen) {
6302						return;
6303					}
6304
6305					// check if holder hasn't changed while image was loading
6306					if(holder && holder.index === index ) {
6307						if( _checkForError(item, true) ) {
6308							item.loadComplete = item.img = null;
6309							_calculateItemSize(item, _viewportSize);
6310							_applyZoomPanToItem(item);
6311
6312							if(holder.index === _currentItemIndex) {
6313								// recalculate dimensions
6314								self.updateCurrZoomItem();
6315							}
6316							return;
6317						}
6318						if( !item.imageAppended ) {
6319							if(_features.transform && (_mainScrollAnimating || _initialZoomRunning) ) {
6320								_imagesToAppendPool.push({
6321									item:item,
6322									baseDiv:baseDiv,
6323									img:item.img,
6324									index:index,
6325									holder:holder,
6326									clearPlaceholder:true
6327								});
6328							} else {
6329								_appendImage(index, item, baseDiv, item.img, _mainScrollAnimating || _initialZoomRunning, true);
6330							}
6331						} else {
6332							// remove preloader & mini-img
6333							if(!_initialZoomRunning && item.placeholder) {
6334								item.placeholder.style.display = 'none';
6335								item.placeholder = null;
6336							}
6337						}
6338					}
6339
6340					item.loadComplete = null;
6341					item.img = null; // no need to store image element after it's added
6342
6343					_shout('imageLoadComplete', index, item);
6344				};
6345
6346				if(framework.features.transform) {
6347					
6348					var placeholderClassName = 'pswp__img pswp__img--placeholder'; 
6349					placeholderClassName += (item.msrc ? '' : ' pswp__img--placeholder--blank');
6350
6351					var placeholder = framework.createEl(placeholderClassName, item.msrc ? 'img' : '');
6352					if(item.msrc) {
6353						placeholder.src = item.msrc;
6354					}
6355					
6356					_setImageSize(item, placeholder);
6357
6358					baseDiv.appendChild(placeholder);
6359					item.placeholder = placeholder;
6360
6361				}
6362				
6363
6364				
6365
6366				if(!item.loading) {
6367					_preloadImage(item);
6368				}
6369
6370
6371				if( self.allowProgressiveImg() ) {
6372					// just append image
6373					if(!_initialContentSet && _features.transform) {
6374						_imagesToAppendPool.push({
6375							item:item, 
6376							baseDiv:baseDiv, 
6377							img:item.img, 
6378							index:index, 
6379							holder:holder
6380						});
6381					} else {
6382						_appendImage(index, item, baseDiv, item.img, true, true);
6383					}
6384				}
6385				
6386			} else if(item.src && !item.loadError) {
6387				// image object is created every time, due to bugs of image loading & delay when switching images
6388				img = framework.createEl('pswp__img', 'img');
6389				img.style.opacity = 1;
6390				img.src = item.src;
6391				_setImageSize(item, img);
6392				_appendImage(index, item, baseDiv, img, true);
6393			}
6394			
6395
6396			if(!_initialContentSet && index === _currentItemIndex) {
6397				_currZoomElementStyle = baseDiv.style;
6398				_showOrHide(item, (img ||item.img) );
6399			} else {
6400				_applyZoomPanToItem(item);
6401			}
6402
6403			holder.el.innerHTML = '';
6404			holder.el.appendChild(baseDiv);
6405		},
6406
6407		cleanSlide: function( item ) {
6408			if(item.img ) {
6409				item.img.onload = item.img.onerror = null;
6410			}
6411			item.loaded = item.loading = item.img = item.imageAppended = false;
6412		}
6413
6414	}
6415});
6416
6417/*>>items-controller*/
6418
6419/*>>tap*/
6420/**
6421 * tap.js:
6422 *
6423 * Displatches tap and double-tap events.
6424 * 
6425 */
6426
6427var tapTimer,
6428	tapReleasePoint = {},
6429	_dispatchTapEvent = function(origEvent, releasePoint, pointerType) {		
6430		var e = document.createEvent( 'CustomEvent' ),
6431			eDetail = {
6432				origEvent:origEvent, 
6433				target:origEvent.target, 
6434				releasePoint: releasePoint, 
6435				pointerType:pointerType || 'touch'
6436			};
6437
6438		e.initCustomEvent( 'pswpTap', true, true, eDetail );
6439		origEvent.target.dispatchEvent(e);
6440	};
6441
6442_registerModule('Tap', {
6443	publicMethods: {
6444		initTap: function() {
6445			_listen('firstTouchStart', self.onTapStart);
6446			_listen('touchRelease', self.onTapRelease);
6447			_listen('destroy', function() {
6448				tapReleasePoint = {};
6449				tapTimer = null;
6450			});
6451		},
6452		onTapStart: function(touchList) {
6453			if(touchList.length > 1) {
6454				clearTimeout(tapTimer);
6455				tapTimer = null;
6456			}
6457		},
6458		onTapRelease: function(e, releasePoint) {
6459			if(!releasePoint) {
6460				return;
6461			}
6462
6463			if(!_moved && !_isMultitouch && !_numAnimations) {
6464				var p0 = releasePoint;
6465				if(tapTimer) {
6466					clearTimeout(tapTimer);
6467					tapTimer = null;
6468
6469					// Check if taped on the same place
6470					if ( _isNearbyPoints(p0, tapReleasePoint) ) {
6471						_shout('doubleTap', p0);
6472						return;
6473					}
6474				}
6475
6476				if(releasePoint.type === 'mouse') {
6477					_dispatchTapEvent(e, releasePoint, 'mouse');
6478					return;
6479				}
6480
6481				var clickedTagName = e.target.tagName.toUpperCase();
6482				// avoid double tap delay on buttons and elements that have class pswp__single-tap
6483				if(clickedTagName === 'BUTTON' || framework.hasClass(e.target, 'pswp__single-tap') ) {
6484					_dispatchTapEvent(e, releasePoint);
6485					return;
6486				}
6487
6488				_equalizePoints(tapReleasePoint, p0);
6489
6490				tapTimer = setTimeout(function() {
6491					_dispatchTapEvent(e, releasePoint);
6492					tapTimer = null;
6493				}, 300);
6494			}
6495		}
6496	}
6497});
6498
6499/*>>tap*/
6500
6501/*>>desktop-zoom*/
6502/**
6503 *
6504 * desktop-zoom.js:
6505 *
6506 * - Binds mousewheel event for paning zoomed image.
6507 * - Manages "dragging", "zoomed-in", "zoom-out" classes.
6508 *   (which are used for cursors and zoom icon)
6509 * - Adds toggleDesktopZoom function.
6510 * 
6511 */
6512
6513var _wheelDelta;
6514	
6515_registerModule('DesktopZoom', {
6516
6517	publicMethods: {
6518
6519		initDesktopZoom: function() {
6520
6521			if(_oldIE) {
6522				// no zoom for old IE (<=8)
6523				return;
6524			}
6525
6526			if(_likelyTouchDevice) {
6527				// if detected hardware touch support, we wait until mouse is used,
6528				// and only then apply desktop-zoom features
6529				_listen('mouseUsed', function() {
6530					self.setupDesktopZoom();
6531				});
6532			} else {
6533				self.setupDesktopZoom(true);
6534			}
6535
6536		},
6537
6538		setupDesktopZoom: function(onInit) {
6539
6540			_wheelDelta = {};
6541
6542			var events = 'wheel mousewheel DOMMouseScroll';
6543			
6544			_listen('bindEvents', function() {
6545				framework.bind(template, events,  self.handleMouseWheel);
6546			});
6547
6548			_listen('unbindEvents', function() {
6549				if(_wheelDelta) {
6550					framework.unbind(template, events, self.handleMouseWheel);
6551				}
6552			});
6553
6554			self.mouseZoomedIn = false;
6555
6556			var hasDraggingClass,
6557				updateZoomable = function() {
6558					if(self.mouseZoomedIn) {
6559						framework.removeClass(template, 'pswp--zoomed-in');
6560						self.mouseZoomedIn = false;
6561					}
6562					if(_currZoomLevel < 1) {
6563						framework.addClass(template, 'pswp--zoom-allowed');
6564					} else {
6565						framework.removeClass(template, 'pswp--zoom-allowed');
6566					}
6567					removeDraggingClass();
6568				},
6569				removeDraggingClass = function() {
6570					if(hasDraggingClass) {
6571						framework.removeClass(template, 'pswp--dragging');
6572						hasDraggingClass = false;
6573					}
6574				};
6575
6576			_listen('resize' , updateZoomable);
6577			_listen('afterChange' , updateZoomable);
6578			_listen('pointerDown', function() {
6579				if(self.mouseZoomedIn) {
6580					hasDraggingClass = true;
6581					framework.addClass(template, 'pswp--dragging');
6582				}
6583			});
6584			_listen('pointerUp', removeDraggingClass);
6585
6586			if(!onInit) {
6587				updateZoomable();
6588			}
6589			
6590		},
6591
6592		handleMouseWheel: function(e) {
6593
6594			if(_currZoomLevel <= self.currItem.fitRatio) {
6595				if( _options.modal ) {
6596
6597					if (!_options.closeOnScroll || _numAnimations || _isDragging) {
6598						e.preventDefault();
6599					} else if(_transformKey && Math.abs(e.deltaY) > 2) {
6600						// close PhotoSwipe
6601						// if browser supports transforms & scroll changed enough
6602						_closedByScroll = true;
6603						self.close();
6604					}
6605
6606				}
6607				return true;
6608			}
6609
6610			// allow just one event to fire
6611			e.stopPropagation();
6612
6613			// https://developer.mozilla.org/en-US/docs/Web/Events/wheel
6614			_wheelDelta.x = 0;
6615
6616			if('deltaX' in e) {
6617				if(e.deltaMode === 1 /* DOM_DELTA_LINE */) {
6618					// 18 - average line height
6619					_wheelDelta.x = e.deltaX * 18;
6620					_wheelDelta.y = e.deltaY * 18;
6621				} else {
6622					_wheelDelta.x = e.deltaX;
6623					_wheelDelta.y = e.deltaY;
6624				}
6625			} else if('wheelDelta' in e) {
6626				if(e.wheelDeltaX) {
6627					_wheelDelta.x = -0.16 * e.wheelDeltaX;
6628				}
6629				if(e.wheelDeltaY) {
6630					_wheelDelta.y = -0.16 * e.wheelDeltaY;
6631				} else {
6632					_wheelDelta.y = -0.16 * e.wheelDelta;
6633				}
6634			} else if('detail' in e) {
6635				_wheelDelta.y = e.detail;
6636			} else {
6637				return;
6638			}
6639
6640			_calculatePanBounds(_currZoomLevel, true);
6641
6642			var newPanX = _panOffset.x - _wheelDelta.x,
6643				newPanY = _panOffset.y - _wheelDelta.y;
6644
6645			// only prevent scrolling in nonmodal mode when not at edges
6646			if (_options.modal ||
6647				(
6648				newPanX <= _currPanBounds.min.x && newPanX >= _currPanBounds.max.x &&
6649				newPanY <= _currPanBounds.min.y && newPanY >= _currPanBounds.max.y
6650				) ) {
6651				e.preventDefault();
6652			}
6653
6654			// TODO: use rAF instead of mousewheel?
6655			self.panTo(newPanX, newPanY);
6656		},
6657
6658		toggleDesktopZoom: function(centerPoint) {
6659			centerPoint = centerPoint || {x:_viewportSize.x/2 + _offset.x, y:_viewportSize.y/2 + _offset.y };
6660
6661			var doubleTapZoomLevel = _options.getDoubleTapZoom(true, self.currItem);
6662			var zoomOut = _currZoomLevel === doubleTapZoomLevel;
6663			
6664			self.mouseZoomedIn = !zoomOut;
6665
6666			self.zoomTo(zoomOut ? self.currItem.initialZoomLevel : doubleTapZoomLevel, centerPoint, 333);
6667			framework[ (!zoomOut ? 'add' : 'remove') + 'Class'](template, 'pswp--zoomed-in');
6668		}
6669
6670	}
6671});
6672
6673
6674/*>>desktop-zoom*/
6675
6676/*>>history*/
6677/**
6678 *
6679 * history.js:
6680 *
6681 * - Back button to close gallery.
6682 * 
6683 * - Unique URL for each slide: example.com/&pid=1&gid=3
6684 *   (where PID is picture index, and GID and gallery index)
6685 *   
6686 * - Switch URL when slides change.
6687 * 
6688 */
6689
6690
6691var _historyDefaultOptions = {
6692	history: true,
6693	galleryUID: 1
6694};
6695
6696var _historyUpdateTimeout,
6697	_hashChangeTimeout,
6698	_hashAnimCheckTimeout,
6699	_hashChangedByScript,
6700	_hashChangedByHistory,
6701	_hashReseted,
6702	_initialHash,
6703	_historyChanged,
6704	_closedFromURL,
6705	_urlChangedOnce,
6706	_windowLoc,
6707
6708	_supportsPushState,
6709
6710	_getHash = function() {
6711		return _windowLoc.hash.substring(1);
6712	},
6713	_cleanHistoryTimeouts = function() {
6714
6715		if(_historyUpdateTimeout) {
6716			clearTimeout(_historyUpdateTimeout);
6717		}
6718
6719		if(_hashAnimCheckTimeout) {
6720			clearTimeout(_hashAnimCheckTimeout);
6721		}
6722	},
6723
6724	// pid - Picture index
6725	// gid - Gallery index
6726	_parseItemIndexFromURL = function() {
6727		var hash = _getHash(),
6728			params = {};
6729
6730		if(hash.length < 5) { // pid=1
6731			return params;
6732		}
6733
6734		var i, vars = hash.split('&');
6735		for (i = 0; i < vars.length; i++) {
6736			if(!vars[i]) {
6737				continue;
6738			}
6739			var pair = vars[i].split('=');	
6740			if(pair.length < 2) {
6741				continue;
6742			}
6743			params[pair[0]] = pair[1];
6744		}
6745		if(_options.galleryPIDs) {
6746			// detect custom pid in hash and search for it among the items collection
6747			var searchfor = params.pid;
6748			params.pid = 0; // if custom pid cannot be found, fallback to the first item
6749			for(i = 0; i < _items.length; i++) {
6750				if(_items[i].pid === searchfor) {
6751					params.pid = i;
6752					break;
6753				}
6754			}
6755		} else {
6756			params.pid = parseInt(params.pid,10)-1;
6757		}
6758		if( params.pid < 0 ) {
6759			params.pid = 0;
6760		}
6761		return params;
6762	},
6763	_updateHash = function() {
6764
6765		if(_hashAnimCheckTimeout) {
6766			clearTimeout(_hashAnimCheckTimeout);
6767		}
6768
6769
6770		if(_numAnimations || _isDragging) {
6771			// changing browser URL forces layout/paint in some browsers, which causes noticable lag during animation
6772			// that's why we update hash only when no animations running
6773			_hashAnimCheckTimeout = setTimeout(_updateHash, 500);
6774			return;
6775		}
6776		
6777		if(_hashChangedByScript) {
6778			clearTimeout(_hashChangeTimeout);
6779		} else {
6780			_hashChangedByScript = true;
6781		}
6782
6783
6784		var pid = (_currentItemIndex + 1);
6785		var item = _getItemAt( _currentItemIndex );
6786		if(item.hasOwnProperty('pid')) {
6787			// carry forward any custom pid assigned to the item
6788			pid = item.pid;
6789		}
6790		var newHash = _initialHash + '&'  +  'gid=' + _options.galleryUID + '&' + 'pid=' + pid;
6791
6792		if(!_historyChanged) {
6793			if(_windowLoc.hash.indexOf(newHash) === -1) {
6794				_urlChangedOnce = true;
6795			}
6796			// first time - add new hisory record, then just replace
6797		}
6798
6799		var newURL = _windowLoc.href.split('#')[0] + '#' +  newHash;
6800
6801		if( _supportsPushState ) {
6802
6803			if('#' + newHash !== window.location.hash) {
6804				history[_historyChanged ? 'replaceState' : 'pushState']('', document.title, newURL);
6805			}
6806
6807		} else {
6808			if(_historyChanged) {
6809				_windowLoc.replace( newURL );
6810			} else {
6811				_windowLoc.hash = newHash;
6812			}
6813		}
6814		
6815		
6816
6817		_historyChanged = true;
6818		_hashChangeTimeout = setTimeout(function() {
6819			_hashChangedByScript = false;
6820		}, 60);
6821	};
6822
6823
6824
6825	
6826
6827_registerModule('History', {
6828
6829	
6830
6831	publicMethods: {
6832		initHistory: function() {
6833
6834			framework.extend(_options, _historyDefaultOptions, true);
6835
6836			if( !_options.history ) {
6837				return;
6838			}
6839
6840
6841			_windowLoc = window.location;
6842			_urlChangedOnce = false;
6843			_closedFromURL = false;
6844			_historyChanged = false;
6845			_initialHash = _getHash();
6846			_supportsPushState = ('pushState' in history);
6847
6848
6849			if(_initialHash.indexOf('gid=') > -1) {
6850				_initialHash = _initialHash.split('&gid=')[0];
6851				_initialHash = _initialHash.split('?gid=')[0];
6852			}
6853			
6854
6855			_listen('afterChange', self.updateURL);
6856			_listen('unbindEvents', function() {
6857				framework.unbind(window, 'hashchange', self.onHashChange);
6858			});
6859
6860
6861			var returnToOriginal = function() {
6862				_hashReseted = true;
6863				if(!_closedFromURL) {
6864
6865					if(_urlChangedOnce) {
6866						history.back();
6867					} else {
6868
6869						if(_initialHash) {
6870							_windowLoc.hash = _initialHash;
6871						} else {
6872							if (_supportsPushState) {
6873
6874								// remove hash from url without refreshing it or scrolling to top
6875								history.pushState('', document.title,  _windowLoc.pathname + _windowLoc.search );
6876							} else {
6877								_windowLoc.hash = '';
6878							}
6879						}
6880					}
6881					
6882				}
6883
6884				_cleanHistoryTimeouts();
6885			};
6886
6887
6888			_listen('unbindEvents', function() {
6889				if(_closedByScroll) {
6890					// if PhotoSwipe is closed by scroll, we go "back" before the closing animation starts
6891					// this is done to keep the scroll position
6892					returnToOriginal();
6893				}
6894			});
6895			_listen('destroy', function() {
6896				if(!_hashReseted) {
6897					returnToOriginal();
6898				}
6899			});
6900			_listen('firstUpdate', function() {
6901				_currentItemIndex = _parseItemIndexFromURL().pid;
6902			});
6903
6904			
6905
6906			
6907			var index = _initialHash.indexOf('pid=');
6908			if(index > -1) {
6909				_initialHash = _initialHash.substring(0, index);
6910				if(_initialHash.slice(-1) === '&') {
6911					_initialHash = _initialHash.slice(0, -1);
6912				}
6913			}
6914			
6915
6916			setTimeout(function() {
6917				if(_isOpen) { // hasn't destroyed yet
6918					framework.bind(window, 'hashchange', self.onHashChange);
6919				}
6920			}, 40);
6921			
6922		},
6923		onHashChange: function() {
6924
6925			if(_getHash() === _initialHash) {
6926
6927				_closedFromURL = true;
6928				self.close();
6929				return;
6930			}
6931			if(!_hashChangedByScript) {
6932
6933				_hashChangedByHistory = true;
6934				self.goTo( _parseItemIndexFromURL().pid );
6935				_hashChangedByHistory = false;
6936			}
6937			
6938		},
6939		updateURL: function() {
6940
6941			// Delay the update of URL, to avoid lag during transition, 
6942			// and to not to trigger actions like "refresh page sound" or "blinking favicon" to often
6943			
6944			_cleanHistoryTimeouts();
6945			
6946
6947			if(_hashChangedByHistory) {
6948				return;
6949			}
6950
6951			if(!_historyChanged) {
6952				_updateHash(); // first time
6953			} else {
6954				_historyUpdateTimeout = setTimeout(_updateHash, 800);
6955			}
6956		}
6957	
6958	}
6959});
6960
6961
6962/*>>history*/
6963	framework.extend(self, publicMethods); };
6964	return PhotoSwipe;
6965});
6966/*! PhotoSwipe Default UI - 4.1.0 - 2015-07-11
6967* http://photoswipe.com
6968* Copyright (c) 2015 Dmitry Semenov; */
6969/**
6970*
6971* UI on top of main sliding area (caption, arrows, close button, etc.).
6972* Built just using public methods/properties of PhotoSwipe.
6973* 
6974*/
6975(function (root, factory) { 
6976	if (typeof define === 'function' && define.amd) {
6977		define(factory);
6978	} else if (typeof exports === 'object') {
6979		module.exports = factory();
6980	} else {
6981		root.PhotoSwipeUI_Default = factory();
6982	}
6983})(this, function () {
6984
6985	'use strict';
6986
6987
6988
6989var PhotoSwipeUI_Default =
6990 function(pswp, framework) {
6991
6992	var ui = this;
6993	var _overlayUIUpdated = false,
6994		_controlsVisible = true,
6995		_fullscrenAPI,
6996		_controls,
6997		_captionContainer,
6998		_fakeCaptionContainer,
6999		_indexIndicator,
7000		_shareButton,
7001		_shareModal,
7002		_shareModalHidden = true,
7003		_initalCloseOnScrollValue,
7004		_isIdle,
7005		_listen,
7006
7007		_loadingIndicator,
7008		_loadingIndicatorHidden,
7009		_loadingIndicatorTimeout,
7010
7011		_galleryHasOneSlide,
7012
7013		_options,
7014		_defaultUIOptions = {
7015			barsSize: {top:44, bottom:'auto'},
7016			closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'], 
7017			timeToIdle: 4000, 
7018			timeToIdleOutside: 1000,
7019			loadingIndicatorDelay: 1000, // 2s
7020			
7021			addCaptionHTMLFn: function(item, captionEl /*, isFake */) {
7022				if(!item.title) {
7023					captionEl.children[0].innerHTML = '';
7024					return false;
7025				}
7026				captionEl.children[0].innerHTML = item.title;
7027				return true;
7028			},
7029
7030			closeEl:true,
7031			captionEl: true,
7032			fullscreenEl: true,
7033			zoomEl: true,
7034			shareEl: true,
7035			counterEl: true,
7036			arrowEl: true,
7037			preloaderEl: true,
7038
7039			tapToClose: false,
7040			tapToToggleControls: true,
7041
7042			clickToCloseNonZoomable: true,
7043
7044			shareButtons: [
7045				{id:'facebook', label:'Share on Facebook', url:'https://www.facebook.com/sharer/sharer.php?u={{url}}'},
7046				{id:'twitter', label:'Tweet', url:'https://twitter.com/intent/tweet?text={{text}}&url={{url}}'},
7047				{id:'pinterest', label:'Pin it', url:'http://www.pinterest.com/pin/create/button/'+
7048													'?url={{url}}&media={{image_url}}&description={{text}}'},
7049				{id:'download', label:'Download image', url:'{{raw_image_url}}', download:true}
7050			],
7051			getImageURLForShare: function( /* shareButtonData */ ) {
7052				return pswp.currItem.src || '';
7053			},
7054			getPageURLForShare: function( /* shareButtonData */ ) {
7055				return window.location.href;
7056			},
7057			getTextForShare: function( /* shareButtonData */ ) {
7058				return pswp.currItem.title || '';
7059			},
7060				
7061			indexIndicatorSep: ' / '
7062
7063		},
7064		_blockControlsTap,
7065		_blockControlsTapTimeout;
7066
7067
7068
7069	var _onControlsTap = function(e) {
7070			if(_blockControlsTap) {
7071				return true;
7072			}
7073
7074
7075			e = e || window.event;
7076
7077			if(_options.timeToIdle && _options.mouseUsed && !_isIdle) {
7078				// reset idle timer
7079				_onIdleMouseMove();
7080			}
7081
7082
7083			var target = e.target || e.srcElement,
7084				uiElement,
7085				clickedClass = target.className,
7086				found;
7087
7088			for(var i = 0; i < _uiElements.length; i++) {
7089				uiElement = _uiElements[i];
7090				if(uiElement.onTap && clickedClass.indexOf('pswp__' + uiElement.name ) > -1 ) {
7091					uiElement.onTap();
7092					found = true;
7093
7094				}
7095			}
7096
7097			if(found) {
7098				if(e.stopPropagation) {
7099					e.stopPropagation();
7100				}
7101				_blockControlsTap = true;
7102
7103				// Some versions of Android don't prevent ghost click event 
7104				// when preventDefault() was called on touchstart and/or touchend.
7105				// 
7106				// This happens on v4.3, 4.2, 4.1, 
7107				// older versions strangely work correctly, 
7108				// but just in case we add delay on all of them)	
7109				var tapDelay = framework.features.isOldAndroid ? 600 : 30;
7110				_blockControlsTapTimeout = setTimeout(function() {
7111					_blockControlsTap = false;
7112				}, tapDelay);
7113			}
7114
7115		},
7116		_fitControlsInViewport = function() {
7117			return !pswp.likelyTouchDevice || _options.mouseUsed || screen.width > 1200;
7118		},
7119		_togglePswpClass = function(el, cName, add) {
7120			framework[ (add ? 'add' : 'remove') + 'Class' ](el, 'pswp__' + cName);
7121		},
7122
7123		// add class when there is just one item in the gallery
7124		// (by default it hides left/right arrows and 1ofX counter)
7125		_countNumItems = function() {
7126			var hasOneSlide = (_options.getNumItemsFn() === 1);
7127
7128			if(hasOneSlide !== _galleryHasOneSlide) {
7129				_togglePswpClass(_controls, 'ui--one-slide', hasOneSlide);
7130				_galleryHasOneSlide = hasOneSlide;
7131			}
7132		},
7133		_toggleShareModalClass = function() {
7134			_togglePswpClass(_shareModal, 'share-modal--hidden', _shareModalHidden);
7135		},
7136		_toggleShareModal = function() {
7137
7138			_shareModalHidden = !_shareModalHidden;
7139			
7140			
7141			if(!_shareModalHidden) {
7142				_toggleShareModalClass();
7143				setTimeout(function() {
7144					if(!_shareModalHidden) {
7145						framework.addClass(_shareModal, 'pswp__share-modal--fade-in');
7146					}
7147				}, 30);
7148			} else {
7149				framework.removeClass(_shareModal, 'pswp__share-modal--fade-in');
7150				setTimeout(function() {
7151					if(_shareModalHidden) {
7152						_toggleShareModalClass();
7153					}
7154				}, 300);
7155			}
7156			
7157			if(!_shareModalHidden) {
7158				_updateShareURLs();
7159			}
7160			return false;
7161		},
7162
7163		_openWindowPopup = function(e) {
7164			e = e || window.event;
7165			var target = e.target || e.srcElement;
7166
7167			pswp.shout('shareLinkClick', e, target);
7168
7169			if(!target.href) {
7170				return false;
7171			}
7172
7173			if( target.hasAttribute('download') ) {
7174				return true;
7175			}
7176
7177			window.open(target.href, 'pswp_share', 'scrollbars=yes,resizable=yes,toolbar=no,'+
7178										'location=yes,width=550,height=420,top=100,left=' + 
7179										(window.screen ? Math.round(screen.width / 2 - 275) : 100)  );
7180
7181			if(!_shareModalHidden) {
7182				_toggleShareModal();
7183			}
7184			
7185			return false;
7186		},
7187		_updateShareURLs = function() {
7188			var shareButtonOut = '',
7189				shareButtonData,
7190				shareURL,
7191				image_url,
7192				page_url,
7193				share_text;
7194
7195			for(var i = 0; i < _options.shareButtons.length; i++) {
7196				shareButtonData = _options.shareButtons[i];
7197
7198				image_url = _options.getImageURLForShare(shareButtonData);
7199				page_url = _options.getPageURLForShare(shareButtonData);
7200				share_text = _options.getTextForShare(shareButtonData);
7201
7202				shareURL = shareButtonData.url.replace('{{url}}', encodeURIComponent(page_url) )
7203									.replace('{{image_url}}', encodeURIComponent(image_url) )
7204									.replace('{{raw_image_url}}', image_url )
7205									.replace('{{text}}', encodeURIComponent(share_text) );
7206
7207				shareButtonOut += '<a href="' + shareURL + '" target="_blank" '+
7208									'class="pswp__share--' + shareButtonData.id + '"' +
7209									(shareButtonData.download ? 'download' : '') + '>
7209' + 
7210									shareButtonData.label + '</a>';
7211
7212				if(_options.parseShareButtonOut) {
7213					shareButtonOut = _options.parseShareButtonOut(shareButtonData, shareButtonOut);
7214				}
7215			}
7216			_shareModal.children[0].innerHTML = shareButtonOut;
7217			_shareModal.children[0].onclick = _openWindowPopup;
7218
7219		},
7220		_hasCloseClass = function(target) {
7221			for(var  i = 0; i < _options.closeElClasses.length; i++) {
7222				if( framework.hasClass(target, 'pswp__' + _options.closeElClasses[i]) ) {
7223					return true;
7224				}
7225			}
7226		},
7227		_idleInterval,
7228		_idleTimer,
7229		_idleIncrement = 0,
7230		_onIdleMouseMove = function() {
7231			clearTimeout(_idleTimer);
7232			_idleIncrement = 0;
7233			if(_isIdle) {
7234				ui.setIdle(false);
7235			}
7236		},
7237		_onMouseLeaveWindow = function(e) {
7238			e = e ? e : window.event;
7239			var from = e.relatedTarget || e.toElement;
7240			if (!from || from.nodeName === 'HTML') {
7241				clearTimeout(_idleTimer);
7242				_idleTimer = setTimeout(function() {
7243					ui.setIdle(true);
7244				}, _options.timeToIdleOutside);
7245			}
7246		},
7247		_setupFullscreenAPI = function() {
7248			if(_options.fullscreenEl) {
7249				if(!_fullscrenAPI) {
7250					_fullscrenAPI = ui.getFullscreenAPI();
7251				}
7252				if(_fullscrenAPI) {
7253					framework.bind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
7254					ui.updateFullscreen();
7255					framework.addClass(pswp.template, 'pswp--supports-fs');
7256				} else {
7257					framework.removeClass(pswp.template, 'pswp--supports-fs');
7258				}
7259			}
7260		},
7261		_setupLoadingIndicator = function() {
7262			// Setup loading indicator
7263			if(_options.preloaderEl) {
7264			
7265				_toggleLoadingIndicator(true);
7266
7267				_listen('beforeChange', function() {
7268
7269					clearTimeout(_loadingIndicatorTimeout);
7270
7271					// display loading indicator with delay
7272					_loadingIndicatorTimeout = setTimeout(function() {
7273
7274						if(pswp.currItem && pswp.currItem.loading) {
7275
7276							if( !pswp.allowProgressiveImg() || (pswp.currItem.img && !pswp.currItem.img.naturalWidth)  ) {
7277								// show preloader if progressive loading is not enabled, 
7278								// or image width is not defined yet (because of slow connection)
7279								_toggleLoadingIndicator(false); 
7280								// items-controller.js function allowProgressiveImg
7281							}
7282							
7283						} else {
7284							_toggleLoadingIndicator(true); // hide preloader
7285						}
7286
7287					}, _options.loadingIndicatorDelay);
7288					
7289				});
7290				_listen('imageLoadComplete', function(index, item) {
7291					if(pswp.currItem === item) {
7292						_toggleLoadingIndicator(true);
7293					}
7294				});
7295
7296			}
7297		},
7298		_toggleLoadingIndicator = function(hide) {
7299			if( _loadingIndicatorHidden !== hide ) {
7300				_togglePswpClass(_loadingIndicator, 'preloader--active', !hide);
7301				_loadingIndicatorHidden = hide;
7302			}
7303		},
7304		_applyNavBarGaps = function(item) {
7305			var gap = item.vGap;
7306
7307			if( _fitControlsInViewport() ) {
7308				
7309				var bars = _options.barsSize; 
7310				if(_options.captionEl && bars.bottom === 'auto') {
7311					if(!_fakeCaptionContainer) {
7312						_fakeCaptionContainer = framework.createEl('pswp__caption pswp__caption--fake');
7313						_fakeCaptionContainer.appendChild( framework.createEl('pswp__caption__center') );
7314						_controls.insertBefore(_fakeCaptionContainer, _captionContainer);
7315						framework.addClass(_controls, 'pswp__ui--fit');
7316					}
7317					if( _options.addCaptionHTMLFn(item, _fakeCaptionContainer, true) ) {
7318
7319						var captionSize = _fakeCaptionContainer.clientHeight;
7320						gap.bottom = parseInt(captionSize,10) || 44;
7321					} else {
7322						gap.bottom = bars.top; // if no caption, set size of bottom gap to size of top
7323					}
7324				} else {
7325					gap.bottom = bars.bottom === 'auto' ? 0 : bars.bottom;
7326				}
7327				
7328				// height of top bar is static, no need to calculate it
7329				gap.top = bars.top;
7330			} else {
7331				gap.top = gap.bottom = 0;
7332			}
7333		},
7334		_setupIdle = function() {
7335			// Hide controls when mouse is used
7336			if(_options.timeToIdle) {
7337				_listen('mouseUsed', function() {
7338					
7339					framework.bind(document, 'mousemove', _onIdleMouseMove);
7340					framework.bind(document, 'mouseout', _onMouseLeaveWindow);
7341
7342					_idleInterval = setInterval(function() {
7343						_idleIncrement++;
7344						if(_idleIncrement === 2) {
7345							ui.setIdle(true);
7346						}
7347					}, _options.timeToIdle / 2);
7348				});
7349			}
7350		},
7351		_setupHidingControlsDuringGestures = function() {
7352
7353			// Hide controls on vertical drag
7354			_listen('onVerticalDrag', function(now) {
7355				if(_controlsVisible && now < 0.95) {
7356					ui.hideControls();
7357				} else if(!_controlsVisible && now >= 0.95) {
7358					ui.showControls();
7359				}
7360			});
7361
7362			// Hide controls when pinching to close
7363			var pinchControlsHidden;
7364			_listen('onPinchClose' , function(now) {
7365				if(_controlsVisible && now < 0.9) {
7366					ui.hideControls();
7367					pinchControlsHidden = true;
7368				} else if(pinchControlsHidden && !_controlsVisible && now > 0.9) {
7369					ui.showControls();
7370				}
7371			});
7372
7373			_listen('zoomGestureEnded', function() {
7374				pinchControlsHidden = false;
7375				if(pinchControlsHidden && !_controlsVisible) {
7376					ui.showControls();
7377				}
7378			});
7379
7380		};
7381
7382
7383
7384	var _uiElements = [
7385		{ 
7386			name: 'caption', 
7387			option: 'captionEl',
7388			onInit: function(el) {  
7389				_captionContainer = el; 
7390			} 
7391		},
7392		{ 
7393			name: 'share-modal', 
7394			option: 'shareEl',
7395			onInit: function(el) {  
7396				_shareModal = el;
7397			},
7398			onTap: function() {
7399				_toggleShareModal();
7400			} 
7401		},
7402		{ 
7403			name: 'button--share', 
7404			option: 'shareEl',
7405			onInit: function(el) { 
7406				_shareButton = el;
7407			},
7408			onTap: function() {
7409				_toggleShareModal();
7410			} 
7411		},
7412		{ 
7413			name: 'button--zoom', 
7414			option: 'zoomEl',
7415			onTap: pswp.toggleDesktopZoom
7416		},
7417		{ 
7418			name: 'counter', 
7419			option: 'counterEl',
7420			onInit: function(el) {  
7421				_indexIndicator = el;
7422			} 
7423		},
7424		{ 
7425			name: 'button--close', 
7426			option: 'closeEl',
7427			onTap: pswp.close
7428		},
7429		{ 
7430			name: 'button--arrow--left', 
7431			option: 'arrowEl',
7432			onTap: pswp.prev
7433		},
7434		{ 
7435			name: 'button--arrow--right', 
7436			option: 'arrowEl',
7437			onTap: pswp.next
7438		},
7439		{ 
7440			name: 'button--fs', 
7441			option: 'fullscreenEl',
7442			onTap: function() {  
7443				if(_fullscrenAPI.isFullscreen()) {
7444					_fullscrenAPI.exit();
7445				} else {
7446					_fullscrenAPI.enter();
7447				}
7448			} 
7449		},
7450		{ 
7451			name: 'preloader', 
7452			option: 'preloaderEl',
7453			onInit: function(el) {  
7454				_loadingIndicator = el;
7455			} 
7456		}
7457
7458	];
7459
7460	var _setupUIElements = function() {
7461		var item,
7462			classAttr,
7463			uiElement;
7464
7465		var loopThroughChildElements = function(sChildren) {
7466			if(!sChildren) {
7467				return;
7468			}
7469
7470			var l = sChildren.length;
7471			for(var i = 0; i < l; i++) {
7472				item = sChildren[i];
7473				classAttr = item.className;
7474
7475				for(var a = 0; a < _uiElements.length; a++) {
7476					uiElement = _uiElements[a];
7477
7478					if(classAttr.indexOf('pswp__' + uiElement.name) > -1  ) {
7479
7480						if( _options[uiElement.option] ) { // if element is not disabled from options
7481							
7482							framework.removeClass(item, 'pswp__element--disabled');
7483							if(uiElement.onInit) {
7484								uiElement.onInit(item);
7485							}
7486							
7487							//item.style.display = 'block';
7488						} else {
7489							framework.addClass(item, 'pswp__element--disabled');
7490							//item.style.display = 'none';
7491						}
7492					}
7493				}
7494			}
7495		};
7496		loopThroughChildElements(_controls.children);
7497
7498		var topBar =  framework.getChildByClass(_controls, 'pswp__top-bar');
7499		if(topBar) {
7500			loopThroughChildElements( topBar.children );
7501		}
7502	};
7503
7504
7505	
7506
7507	ui.init = function() {
7508
7509		// extend options
7510		framework.extend(pswp.options, _defaultUIOptions, true);
7511
7512		// create local link for fast access
7513		_options = pswp.options;
7514
7515		// find pswp__ui element
7516		_controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui');
7517
7518		// create local link
7519		_listen = pswp.listen;
7520
7521
7522		_setupHidingControlsDuringGestures();
7523
7524		// update controls when slides change
7525		_listen('beforeChange', ui.update);
7526
7527		// toggle zoom on double-tap
7528		_listen('doubleTap', function(point) {
7529			var initialZoomLevel = pswp.currItem.initialZoomLevel;
7530			if(pswp.getZoomLevel() !== initialZoomLevel) {
7531				pswp.zoomTo(initialZoomLevel, point, 333);
7532			} else {
7533				pswp.zoomTo(_options.getDoubleTapZoom(false, pswp.currItem), point, 333);
7534			}
7535		});
7536
7537		// Allow text selection in caption
7538		_listen('preventDragEvent', function(e, isDown, preventObj) {
7539			var t = e.target || e.srcElement;
7540			if(
7541				t && 
7542				t.className && e.type.indexOf('mouse') > -1 && 
7543				( t.className.indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName) ) 
7544			) {
7545				preventObj.prevent = false;
7546			}
7547		});
7548
7549		// bind events for UI
7550		_listen('bindEvents', function() {
7551			framework.bind(_controls, 'pswpTap click', _onControlsTap);
7552			framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
7553
7554			if(!pswp.likelyTouchDevice) {
7555				framework.bind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);
7556			}
7557		});
7558
7559		// unbind events for UI
7560		_listen('unbindEvents', function() {
7561			if(!_shareModalHidden) {
7562				_toggleShareModal();
7563			}
7564
7565			if(_idleInterval) {
7566				clearInterval(_idleInterval);
7567			}
7568			framework.unbind(document, 'mouseout', _onMouseLeaveWindow);
7569			framework.unbind(document, 'mousemove', _onIdleMouseMove);
7570			framework.unbind(_controls, 'pswpTap click', _onControlsTap);
7571			framework.unbind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
7572			framework.unbind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);
7573
7574			if(_fullscrenAPI) {
7575				framework.unbind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
7576				if(_fullscrenAPI.isFullscreen()) {
7577					_options.hideAnimationDuration = 0;
7578					_fullscrenAPI.exit();
7579				}
7580				_fullscrenAPI = null;
7581			}
7582		});
7583
7584
7585		// clean up things when gallery is destroyed
7586		_listen('destroy', function() {
7587			if(_options.captionEl) {
7588				if(_fakeCaptionContainer) {
7589					_controls.removeChild(_fakeCaptionContainer);
7590				}
7591				framework.removeClass(_captionContainer, 'pswp__caption--empty');
7592			}
7593
7594			if(_shareModal) {
7595				_shareModal.children[0].onclick = null;
7596			}
7597			framework.removeClass(_controls, 'pswp__ui--over-close');
7598			framework.addClass( _controls, 'pswp__ui--hidden');
7599			ui.setIdle(false);
7600		});
7601		
7602
7603		if(!_options.showAnimationDuration) {
7604			framework.removeClass( _controls, 'pswp__ui--hidden');
7605		}
7606		_listen('initialZoomIn', function() {
7607			if(_options.showAnimationDuration) {
7608				framework.removeClass( _controls, 'pswp__ui--hidden');
7609			}
7610		});
7611		_listen('initialZoomOut', function() {
7612			framework.addClass( _controls, 'pswp__ui--hidden');
7613		});
7614
7615		_listen('parseVerticalMargin', _applyNavBarGaps);
7616		
7617		_setupUIElements();
7618
7619		if(_options.shareEl && _shareButton && _shareModal) {
7620			_shareModalHidden = true;
7621		}
7622
7623		_countNumItems();
7624
7625		_setupIdle();
7626
7627		_setupFullscreenAPI();
7628
7629		_setupLoadingIndicator();
7630	};
7631
7632	ui.setIdle = function(isIdle) {
7633		_isIdle = isIdle;
7634		_togglePswpClass(_controls, 'ui--idle', isIdle);
7635	};
7636
7637	ui.update = function() {
7638		// Don't update UI if it's hidden
7639		if(_controlsVisible && pswp.currItem) {
7640			
7641			ui.updateIndexIndicator();
7642
7643			if(_options.captionEl) {
7644				_options.addCaptionHTMLFn(pswp.currItem, _captionContainer);
7645
7646				_togglePswpClass(_captionContainer, 'caption--empty', !pswp.currItem.title);
7647			}
7648
7649			_overlayUIUpdated = true;
7650
7651		} else {
7652			_overlayUIUpdated = false;
7653		}
7654
7655		if(!_shareModalHidden) {
7656			_toggleShareModal();
7657		}
7658
7659		_countNumItems();
7660	};
7661
7662	ui.updateFullscreen = function(e) {
7663
7664		if(e) {
7665			// some browsers change window scroll position during the fullscreen
7666			// so PhotoSwipe updates it just in case
7667			setTimeout(function() {
7668				pswp.setScrollOffset( 0, framework.getScrollY() );
7669			}, 50);
7670		}
7671		
7672		// toogle pswp--fs class on root element
7673		framework[ (_fullscrenAPI.isFullscreen() ? 'add' : 'remove') + 'Class' ](pswp.template, 'psw
7673p--fs');
7674	};
7675
7676	ui.updateIndexIndicator = function() {
7677		if(_options.counterEl) {
7678			_indexIndicator.innerHTML = (pswp.getCurrentIndex()+1) + 
7679										_options.indexIndicatorSep + 
7680										_options.getNumItemsFn();
7681		}
7682	};
7683	
7684	ui.onGlobalTap = function(e) {
7685		e = e || window.event;
7686		var target = e.target || e.srcElement;
7687
7688		if(_blockControlsTap) {
7689			return;
7690		}
7691
7692		if(e.detail && e.detail.pointerType === 'mouse') {
7693
7694			// close gallery if clicked outside of the image
7695			if(_hasCloseClass(target)) {
7696				pswp.close();
7697				return;
7698			}
7699
7700			if(framework.hasClass(target, 'pswp__img')) {
7701				if(pswp.getZoomLevel() === 1 && pswp.getZoomLevel() <= pswp.currItem.fitRatio) {
7702					if(_options.clickToCloseNonZoomable) {
7703						pswp.close();
7704					}
7705				} else {
7706					pswp.toggleDesktopZoom(e.detail.releasePoint);
7707				}
7708			}
7709			
7710		} else {
7711
7712			// tap anywhere (except buttons) to toggle visibility of controls
7713			if(_options.tapToToggleControls) {
7714				if(_controlsVisible) {
7715					ui.hideControls();
7716				} else {
7717					ui.showControls();
7718				}
7719			}
7720
7721			// tap to close gallery
7722			if(_options.tapToClose && (framework.hasClass(target, 'pswp__img') || _hasCloseClass(target)) ) {
7723				pswp.close();
7724				return;
7725			}
7726			
7727		}
7728	};
7729	ui.onMouseOver = function(e) {
7730		e = e || window.event;
7731		var target = e.target || e.srcElement;
7732
7733		// add class when mouse is over an element that should close the gallery
7734		_togglePswpClass(_controls, 'ui--over-close', _hasCloseClass(target));
7735	};
7736
7737	ui.hideControls = function() {
7738		framework.addClass(_controls,'pswp__ui--hidden');
7739		_controlsVisible = false;
7740	};
7741
7742	ui.showControls = function() {
7743		_controlsVisible = true;
7744		if(!_overlayUIUpdated) {
7745			ui.update();
7746		}
7747		framework.removeClass(_controls,'pswp__ui--hidden');
7748	};
7749
7750	ui.supportsFullscreen = function() {
7751		var d = document;
7752		return !!(d.exitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen || d.msExitFullscreen);
7753	};
7754
7755	ui.getFullscreenAPI = function() {
7756		var dE = document.documentElement,
7757			api,
7758			tF = 'fullscreenchange';
7759
7760		if (dE.requestFullscreen) {
7761			api = {
7762				enterK: 'requestFullscreen',
7763				exitK: 'exitFullscreen',
7764				elementK: 'fullscreenElement',
7765				eventK: tF
7766			};
7767
7768		} else if(dE.mozRequestFullScreen ) {
7769			api = {
7770				enterK: 'mozRequestFullScreen',
7771				exitK: 'mozCancelFullScreen',
7772				elementK: 'mozFullScreenElement',
7773				eventK: 'moz' + tF
7774			};
7775
7776			
7777
7778		} else if(dE.webkitRequestFullscreen) {
7779			api = {
7780				enterK: 'webkitRequestFullscreen',
7781				exitK: 'webkitExitFullscreen',
7782				elementK: 'webkitFullscreenElement',
7783				eventK: 'webkit' + tF
7784			};
7785
7786		} else if(dE.msRequestFullscreen) {
7787			api = {
7788				enterK: 'msRequestFullscreen',
7789				exitK: 'msExitFullscreen',
7790				elementK: 'msFullscreenElement',
7791				eventK: 'MSFullscreenChange'
7792			};
7793		}
7794
7795		if(api) {
7796			api.enter = function() { 
7797				// disable close-on-scroll in fullscreen
7798				_initalCloseOnScrollValue = _options.closeOnScroll; 
7799				_options.closeOnScroll = false; 
7800
7801				if(this.enterK === 'webkitRequestFullscreen') {
7802					pswp.template[this.enterK]( Element.ALLOW_KEYBOARD_INPUT );
7803				} else {
7804					return pswp.template[this.enterK](); 
7805				}
7806			};
7807			api.exit = function() { 
7808				_options.closeOnScroll = _initalCloseOnScrollValue;
7809
7810				return document[this.exitK](); 
7811
7812			};
7813			api.isFullscreen = function() { return document[this.elementK]; };
7814		}
7815
7816		return api;
7817	};
7818
7819
7820
7821};
7822return PhotoSwipeUI_Default;
7823
7824
7825});
7826/*! Picturefill - v2.2.0-beta - 2014-10-30
7827* http://scottjehl.github.io/picturefill
7828* Copyright (c) 2014 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */
7829/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */
7830
7831window.matchMedia || (window.matchMedia = function() {
7832	"use strict";
7833
7834	// For browsers that support matchMedium api such as IE 9 and webkit
7835	var styleMedia = (window.styleMedia || window.media);
7836
7837	// For those that don't support matchMedium
7838	if (!styleMedia) {
7839		var style       = document.createElement('style'),
7840			script      = document.getElementsByTagName('script')[0],
7841			info        = null;
7842
7843		style.type  = 'text/css';
7844		style.id    = 'matchmediajs-test';
7845
7846		script.parentNode.insertBefore(style, script);
7847
7848		// 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers
7849		info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle;
7850
7851		styleMedia = {
7852			matchMedium: function(media) {
7853				var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }';
7854
7855				// 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers
7856				if (style.styleSheet) {
7857					style.styleSheet.cssText = text;
7858				} else {
7859					style.textContent = text;
7860				}
7861
7862				// Test if media query is true or false
7863				return info.width === '1px';
7864			}
7865		};
7866	}
7867
7868	return function(media) {
7869		return {
7870			matches: styleMedia.matchMedium(media || 'all'),
7871			media: media || 'all'
7872		};
7873	};
7874}());
7875/*! Picturefill - Responsive Images that work today.
7876*  Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar )
7877*  License: MIT/GPLv2
7878*  Spec: http://picture.responsiveimages.org/
7879*/
7880(function( w, doc, image ) {
7881	// Enable strict mode
7882	"use strict";
7883
7884	// If picture is supported, well, that's awesome. Let's get outta here...
7885	if ( w.HTMLPictureElement ) {
7886		w.picturefill = function() { };
7887		return;
7888	}
7889
7890	// HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround)
7891	doc.createElement( "picture" );
7892
7893	// local object for method references and testing exposure
7894	var pf = {};
7895
7896	// namespace
7897	pf.ns = "picturefill";
7898
7899	// srcset support test
7900	(function() {
7901		pf.srcsetSupported = "srcset" in image;
7902		pf.sizesSupported = "sizes" in image;
7903	})();
7904
7905	// just a string trim workaround
7906	pf.trim = function( str ) {
7907		return str.trim ? str.trim() : str.replace( /^\s+|\s+$/g, "" );
7908	};
7909
7910	// just a string endsWith workaround
7911	pf.endsWith = function( str, suffix ) {
7912		return str.endsWith ? str.endsWith( suffix ) : str.indexOf( suffix, str.length - suffix.length ) !== -1;
7913	};
7914
7915	/**
7916	 * Shortcut method for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests )
7917	 */
7918	pf.restrictsMixedContent = function() {
7919		return w.location.protocol === "https:";
7920	};
7921	/**
7922	 * Shortcut method for matchMedia ( for easy overriding in tests )
7923	 */
7924
7925	pf.matchesMedia = function( media ) {
7926		return w.matchMedia && w.matchMedia( media ).matches;
7927	};
7928
7929	// Shortcut method for `devicePixelRatio` ( for easy overriding in tests )
7930	pf.getDpr = function() {
7931		return ( w.devicePixelRatio || 1 );
7932	};
7933
7934	/**
7935	 * Get width in css pixel value from a "length" value
7936	 * http://dev.w3.org/csswg/css-values-3/#length-value
7937	 */
7938	pf.getWidthFromLength = function( length ) {
7939		// If a length is specified and doesn’t contain a percentage, and it is greater than 0 or using `calc`, use it. Else, use the `100vw` default.
7940		length = length && length.indexOf( "%" ) > -1 === false && ( parseFloat( length ) > 0 || length.indexOf( "calc(" ) > -1 ) ? length : "100vw";
7941
7942		/**
7943		 * If length is specified in  `vw` units, use `%` instead since the div we’re measuring
7944		 * is injected at the top of the document.
7945		 *
7946		 * TODO: maybe we should put this behind a feature test for `vw`?
7947		 */
7948		length = length.replace( "vw", "%" );
7949
7950		// Create a cached element for getting length value widths
7951		if ( !pf.lengthEl ) {
7952			pf.lengthEl = doc.createElement( "div" );
7953
7954			// Positioning styles help prevent padding/margin/width on `html` or `body` from throwing calculations off.
7955			pf.lengthEl.style.cssText = "border:0;display:block;font-size:1em;
7955left:0;margin:0;padding:0;position:absolute;visibility:hidden";
7956		}
7957
7958		pf.lengthEl.style.width = length;
7959
7960		doc.body.appendChild(pf.lengthEl);
7961
7962		// Add a class, so that everyone knows where this element comes from
7963		pf.lengthEl.className = "helper-from-picturefill-js";
7964
7965		if ( pf.lengthEl.offsetWidth <= 0 ) {
7966			// Something has gone wrong. `calc()` is in use and unsupported, most likely. Default to `100vw` (`100%`, for broader support.):
7967			pf.lengthEl.style.width = doc.documentElement.offsetWidth + "px";
7968		}
7969
7970		var offsetWidth = pf.lengthEl.offsetWidth;
7971
7972		doc.body.removeChild( pf.lengthEl );
7973
7974		return offsetWidth;
7975	};
7976
7977	// container of supported mime types that one might need to qualify before using
7978	pf.types =  {};
7979
7980	// Add support for standard mime types
7981	pf.types[ "image/jpeg" ] = true;
7982	pf.types[ "image/gif" ] = true;
7983	pf.types[ "image/png" ] = true;
7984
7985	// test svg support
7986	pf.types[ "image/svg+xml" ] = doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1");
7987
7988	// test webp support, only when the markup calls for it
7989	pf.types[ "image/webp" ] = function() {
7990		// based on Modernizr's lossless img-webp test
7991		// note: asynchronous
7992		var type = "image/webp";
7993
7994		image.onerror = function() {
7995			pf.types[ type ] = false;
7996			picturefill();
7997		};
7998		image.onload = function() {
7999			pf.types[ type ] = image.width === 1;
8000			picturefill();
8001		};
8002		image.src = "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=";
8003	};
8004
8005	/**
8006	 * Takes a source element and checks if its type attribute is present and if so, supported
8007	 * Note: for type tests that require a async logic,
8008	 * you can define them as a function that'll run only if that type needs to be tested. Just make the test function call picturefill again when it is complete.
8009	 * see the async webp test above for example
8010	 */
8011	pf.verifyTypeSupport = function( source ) {
8012		var type = source.getAttribute( "type" );
8013		// if type attribute exists, return test result, otherwise return true
8014		if ( type === null || type === "" ) {
8015			return true;
8016		} else {
8017			// if the type test is a function, run it and return "pending" status. The function will rerun picturefill on pending elements once finished.
8018			if ( typeof( pf.types[ type ] ) === "function" ) {
8019				pf.types[ type ]();
8020				return "pending";
8021			} else {
8022				return pf.types[ type ];
8023			}
8024		}
8025	};
8026
8027	// Parses an individual `size` and returns the length, and optional media query
8028	pf.parseSize = function( sourceSizeStr ) {
8029		var match = /(\([^)]+\))?\s*(.+)/g.exec( sourceSizeStr );
8030		return {
8031			media: match && match[1],
8032			length: match && match[2]
8033		};
8034	};
8035
8036	// Takes a string of sizes and returns the width in pixels as a number
8037	pf.findWidthFromSourceSize = function( sourceSizeListStr ) {
8038		// Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33%
8039		//                            or (min-width:30em) calc(30% - 15px)
8040		var sourceSizeList = pf.trim( sourceSizeListStr ).split( /\s*,\s*/ ),
8041			winningLength;
8042
8043		for ( var i = 0, len = sourceSizeList.length; i < len; i++ ) {
8044			// Match <media-condition>? length, ie ( min-width: 50em ) 100%
8045			var sourceSize = sourceSizeList[ i ],
8046				// Split "( min-width: 50em ) 100%" into separate strings
8047				parsedSize = pf.parseSize( sourceSize ),
8048				length = parsedSize.length,
8049				media = parsedSize.media;
8050
8051			if ( !length ) {
8052				continue;
8053			}
8054			if ( !media || pf.matchesMedia( media ) ) {
8055				// if there is no media query or it matches, choose this as our winning length
8056				// and end algorithm
8057				winningLength = length;
8058				break;
8059			}
8060		}
8061
8062		// pass the length to a method that can properly determine length
8063		// in pixels based on these formats: http://dev.w3.org/csswg/css-values-3/#length-value
8064		return pf.getWidthFromLength( winningLength );
8065	};
8066
8067	pf.parseSrcset = function( srcset ) {
8068		/**
8069		 * A lot of this was pulled from Boris Smus’ parser for the now-defunct WHATWG `srcset`
8070		 * https://github.com/borismus/srcset-polyfill/blob/master/js/sr
8070cset-info.js
8071		 *
8072		 * 1. Let input (`srcset`) be the value passed to this algorithm.
8073		 * 2. Let position be a pointer into input, initially pointing at the start of the string.
8074		 * 3. Let raw candidates be an initially empty ordered list of URLs with associated 
8075		 *    unparsed descriptors. The order of entries in the list is the order in which entries 
8076		 *    are added to the list.
8077		 */
8078		var candidates = [];
8079
8080		while ( srcset !== "" ) {
8081			srcset = srcset.replace( /^\s+/g, "" );
8082
8083			// 5. Collect a sequence of characters that are not space characters, and let that be url.
8084			var pos = srcset.search(/\s/g),
8085				url, descriptor = null;
8086
8087			if ( pos !== -1 ) {
8088				url = srcset.slice( 0, pos );
8089
8090				var last = url.slice(-1);
8091
8092				// 6. If url ends with a U+002C COMMA character (,), remove that character from url
8093				// and let descriptors be the empty string. Otherwise, follow these substeps
8094				// 6.1. If url is empty, then jump to the step labeled descriptor parser.
8095
8096				if ( last === "," || url === "" ) {
8097					url = url.replace( /,+$/, "" );
8098					descriptor = "";
8099				}
8100				srcset = srcset.slice( pos + 1 );
8101
8102				// 6.2. Collect a sequence of characters that are not U+002C COMMA characters (,), and 
8103				// let that be descriptors.
8104				if ( descriptor === null ) {
8105					var descpos = srcset.indexOf( "," );
8106					if ( descpos !== -1 ) {
8107						descriptor = srcset.slice( 0, descpos );
8108						srcset = srcset.slice( descpos + 1 );
8109					} else {
8110						descriptor = srcset;
8111						srcset = "";
8112					}
8113				}
8114			} else {
8115				url = srcset;
8116				srcset = "";
8117			}
8118
8119			// 7. Add url to raw candidates, associated with descriptors.
8120			if ( url || descriptor ) {
8121				candidates.push({
8122					url: url,
8123					descriptor: descriptor
8124				});
8125			}
8126		}
8127		return candidates;
8128	};
8129
8130	pf.parseDescriptor = function( descriptor, sizesattr ) {
8131		// 11. Descriptor parser: Let candidates be an initially empty source set. The order of entries in the list 
8132		// is the order in which entries are added to the list.
8133		var sizes = sizesattr || "100vw",
8134			sizeDescriptor = descriptor && descriptor.replace( /(^\s+|\s+$)/g, "" ),
8135			widthInCssPixels = pf.findWidthFromSourceSize( sizes ),
8136			resCandidate;
8137
8138			if ( sizeDescriptor ) {
8139				var splitDescriptor = sizeDescriptor.split(" ");
8140
8141				for (var i = splitDescriptor.length - 1; i >= 0; i--) {
8142					var curr = splitDescriptor[ i ],
8143						lastchar = curr && curr.slice( curr.length - 1 );
8144
8145					if ( ( lastchar === "h" || lastchar === "w" ) && !pf.sizesSupported ) {
8146						resCandidate = parseFloat( ( parseInt( curr, 10 ) / widthInCssPixels ) );
8147					} else if ( lastchar === "x" ) {
8148						var res = curr && parseFloat( curr, 10 );
8149						resCandidate = res && !isNaN( res ) ? res : 1;
8150					}
8151				}
8152			}
8153		return resCandidate || 1;
8154	};
8155
8156	/**
8157	 * Takes a srcset in the form of url/
8158	 * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or
8159	 *     "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or
8160	 *     "images/pic-small.png"
8161	 * Get an array of image candidates in the form of
8162	 *      {url: "/foo/bar.png", resolution: 1}
8163	 * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value
8164	 * If sizes is specified, resolution is calculated
8165	 */
8166	pf.getCandidatesFromSourceSet = function( srcset, sizes ) {
8167		var candidates = pf.parseSrcset( srcset ),
8168			formattedCandidates = [];
8169
8170		for ( var i = 0, len = candidates.length; i < len; i++ ) {
8171			var candidate = candidates[ i ];
8172
8173			formattedCandidates.push({
8174				url: candidate.url,
8175				resolution: pf.parseDescriptor( candidate.descriptor, sizes )
8176			});
8177		}
8178		return formattedCandidates;
8179	};
8180
8181	/**
8182	 * if it's an img element and it has a srcset property,
8183	 * we need to remove the attribute so we can manipulate src
8184	 * (the property's existence infers native srcset support, and a srcset-supporting browser will prioritize srcset's value over our winning picture candidate)
8185	 * this moves srcset's value to memory for later use and removes the attr
8186	 */
8187	pf.dodgeSrcset = function( img ) {
8188		if ( img.srcset ) {
8189			img[ pf.ns ].srcset = img.srcset;
8190			img.removeAttribute( "srcset" );
8191		}
8192	};
8193
8194	// Accept a source or img element and process its srcset and sizes attrs
8195	pf.processSourceSet = function( el ) {
8196		var srcset = el.getAttribute( "srcset" ),
8197			sizes = el.getAttribute( "sizes" ),
8198			candidates = [];
8199
8200		// if it's an img element, use the cached srcset property (defined or not)
8201		if ( el.nodeName.toUpperCase() === "IMG" && el[ pf.ns ] && el[ pf.ns ].srcset ) {
8202			srcset = el[ pf.ns ].srcset;
8203		}
8204
8205		if ( srcset ) {
8206			candidates = pf.getCandidatesFromSourceSet( srcset, sizes );
8207		}
8208		return candidates;
8209	};
8210
8211	pf.applyBestCandidate = function( candidates, picImg ) {
8212		var candidate,
8213			length,
8214			bestCandidate;
8215
8216		candidates.sort( pf.ascendingSort );
8217
8218		length = candidates.length;
8219		bestCandidate = candidates[ length - 1 ];
8220
8221		for ( var i = 0; i < length; i++ ) {
8222			candidate = candidates[ i ];
8223			if ( candidate.resolution >= pf.getDpr() ) {
8224				bestCandidate = candidate;
8225				break;
8226			}
8227		}
8228
8229		if ( bestCandidate && !pf.endsWith( picImg.src, bestCandidate.url ) ) {
8230			if ( pf.restrictsMixedContent() && bestCandidate.url.substr(0, "http:".length).toLowerCase() === "http:" ) {
8231				if ( typeof console !== undefined ) {
8232					console.warn( "Blocked mixed content image " + bestCandidate.url );
8233				}
8234			} else {
8235				picImg.src = bestCandidate.url;
8236				// currentSrc attribute and property to match
8237				// http://picture.responsiveimages.org/#the-img-element
8238				picImg.currentSrc = picImg.src;
8239
8240				var style = picImg.style || {},
8241					WebkitBackfaceVisibility = "webkitBackfaceVisibility" in style,
8242					currentZoom = style.zoom;
8243
8244				if (WebkitBackfaceVisibility) { // See: https://github.com/scottjehl/picturefill/issues/332
8245					style.zoom = ".999";
8246
8247					WebkitBackfaceVisibility = picImg.offsetWidth;
8248
8249					style.zoom = currentZoom;
8250				}
8251			}
8252		}
8253	};
8254
8255	pf.ascendingSort = function( a, b ) {
8256		return a.resolution - b.resolution;
8257	};
8258
8259	/**
8260	 * In IE9, <source> elements get removed if they aren't children of
8261	 * video elements. Thus, we conditionally wrap source elements
8262	 * using <!--[if IE 9]><video style="display: none;"><![endif]-->
8263	 * and must account for that here by moving those source elements
8264	 * back into the picture element.
8265	 */
8266	pf.removeVideoShim = function( picture ) {
8267		var videos = picture.getElementsByTagName( "video" );
8268		if ( videos.length ) {
8269			var video = videos[ 0 ],
8270				vsources = video.getElementsByTagName( "source" );
8271			while ( vsources.length ) {
8272				picture.insertBefore( vsources[ 0 ], video );
8273			}
8274			// Remove the video element once we're finished removing its children
8275			video.parentNode.removeChild( video );
8276		}
8277	};
8278
8279	/**
8280	 * Find all `img` elements, and add them to the candidate list if they have
8281	 * a `picture` parent, a `sizes` attribute in basic `srcset` supporting browsers,
8282	 * a `srcset` attribute at all, and they haven’t been evaluated already.
8283	 */
8284	pf.getAllElements = function() {
8285		var elems = [],
8286			imgs = doc.getElementsByTagName( "img" );
8287
8288		for ( var h = 0, len = imgs.length; h < len; h++ ) {
8289			var currImg = imgs[ h ];
8290
8291			if ( currImg.parentNode.nodeName.toUpperCase() === "PICTURE" ||
8292			( currImg.getAttribute( "srcset" ) !== null ) || currImg[ pf.ns ] && currImg[ pf.ns ].srcset !== null ) {
8293				elems.push( currImg );
8294			}
8295		}
8296		return elems;
8297	};
8298
8299	pf.getMatch = function( img, picture ) {
8300		var sources = picture.childNodes,
8301			match;
8302
8303		// Go through each child, and if they have media queries, evaluate them
8304		for ( var j = 0, slen = sources.length; j < slen; j++ ) {
8305			var source = sources[ j ];
8306
8307			// ignore non-element nodes
8308			if ( source.nodeType !== 1 ) {
8309				continue;
8310			}
8311
8312			// Hitting the `img` element that started everything stops the search for `sources`.
8313			// If no previous `source` matches, the `img` itself is evaluated later.
8314			if ( source === img ) {
8315				return match;
8316			}
8317
8318			// ignore non-`source` nodes
8319			if ( source.nodeName.toUpperCase() !== "SOURCE" ) {
8320				continue;
8321			}
8322			// if it's a source element that has the `src` property set, throw a warning in the console
8323			if ( source.getAttribute( "src" ) !== null && typeof console !== undefined ) {
8324				console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`.");
8325			}
8326
8327			var media = source.getAttribute( "media" );
8328
8329			// if source does not have a srcset attribute, skip
8330			if ( !source.getAttribute( "srcset" ) ) {
8331				continue;
8332			}
8333
8334			// if there's no media specified, OR w.matchMedia is supported
8335			if ( ( !media || pf.matchesMedia( media ) ) ) {
8336				var typeSupported = pf.verifyTypeSupport( source );
8337
8338				if ( typeSupported === true ) {
8339					match = source;
8340					break;
8341				} else if ( typeSupported === "pending" ) {
8342					return false;
8343				}
8344			}
8345		}
8346
8347		return match;
8348	};
8349
8350	function picturefill( opt ) {
8351		var elements,
8352			element,
8353			parent,
8354			firstMatch,
8355			candidates,
8356			options = opt || {};
8357
8358		elements = options.elements || pf.getAllElements();
8359
8360		// Loop through all elements
8361		for ( var i = 0, plen = elements.length; i < plen; i++ ) {
8362			element = elements[ i ];
8363			parent = element.parentNode;
8364			firstMatch = undefined;
8365			candidates = undefined;
8366
8367			// immediately skip non-`img` nodes
8368			if ( element.nodeName.toUpperCase() !== "IMG" ) {
8369				continue;
8370			}
8371
8372			// expando for caching data on the img
8373			if ( !element[ pf.ns ] ) {
8374				element[ pf.ns ] = {};
8375			}
8376
8377			// if the element has already been evaluated, skip it unless
8378			// `options.reevaluate` is set to true ( this, for example,
8379			// is set to true when running `picturefill` on `resize` ).
8380			if ( !options.reevaluate && element[ pf.ns ].evaluated ) {
8381				continue;
8382			}
8383
8384			// if `img` is in a `picture` element
8385			if ( parent.nodeName.toUpperCase() === "PICTURE" ) {
8386
8387				// IE9 video workaround
8388				pf.removeVideoShim( parent );
8389
8390				// return the first match which might undefined
8391				// returns false if there is a pending source
8392				// TODO the return type here is brutal, cleanup
8393				firstMatch = pf.getMatch( element, parent );
8394
8395				// if any sources are pending in this picture due to async type test(s)
8396				// remove the evaluated attr and skip for now ( the pending test will
8397				// rerun picturefill on this element when complete)
8398				if ( firstMatch === false ) {
8399					continue;
8400				}
8401			} else {
8402				firstMatch = undefined;
8403			}
8404
8405			// Cache and remove `srcset` if present and we’re going to be doing `picture`/`srcset`/`sizes` polyfilling to it.
8406			if ( parent.nodeName.toUpperCase() === "PICTURE" ||
8407			( element.srcset && !pf.srcsetSupported ) ||
8408			( !pf.sizesSupported && ( element.srcset && element.srcset.indexOf("w") > -1 ) ) ) {
8409				pf.dodgeSrcset( element );
8410			}
8411
8412			if ( firstMatch ) {
8413				candidates = pf.processSourceSet( firstMatch );
8414				pf.applyBestCandidate( candidates, element );
8415			} else {
8416				// No sources matched, so we’re down to processing the inner `img` as a source.
8417				candidates = pf.processSourceSet( element );
8418
8419				if ( element.srcset === undefined || element[ pf.ns ].srcset ) {
8420					// Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality.
8421					pf.applyBestCandidate( candidates, element );
8422				} // Else, resolution-only `srcset` is supported natively.
8423			}
8424
8425			// set evaluated to true to avoid unnecessary reparsing
8426			element[ pf.ns ].evaluated = true;
8427		}
8428	}
8429
8430	/**
8431	 * Sets up picture polyfill by polling the document and running
8432	 * the polyfill every 250ms until the document is ready.
8433	 * Also attaches picturefill on resize
8434	 */
8435	function runPicturefill() {
8436		picturefill();
8437		var intervalId = setInterval( function() {
8438			// When the document has finished loading, stop checking for new images
8439			// https://github.com/ded/domready/blob/master/ready.js#L15
8440			picturefill();
8441			if ( /^loaded|^i|^c/.test( doc.readyState ) ) {
8442				clearInterval( intervalId );
8443				return;
8444			}
8445		}, 250 );
8446
8447		function checkResize() {
8448			var resizeThrottle;
8449
8450			if ( !w._picturefillWorking ) {
8451				w._picturefillWorking = true;
8452				w.clearTimeout( resizeThrottle );
8453				resizeThrottle = w.setTimeout( function() {
8454					picturefill({ reevaluate: true });
8455					w._picturefillWorking = false;
8456				}, 60 );
8457			}
8458		}
8459
8460		if ( w.addEventListener ) {
8461			w.addEventListener( "resize", checkResize, false );
8462		} else if ( w.attachEvent ) {
8463			w.attachEvent( "onresize", checkResize );
8464		}
8465	}
8466
8467	runPicturefill();
8468
8469	/* expose methods for testing */
8470	picturefill._ = pf;
8471
8472	/* expose picturefill */
8473	if ( typeof module === "object" && typeof module.exports === "object" ) {
8474		// CommonJS, just export
8475		module.exports = picturefill;
8476	} else if ( typeof define === "function" && define.amd ) {
8477		// AMD support
8478		define( function() { return picturefill; } );
8479	} else if ( typeof w === "object" ) {
8480		// If no AMD and we are in the browser, attach to window
8481		w.picturefill = picturefill;
8482	}
8483
8484} )( this, this.document, new this.Image() );
8485/*! Pushy - v0.9.2 - 2014-9-13
8486* Pushy is a responsive off-canvas navigation menu using CSS transforms & transitions.
8487* https://github.com/christophery/pushy/
8488* by Christopher Yee */
8489
8490
8491$(function() {
8492	var pushy = $('.mobile-nav'), //menu css class
8493		body = $('body'),
8494		container = $('.content'), //container css class
8495		push = $('.push, .main-header, .main-nav, .main-footer'), //css class to add pushy capability
8496		siteOverlay = $('.site-overlay, .btn--close-menu'), //site overlay
8497		pushyClass = "pushy-left pushy-open", //menu position & menu open class
8498		pushyActiveClass = "pushy-active", //css class to toggle site overlay
8499		containerClass = "container-push", //container open class
8500		pushClass = "push-push", //css class to add pushy capability
8501		menuBtn = $('.btn--toggle-menu'), //css classes to toggle the menu
8502		menuSpeed = 200, //jQuery fallback menu speed
8503		menuWidth = pushy.width() + "px"; //jQuery fallback menu width
8504
8505	function togglePushy(){
8506		body.toggleClass(pushyActiveClass); //toggle site overlay
8507		pushy.toggleClass(pushyClass);
8508		container.toggleClass(containerClass);
8509		push.toggleClass(pushClass); //css class to add pushy capability
8510	}
8511
8512	function openPushyFallback(){
8513		body.addClass(pushyActiveClass);
8514
8515		if ($('html[dir="rtl"]').length) {
8516			pushy.animate({right: "0px"}, menuSpeed);
8517			container.animate({right: menuWidth}, menuSpeed);
8518			push.animate({right: menuWidth}, menuSpeed); //css class to add pushy capability
8519		} else {
8520			pushy.animate({left: "0px"}, menuSpeed);
8521			container.animate({left: menuWidth}, menuSpeed);
8522			push.animate({left: menuWidth}, menuSpeed); //css class to add pushy capability
8523		}
8524	}
8525
8526	function closePushyFallback(){
8527		body.removeClass(pushyActiveClass);
8528
8529		if ($('html[dir="rtl"]').length) {
8530			pushy.animate({right: "-" + menuWidth}, menuSpeed);
8531			container.animate({right: "0px"}, menuSpeed);
8532			push.animate({right: "0px"}, menuSpeed); //css class to add pushy capability
8533		} else {
8534			pushy.animate({left: "-" + menuWidth}, menuSpeed);
8535			container.animate({left: "0px"}, menuSpeed);
8536			push.animate({left: "0px"}, menuSpeed); //css class to add pushy capability
8537		}
8538	}
8539
8540	//checks if 3d transforms are supported removing the modernizr dependency
8541	cssTransforms3d = (function csstransforms3d(){
8542		var el = document.createElement('p'),
8543		supported = false,
8544		transforms = {
8545		    'webkitTransform':'-webkit-transform',
8546		    'OTransform':'-o-transform',
8547		    'msTransform':'-ms-transform',
8548		    'MozTransform':'-moz-transform',
8549		    'transform':'transform'
8550		};
8551
8552		// Add it to the body to get the computed style
8553		document.body.insertBefore(el, null);
8554
8555		for(var t in transforms){
8556		    if( el.style[t] !== undefined ){
8557		        el.style[t] = 'translate3d(1px,1px,1px)';
8558		        supported = window.getComputedStyle(el).getPropertyValue(transforms[t]);
8559		    }
8560		}
8561
8562		document.body.removeChild(el);
8563
8564		return (supported !== undefined && supported.length > 0 && supported !== "none");
8565	})();
8566
8567	if(cssTransforms3d){
8568		//toggle menu
8569		menuBtn.click(function() {
8570			togglePushy();
8571		});
8572		//close menu when clicking site overlay
8573		siteOverlay.click(function(){ 
8574			togglePushy();
8575		});
8576	}else{
8577		//jQuery fallback
8578		if ($('html[dir="rtl"]').length) {
8579			pushy.css({right: "-" + menuWidth}); //hide menu by default
8580		} else {
8581			pushy.css({left: "-" + menuWidth}); //hide menu by default
8582		}
8583		container.css({"overflow-x": "hidden"}); //fixes IE scrollbar issue
8584
8585		//keep track of menu state (open/close)
8586		var state = true;
8587
8588		//toggle menu
8589		menuBtn.click(function() {
8590			if (state) {
8591				openPushyFallback();
8592				state = false;
8593			} else {
8594				closePushyFallback();
8595				state = true;
8596			}
8597		});
8598
8599		//close menu when clicking site overlay
8600		siteOverlay.click(function(){ 
8601			if (state) {
8602				openPushyFallback();
8603				state = false;
8604			} else {
8605				closePushyFallback();
8606				state = true;
8607			}
8608		});
8609	}
8610});
8611var Accordion = {
8612	init: function() {
8613		Accordion.toggle();
8614	},
8615	toggle: function() {
8616		var $toggleButton = $('.accordion__button');
8617
8618		$toggleButton.on('click', function() {
8619			$(this).parents('.accordion__item').toggleClass('is--open');
8620			$(this).parents('.accordion__item').find('.accordion__content-more').slideToggle(200);
8621		});
8622	}
8623};
8624
8625$(document).ready(Accordion.init);
8626
8627/*jshint multistr: true */
8628var audioplayer = {
8629	instances: null,
8630	init: function() {
8631
8632		if (!$('.audioplayer').length) {
8633			return;
8634		}
8635
8636		// Setup the player to autoplay the next track
8637		audioplayer.instances = audiojs.createAll({
8638			swfLocation: 'typo3conf/ext/ueberdosisprovider/Resources/Public/JavaScript/audiojs.swf',
8639			createPlayer: {
8640				markup: '\
8641						<div class="audioplayer__play-pause"> \
8642							<p class="audioplayer__play"></p> \
8643							<p class="audioplayer__pause"></p> \
8644							<p class="is-loading"></p> \
8645							<p class="audioplayer__error"></p> \
8646						</div> \
8647						<div class="audioplayer__scrubber-outer"> \
8648							<div class="audioplayer__scrubber"> \
8649								<div class="audioplayer__progress"></div> \
8650								<div class="audioplayer__loaded"></div> \
8651							</div> \
8652						</div> \
8653						<div class="audioplayer__time"> \
8654							<span class="audioplayer__played">00:00</span> / <span class="audioplayer__duration">00:00</span> \
8655						</div> \
8656						<div class="audioplayer__error-message"></div>',
8657				playPauseClass: 'audioplayer__play-pause',
8658				scrubberClass: 'audioplayer__scrubber',
8659				progressClass: 'audioplayer__progress',
8660				loaderClass: 'audioplayer__loaded',
8661				timeClass: 'audioplayer__time',
8662				durationClass: 'audioplayer__duration',
8663				playedClass: 'audioplayer__played',
8664				errorMessageClass: 'audioplayer__error-message',
8665				playingClass: 'is-playing',
8666				loadingClass: 'is-loading',
8667				errorClass: 'audioplayer__error'
8668			},
8669			css: null,
8670			trackEnded: function() {
8671				var instance = audioplayer.instances[0],
8672					next = $('.audioplayer__playlist__item.is-playing').next();
8673
8674				if (!next.length) {
8675					next = $('.audioplayer__playlist__item').first();
8676				}
8677				next.addClass('is-playing')
8678					.siblings()
8679					.removeClass('is-playing');
8680
8681				instance.load( $('a', next).attr('href') );
8682				instance.play();
8683			}
8684		});
8685
8686		audioplayer.onLoad();
8687
8688		$('.audioplayer__playlist__item').on('click', audioplayer.onClick);
8689	},
8690	onLoad: function() {
8691		var instance = audioplayer.instances[0],
8692			first = $('.audioplayer__playlist a').attr('href');
8693
8694		instance.load(first);
8695
8696		$('.audioplayer__playlist__item')
8697			.first()
8698			.addClass('is-playing');
8699	},
8700	onClick: function(e) {
8701		e.preventDefault();
8702
8703		var instance = audioplayer.instances[0];
8704
8705		instance.load( $('a', this).attr('href') );
8706		instance.play();
8707
8708		$(this)
8709			.addClass('is-playing')
8710			.siblings()
8711			.removeClass('is-playing');
8712	}
8713};
8714
8715$(document).ready(audioplayer.init);
8716
8717/**
8718 * Object sets equal height for col-wrapper next to each other
8719 */
8720var ColWrapper = {
8721	init: function() {
8722		ColWrapper.setHeight();
8723		$(window).resize(ColWrapper.setHeight);
8724	},
8725	setHeight: function() {
8726		var $colWrappers = $('[class*="col-wrapper"] > .col-inner');
8727
8728		// iterate through "col-inner" = first level
8729		if ($colWrappers.length > 0) {
8730			$colWrappers.each(function() {
8731				var $cols = $(this).children('.col');
8732				if ($cols.length > 1) {
8733					var colWrappers = [],
8734						colWrapperCount = 0;
8735
8736					// iterate through "col" = second level
8737					$cols.each(function(index) {
8738						var $colWrapper = $(this).children('[class*="col-wrapper"]');
8739						if ($colWrapper.length > 0) {
8740							// reset height of boxes
8741							$colWrapper.removeAttr('style');
8742
8743							// reset no-padding class
8744							$(this).removeClass('no-padding');
8745
8746							// set colWrapper properties
8747							colWrappers.push({
8748								index: 		index,
8749								height: 	$colWrapper.height(),
8750								position: 	$colWrapper.position()
8751							});
8752							colWrapperCount++;
8753						}
8754					});
8755
8756					// the col has at least 2 colWrapper so we need the same height for the elements
8757					if (colWrapperCount > 1) {
8758						var colWrapperOnSameTopPosition = [];
8759						for (var i = 0; i < colWrappers.length; i++) {
8760							var colWrapperTopPosition = colWrappers[i].position.top;
8761							if (!Array.isArray(colWrapperOnSameTopPosition[colWrapperTopPosition])) {
8762								colWrapperOnSameTopPosition[colWrapperTopPosition] = [];
8763							}
8764							colWrapperOnSameTopPosition[colWrapperTopPosition].push(colWrappers[i]);
8765						}
8766
8767						for (var index in colWrapperOnSameTopPosition) {
8768							if (colWrapperOnSameTopPosition[index].length > 1) {
8769								// get the highest Wrapper on the same position
8770								var maxColwrapperHeight = 0;
8771								for (var j = 0; j < colWrapperOnSameTopPosition[index].length; j++) {
8772									if (typeof colWrapperOnSameTopPosition[index][j] !== 'undefined') {
8773										if (colWrapperOnSameTopPosition[index][j].height > maxColwrapperHeight) {
8774											maxColwrapperHeight = colWrapperOnSameTopPosition[index][j].height;
8775										}
8776									}
8777								}
8778
8779								// Set height for the wrapper on the same top position
8780								for (var k = 0; k < colWrapperOnSameTopPosition[index].length; k++) {
8781									$cols.children('[class*="col-wrapper"]').eq(k).height(maxColwrapperHeight);
8782								}
8783							} else {
8784								// Set no-padding class to prevent padding top for wrapped cols
8785								var elementIndex = colWrapperOnSameTopPosition[index][0].index;
8786								$cols.children('[class*="col-wrapper"]').eq(elementIndex).parents('.col').addClass('no-padding');
8787							}
8788						}
8789					}
8790				}
8791			});
8792		}
8793	}
8794};
8795
8796$(document).ready(ColWrapper.init);
8797
8798var MainHeaderActionBar = {
8799	initialized: false,
8800	onInit: function() {
8801		MainHeaderActionBar.smallDeviceDetection();
8802		$(window).resize(MainHeaderActionBar.smallDeviceDetection);
8803		MainHeaderActionBar.initialized = true;
8804	},
8805	smallDeviceDetection: function() {
8806		var $container = $('.main-header-inner'),
8807			$logo = $('.main-header__logo'),
8808			$actionBar = $('.main-header__action'),
8809			smallClass = 'main-header-inner--small',
8810			mediumClass = 'main-header-inner--medium',
8811			largeClass = 'main-header-inner--large';
8812	}
8813};
8814$(document).ready(MainHeaderActionBar.onInit);
8815
8816var map = {
8817	$element: $('.map'),
8818	frame: '.map__frame',
8819	icon_svg: 'typo3conf/ext/ueberdosisprovider/Resources/Public/Img/pin.svg',
8820	icon_png: 'typo3conf/ext/ueberdosisprovider/Resources/Public/Img/pin.png',
8821	options: {
8822		zoom: 17,
8823		disableDefaultUI: true
8824	},
8825	init: function() {
8826		if (!map.$element.length) {
8827			return;
8828		}
8829
8830		map.$element.each(function(index) {
8831			map.initMaps( $(this) );
8832		});
8833	},
8834	initMaps: function($element) {
8835		var lat = $element.data('lat'),
8836			lng = $element.data('lng');
8837
8838		map.options.center = new google.maps.LatLng(lat, lng);
8839		map.options.zoom = $element.data('zoom');
8840
8841		var googlemaps = new google.maps.Map( $element.find(map.frame)[0], map.options);
8842
8843		var isIE11 = !!navigator.userAgent.match(/Trident.*rv[ :]*11\./);
8844
8845		var icon = map.icon_png;
8846		if (Modernizr.svg && !isIE11) {
8847			icon = map.icon_svg;
8848		}
8849
8850		var image = {
8851			url: icon,
8852		};
8853
8854		var marker = new google.maps.Marker({
8855			position: map.options.center,
8856			map: googlemaps,
8857			icon: image
8858		});
8859	}
8860};
8861
8862$(document).ready(map.init);
8863
8864/**
8865 * @author: ideawu
8866 * @link: http://www.ideawu.net/prj/jquery-lazy-bind/
8867 * Modified by Patrick Baber to use $(this).
8868 */
8869
8870(function($){
8871	// lazybind for menu
8872	$.fn.lazybind = function(types, fn, timeout, abort){
8873		var timer = null;
8874		if (abort !== undefined){
8875			$(this).bind(abort, function(){
8876				clearTimeout(timer);
8877			});
8878		}
8879		return $(this).bind(types, function(event){
8880			var data = {
8881				event: event,
8882				element: this
8883			};
8884			timer = setTimeout(function(){
8885				fn.call(data.element, data.event);
8886			}, timeout);
8887		});
8888	};
8889})(jQuery);
8890
8891/**
8892 * This file is enhancing the navigation for mobile and desktop usage
8893 */
8894var Navigation = {
8895	initialized: false,
8896	init: function() {
8897		// Subnavigation delay
8898		Navigation.hoverDelay();
8899
8900		// sticky or not sticky
8901		Navigation.toggleSticky();
8902		$(window).on('scroll', Navigation.toggleSticky);
8903
8904		// size detection
8905		Navigation.detectSize();
8906		$(window).resize(Navigation.detectSize);
8907		Navigation.initialized = true;
8908
8909		// init language and contact
8910		Navigation.Language.init();
8911		Navigation.Contact.init();
8912	},
8913	/**
8914	 * Delay subnavigation opening by hover.
8915	 */
8916	hoverDelay: function() {
8917		var $navigationWithChild = $('.main-nav__lvl-1 > li.has-child');
8918
8919		$navigationWithChild.each(function() {
8920			var $this = $(this);
8921
8922			$this.lazybind(
8923				'mouseenter',
8924				function(){
8925					$this.addClass('hover');
8926				},
8927				200,
8928				'mouseleave'
8929			);
8930
8931			$this.lazybind(
8932				'mouseleave',
8933				function(){
8934					$this.removeClass('hover');
8935				},
8936				200,
8937				'mouseenter'
8938			);
8939		});
8940	},
8941	/**
8942	 * Toggle the navigation to the sticky mode if the user scrolls bellow the header.
8943	 */
8944	toggleSticky: function() {
8945		var $navigation			= $('.main-nav'),
8946			$header				 = $('.main-header'),
8947			headerHeight		  = $header.height(),
8948			windowScrollOffset  = $(document).scrollTop(),
8949			stickyClass			= 'main-nav--sticky';
8950
8951		if (windowScrollOffset > headerHeight) {
8952			if (!$navigation.hasClass(stickyClass) || Navigation.initialized === false) {
8953				$navigation.addClass(stickyClass);
8954				$navigation.trigger('sticky.navigation');
8955			}
8956		} else {
8957			if ($navigation.hasClass(stickyClass) || Navigation.initialized === false) {
8958				$navigation.removeClass(stickyClass);
8959				$navigation.trigger('unsticky.navigation');
8960			}
8961		}
8962	},
8963	/**
8964	 * Detection, which size is needed for the best user experience. Set classes "medium" or "large" for the navigation.
8965	 */
8966	detectSize: function() {
8967		//we are using the shadow nav because of wrong dimensions of collapsing elements in non sticky navigation.
8968		var $nav		  = $('.main-nav'),
8969			$shadowNav  = Navigation.createShadow(),
8970			$items		= $shadowNav.find('.main-nav__lvl-1'),
8971			$actionBar  = $shadowNav.find('.main-nav__meta-action'),
8972			$container  = $shadowNav.find('.main-nav-inner');
8973
8974		if ($items.outerWidth() + $actionBar.outerWidth() <= $container.width()) {
8975			$nav.removeClass('medium');
8976			$nav.addClass('large');
8977		} else {
8978			$nav.addClass('medium');
8979			$nav.removeClass('large');
8980		}
8981	},
8982	/**
8983	 * For the size detection of the navigation we need a "shadow navigation" for calculating the right dimensions.
8984	 */
8985	createShadow: function() {
8986		var $shadow = $('.main-nav--clone');
8987		if (!$shadow.length) {
8988			var $mainNav = $('.main-nav');
8989			$shadow = $mainNav.clone();
8990
8991			//change class of clone
8992			$shadow
8993				.removeClass('main-nav')
8994				.addClass('main-nav--clone');
8995
8996			//insert clone nav into DOM
8997			$mainNav.after($shadow);
8998		}
8999
9000		return $shadow;
9001	},
9002	Language: {
9003		init: function() {
9004			$('.main-nav__language').click(Navigation.Language.toggle);
9005			$('.main-nav__meta-action').on('open.search open.contact', Navigation.Language.hide);
9006			// init language hover delay
9007			Navigation.Language.hoverDelay();
9008		},
9009		/**
9010		 * Delay subnavigation opening by hover.
9011		 */
9012		hoverDelay: function() {
9013			var $languageNavigation = $('.main-header__language');
9014
9015			$languageNavigation.each(function() {
9016				var $this = $(this);
9017
9018				$this.lazybind(
9019					'mouseenter',
9020					function(){
9021						$this.addClass('hover');
9022					},
9023					200,
9024					'mouseleave'
9025				);
9026
9027				$this.lazybind(
9028					'mouseleave',
9029					function(){
9030						$this.removeClass('hover');
9031					},
9032					200,
9033					'mouseenter'
9034				);
9035			});
9036		},
9037		toggle: function() {
9038			if ($('.main-nav__language').hasClass('is-open')) {
9039				Navigation.Language.hide();
9040			} else {
9041				Navigation.Language.show();
9042			}
9043		},
9044		show: function() {
9045			$('.main-nav__language').addClass('is-open').trigger('open.language');
9046		},
9047		hide: function() {
9048			$('.main-nav__language').removeClass('is-open');
9049		}
9050	},
9051	Contact: {
9052		init: function() {
9053			$('.main-nav__contact').click(Navigation.Contact.toggle);
9054			$('.main-nav__meta-action').on('open.search open.language', Navigation.Contact.hide);
9055		},
9056		toggle: function() {
9057			if ($('.main-nav__contact').hasClass('is-open')) {
9058				Navigation.Contact.hide();
9059			} else {
9060				Navigation.Contact.show();
9061			}
9062		},
9063		show: function() {
9064			$('.main-nav__contact').addClass('is-open').trigger('open.contact');
9065		},
9066		hide: function() {
9067			$('.main-nav__contact').removeClass('is-open');
9068		}
9069	}
9070};
9071
9072$(document).ready(Navigation.init);
9073
9074//off-canvas navigation
9075
9076var OffCanvasNav = {
9077	onReady: function() {
9078		var $mobileNavigation = $('.mobile-nav'),
9079			$btnToggleMenu = $('.btn--toggle-menu'),
9080			$btnCloseMenu = $('.btn--close-menu'),
9081			$lvl1ItemWithSubNav = $('.mobile-nav .has-child > a');
9082
9083		// Open subnavigation, if available.
9084		$lvl1ItemWithSubNav.on('click', OffCanvasNav.toggleMenu);
9085
9086		// Open subnavigation, if current page is in subnavigation
9087		$('.mobile-nav__lvl-1 > .has-child.is-current > a').addClass('open').next('ul').show();
9088	},
9089	toggleMenu: function(e) {
9090		var $this = $(this),
9091			$lvl1ItemWithSubNav = $('.mobile-nav .has-child > a'),
9092			$lvl2 = $('.mobile-nav .mobile-nav__lvl-2');
9093
9094		e.preventDefault();
9095		$lvl1ItemWithSubNav.removeClass('open');
9096		$lvl2.stop().slideUp();
9097
9098		if ($this.next().is(':visible')) {
9099			$this.
9100			removeClass('open')
9101			.next().stop().slideUp();
9102		} else {
9103			$this.
9104			addClass('open')
9105			.next().stop().slideDown();
9106		}
9107	}
9108};
9109
9110$(document).ready(OffCanvasNav.onReady);
9111
9112//add jquery event 'outside'
9113$.fn.outside = function(ename, cb){
9114	return this.each(function(){
9115		var $this = $(this),
9116			self = this;
9117		$(document.body).on(ename, function tempo(e){
9118			if(e.target !== self && !$.contains(self, e.target)){
9119				cb.apply(self, [e]);
9120				if(!self.parentNode) $(document.body).off(ename, tempo);
9121			}
9122		});
9123	});
9124};
9125
9126/* ==========================================================================
9127	Photoswipe
9128	========================================================================== */
9129
9130var photoswipe = {
9131	init: function() {
9132
9133		var $pic = $('a.photoswipe');
9134
9135		var items = function() {
9136
9137			var items = [];
9138			$pic.each(function(index) {
9139				var $this    = $(this),
9140					$href    = $this.attr('href'),
9141					$size    = $this.data('size').split('x'),
9142					$width   = $size[0],
9143					$height  = $size[1],
9144					$title   = $this.attr('title'),
9145					$alt     = $this.find('img').attr('alt'),
9146					$caption = $(this).next('.news-img-caption, .csc-textpic-caption').text(),
9147					slide_caption = null;
9148
9149				if (typeof $caption === 'string' && $caption.length > 0) {
9150					slide_caption = $caption;
9151				} else if (typeof $title === 'string' && $title.length > 0) {
9152					slide_caption = $title;
9153				} else if (typeof $alt === 'string' && $alt.length > 0) {
9154					slide_caption = $alt;
9155				}
9156
9157				var item = {
9158					src 	: $href,
9159					w   	: parseInt($width),
9160					h   	: parseInt($height),
9161					title	: slide_caption,
9162					pid 	: index
9163				};
9164
9165				items.push(item);
9166
9167				$this.attr('data-index', index);
9168
9169			});
9170
9171			return items;
9172
9173		}();
9174
9175		var $pswp = $('.pswp')[0];
9176
9177		$pic.on('click', function(event) {
9178			event.preventDefault();
9179
9180			var options = {
9181				index: 		parseInt( $(this).attr('data-index') ),
9182				bgOpacity: 	0.9,
9183				shareEl: 	false,
9184				history: false
9185			};
9186
9187			// Initialize PhotoSwipe
9188			var lightBox = new PhotoSwipe($pswp, PhotoSwipeUI_Default, items, options);
9189			lightBox.init();
9190		});
9191	}
9192};
9193
9194$(document).ready(photoswipe.init);
9195
9196var ResponsiveTable = (function() {
9197	'use strict';
9198
9199	var $responsiveTable = $('.contenttable').has('thead');
9200
9201	var init = function() {
9202
9203		if ($responsiveTable) {
9204			setHeaders();
9205		}
9206
9207	};
9208
9209	var setHeaders = function() {
9210
9211		$responsiveTable.find('td').each(function() {
9212			var $this = $(this),
9213				tableCellIndex = $this.index(),
9214				headerContent = $responsiveTable.find('th').eq(tableCellIndex).text();
9215
9216			$this
9217				.attr('headers', headerContent)
9218				.wrapInner('<span />');
9219		});
9220
9221	};
9222
9223	return {
9224		init: init
9225	};
9226
9227})();
9228
9229$(document).ready( ResponsiveTable.init );
9230
9231/**
9232 * Object controls the appearance of the search form in the header and the navigation.
9233 */
9234var Search = {
9235	init: function() {
9236		Search.Header.init();
9237		Search.Navigation.init();
9238	},
9239	/**
9240	 * Search form in header.
9241	 */
9242	Header: {
9243		init: function() {
9244			Search.Header.toggle();
9245			$(window).resize(Search.Header.toggle);
9246		},
9247		/**
9248		 * Bind toggle handler to the search field, if the search has size "small".
9249		 */
9250		toggle: function() {
9251			var $queryField = $('.main-header__search input[type="text"]'),
9252				$submitButton = $('.main-header__search .btn--search');
9253
9254			// detect search field size
9255			if (!$queryField.is(':visible')) {
9256				// small size
9257				// add click handler once
9258				$submitButton
9259					.off('click', Search.Header.smallSearchClick)
9260					.on('click', Search.Header.smallSearchClick);
9261			} else {
9262				// normal size
9263				$submitButton.off('click', Search.Header.smallSearchClick);
9264			}
9265		},
9266		/**
9267		 * Open search field on first click. Search on second click, if the field is not empty.
9268		 */
9269		smallSearchClick: function(e) {
9270			var $queryField = $('.main-header__search input[type="text"]'),
9271				queryValue = $queryField.val();
9272
9273			if (!$queryField.hasClass('is-open')) {
9274				Search.Header.show();
9275			} else {
9276				if (queryValue !== '') {
9277					Search.Header.submit();
9278				} else {
9279					Search.Header.hide();
9280				}
9281			}
9282
9283			e.preventDefault();
9284		},
9285		/**
9286		 * Open search field and set focus.
9287		 */
9288		show: function() {
9289			$('.main-header__search input[type="text"]')
9290				.addClass('is-open')
9291				.focus();
9292		},
9293		/**
9294		 * Hide search field.
9295		 */
9296		hide: function() {
9297			$('.main-header__search input[type="text"]').removeClass('is-open');
9298		},
9299		/**
9300		 * Submit form.
9301		 */
9302		submit: function() {
9303			$('.main-header__search form').submit();
9304		}
9305	},
9306	/**
9307	 * Search form in navigation.
9308	 */
9309	Navigation: {
9310		init: function() {
9311			//collapse and expand
9312			$('.main-nav__search .btn--search').click(Search.Navigation.toggle);
9313
9314			//set focus in field after reset button click
9315			$('.main-nav__search__btn--reset').click(Search.Navigation.focus);
9316
9317			//listen on other navigation action bar events
9318			$('.main-nav__meta-action').on('open.language open.contact', Search.Navigation.hide);
9319		},
9320		/**
9321		 * Open or close search field.
9322		 */
9323		toggle: function(e) {
9324			if ($('.main-nav__search-form').is(':visible')) {
9325				Search.Navigation.hide();
9326			} else {
9327				Search.Navigation.show();
9328			}
9329
9330			e.preventDefault();
9331		},
9332		/**
9333		 * Open search field and set focus.
9334		 */
9335		show: function() {
9336			$('.main-nav__search-form').addClass('is-open');
9337			Search.Navigation.focus();
9338			$('.main-nav__search').trigger('open.search');
9339		},
9340		/**
9341		 * Hide search field.
9342		 */
9343		hide: function() {
9344			$('.main-nav__search-form').removeClass('is-open');
9345		},
9346		/**
9347		 * Set focus in input
9348		 */
9349		focus: function() {
9350			$('.main-nav__search__input').focus();
9351		}
9352	}
9353};
9354$(document).ready(Search.init);
9355
9356var SectionIndexMenu = {
9357	initialized: 		false,
9358	$sectionIndex: 		$('.section-index'),
9359	stickyClass: 		'section-index--sticky',
9360	openClass: 			'section-index--open',
9361	sectionMarginTop: 	null,
9362	init: function() {
9363		if (SectionIndexMenu.$sectionIndex.length) {
9364			SectionIndexMenu.sectionMarginTop = parseInt($('.section-index__inner').css('marginTop').replace('px', ''));
9365
9366			SectionIndexMenu.toggleSticky();
9367			SectionIndexMenu.toggleMenu();
9368			$(window).on('scroll', SectionIndexMenu.toggleSticky);
9369			$(window).on('resize', SectionIndexMenu.toggleSticky);
9370
9371			SectionIndexMenu.$sectionIndex.find('a').on('click', SectionIndexMenu.closeMenu);
9372		}
9373	},
9374	toggleSticky: function(forceToggle) {
9375		var windowScrollOffset	= $(document).scrollTop(),
9376			$navigation			= $('.main-nav'),
9377			navigationHeight	= $navigation.outerHeight(),
9378			parentColWrapper	= $('[data-toggle-section-index]').parents('[class*="col-wrapper"]'),
9379			buttonOffset 		= parentColWrapper.offset().top + parentColWrapper.outerHeight() + SectionIndexMenu.sectionMarginTop;
9380
9381		if (windowScrollOffset > (buttonOffset - navigationHeight)) {
9382			if (!SectionIndexMenu.$sectionIndex.hasClass(SectionIndexMenu.stickyClass) || SectionIndexMenu.initialized === false) {
9383				SectionIndexMenu.$sectionIndex.addClass(SectionIndexMenu.stickyClass);
9384				SectionIndexMenu.$sectionIndex.trigger('sticky.navigation');
9385			}
9386		} else {
9387			if (SectionIndexMenu.$sectionIndex.hasClass(SectionIndexMenu.stickyClass) || SectionIndexMenu.initialized === false) {
9388				SectionIndexMenu.$sectionIndex.removeClass(SectionIndexMenu.stickyClass);
9389				SectionIndexMenu.$sectionIndex.trigger('unsticky.navigation');
9390			}
9391		}
9392	},
9393	toggleMenu: function() {
9394		$('[data-toggle-section-index]').on('click', function() {
9395			SectionIndexMenu.$sectionIndex.toggleClass(SectionIndexMenu.openClass);
9396		});
9397	},
9398	closeMenu: function() {
9399		SectionIndexMenu.$sectionIndex.removeClass(SectionIndexMenu.openClass);
9400	}
9401};
9402
9403$(document).ready(SectionIndexMenu.init);
9404
9405/* ==========================================================================
9406	Slider
9407	========================================================================== */
9408
9409var slider = {
9410	init: function() {
9411		var $slider = $('.slider'),
9412			$sliderSmall = $('.slider--small'),
9413			$sliderLarge = $('.slider--large');
9414
9415		// V2: Slider Items zufällig ausgeben
9416		// if ($slider.length) {
9417		// 	//sort random function
9418		// 	$slider.each(function() {
9419		// 		var $slider = $(this);
9420
9421		// 		$slider.children().sort(function() {
9422		// 			return Math.round(Math.random()) - 0.5;
9423		// 		}).each(function(){
9424		// 			$(this).appendTo($slider);
9425		// 		});
9426		// 	});
9427		// }
9428
9429		var rtl = false;
9430		if ($('html[dir="rtl"]').length) {
9431			rtl = true;
9432		}
9433
9434		if ($sliderSmall.length) {
9435			$sliderSmall.owlCarousel({
9436				navText: [],
9437				nav: true,
9438				navRewind: false,
9439				dots: false,
9440				rtl: rtl,
9441				responsiveClass: true,
9442				responsive: {
9443					0: {
9444						items: 1
9445					},
9446					700: {
9447						items: 2
9448					},
9449					940: {
9450						items: 3
9451					},
9452					1420: {
9453						items: 4
9454					}
9455				}
9456			});
9457		}
9458
9459		if ($sliderLarge.length) {
9460			$sliderLarge.owlCarousel({
9461				navText: [],
9462				nav: true,
9463				navRewind: false,
9464				dots: false,
9465				rtl: rtl,
9466				responsiveClass: true,
9467				responsive: {
9468					0: {
9469						items: 1
9470					},
9471					500: {
9472						items: 2
9473					},
9474					700: {
9475						items: 3
9476					},
9477					940: {
9478						items: 4
9479					},
9480					1420: {
9481						items: 5
9482					}
9483				}
9484			});
9485		}
9486	}
9487};
9488
9489$(document).ready(slider.init);
9490
9491/* ==========================================================================
9492	Slideshow
9493	========================================================================== */
9494
9495var slideshow = {
9496	$element: $('.slideshow'),
9497	init: function() {
9498		var rtl = false;
9499		if ($('html[dir="rtl"]').length) {
9500			rtl = true;
9501		}
9502
9503		if (slideshow.$element.length) {
9504			slideshow.$element.owlCarousel({
9505				slideBy:            'page',
9506				navText:            [],
9507				loop:               true,
9508				nav:                true,
9509				rtl: 				rtl,
9510				items:              1,
9511				autoplayTimeout:    4000,
9512				autoplayHoverPause: true,
9513				animateIn:          'fadeIn',
9514				animateOut:         'fadeOut',
9515				mouseDrag:          false,
9516				touchDrag:          false,
9517				autoHeight:         true,
9518				responsive: {
9519					940: {
9520						autoplay: 	true
9521					}
9522				},
9523				onInitialized: function() {
9524					slideshow.controls(this.$element);
9525				},
9526				onResized: function() {
9527					slideshow.controls(this.$element);
9528				},
9529				onRefreshed: function() {
9530					slideshow.controls(this.$element);
9531				},
9532				onChanged: function() {
9533					slideshow.controls(this.$element);
9534				}
9535			});
9536
9537			// FIX: Autoheight on window resize
9538			slideshow.$element.on('resized.owl.carousel', slideshow.refresh);
9539			$(window).load(slideshow.refresh);
9540		}
9541	},
9542	controls: function($element) {
9543		var $content = $element.find('.active .slideshow__content'),
9544			$controls = $element.find('.owl-nav > div'),
9545			$image = $element.find('.active .slideshow__image'),
9546			height = $image.outerHeight(),
9547			top = height/2;
9548
9549		if (top > 0 && $content.css('position') != 'absolute') {
9550			$controls.css('top', top);
9551		} else {
9552			$controls.css('top', '50%');
9553		}
9554	},
9555	refresh: function () {
9556		slideshow.$element.find('.owl-height').css('height', slideshow.$element.find('.owl-item.active').height());
9557	}
9558};
9559
9560$(document).ready(slideshow.init);
9561
9562//replace svg files with png files on old browsers
9563jQuery(function($) {
9564	if (!Modernizr.svg) {
9565		$('img[src*="svg"]').attr('src', function() {
9566			return $(this).attr('src').replace('.svg', '.png');
9567		});
9568	}
9569});
9570
9571var ToggleFeedbackText = {
9572	init: function() {
9573		ToggleFeedbackText.toggle();
9574	},
9575	toggle: function() {
9576		var $toggleButton = $('.feedback__more');
9577
9578		$toggleButton.on('click', function() {
9579			$(this).toggleClass('is--open');
9580			$(this).parent().find('.feedback__content-more').slideToggle();
9581		});
9582	}
9583};
9584
9585$(document).ready(ToggleFeedbackText.init);

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.