PageSourceSearch

https://www.kentridge.studio/wp-content/themes/william-kentridge-studio/photoswipe4new10.js?ver=930069265

js kentridge.studio collected 2026-09-24 22:57:30 UTC 133,503 bytes, 4,998 lines download raw bytes

1/*! PhotoSwipe - v4.1.3 - 2019-01-08
2* http://photoswipe.com
3* Copyright (c) 2019 Dmitry Semenov; */
4(function (root, factory) {
5	if (typeof define === 'function' && define.amd) {
6		define(factory);
7	} else if (typeof module !== 'undefined' && typeof exports === 'object') { // JIG addition of extra clause
8		module.exports = factory();
9	} else {
10		root.PhotoSwipe = factory();
11	}
12})(this, function () {
13
14	'use strict';
15	var PhotoSwipe = function(template, UiClass, items, options){
16
17/*>>framework-bridge*/
18/**
19 *
20 * Set of generic functions used by gallery.
21 * 
22 * You're free to modify anything here as long as functionality is kept.
23 * 
24 */
25var framework = {
26	features: null,
27	bind: function(target, type, listener, unbind) {
28		var methodName = (unbind ? 'remove' : 'add') + 'EventListener';
29		type = type.split(' ');
30		for(var i = 0; i < type.length; i++) {
31			if(type[i]) {
32				target[methodName]( type[i], listener, false);
33			}
34		}
35	},
36	isArray: function(obj) {
37		return (obj instanceof Array);
38	},
39	createEl: function(classes, tag) {
40		var el = document.createElement(tag || 'div');
41		if(classes) {
42			el.className = classes;
43		}
44		return el;
45	},
46	getScrollY: function() {
47		var yOffset = window.pageYOffset;
48		return yOffset !== undefined ? yOffset : document.documentElement.scrollTop;
49	},
50	unbind: function(target, type, listener) {
51		framework.bind(target,type,listener,true);
52	},
53	removeClass: function(el, className) {
54		var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
55		el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 
56	},
57	addClass: function(el, className) {
58		if( !framework.hasClass(el,className) ) {
59			el.className += (el.className ? ' ' : '') + className;
60		}
61	},
62	hasClass: function(el, className) {
63		return el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
64	},
65	getChildByClass: function(parentEl, childClassName) {
66		var node = parentEl.firstChild;
67		while(node) {
68			if( framework.hasClass(node, childClassName) ) {
69				return node;
70			}
71			node = node.nextSibling;
72		}
73	},
74	arraySearch: function(array, value, key) {
75		var i = array.length;
76		while(i--) {
77			if(array[i][key] === value) {
78				return i;
79			} 
80		}
81		return -1;
82	},
83	extend: function(o1, o2, preventOverwrite) {
84		for (var prop in o2) {
85			if (o2.hasOwnProperty(prop)) {
86				if(preventOverwrite && o1.hasOwnProperty(prop)) {
87					continue;
88				}
89				o1[prop] = o2[prop];
90			}
91		}
92	},
93	easing: {
94		sine: {
95			out: function(k) {
96				return Math.sin(k * (Math.PI / 2));
97			},
98			inOut: function(k) {
99				return - (Math.cos(Math.PI * k) - 1) / 2;
100			}
101		},
102		cubic: {
103			out: function(k) {
104				return --k * k * k + 1;
105			}
106		}
107		/*
108			elastic: {
109				out: function ( k ) {
110
111					var s, a = 0.1, p = 0.4;
112					if ( k === 0 ) return 0;
113					if ( k === 1 ) return 1;
114					if ( !a || a < 1 ) { a = 1; s = p / 4; }
115					else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
116					return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
117
118				},
119			},
120			back: {
121				out: function ( k ) {
122					var s = 1.70158;
123					return --k * k * ( ( s + 1 ) * k + s ) + 1;
124				}
125			}
126		*/
127	},
128
129	/**
130	 * 
131	 * @return {object}
132	 * 
133	 * {
134	 *  raf : request animation frame function
135	 *  caf : cancel animation frame function
136	 *  transfrom : transform property key (with vendor), or null if not supported
137	 *  oldIE : IE8 or below
138	 * }
139	 * 
140	 */
141	detectFeatures: function() {
142		if(framework.features) {
143			return framework.features;
144		}
145		var helperEl = framework.createEl(),
146			helperStyle = helperEl.style,
147			vendor = '',
148			features = {};
149
150		// IE8 and below
151		features.oldIE = document.all && !document.addEventListener;
152
153		features.touch = 'ontouchstart' in window;
154
155		if(window.requestAnimationFrame) {
156			features.raf = window.requestAnimationFrame;
157			features.caf = window.cancelAnimationFrame;
158		}
159
160		features.pointerEvent = !!(window.PointerEvent) || navigator.msPointerEnabled;
161
162		// fix false-positive detection of old Android in new IE
163		// (IE11 ua string contains "Android 4.0")
164		
165		if(!features.pointerEvent) { 
166
167			var ua = navigator.userAgent;
168
169			// Detect if device is iPhone or iPod and if it's older than iOS 8
170			// http://stackoverflow.com/a/14223920
171			// 
172			// This detection is made because of buggy top/bottom toolbars
173			// that don't trigger window.resize event.
174			// For more info refer to _isFixedPosition variable in core.js
175
176			if (/iP(hone|od)/.test(navigator.platform)) {
177				var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
178				if(v && v.length > 0) {
179					v = parseInt(v[1], 10);
180					if(v >= 1 && v < 8 ) {
181						features.isOldIOSPhone = true;
182					}
183				}
184			}
185
186			// Detect old Android (before KitKat)
187			// due to bugs related to position:fixed
188			// http://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript
189			
190			var match = ua.match(/Android\s([0-9\.]*)/);
191			var androidversion =  match ? match[1] : 0;
192			androidversion = parseFloat(androidversion);
193			if(androidversion >= 1 ) {
194				if(androidversion < 4.4) {
195					features.isOldAndroid = true; // for fixed position bug & performance
196				}
197				features.androidVersion = androidversion; // for touchend bug
198			}	
199			features.isMobileOpera = /opera mini|opera mobi/i.test(ua);
200
201			// p.s. yes, yes, UA sniffing is bad, propose your solution for above bugs.
202		}
203		
204		var styleChecks = ['transform', 'perspective', 'animationName'],
205			vendors = ['', 'webkit','Moz','ms','O'],
206			styleCheckItem,
207			styleName;
208
209		for(var i = 0; i < 4; i++) {
210			vendor = vendors[i];
211
212			for(var a = 0; a < 3; a++) {
213				styleCheckItem = styleChecks[a];
214
215				// uppercase first letter of property name, if vendor is present
216				styleName = vendor + (vendor ? 
217										styleCheckItem.charAt(0).toUpperCase() + styleCheckItem.slice(1) : 
218										styleCheckItem);
219			
220				if(!features[styleCheckItem] && styleName in helperStyle ) {
221					features[styleCheckItem] = styleName;
222				}
223			}
224
225			if(vendor && !features.raf) {
226				vendor = vendor.toLowerCase();
227				features.raf = window[vendor+'RequestAnimationFrame'];
228				if(features.raf) {
229					features.caf = window[vendor+'CancelAnimationFrame'] || 
230									window[vendor+'CancelRequestAnimationFrame'];
231				}
232			}
233		}
234			
235		if(!features.raf) {
236			var lastTime = 0;
237			features.raf = function(fn) {
238				var currTime = new Date().getTime();
239				var timeToCall = Math.max(0, 16 - (currTime - lastTime));
240				var id = window.setTimeout(function() { fn(currTime + timeToCall); }, timeToCall);
241				lastTime = currTime + timeToCall;
242				return id;
243			};
244			features.caf = function(id) { clearTimeout(id); };
245		}
246
247		// Detect SVG support
248		features.svg = !!document.createElementNS && 
249						!!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect;
250
251		framework.features = features;
252
253		return features;
254	}
255};
256
257framework.detectFeatures();
258
259// Override addEventListener for old versions of IE
260if(framework.features.oldIE) {
261
262	framework.bind = function(target, type, listener, unbind) {
263		
264		type = type.split(' ');
265
266		var methodName = (unbind ? 'detach' : 'attach') + 'Event',
267			evName,
268			_handleEv = function() {
269				listener.handleEvent.call(listener);
270			};
271
272		for(var i = 0; i < type.length; i++) {
273			evName = type[i];
274			if(evName) {
275
276				if(typeof listener === 'object' && listener.handleEvent) {
277					if(!unbind) {
278						listener['oldIE' + evName] = _handleEv;
279					} else {
280						if(!listener['oldIE' + evName]) {
281							return false;
282						}
283					}
284
285					target[methodName]( 'on' + evName, listener['oldIE' + evName]);
286				} else {
287					target[methodName]( 'on' + evName, listener);
288				}
289
290			}
291		}
292	};
293	
294}
295
296/*>>framework-bridge*/
297
298/*>>core*/
299//function(template, UiClass, items, options)
300
301var self = this;
302
303/**
304 * Static vars, don't change unless you know what you're doing.
305 */
306var DOUBLE_TAP_RADIUS = 25, 
307	NUM_HOLDERS = 3;
308
309/**
310 * Options
311 */
312var _options = {
313	allowPanToNext:true,
314	spacing: 0.12,
315	bgOpacity: 1,
316	mouseUsed: false,
317	loop: true,
318	pinchToClose: true,
319	closeOnScroll: true,
320	closeOnVerticalDrag: true,
321	verticalDragRange: 0.75,
322	hideAnimationDuration: 333,
323	showAnimationDuration: 333,
324	showHideOpacity: false,
325	focus: true,
326	escKey: true,
327	arrowKeys: true,
328	mainScrollEndFriction: 0.35,
329	panEndFriction: 0.35,
330	isClickableElement: function(el) {
331        return el.tagName === 'A';
332    },
333    getDoubleTapZoom: function(isMouseClick, item) {
334    	if(isMouseClick) {
335    		return 1;
336    	} else {
337    		return item.initialZoomLevel < 0.7 ? 1 : 1.33;
338    	}
339    },
340    maxSpreadZoom: 1.33,
341	modal: true,
342
343	// not fully implemented yet
344	scaleMode: 'fit' // TODO
345};
346framework.extend(_options, options);
347
348
349/**
350 * Private helper variables & functions
351 */
352
353var _getEmptyPoint = function() { 
354		return {x:0,y:0}; 
355	};
356
357var _isOpen,
358	_isDestroying,
359	_closedByScroll,
360	_currentItemIndex,
361	_containerStyle,
362	_containerShiftIndex,
363	_currPanDist = _getEmptyPoint(),
364	_startPanOffset = _getEmptyPoint(),
365	_panOffset = _getEmptyPoint(),
366	_upMoveEvents, // drag move, drag end & drag cancel events array
367	_downEvents, // drag start events array
368	_globalEventHandlers,
369	_viewportSize = {},
370	_currZoomLevel,
371	_startZoomLevel,
372	_translatePrefix,
373	_translateSufix,
374	_updateSizeInterval,
375	_itemsNeedUpdate,
376	_currPositionIndex = 0,
377	_offset = {},
378	_slideSize = _getEmptyPoint(), // size of slide area, including spacing
379	_itemHolders,
380	_prevItemIndex,
381	_indexDiff = 0, // difference of indexes since last content update
382	_dragStartEvent,
383	_dragMoveEvent,
384	_dragEndEvent,
385	_dragCancelEvent,
386	_transformKey,
387	_pointerEventEnabled,
388	_isFixedPosition = true,
389	_likelyTouchDevice,
390	_modules = [],
391	_requestAF,
392	_cancelAF,
393	_initalClassName,
394	_initalWindowScrollY,
395	_oldIE,
396	_currentWindowScrollY,
397	_features,
398	_windowVisibleSize = {},
399	_renderMaxResolution = false,
400	_orientationChangeTimeout,
401	_orientationChangeTimeout2,
402
403	// Registers PhotoSWipe module (History, Controller ...)
404	_registerModule = function(name, module) {
405		framework.extend(self, module.publicMethods);
406		_modules.push(name);
407	},
408
409	_getLoopedId = function(index) {
410		var numSlides = _getNumItems();
411		if(index > numSlides - 1) {
412			return index - numSlides;
413		} else  if(index < 0) {
414			return numSlides + index;
415		}
416		return index;
417	},
418	
419	// Micro bind/trigger
420	_listeners = {},
421	_listen = function(name, fn) {
422		if(!_listeners[name]) {
423			_listeners[name] = [];
424		}
425		return _listeners[name].push(fn);
426	},
427	_shout = function(name) {
428		var listeners = _listeners[name];
429
430		if(listeners) {
431			var args = Array.prototype.slice.call(arguments);
432			args.shift();
433
434			for(var i = 0; i < listeners.length; i++) {
435				listeners[i].apply(self, args);
436			}
437		}
438	},
439
440	_getCurrentTime = function() {
441		return new Date().getTime();
442	},
443	_applyBgOpacity = function(opacity) {
444		_bgOpacity = opacity;
445		self.bg.style.opacity = opacity * _options.bgOpacity;
446	},
447
448	_applyZoomTransform = function(styleObj,x,y,zoom,item) {
449		if(!_renderMaxResolution || (item && item !== self.currItem) ) {
450			zoom = zoom / (item ? item.fitRatio : self.currItem.fitRatio);	
451		}
452			
453		styleObj[_transformKey] = _translatePrefix + x + 'px, ' + y + 'px' + _translateSufix + ' scale(' + zoom + ')';
454	},
455	_applyCurrentZoomPan = function( allowRenderResolution ) {
456		if(_currZoomElementStyle) {
457
458			if(allowRenderResolution) {
459				if(_currZoomLevel > self.currItem.fitRatio) {
460					if(!_renderMaxResolution) {
461						_setImageSize(self.currItem, false, true);
462						_renderMaxResolution = true;
463					}
464				} else {
465					if(_renderMaxResolution) {
466						_setImageSize(self.currItem);
467						_renderMaxResolution = false;
468					}
469				}
470			}
471			
472
473			_applyZoomTransform(_currZoomElementStyle, _panOffset.x, _panOffset.y, _currZoomLevel);
474		}
475	},
476	_applyZoomPanToItem = function(item) {
477		if(item.container) {
478
479			_applyZoomTransform(item.container.style, 
480								item.initialPosition.x, 
481								item.initialPosition.y, 
482								item.initialZoomLevel,
483								item);
484		}
485	},
486	_setTranslateX = function(x, elStyle) {
487		elStyle[_transformKey] = _translatePrefix + x + 'px, 0px' + _translateSufix;
488	},
489	_moveMainScroll = function(x, dragging) {
490
491		if(!_options.loop && dragging) {
492			var newSlideIndexOffset = _currentItemIndex + (_slideSize.x * _currPositionIndex - x) / _slideSize.x,
493				delta = Math.round(x - _mainScrollPos.x);
494
495			if( (newSlideIndexOffset < 0 && delta > 0) || 
496				(newSlideIndexOffset >= _getNumItems() - 1 && delta < 0) ) {
497				x = _mainScrollPos.x + delta * _options.mainScrollEndFriction;
498			} 
499		}
500		
501		_mainScrollPos.x = x;
502		_setTranslateX(x, _containerStyle);
503	},
504	_calculatePanOffset = function(axis, zoomLevel) {
505		var m = _midZoomPoint[axis] - _offset[axis];
506		return _startPanOffset[axis] + _currPanDist[axis] + m - m * ( zoomLevel / _startZoomLevel );
507	},
508	
509	_equalizePoints = function(p1, p2) {
510		p1.x = p2.x;
511		p1.y = p2.y;
512		if(p2.id) {
513			p1.id = p2.id;
514		}
515	},
516	_roundPoint = function(p) {
517		p.x = Math.round(p.x);
518		p.y = Math.round(p.y);
519	},
520
521	_mouseMoveTimeout = null,
522	_onFirstMouseMove = function() {
523		// Wait until mouse move event is fired at least twice during 100ms
524		// We do this, because some mobile browsers trigger it on touchstart
525		if(_mouseMoveTimeout ) { 
526			framework.unbind(document, 'mousemove', _onFirstMouseMove);
527			framework.addClass(template, 'pswp--has_mouse');
528			_options.mouseUsed = true;
529			_shout('mouseUsed');
530		}
531		_mouseMoveTimeout = setTimeout(function() {
532			_mouseMoveTimeout = null;
533		}, 100);
534	},
535
536	_bindEvents = function() {
537		framework.bind(document, 'keydown', self);
538
539		if(_features.transform) {
540			// don't bind click event in browsers that don't support transform (mostly IE8)
541			framework.bind(self.scrollWrap, 'click', self);
542		}
543		
544
545		if(!_options.mouseUsed) {
546			framework.bind(document, 'mousemove', _onFirstMouseMove);
547		}
548
549		framework.bind(window, 'resize scroll orientationchange', self);
550
551		_shout('bindEvents');
552	},
553
554	_unbindEvents = function() {
555		framework.unbind(window, 'resize scroll orientationchange', self);
556		framework.unbind(window, 'scroll', _globalEventHandlers.scroll);
557		framework.unbind(document, 'keydown', self);
558		framework.unbind(document, 'mousemove', _onFirstMouseMove);
559
560		if(_features.transform) {
561			framework.unbind(self.scrollWrap, 'click', self);
562		}
563
564		if(_isDragging) {
565			framework.unbind(window, _upMoveEvents, self);
566		}
567
568		clearTimeout(_orientationChangeTimeout);
569		clearTimeout(_orientationChangeTimeout2);
570
571		_shout('unbindEvents');
572	},
573	
574	_calculatePanBounds = function(zoomLevel, update) {
575		var bounds = _calculateItemSize( self.currItem, _viewportSize, zoomLevel );
576		if(update) {
577			_currPanBounds = bounds;
578		}
579		return bounds;
580	},
581	
582	_getMinZoomLevel = function(item) {
583		if(!item) {
584			item = self.currItem;
585		}
586		return item.initialZoomLevel;
587	},
588	_getMaxZoomLevel = function(item) {
589		if(!item) {
590			item = self.currItem;
591		}
592		return item.w > 0 ? _options.maxSpreadZoom : 1;
593	},
594
595	// Return true if offset is out of the bounds
596	_modifyDestPanOffset = function(axis, destPanBounds, destPanOffset, destZoomLevel) {
597		if(destZoomLevel === self.currItem.initialZoomLevel) {
598			destPanOffset[axis] = self.currItem.initialPosition[axis];
599			return true;
600		} else {
601			destPanOffset[axis] = _calculatePanOffset(axis, destZoomLevel); 
602
603			if(destPanOffset[axis] > destPanBounds.min[axis]) {
604				destPanOffset[axis] = destPanBounds.min[axis];
605				return true;
606			} else if(destPanOffset[axis] < destPanBounds.max[axis] ) {
607				destPanOffset[axis] = destPanBounds.max[axis];
608				return true;
609			}
610		}
611		return false;
612	},
613
614	_setupTransforms = function() {
615
616		if(_transformKey) {
617			// setup 3d transforms
618			var allow3dTransform = _features.perspective && !_likelyTouchDevice;
619			_translatePrefix = 'translate' + (allow3dTransform ? '3d(' : '(');
620			_translateSufix = _features.perspective ? ', 0px)' : ')';	
621			return;
622		}
623
624		// Override zoom/pan/move functions in case old browser is used (most likely IE)
625		// (so they use left/top/width/height, instead of CSS transform)
626	
627		_transformKey = 'left';
628		framework.addClass(template, 'pswp--ie');
629
630		_setTranslateX = function(x, elStyle) {
631			elStyle.left = x + 'px';
632		};
633		_applyZoomPanToItem = function(item) {
634
635			var zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
636				s = item.container.style,
637				w = zoomRatio * item.w,
638				h = zoomRatio * item.h;
639
640			s.width = w + 'px';
641			s.height = h + 'px';
642			s.left = item.initialPosition.x + 'px';
643			s.top = item.initialPosition.y + 'px';
644
645		};
646		_applyCurrentZoomPan = function() {
647			if(_currZoomElementStyle) {
648
649				var s = _currZoomElementStyle,
650					item = self.currItem,
651					zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
652					w = zoomRatio * item.w,
653					h = zoomRatio * item.h;
654
655				s.width = w + 'px';
656				s.height = h + 'px';
657
658
659				s.left = _panOffset.x + 'px';
660				s.top = _panOffset.y + 'px';
661			}
662			
663		};
664	},
665
666	_onKeyDown = function(e) {
667		var keydownAction = '';
668		if(_options.escKey && e.keyCode === 27) { 
669			keydownAction = 'close';
670		} else if(_options.arrowKeys) {
671			if(e.keyCode === 37) {
672				keydownAction = 'prev';
673			} else if(e.keyCode === 39) { 
674				keydownAction = 'next';
675			}
676		}
677
678		if(keydownAction) {
679			// don't do anything if special key pressed to prevent from overriding default browser actions
680			// e.g. in Chrome on Mac cmd+arrow-left returns to previous page
681			if( !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey ) {
682				if(e.preventDefault) {
683					e.preventDefault();
684				} else {
685					e.returnValue = false;
686				} 
687				self[keydownAction]();
688			}
689		}
690	},
691
692	_onGlobalClick = function(e) {
693		if(!e) {
694			return;
695		}
696
697		// don't allow click event to pass through when triggering after drag or some other gesture
698		if(_moved || _zoomStarted || _mainScrollAnimating || _verticalDragInitiated) {
699			e.preventDefault();
700			e.stopPropagation();
701		}
702	},
703
704	_updatePageScrollOffset = function() {
705		self.setScrollOffset(0, framework.getScrollY());		
706	};
707	
708
709
710	
711
712
713
714// Micro animation engine
715var _animations = {},
716	_numAnimations = 0,
717	_stopAnimation = function(name) {
718		if(_animations[name]) {
719			if(_animations[name].raf) {
720				_cancelAF( _animations[name].raf );
721			}
722			_numAnimations--;
723			delete _animations[name];
724		}
725	},
726	_registerStartAnimation = function(name) {
727		if(_animations[name]) {
728			_stopAnimation(name);
729		}
730		if(!_animations[name]) {
731			_numAnimations++;
732			_animations[name] = {};
733		}
734	},
735	_stopAllAnimations = function() {
736		for (var prop in _animations) {
737
738			if( _animations.hasOwnProperty( prop ) ) {
739				_stopAnimation(prop);
740			} 
741			
742		}
743	},
744	_animateProp = function(name, b, endProp, d, easingFn, onUpdate, onComplete) {
745		var startAnimTime = _getCurrentTime(), t;
746		_registerStartAnimation(name);
747
748		var animloop = function(){
749			if ( _animations[name] ) {
750				
751				t = _getCurrentTime() - startAnimTime; // time diff
752				//b - beginning (start prop)
753				//d - anim duration
754
755				if ( t >= d ) {
756					_stopAnimation(name);
757					onUpdate(endProp);
758					if(onComplete) {
759						onComplete();
760					}
761					return;
762				}
763				onUpdate( (endProp - b) * easingFn(t/d) + b );
764
765				_animations[name].raf = _requestAF(animloop);
766			}
767		};
768		animloop();
769	};
770	
771
772
773var publicMethods = {
774
775	// make a few local variables and functions public
776	shout: _shout,
777	listen: _listen,
778	viewportSize: _viewportSize,
779	options: _options,
780
781	isMainScrollAnimating: function() {
782		return _mainScrollAnimating;
783	},
784	getZoomLevel: function() {
785		return _currZoomLevel;
786	},
787	getCurrentIndex: function() {
788		return _currentItemIndex;
789	},
790	isDragging: function() {
791		return _isDragging;
792	},	
793	isZooming: function() {
794		return _isZooming;
795	},
796	setScrollOffset: function(x,y) {
797		_offset.x = x;
798		_currentWindowScrollY = _offset.y = y;
799		_shout('updateScrollOffset', _offset);
800	},
801	applyZoomPan: function(zoomLevel,panX,panY,allowRenderResolution) {
802		_panOffset.x = panX;
803		_panOffset.y = panY;
804		_currZoomLevel = zoomLevel;
805		_applyCurrentZoomPan( allowRenderResolution );
806	},
807
808	init: function() {
809
810		if(_isOpen || _isDestroying) {
811			return;
812		}
813
814		var i;
815
816		self.framework = framework; // basic functionality
817		self.template = template; // root DOM element of PhotoSwipe
818		self.bg = framework.getChildByClass(template, 'pswp__bg');
819
820		_initalClassName = template.className;
821		_isOpen = true;
822				
823		_features = framework.detectFeatures();
824		_requestAF = _features.raf;
825		_cancelAF = _features.caf;
826		_transformKey = _features.transform;
827		_oldIE = _features.oldIE;
828		
829		self.scrollWrap = framework.getChildByClass(template, 'pswp__scroll-wrap');
830		self.container = framework.getChildByClass(self.scrollWrap, 'pswp__container');
831
832		_containerStyle = self.container.style; // for fast access
833
834		// Objects that hold slides (there are only 3 in DOM)
835		self.itemHolders = _itemHolders = [
836			{el:self.container.children[0] , wrap:0, index: -1},
837			{el:self.container.children[1] , wrap:0, index: -1},
838			{el:self.container.children[2] , wrap:0, index: -1}
839		];
840
841		// hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
842		_itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'none';
843
844		_setupTransforms();
845
846		// Setup global events
847		_globalEventHandlers = {
848			resize: self.updateSize,
849			
850			// Fixes: iOS 10.3 resize event
851			// does not update scrollWrap.clientWidth instantly after resize
852			// https://github.com/dimsemenov/PhotoSwipe/issues/1315
853			orientationchange: function() {
854				clearTimeout(_orientationChangeTimeout);
855				_orientationChangeTimeout = setTimeout(function() {
856					if(_viewportSize.x !== self.scrollWrap.clientWidth) {
857						self.updateSize();
858					}
859				}, 500);
860				// JIG adds a secondary backup thing here, just in case -.-
861				clearTimeout(_orientationChangeTimeout2);
862				_orientationChangeTimeout2 = setTimeout(function() {
863					if(_viewportSize.x !== self.scrollWrap.clientWidth) {
864						self.updateSize();
865					}
866				}, 1000);
867			},
868			scroll: _updatePageScrollOffset,
869			keydown: _onKeyDown,
870			click: _onGlobalClick
871		};
872
873		// disable show/hide effects on old browsers that don't support CSS animations or transforms, 
874		// old IOS, Android and Opera mobile. Blackberry seems to work fine, even older models.
875		var oldPhone = _features.isOldIOSPhone || _features.isOldAndroid || _features.isMobileOpera;
876		if(!_features.animationName || !_features.transform || oldPhone) {
877			_options.showAnimationDuration = _options.hideAnimationDuration = 0;
878		}
879
880		// init modules
881		for(i = 0; i < _modules.length; i++) {
882			self['init' + _modules[i]]();
883		}
884		
885		// init
886		if(UiClass) {
887			var ui = self.ui = new UiClass(self, framework);
888			ui.init();
889		}
890
891		_shout('firstUpdate');
892		_currentItemIndex = _currentItemIndex || _options.index || 0;
893		// validate index
894		if( isNaN(_currentItemIndex) || _currentItemIndex < 0 || _currentItemIndex >= _getNumItems() ) {
895			_currentItemIndex = 0;
896		}
897		self.currItem = _getItemAt( _currentItemIndex );
898
899		
900		if(_features.isOldIOSPhone || _features.isOldAndroid) {
901			_isFixedPosition = false;
902		}
903		
904		template.setAttribute('aria-hidden', 'false');
905		if(_options.modal) {
906			if(!_isFixedPosition) {
907				template.style.position = 'absolute';
908				template.style.top = framework.getScrollY() + 'px';
909			} else {
910				template.style.position = 'fixed';
911			}
912		}
913
914		if(_currentWindowScrollY === undefined) {
915			_shout('initialLayout');
916			_currentWindowScrollY = _initalWindowScrollY = framework.getScrollY();
917		}
918		
919		// add classes to root element of PhotoSwipe
920		var rootClasses = 'pswp--open ';
921		if(_options.mainClass) {
922			rootClasses += _options.mainClass + ' ';
923		}
924		if(_options.showHideOpacity) {
925			rootClasses += 'pswp--animate_opacity ';
926		}
927		rootClasses += _likelyTouchDevice ? 'pswp--touch' : 'pswp--notouch';
928		rootClasses += _features.animationName ? ' pswp--css_animation' : '';
929		rootClasses += _features.svg ? ' pswp--svg' : '';
930		// JIG addition to disable magnifier icon when zoom is disabled
931		if(_options.maxSpreadZoom === 1 && _options.pinchToClose === false && _options.zoomEl === false){
932			rootClasses += ' pswp--zoom-prevented';
933		}
934		// End of JIG addition
935		framework.addClass(template, rootClasses);
936
937		self.updateSize();
938
939		// initial update
940		_containerShiftIndex = -1;
941		_indexDiff = null;
942		for(i = 0; i < NUM_HOLDERS; i++) {
943			_setTranslateX( (i+_containerShiftIndex) * _slideSize.x, _itemHolders[i].el.style);
944		}
945
946		if(!_oldIE) {
947			framework.bind(self.scrollWrap, _downEvents, self); // no dragging for old IE
948		}	
949
950		_listen('initialZoomInEnd', function() {
951			self.setContent(_itemHolders[0], _currentItemIndex-1);
952			self.setContent(_itemHolders[2], _currentItemIndex+1);
953
954			_itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'block';
955
956			if(_options.focus) {
957				// focus causes layout, 
958				// which causes lag during the animation, 
959				// that's why we delay it untill the initial zoom transition ends
960				template.focus();
961			}
962			 
963
964			_bindEvents();
965		});
966
967		// set content for center slide (first time)
968		self.setContent(_itemHolders[1], _currentItemIndex);
969		
970		self.updateCurrItem();
971
972		_shout('afterInit');
973
974		if(!_isFixedPosition) {
975
976			// On all versions of iOS lower than 8.0, we check size of viewport every second.
977			// 
978			// This is done to detect when Safari top & bottom bars appear, 
979			// as this action doesn't trigger any events (like resize). 
980			// 
981			// On iOS8 they fixed this.
982			// 
983			// 10 Nov 2014: iOS 7 usage ~40%. iOS 8 usage 56%.
984			
985			_updateSizeInterval = setInterval(function() {
986				if(!_numAnimations && !_isDragging && !_isZooming && (_currZoomLevel === self.currItem.initialZoomLevel)  ) {
987					self.updateSize();
988				}
989			}, 1000);
990		}
991
992		framework.addClass(template, 'pswp--visible');
993	},
994
995	// Close the gallery, then destroy it
996	close: function() {
997		if(!_isOpen) {
998			return;
999		}
1000
1001		_isOpen = false;
1002		_isDestroying = true;
1003		_shout('close');
1004		_unbindEvents();
1005
1006		//Johann
1007		if(document.getElementById("ifrm")) {
1008		document.getElementById("ifrm").remove();}
1009
1010		_showOrHide(self.currItem, null, true, self.destroy);
1011	},
1012
1013	// destroys the gallery (unbinds events, cleans up intervals and timeouts to avoid memory leaks)
1014	destroy: function() {
1015		_shout('destroy');
1016
1017		if(_showOrHideTimeout) {
1018			clearTimeout(_showOrHideTimeout);
1019		}
1020		
1021		template.setAttribute('aria-hidden', 'true');
1022		template.className = _initalClassName;
1023
1024		if(_updateSizeInterval) {
1025			clearInterval(_updateSizeInterval);
1026		}
1027
1028		framework.unbind(self.scrollWrap, _downEvents, self);
1029
1030		// we unbind scroll event at the end, as closing animation may depend on it
1031		framework.unbind(window, 'scroll', self);
1032
1033		_stopDragUpdateLoop();
1034
1035		_stopAllAnimations();
1036
1037		_listeners = null;
1038	},
1039
1040	/**
1041	 * Pan image to position
1042	 * @param {Number} x     
1043	 * @param {Number} y     
1044	 * @param {Boolean} force Will ignore bounds if set to true.
1045	 */
1046	panTo: function(x,y,force) {
1047		if(!force) {
1048			if(x > _currPanBounds.min.x) {
1049				x = _currPanBounds.min.x;
1050			} else if(x < _currPanBounds.max.x) {
1051				x = _currPanBounds.max.x;
1052			}
1053
1054			if(y > _currPanBounds.min.y) {
1055				y = _currPanBounds.min.y;
1056			} else if(y < _currPanBounds.max.y) {
1057				y = _currPanBounds.max.y;
1058			}
1059		}
1060		
1061		_panOffset.x = x;
1062		_panOffset.y = y;
1063		_applyCurrentZoomPan();
1064	},
1065	
1066	handleEvent: function (e) {
1067		e = e || window.event;
1068		if(_globalEventHandlers[e.type]) {
1069			_globalEventHandlers[e.type](e);
1070		}
1071	},
1072
1073
1074	goTo: function(index) {
1075
1076		index = _getLoopedId(index);
1077
1078		var diff = index - _currentItemIndex;
1079		_indexDiff = diff;
1080
1081		_currentItemIndex = index;
1082		self.currItem = _getItemAt( _currentItemIndex );
1083		_currPositionIndex -= diff;
1084		
1085		_moveMainScroll(_slideSize.x * _currPositionIndex);
1086		
1087
1088		_stopAllAnimations();
1089		_mainScrollAnimating = false;
1090
1091		self.updateCurrItem();
1092	},
1093	next: function() {
1094		self.goTo( _currentItemIndex + 1);
1095	},
1096	prev: function() {
1097		self.goTo( _currentItemIndex - 1);
1098	},
1099
1100	// update current zoom/pan objects
1101	updateCurrZoomItem: function(emulateSetContent) {
1102		if(emulateSetContent) {
1103			_shout('beforeChange', 0);
1104		}
1105
1106		// itemHolder[1] is middle (current) item
1107		if(_itemHolders[1].el.children.length) {
1108			var zoomElement = _itemHolders[1].el.children[0];
1109			if( framework.hasClass(zoomElement, 'pswp__zoom-wrap') ) {
1110				_currZoomElementStyle = zoomElement.style;
1111			} else {
1112				_currZoomElementStyle = null;
1113			}
1114		} else {
1115			_currZoomElementStyle = null;
1116		}
1117		
1118		_currPanBounds = self.currItem.bounds;	
1119		_startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
1120
1121		_panOffset.x = _currPanBounds.center.x;
1122		_panOffset.y = _currPanBounds.center.y;
1123
1124		if(emulateSetContent) {
1125			_shout('afterChange');
1126		}
1127	},
1128
1129
1130	invalidateCurrItems: function() {
1131		_itemsNeedUpdate = true;
1132		for(var i = 0; i < NUM_HOLDERS; i++) {
1133			if( _itemHolders[i].item ) {
1134				_itemHolders[i].item.needsUpdate = true;
1135			}
1136		}
1137	},
1138
1139	updateCurrItem: function(beforeAnimation) {
1140
1141		if(_indexDiff === 0) {
1142			return;
1143		}
1144
1145		var diffAbs = Math.abs(_indexDiff),
1146			tempHolder;
1147
1148		if(beforeAnimation && diffAbs < 2) {
1149			return;
1150		}
1151
1152
1153		self.currItem = _getItemAt( _currentItemIndex );
1154		_renderMaxResolution = false;
1155		
1156		_shout('beforeChange', _indexDiff);
1157
1158		if(diffAbs >= NUM_HOLDERS) {
1159			_containerShiftIndex += _indexDiff + (_indexDiff > 0 ? -NUM_HOLDERS : NUM_HOLDERS);
1160			diffAbs = NUM_HOLDERS;
1161		}
1162		for(var i = 0; i < diffAbs; i++) {
1163			if(_indexDiff > 0) {
1164				tempHolder = _itemHolders.shift();
1165				_itemHolders[NUM_HOLDERS-1] = tempHolder; // move first to last
1166
1167				_containerShiftIndex++;
1168				_setTranslateX( (_containerShiftIndex+2) * _slideSize.x, tempHolder.el.style);
1169				self.setContent(tempHolder, _currentItemIndex - diffAbs + i + 1 + 1);
1170			} else {
1171				tempHolder = _itemHolders.pop();
1172				_itemHolders.unshift( tempHolder ); // move last to first
1173
1174				_containerShiftIndex--;
1175				_setTranslateX( _containerShiftIndex * _slideSize.x, tempHolder.el.style);
1176				self.setContent(tempHolder, _currentItemIndex + diffAbs - i - 1 - 1);
1177			}
1178			
1179		}
1180
1181		// reset zoom/pan on previous item
1182		if(_currZoomElementStyle && Math.abs(_indexDiff) === 1) {
1183
1184			var prevItem = _getItemAt(_prevItemIndex);
1185			if(prevItem.initialZoomLevel !== _currZoomLevel) {
1186				_calculateItemSize(prevItem , _viewportSize );
1187				_setImageSize(prevItem);
1188				_applyZoomPanToItem( prevItem ); 				
1189			}
1190
1191		}
1192
1193		// reset diff after update
1194		_indexDiff = 0;
1195
1196		self.updateCurrZoomItem();
1197
1198		_prevItemIndex = _currentItemIndex;
1199
1200		_shout('afterChange');
1201		
1202	},
1203
1204
1205
1206	updateSize: function(force) {
1207		
1208		if(!_isFixedPosition && _options.modal) {
1209			var windowScrollY = framework.getScrollY();
1210			if(_currentWindowScrollY !== windowScrollY) {
1211				template.style.top = windowScrollY + 'px';
1212				_currentWindowScrollY = windowScrollY;
1213			}
1214			if(!force && _windowVisibleSize.x === window.innerWidth && _windowVisibleSize.y === window.innerHeight) {
1215				return;
1216			}
1217			_windowVisibleSize.x = window.innerWidth;
1218			_windowVisibleSize.y = window.innerHeight;
1219
1220			//template.style.width = _windowVisibleSize.x + 'px';
1221			template.style.height = _windowVisibleSize.y + 'px';
1222		}
1223
1224
1225
1226		_viewportSize.x = self.scrollWrap.clientWidth;
1227		_viewportSize.y = self.scrollWrap.clientHeight;
1228
1229		_updatePageScrollOffset();
1230
1231		_slideSize.x = _viewportSize.x + Math.round(_viewportSize.x * _options.spacing);
1232		_slideSize.y = _viewportSize.y;
1233
1234		_moveMainScroll(_slideSize.x * _currPositionIndex);
1235
1236		_shout('beforeResize'); // even may be used for example to switch image sources
1237
1238
1239		// don't re-calculate size on inital size update
1240		if(_containerShiftIndex !== undefined) {
1241
1242			var holder,
1243				item,
1244				hIndex;
1245
1246			for(var i = 0; i < NUM_HOLDERS; i++) {
1247				holder = _itemHolders[i];
1248				_setTranslateX( (i+_containerShiftIndex) * _slideSize.x, holder.el.style);
1249
1250				hIndex = _currentItemIndex+i-1;
1251
1252				if(_options.loop && _getNumItems() > 2) {
1253					hIndex = _getLoopedId(hIndex);
1254				}
1255
1256				// update zoom level on items and refresh source (if needsUpdate)
1257				item = _getItemAt( hIndex );
1258
1259				// re-render gallery item if `needsUpdate`,
1260				// or doesn't have `bounds` (entirely new slide object)
1261				if( item && (_itemsNeedUpdate || item.needsUpdate || !item.bounds) ) {
1262
1263					self.cleanSlide( item );
1264					
1265					self.setContent( holder, hIndex );
1266
1267					// if "center" slide
1268					if(i === 1) {
1269						self.currItem = item;
1270						self.updateCurrZoomItem(true);
1271					}
1272
1273					item.needsUpdate = false;
1274
1275				} else if(holder.index === -1 && hIndex >= 0) {
1276					// add content first time
1277					self.setContent( holder, hIndex );
1278				}
1279				if(item && item.container) {
1280					_calculateItemSize(item, _viewportSize);
1281					_setImageSize(item);
1282					_applyZoomPanToItem( item );
1283				}
1284				
1285			}
1286			_itemsNeedUpdate = false;
1287		}	
1288
1289		_startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
1290		_currPanBounds = self.currItem.bounds;
1291
1292		if(_currPanBounds) {
1293			_panOffset.x = _currPanBounds.center.x;
1294			_panOffset.y = _currPanBounds.center.y;
1295			_applyCurrentZoomPan( true );
1296		}
1297		
1298		_shout('resize');
1299	},
1300	
1301	// Zoom current item to
1302	zoomTo: function(destZoomLevel, centerPoint, speed, easingFn, updateFn) {
1303		/*
1304			if(destZoomLevel === 'fit') {
1305				destZoomLevel = self.currItem.fitRatio;
1306			} else if(destZoomLevel === 'fill') {
1307				destZoomLevel = self.currItem.fillRatio;
1308			}
1309		*/
1310
1311		if(centerPoint) {
1312			_startZoomLevel = _currZoomLevel;
1313			_midZoomPoint.x = Math.abs(centerPoint.x) - _panOffset.x ;
1314			_midZoomPoint.y = Math.abs(centerPoint.y) - _panOffset.y ;
1315			_equalizePoints(_startPanOffset, _panOffset);
1316		}
1317
1318		var destPanBounds = _calculatePanBounds(destZoomLevel, false),
1319			destPanOffset = {};
1320
1321		_modifyDestPanOffset('x', destPanBounds, destPanOffset, destZoomLevel);
1322		_modifyDestPanOffset('y', destPanBounds, destPanOffset, destZoomLevel);
1323
1324		var initialZoomLevel = _currZoomLevel;
1325		var initialPanOffset = {
1326			x: _panOffset.x,
1327			y: _panOffset.y
1328		};
1329
1330		_roundPoint(destPanOffset);
1331
1332		var onUpdate = function(now) {
1333			if(now === 1) {
1334				_currZoomLevel = destZoomLevel;
1335				_panOffset.x = destPanOffset.x;
1336				_panOffset.y = destPanOffset.y;
1337			} else {
1338				_currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
1339				_panOffset.x = (destPanOffset.x - initialPanOffset.x) * now + initialPanOffset.x;
1340				_panOffset.y = (destPanOffset.y - initialPanOffset.y) * now + initialPanOffset.y;
1341			}
1342
1343			if(updateFn) {
1344				updateFn(now);
1345			}
1346
1347			_applyCurrentZoomPan( now === 1 );
1348		};
1349
1350		if(speed) {
1351			_animateProp('customZoomTo', 0, 1, speed, easingFn || framework.easing.sine.inOut, onUpdate);
1352		} else {
1353			onUpdate(1);
1354		}
1355	}
1356
1357
1358};
1359
1360
1361/*>>core*/
1362
1363/*>>gestures*/
1364/**
1365 * Mouse/touch/pointer event handlers.
1366 * 
1367 * separated from @core.js for readability
1368 */
1369
1370var MIN_SWIPE_DISTANCE = 30,
1371	DIRECTION_CHECK_OFFSET = 10; // amount of pixels to drag to determine direction of swipe
1372
1373var _gestureStartTime,
1374	_gestureCheckSpeedTime,
1375
1376	// pool of objects that are used during dragging of zooming
1377	p = {}, // first point
1378	p2 = {}, // second point (for zoom gesture)
1379	delta = {},
1380	_currPoint = {},
1381	_startPoint = {},
1382	_currPointers = [],
1383	_startMainScrollPos = {},
1384	_releaseAnimData,
1385	_posPoints = [], // array of points during dragging, used to determine type of gesture
1386	_tempPoint = {},
1387
1388	_isZoomingIn,
1389	_verticalDragInitiated,
1390	_oldAndroidTouchEndTimeout,
1391	_currZoomedItemIndex = 0,
1392	_centerPoint = _getEmptyPoint(),
1393	_lastReleaseTime = 0,
1394	_isDragging, // at least one pointer is down
1395	_isMultitouch, // at least two _pointers are down
1396	_zoomStarted, // zoom level changed during zoom gesture
1397	_moved,
1398	_dragAnimFrame,
1399	_mainScrollShifted,
1400	_currentPoints, // array of current touch points
1401
1402	_isZooming,
1403	_currPointsDistance,
1404	_startPointsDistance,
1405	_currPanBounds,
1406	_mainScrollPos = _getEmptyPoint(),
1407	_currZoomElementStyle,
1408	_mainScrollAnimating, // true, if animation after swipe gesture is running
1409	_midZoomPoint = _getEmptyPoint(),
1410	_currCenterPoint = _getEmptyPoint(),
1411	_direction,
1412	_isFirstMove,
1413	_opacityChanged,
1414	_bgOpacity,
1415	_wasOverInitialZoom,
1416
1417	_isEqualPoints = function(p1, p2) {
1418		return p1.x === p2.x && p1.y === p2.y;
1419	},
1420	_isNearbyPoints = function(touch0, touch1) {
1421		return Math.abs(touch0.x - touch1.x) < DOUBLE_TAP_RADIUS && Math.abs(touch0.y - touch1.y) < DOUBLE_TAP_RADIUS;
1422	},
1423	_calculatePointsDistance = function(p1, p2) {
1424		_tempPoint.x = Math.abs( p1.x - p2.x );
1425		_tempPoint.y = Math.abs( p1.y - p2.y );
1426		return Math.sqrt(_tempPoint.x * _tempPoint.x + _tempPoint.y * _tempPoint.y);
1427	},
1428	_stopDragUpdateLoop = function() {
1429		if(_dragAnimFrame) {
1430			_cancelAF(_dragAnimFrame);
1431			_dragAnimFrame = null;
1432		}
1433	},
1434	_dragUpdateLoop = function() {
1435		if(_isDragging) {
1436			_dragAnimFrame = _requestAF(_dragUpdateLoop);
1437			_renderMovement();
1438		}
1439	},
1440	_canPan = function() {
1441		return !(_options.scaleMode === 'fit' && _currZoomLevel ===  self.currItem.initialZoomLevel);
1442	},
1443	
1444	// find the closest parent DOM element
1445	_closestElement = function(el, fn) {
1446	  	if(!el || el === document) {
1447	  		return false;
1448	  	}
1449
1450	  	// don't search elements above pswp__scroll-wrap
1451	  	if(el.getAttribute('class') && el.getAttribute('class').indexOf('pswp__scroll-wrap') > -1 ) {
1452	  		return false;
1453	  	}
1454
1455	  	if( fn(el) ) {
1456	  		return el;
1457	  	}
1458
1459	  	return _closestElement(el.parentNode, fn);
1460	},
1461
1462	_preventObj = {},
1463	_preventDefaultEventBehaviour = function(e, isDown) {
1464	    _preventObj.prevent = !_closestElement(e.target, _options.isClickableElement);
1465
1466		_shout('preventDragEvent', e, isDown, _preventObj);
1467		return _preventObj.prevent;
1468
1469	},
1470	_convertTouchToPoint = function(touch, p) {
1471		p.x = touch.pageX;
1472		p.y = touch.pageY;
1473		p.id = touch.identifier;
1474		return p;
1475	},
1476	_findCenterOfPoints = function(p1, p2, pCenter) {
1477		pCenter.x = (p1.x + p2.x) * 0.5;
1478		pCenter.y = (p1.y + p2.y) * 0.5;
1479	},
1480	_pushPosPoint = function(time, x, y) {
1481		if(time - _gestureCheckSpeedTime > 50) {
1482			var o = _posPoints.length > 2 ? _posPoints.shift() : {};
1483			o.x = x;
1484			o.y = y; 
1485			_posPoints.push(o);
1486			_gestureCheckSpeedTime = time;
1487		}
1488	},
1489
1490	_calculateVerticalDragOpacityRatio = function() {
1491		var yOffset = _panOffset.y - self.currItem.initialPosition.y; // difference between initial and current position
1492		return 1 -  Math.abs( yOffset / (_viewportSize.y / 2)  );
1493	},
1494
1495	
1496	// points pool, reused during touch events
1497	_ePoint1 = {},
1498	_ePoint2 = {},
1499	_tempPointsArr = [],
1500	_tempCounter,
1501	_getTouchPoints = function(e) {
1502		// clean up previous points, without recreating array
1503		while(_tempPointsArr.length > 0) {
1504			_tempPointsArr.pop();
1505		}
1506
1507		if(!_pointerEventEnabled) {
1508			if(e.type.indexOf('touch') > -1) {
1509
1510				if(e.touches && e.touches.length > 0) {
1511					_tempPointsArr[0] = _convertTouchToPoint(e.touches[0], _ePoint1);
1512					if(e.touches.length > 1) {
1513						_tempPointsArr[1] = _convertTouchToPoint(e.touches[1], _ePoint2);
1514					}
1515				}
1516				
1517			} else {
1518				_ePoint1.x = e.pageX;
1519				_ePoint1.y = e.pageY;
1520				_ePoint1.id = '';
1521				_tempPointsArr[0] = _ePoint1;//_ePoint1;
1522			}
1523		} else {
1524			_tempCounter = 0;
1525			// we can use forEach, as pointer events are supported only in modern browsers
1526			_currPointers.forEach(function(p) {
1527				if(_tempCounter === 0) {
1528					_tempPointsArr[0] = p;
1529				} else if(_tempCounter === 1) {
1530					_tempPointsArr[1] = p;
1531				}
1532				_tempCounter++;
1533
1534			});
1535		}
1536		return _tempPointsArr;
1537	},
1538
1539	_panOrMoveMainScroll = function(axis, delta) {
1540
1541		var panFriction,
1542			overDiff = 0,
1543			newOffset = _panOffset[axis] + delta[axis],
1544			startOverDiff,
1545			dir = delta[axis] > 0,
1546			newMainScrollPosition = _mainScrollPos.x + delta.x,
1547			mainScrollDiff = _mainScrollPos.x - _startMainScrollPos.x,
1548			newPanPos,
1549			newMainScrollPos;
1550
1551		// calculate fdistance over the bounds and friction
1552		if(newOffset > _currPanBounds.min[axis] || newOffset < _currPanBounds.max[axis]) {
1553			panFriction = _options.panEndFriction;
1554			// Linear increasing of friction, so at 1/4 of viewport it's at max value. 
1555			// Looks not as nice as was expected. Left for history.
1556			// panFriction = (1 - (_panOffset[axis] + delta[axis] + panBounds.min[axis]) / (_viewportSize[axis] / 4) );
1557		} else {
1558			panFriction = 1;
1559		}
1560		
1561		newOffset = _panOffset[axis] + delta[axis] * panFriction;
1562
1563		// move main scroll or start panning
1564		if(_options.allowPanToNext || _currZoomLevel === self.currItem.initialZoomLevel) {
1565
1566
1567			if(!_currZoomElementStyle) {
1568				
1569				newMainScrollPos = newMainScrollPosition;
1570
1571			} else if(_direction === 'h' && axis === 'x' && !_zoomStarted ) {
1572				
1573				if(dir) {
1574					if(newOffset > _currPanBounds.min[axis]) {
1575						panFriction = _options.panEndFriction;
1576						overDiff = _currPanBounds.min[axis] - newOffset;
1577						startOverDiff = _currPanBounds.min[axis] - _startPanOffset[axis];
1578					}
1579					
1580					// drag right
1581					if( (startOverDiff <= 0 || mainScrollDiff < 0) && _getNumItems() > 1 ) {
1582						newMainScrollPos = newMainScrollPosition;
1583						if(mainScrollDiff < 0 && newMainScrollPosition > _startMainScrollPos.x) {
1584							newMainScrollPos = _startMainScrollPos.x;
1585						}
1586					} else {
1587						if(_currPanBounds.min.x !== _currPanBounds.max.x) {
1588							newPanPos = newOffset;
1589						}
1590						
1591					}
1592
1593				} else {
1594
1595					if(newOffset < _currPanBounds.max[axis] ) {
1596						panFriction =_options.panEndFriction;
1597						overDiff = newOffset - _currPanBounds.max[axis];
1598						startOverDiff = _startPanOffset[axis] - _currPanBounds.max[axis];
1599					}
1600
1601					if( (startOverDiff <= 0 || mainScrollDiff > 0) && _getNumItems() > 1 ) {
1602						newMainScrollPos = newMainScrollPosition;
1603
1604						if(mainScrollDiff > 0 && newMainScrollPosition < _startMainScrollPos.x) {
1605							newMainScrollPos = _startMainScrollPos.x;
1606						}
1607
1608					} else {
1609						if(_currPanBounds.min.x !== _currPanBounds.max.x) {
1610							newPanPos = newOffset;
1611						}
1612					}
1613
1614				}
1615
1616
1617				//
1618			}
1619
1620			if(axis === 'x') {
1621
1622				if(newMainScrollPos !== undefined) {
1623					_moveMainScroll(newMainScrollPos, true);
1624					if(newMainScrollPos === _startMainScrollPos.x) {
1625						_mainScrollShifted = false;
1626					} else {
1627						_mainScrollShifted = true;
1628					}
1629				}
1630
1631				if(_currPanBounds.min.x !== _currPanBounds.max.x) {
1632					if(newPanPos !== undefined) {
1633						_panOffset.x = newPanPos;
1634					} else if(!_mainScrollShifted) {
1635						_panOffset.x += delta.x * panFriction;
1636					}
1637				}
1638
1639				return newMainScrollPos !== undefined;
1640			}
1641
1642		}
1643
1644		if(!_mainScrollAnimating) {
1645			
1646			if(!_mainScrollShifted) {
1647				if(_currZoomLevel > self.currItem.fitRatio) {
1648					_panOffset[axis] += delta[axis] * panFriction;
1649				
1650				}
1651			}
1652
1653			
1654		}
1655		
1656	},
1657
1658	// Pointerdown/touchstart/mousedown handler
1659	_onDragStart = function(e) {
1660
1661		// Allow dragging only via left mouse button.
1662		// As this handler is not added in IE8 - we ignore e.which
1663		// 
1664		// http://www.quirksmode.org/js/events_properties.html
1665		// https://developer.mozilla.org/en-US/docs/Web/API/event.button
1666		if(e.type === 'mousedown' && e.button > 0  ) {
1667			return;
1668		}
1669
1670		if(_initialZoomRunning) {
1671			e.preventDefault();
1672			return;
1673		}
1674
1675		if(_oldAndroidTouchEndTimeout && e.type === 'mousedown') {
1676			return;
1677		}
1678
1679		if(_preventDefaultEventBehaviour(e, true)) {
1680			e.preventDefault();
1681		}
1682
1683
1684
1685		_shout('pointerDown');
1686
1687		if(_pointerEventEnabled) {
1688			var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
1689			if(pointerIndex < 0) {
1690				pointerIndex = _currPointers.length;
1691			}
1692			_currPointers[pointerIndex] = {x:e.pageX, y:e.pageY, id: e.pointerId};
1693		}
1694		
1695
1696
1697		var startPointsList = _getTouchPoints(e),
1698			numPoints = startPointsList.length;
1699
1700		_currentPoints = null;
1701
1702		_stopAllAnimations();
1703
1704		// init drag
1705		if(!_isDragging || numPoints === 1) {
1706
1707			
1708
1709			_isDragging = _isFirstMove = true;
1710			framework.bind(window, _upMoveEvents, self);
1711
1712			_isZoomingIn = 
1713				_wasOverInitialZoom = 
1714				_opacityChanged = 
1715				_verticalDragInitiated = 
1716				_mainScrollShifted = 
1717				_moved = 
1718				_isMultitouch = 
1719				_zoomStarted = false;
1720
1721			_direction = null;
1722
1723			_shout('firstTouchStart', startPointsList);
1724
1725			_equalizePoints(_startPanOffset, _panOffset);
1726
1727			_currPanDist.x = _currPanDist.y = 0;
1728			_equalizePoints(_currPoint, startPointsList[0]);
1729			_equalizePoints(_startPoint, _currPoint);
1730
1731			//_equalizePoints(_startMainScrollPos, _mainScrollPos);
1732			_startMainScrollPos.x = _slideSize.x * _currPositionIndex;
1733
1734			_posPoints = [{
1735				x: _currPoint.x,
1736				y: _currPoint.y
1737			}];
1738
1739			_gestureCheckSpeedTime = _gestureStartTime = _getCurrentTime();
1740
1741			//_mainScrollAnimationEnd(true);
1742			_calculatePanBounds( _currZoomLevel, true );
1743			
1744			// Start rendering
1745			_stopDragUpdateLoop();
1746			_dragUpdateLoop();
1747			
1748		}
1749
1750		// init zoom
1751		if(!_isZooming && numPoints > 1 && !_mainScrollAnimating && !_mainScrollShifted) {
1752			_startZoomLevel = _currZoomLevel;
1753			_zoomStarted = false; // true if zoom changed at least once
1754
1755			_isZooming = _isMultitouch = true;
1756			_currPanDist.y = _currPanDist.x = 0;
1757
1758			_equalizePoints(_startPanOffset, _panOffset);
1759
1760			_equalizePoints(p, startPointsList[0]);
1761			_equalizePoints(p2, startPointsList[1]);
1762
1763			_findCenterOfPoints(p, p2, _currCenterPoint);
1764
1765			_midZoomPoint.x = Math.abs(_currCenterPoint.x) - _panOffset.x;
1766			_midZoomPoint.y = Math.abs(_currCenterPoint.y) - _panOffset.y;
1767			_currPointsDistance = _startPointsDistance = _calculatePointsDistance(p, p2);
1768		}
1769
1770
1771	},
1772
1773	// Pointermove/touchmove/mousemove handler
1774	_onDragMove = function(e) {
1775
1776		e.preventDefault();
1777
1778		if(_pointerEventEnabled) {
1779			var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
1780			if(pointerIndex > -1) {
1781				var p = _currPointers[pointerIndex];
1782				p.x = e.pageX;
1783				p.y = e.pageY; 
1784			}
1785		}
1786
1787		if(_isDragging) {
1788			var touchesList = _getTouchPoints(e);
1789			if(!_direction && !_moved && !_isZooming) {
1790
1791				if(_mainScrollPos.x !== _slideSize.x * _currPositionIndex) {
1792					// if main scroll position is shifted – direction is always horizontal
1793					_direction = 'h';
1794				} else {
1795					var diff = Math.abs(touchesList[0].x - _currPoint.x) - Math.abs(touchesList[0].y - _currPoint.y);
1796					// check the direction of movement
1797					if(Math.abs(diff) >= DIRECTION_CHECK_OFFSET) {
1798						_direction = diff > 0 ? 'h' : 'v';
1799						_currentPoints = touchesList;
1800					}
1801				}
1802				
1803			} else {
1804				_currentPoints = touchesList;
1805			}
1806		}	
1807	},
1808	// 
1809	_renderMovement =  function() {
1810
1811		if(!_currentPoints) {
1812			return;
1813		}
1814
1815		var numPoints = _currentPoints.length;
1816
1817		if(numPoints === 0) {
1818			return;
1819		}
1820
1821		_equalizePoints(p, _currentPoints[0]);
1822
1823		delta.x = p.x - _currPoint.x;
1824		delta.y = p.y - _currPoint.y;
1825
1826		if(_isZooming && numPoints > 1) {
1827			// Handle behaviour for more than 1 point
1828
1829			_currPoint.x = p.x;
1830			_currPoint.y = p.y;
1831		
1832			// check if one of two points changed
1833			if( !delta.x && !delta.y && _isEqualPoints(_currentPoints[1], p2) ) {
1834				return;
1835			}
1836
1837			_equalizePoints(p2, _currentPoints[1]);
1838
1839
1840			if(!_zoomStarted) {
1841				_zoomStarted = true;
1842				_shout('zoomGestureStarted');
1843			}
1844			
1845			// Distance between two points
1846			var pointsDistance = _calculatePointsDistance(p,p2);
1847
1848			var zoomLevel = _calculateZoomLevel(pointsDistance);
1849
1850			// slightly over the of initial zoom level
1851			if(zoomLevel > self.currItem.initialZoomLevel + self.currItem.initialZoomLevel / 15) {
1852				_wasOverInitialZoom = true;
1853			}
1854
1855			// Apply the friction if zoom level is out of the bounds
1856			var zoomFriction = 1,
1857				minZoomLevel = _getMinZoomLevel(),
1858				maxZoomLevel = _getMaxZoomLevel();
1859
1860			if ( zoomLevel < minZoomLevel ) {
1861				
1862				if(_options.pinchToClose && !_wasOverInitialZoom && _startZoomLevel <= self.currItem.initialZoomLevel) {
1863					// fade out background if zooming out
1864					var minusDiff = minZoomLevel - zoomLevel;
1865					var percent = 1 - minusDiff / (minZoomLevel / 1.2);
1866
1867					_applyBgOpacity(percent);
1868					_shout('onPinchClose', percent);
1869					_opacityChanged = true;
1870				} else {
1871					zoomFriction = (minZoomLevel - zoomLevel) / minZoomLevel;
1872					if(zoomFriction > 1) {
1873						zoomFriction = 1;
1874					}
1875					zoomLevel = minZoomLevel - zoomFriction * (minZoomLevel / 3);
1876				}
1877				
1878			} else if ( zoomLevel > maxZoomLevel ) {
1879				// 1.5 - extra zoom level above the max. E.g. if max is x6, real max 6 + 1.5 = 7.5
1880				zoomFriction = (zoomLevel - maxZoomLevel) / ( minZoomLevel * 6 );
1881				if(zoomFriction > 1) {
1882					zoomFriction = 1;
1883				}
1884				zoomLevel = maxZoomLevel + zoomFriction * minZoomLevel;
1885			}
1886
1887			if(zoomFriction < 0) {
1888				zoomFriction = 0;
1889			}
1890
1891			// distance between touch points after friction is applied
1892			_currPointsDistance = pointsDistance;
1893
1894			// _centerPoint - The point in the middle of two pointers
1895			_findCenterOfPoints(p, p2, _centerPoint);
1896		
1897			// paning with two pointers pressed
1898			_currPanDist.x += _centerPoint.x - _currCenterPoint.x;
1899			_currPanDist.y += _centerPoint.y - _currCenterPoint.y;
1900			_equalizePoints(_currCenterPoint, _centerPoint);
1901
1902			_panOffset.x = _calculatePanOffset('x', zoomLevel);
1903			_panOffset.y = _calculatePanOffset('y', zoomLevel);
1904
1905			_isZoomingIn = zoomLevel > _currZoomLevel;
1906			_currZoomLevel = zoomLevel;
1907			_applyCurrentZoomPan();
1908
1909		} else {
1910
1911			// handle behaviour for one point (dragging or panning)
1912
1913			if(!_direction) {
1914				return;
1915			}
1916
1917			if(_isFirstMove) {
1918				_isFirstMove = false;
1919
1920				// subtract drag distance that was used during the detection direction  
1921
1922				if( Math.abs(delta.x) >= DIRECTION_CHECK_OFFSET) {
1923					delta.x -= _currentPoints[0].x - _startPoint.x;
1924				}
1925				
1926				if( Math.abs(delta.y) >= DIRECTION_CHECK_OFFSET) {
1927					delta.y -= _currentPoints[0].y - _startPoint.y;
1928				}
1929			}
1930
1931			_currPoint.x = p.x;
1932			_currPoint.y = p.y;
1933
1934			// do nothing if pointers position hasn't changed
1935			if(delta.x === 0 && delta.y === 0) {
1936				return;
1937			}
1938
1939			if(_direction === 'v' && _options.closeOnVerticalDrag) {
1940				if(!_canPan()) {
1941					_currPanDist.y += delta.y;
1942					_panOffset.y += delta.y;
1943
1944					var opacityRatio = _calculateVerticalDragOpacityRatio();
1945
1946					_verticalDragInitiated = true;
1947					_shout('onVerticalDrag', opacityRatio);
1948
1949					_applyBgOpacity(opacityRatio);
1950					_applyCurrentZoomPan();
1951					return ;
1952				}
1953			}
1954
1955			_pushPosPoint(_getCurrentTime(), p.x, p.y);
1956
1957			_moved = true;
1958			_currPanBounds = self.currItem.bounds;
1959			
1960			var mainScrollChanged = _panOrMoveMainScroll('x', delta);
1961			if(!mainScrollChanged) {
1962				_panOrMoveMainScroll('y', delta);
1963
1964				_roundPoint(_panOffset);
1965				_applyCurrentZoomPan();
1966			}
1967
1968		}
1969
1970	},
1971	
1972	// Pointerup/pointercancel/touchend/touchcancel/mouseup event handler
1973	_onDragRelease = function(e) {
1974
1975		if(_features.isOldAndroid ) {
1976
1977			if(_oldAndroidTouchEndTimeout && e.type === 'mouseup') {
1978				return;
1979			}
1980
1981			// on Android (v4.1, 4.2, 4.3 & possibly older) 
1982			// ghost mousedown/up event isn't preventable via e.preventDefault,
1983			// which causes fake mousedown event
1984			// so we block mousedown/up for 600ms
1985			if( e.type.indexOf('touch') > -1 ) {
1986				clearTimeout(_oldAndroidTouchEndTimeout);
1987				_oldAndroidTouchEndTimeout = setTimeout(function() {
1988					_oldAndroidTouchEndTimeout = 0;
1989				}, 600);
1990			}
1991			
1992		}
1993
1994		_shout('pointerUp');
1995
1996		if(_preventDefaultEventBehaviour(e, false)) {
1997			e.preventDefault();
1998		}
1999
2000		var releasePoint;
2001
2002		if(_pointerEventEnabled) {
2003			var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
2004			
2005			if(pointerIndex > -1) {
2006				releasePoint = _currPointers.splice(pointerIndex, 1)[0];
2007
2008				if(navigator.msPointerEnabled) {
2009					var MSPOINTER_TYPES = {
2010						4: 'mouse', // event.MSPOINTER_TYPE_MOUSE
2011						2: 'touch', // event.MSPOINTER_TYPE_TOUCH 
2012						3: 'pen' // event.MSPOINTER_TYPE_PEN
2013					};
2014					releasePoint.type = MSPOINTER_TYPES[e.pointerType];
2015
2016					if(!releasePoint.type) {
2017						releasePoint.type = e.pointerType || 'mouse';
2018					}
2019				} else {
2020					releasePoint.type = e.pointerType || 'mouse';
2021				}
2022
2023			}
2024		}
2025
2026
2027		var touchList = _getTouchPoints(e),
2028			gestureType,
2029			numPoints = touchList.length;
2030
2031		if(e.type === 'mouseup') {
2032			numPoints = 0;
2033		}
2034
2035		// Do nothing if there were 3 touch points or more
2036		if(numPoints === 2) {
2037			_currentPoints = null;
2038			return true;
2039		}
2040
2041		// if second pointer released
2042		if(numPoints === 1) {
2043			_equalizePoints(_startPoint, touchList[0]);
2044		}				
2045
2046
2047		// pointer hasn't moved, send "tap release" point
2048		if(numPoints === 0 && !_direction && !_mainScrollAnimating) {
2049			if(!releasePoint) {
2050				if(e.type === 'mouseup') {
2051					releasePoint = {x: e.pageX, y: e.pageY, type:'mouse'};
2052				} else if(e.changedTouches && e.changedTouches[0]) {
2053					releasePoint = {x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY, type:'touch'};
2054				}		
2055			}
2056
2057			_shout('touchRelease', e, releasePoint);
2058		}
2059
2060		// Difference in time between releasing of two last touch points (zoom gesture)
2061		var releaseTimeDiff = -1;
2062
2063		// Gesture completed, no pointers left
2064		if(numPoints === 0) {
2065			_isDragging = false;
2066			framework.unbind(window, _upMoveEvents, self);
2067
2068			_stopDragUpdateLoop();
2069
2070			if(_isZooming) {
2071				// Two points released at the same time
2072				releaseTimeDiff = 0;
2073			} else if(_lastReleaseTime !== -1) {
2074				releaseTimeDiff = _getCurrentTime() - _lastReleaseTime;
2075			}
2076		}
2077		_lastReleaseTime = numPoints === 1 ? _getCurrentTime() : -1;
2078		
2079		if(releaseTimeDiff !== -1 && releaseTimeDiff < 150) {
2080			gestureType = 'zoom';
2081		} else {
2082			gestureType = 'swipe';
2083		}
2084
2085		if(_isZooming && numPoints < 2) {
2086			_isZooming = false;
2087
2088			// Only second point released
2089			if(numPoints === 1) {
2090				gestureType = 'zoomPointerUp';
2091			}
2092			_shout('zoomGestureEnded');
2093		}
2094
2095		_currentPoints = null;
2096		if(!_moved && !_zoomStarted && !_mainScrollAnimating && !_verticalDragInitiated) {
2097			// nothing to animate
2098			return;
2099		}
2100	
2101		_stopAllAnimations();
2102
2103		
2104		if(!_releaseAnimData) {
2105			_releaseAnimData = _initDragReleaseAnimationData();
2106		}
2107		
2108		_releaseAnimData.calculateSwipeSpeed('x');
2109
2110
2111		if(_verticalDragInitiated) {
2112
2113			var opacityRatio = _calculateVerticalDragOpacityRatio();
2114
2115			if(opacityRatio < _options.verticalDragRange) {
2116				self.close();
2117			} else {
2118				var initalPanY = _panOffset.y,
2119					initialBgOpacity = _bgOpacity;
2120
2121				_animateProp('verticalDrag', 0, 1, 300, framework.easing.cubic.out, function(now) {
2122					
2123					_panOffset.y = (self.currItem.initialPosition.y - initalPanY) * now + initalPanY;
2124
2125					_applyBgOpacity(  (1 - initialBgOpacity) * now + initialBgOpacity );
2126					_applyCurrentZoomPan();
2127				});
2128
2129				_shout('onVerticalDrag', 1);
2130			}
2131
2132			return;
2133		}
2134
2135
2136		// main scroll 
2137		if(  (_mainScrollShifted || _mainScrollAnimating) && numPoints === 0) {
2138			var itemChanged = _finishSwipeMainScrollGesture(gestureType, _releaseAnimData);
2139			if(itemChanged) {
2140				return;
2141			}
2142			gestureType = 'zoomPointerUp';
2143		}
2144
2145		// prevent zoom/pan animation when main scroll animation runs
2146		if(_mainScrollAnimating) {
2147			return;
2148		}
2149		
2150		// Complete simple zoom gesture (reset zoom level if it's out of the bounds)  
2151		if(gestureType !== 'swipe') {
2152			_completeZoomGesture();
2153			return;
2154		}
2155	
2156		// Complete pan gesture if main scroll is not shifted, and it's possible to pan current image
2157		if(!_mainScrollShifted && _currZoomLevel > self.currItem.fitRatio) {
2158			_completePanGesture(_releaseAnimData);
2159		}
2160	},
2161
2162
2163	// Returns object with data about gesture
2164	// It's created only once and then reused
2165	_initDragReleaseAnimationData  = function() {
2166		// temp local vars
2167		var lastFlickDuration,
2168			tempReleasePos;
2169
2170		// s = this
2171		var s = {
2172			lastFlickOffset: {},
2173			lastFlickDist: {},
2174			lastFlickSpeed: {},
2175			slowDownRatio:  {},
2176			slowDownRatioReverse:  {},
2177			speedDecelerationRatio:  {},
2178			speedDecelerationRatioAbs:  {},
2179			distanceOffset:  {},
2180			backAnimDestination: {},
2181			backAnimStarted: {},
2182			calculateSwipeSpeed: function(axis) {
2183				
2184
2185				if( _posPoints.length > 1) {
2186					lastFlickDuration = _getCurrentTime() - _gestureCheckSpeedTime + 50;
2187					tempReleasePos = _posPoints[_posPoints.length-2][axis];
2188				} else {
2189					lastFlickDuration = _getCurrentTime() - _gestureStartTime; // total gesture duration
2190					tempReleasePos = _startPoint[axis];
2191				}
2192				s.lastFlickOffset[axis] = _currPoint[axis] - tempReleasePos;
2193				s.lastFlickDist[axis] = Math.abs(s.lastFlickOffset[axis]);
2194				if(s.lastFlickDist[axis] > 20) {
2195					s.lastFlickSpeed[axis] = s.lastFlickOffset[axis] / lastFlickDuration;
2196				} else {
2197					s.lastFlickSpeed[axis] = 0;
2198				}
2199				if( Math.abs(s.lastFlickSpeed[axis]) < 0.1 ) {
2200					s.lastFlickSpeed[axis] = 0;
2201				}
2202				
2203				s.slowDownRatio[axis] = 0.95;
2204				s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
2205				s.speedDecelerationRatio[axis] = 1;
2206			},
2207
2208			calculateOverBoundsAnimOffset: function(axis, speed) {
2209				if(!s.backAnimStarted[axis]) {
2210
2211					if(_panOffset[axis] > _currPanBounds.min[axis]) {
2212						s.backAnimDestination[axis] = _currPanBounds.min[axis];
2213						
2214					} else if(_panOffset[axis] < _currPanBounds.max[axis]) {
2215						s.backAnimDestination[axis] = _currPanBounds.max[axis];
2216					}
2217
2218					if(s.backAnimDestination[axis] !== undefined) {
2219						s.slowDownRatio[axis] = 0.7;
2220						s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
2221						if(s.speedDecelerationRatioAbs[axis] < 0.05) {
2222
2223							s.lastFlickSpeed[axis] = 0;
2224							s.backAnimStarted[axis] = true;
2225
2226							_animateProp('bounceZoomPan'+axis,_panOffset[axis], 
2227								s.backAnimDestination[axis], 
2228								speed || 300, 
2229								framework.easing.sine.out, 
2230								function(pos) {
2231									_panOffset[axis] = pos;
2232									_applyCurrentZoomPan();
2233								}
2234							);
2235
2236						}
2237					}
2238				}
2239			},
2240
2241			// Reduces the speed by slowDownRatio (per 10ms)
2242			calculateAnimOffset: function(axis) {
2243				if(!s.backAnimStarted[axis]) {
2244					s.speedDecelerationRatio[axis] = s.speedDecelerationRatio[axis] * (s.slowDownRatio[axis] + 
2245												s.slowDownRatioReverse[axis] - 
2246												s.slowDownRatioReverse[axis] * s.timeDiff / 10);
2247
2248					s.speedDecelerationRatioAbs[axis] = Math.abs(s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis]);
2249					s.distanceOffset[axis] = s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis] * s.timeDiff;
2250					_panOffset[axis] += s.distanceOffset[axis];
2251
2252				}
2253			},
2254
2255			panAnimLoop: function() {
2256				if ( _animations.zoomPan ) {
2257					_animations.zoomPan.raf = _requestAF(s.panAnimLoop);
2258
2259					s.now = _getCurrentTime();
2260					s.timeDiff = s.now - s.lastNow;
2261					s.lastNow = s.now;
2262					
2263					s.calculateAnimOffset('x');
2264					s.calculateAnimOffset('y');
2265
2266					_applyCurrentZoomPan();
2267					
2268					s.calculateOverBoundsAnimOffset('x');
2269					s.calculateOverBoundsAnimOffset('y');
2270
2271
2272					if (s.speedDecelerationRatioAbs.x < 0.05 && s.speedDecelerationRatioAbs.y < 0.05) {
2273
2274						// round pan position
2275						_panOffset.x = Math.round(_panOffset.x);
2276						_panOffset.y = Math.round(_panOffset.y);
2277						_applyCurrentZoomPan();
2278						
2279						_stopAnimation('zoomPan');
2280						return;
2281					}
2282				}
2283
2284			}
2285		};
2286		return s;
2287	},
2288
2289	_completePanGesture = function(animData) {
2290		// calculate swipe speed for Y axis (paanning)
2291		animData.calculateSwipeSpeed('y');
2292
2293		_currPanBounds = self.currItem.bounds;
2294		
2295		animData.backAnimDestination = {};
2296		animData.backAnimStarted = {};
2297
2298		// Avoid acceleration animation if speed is too low
2299		if(Math.abs(animData.lastFlickSpeed.x) <= 0.05 && Math.abs(animData.lastFlickSpeed.y) <= 0.05 ) {
2300			animData.speedDecelerationRatioAbs.x = animData.speedDecelerationRatioAbs.y = 0;
2301
2302			// Run pan drag release animation. E.g. if you drag image and release finger without momentum.
2303			animData.calculateOverBoundsAnimOffset('x');
2304			animData.calculateOverBoundsAnimOffset('y');
2305			return true;
2306		}
2307
2308		// Animation loop that controls the acceleration after pan gesture ends
2309		_registerStartAnimation('zoomPan');
2310		animData.lastNow = _getCurrentTime();
2311		animData.panAnimLoop();
2312	},
2313
2314
2315	_finishSwipeMainScrollGesture = function(gestureType, _releaseAnimData) {
2316		var itemChanged;
2317		if(!_mainScrollAnimating) {
2318			_currZoomedItemIndex = _currentItemIndex;
2319		}
2320
2321
2322		
2323		var itemsDiff;
2324
2325		if(gestureType === 'swipe') {
2326			var totalShiftDist = _currPoint.x - _startPoint.x,
2327				isFastLastFlick = _releaseAnimData.lastFlickDist.x < 10;
2328
2329			// if container is shifted for more than MIN_SWIPE_DISTANCE, 
2330			// and last flick gesture was in right direction
2331			if(totalShiftDist > MIN_SWIPE_DISTANCE && 
2332				(isFastLastFlick || _releaseAnimData.lastFlickOffset.x > 20) ) {
2333				// go to prev item
2334				itemsDiff = -1;
2335			} else if(totalShiftDist < -MIN_SWIPE_DISTANCE && 
2336				(isFastLastFlick || _releaseAnimData.lastFlickOffset.x < -20) ) {
2337				// go to next item
2338				itemsDiff = 1;
2339			}
2340		}
2341
2342		var nextCircle;
2343
2344		if(itemsDiff) {
2345			
2346			_currentItemIndex += itemsDiff;
2347
2348			if(_currentItemIndex < 0) {
2349				_currentItemIndex = _options.loop ? _getNumItems()-1 : 0;
2350				nextCircle = true;
2351			} else if(_currentItemIndex >= _getNumItems()) {
2352				_currentItemIndex = _options.loop ? 0 : _getNumItems()-1;
2353				nextCircle = true;
2354			}
2355
2356			if(!nextCircle || _options.loop) {
2357				_indexDiff += itemsDiff;
2358				_currPositionIndex -= itemsDiff;
2359				itemChanged = true;
2360			}
2361			
2362
2363			
2364		}
2365
2366		var animateToX = _slideSize.x * _currPositionIndex;
2367		var animateToDist = Math.abs( animateToX - _mainScrollPos.x );
2368		var finishAnimDuration;
2369
2370
2371		if(!itemChanged && animateToX > _mainScrollPos.x !== _releaseAnimData.lastFlickSpeed.x > 0) {
2372			// "return to current" duration, e.g. when dragging from slide 0 to -1
2373			finishAnimDuration = 333; 
2374		} else {
2375			finishAnimDuration = Math.abs(_releaseAnimData.lastFlickSpeed.x) > 0 ? 
2376									animateToDist / Math.abs(_releaseAnimData.lastFlickSpeed.x) : 
2377									333;
2378
2379			finishAnimDuration = Math.min(finishAnimDuration, 400);
2380			finishAnimDuration = Math.max(finishAnimDuration, 250);
2381		}
2382
2383		if(_currZoomedItemIndex === _currentItemIndex) {
2384			itemChanged = false;
2385		}
2386		
2387		_mainScrollAnimating = true;
2388		
2389		_shout('mainScrollAnimStart');
2390
2391		_animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out, 
2392			_moveMainScroll,
2393			function() {
2394				_stopAllAnimations();
2395				_mainScrollAnimating = false;
2396				_currZoomedItemIndex = -1;
2397				
2398				if(itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
2399					self.updateCurrItem();
2400				}
2401				
2402				_shout('mainScrollAnimComplete');
2403			}
2404		);
2405
2406		if(itemChanged) {
2407			self.updateCurrItem(true);
2408		}
2409
2410		return itemChanged;
2411	},
2412
2413	_calculateZoomLevel = function(touchesDistance) {
2414		return  1 / _startPointsDistance * touchesDistance * _startZoomLevel;
2415	},
2416
2417	// Resets zoom if it's out of bounds
2418	_completeZoomGesture = function() {
2419		var destZoomLevel = _currZoomLevel,
2420			minZoomLevel = _getMinZoomLevel(),
2421			maxZoomLevel = _getMaxZoomLevel();
2422
2423		if ( _currZoomLevel < minZoomLevel ) {
2424			destZoomLevel = minZoomLevel;
2425		} else if ( _currZoomLevel > maxZoomLevel ) {
2426			destZoomLevel = maxZoomLevel;
2427		}
2428
2429		var destOpacity = 1,
2430			onUpdate,
2431			initialOpacity = _bgOpacity;
2432
2433		if(_opacityChanged && !_isZoomingIn && !_wasOverInitialZoom && _currZoomLevel < minZoomLevel) {
2434			//_closedByScroll = true;
2435			self.close();
2436			return true;
2437		}
2438
2439		if(_opacityChanged) {
2440			onUpdate = function(now) {
2441				_applyBgOpacity(  (destOpacity - initialOpacity) * now + initialOpacity );
2442			};
2443		}
2444
2445		self.zoomTo(destZoomLevel, 0, 200,  framework.easing.cubic.out, onUpdate);
2446		return true;
2447	};
2448
2449
2450_registerModule('Gestures', {
2451	publicMethods: {
2452
2453		initGestures: function() {
2454
2455			// helper function that builds touch/pointer/mouse events
2456			var addEventNames = function(pref, down, move, up, cancel) {
2457				_dragStartEvent = pref + down;
2458				_dragMoveEvent = pref + move;
2459				_dragEndEvent = pref + up;
2460				if(cancel) {
2461					_dragCancelEvent = pref + cancel;
2462				} else {
2463					_dragCancelEvent = '';
2464				}
2465			};
2466
2467			_pointerEventEnabled = _features.pointerEvent;
2468			if(_pointerEventEnabled && _features.touch) {
2469				// we don't need touch events, if browser supports pointer events
2470				_features.touch = false;
2471			}
2472
2473			if(_pointerEventEnabled) {
2474				if(navigator.msPointerEnabled) {
2475					// IE10 pointer events are case-sensitive
2476					addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
2477				} else {
2478					addEventNames('pointer', 'down', 'move', 'up', 'cancel');
2479				}
2480			} else if(_features.touch) {
2481				addEventNames('touch', 'start', 'move', 'end', 'cancel');
2482				_likelyTouchDevice = true;
2483			} else {
2484				addEventNames('mouse', 'down', 'move', 'up');	
2485			}
2486
2487
2488			_upMoveEvents = _dragMoveEvent + ' ' + _dragEndEvent  + ' ' +  _dragCancelEvent;
2489			_downEvents = _dragStartEvent;
2490
2491			if(_pointerEventEnabled && !_likelyTouchDevice) {
2492				_likelyTouchDevice = (navigator.maxTouchPoints > 1) || (navigator.msMaxTouchPoints > 1);
2493			}
2494			// make variable public
2495			self.likelyTouchDevice = _likelyTouchDevice; 
2496			
2497			_globalEventHandlers[_dragStartEvent] = _onDragStart;
2498			_globalEventHandlers[_dragMoveEvent] = _onDragMove;
2499			_globalEventHandlers[_dragEndEvent] = _onDragRelease; // the Kraken
2500
2501			if(_dragCancelEvent) {
2502				_globalEventHandlers[_dragCancelEvent] = _globalEventHandlers[_dragEndEvent];
2503			}
2504
2505			// Bind mouse events on device with detected hardware touch support, in case it supports multiple types of input.
2506			if(_features.touch) {
2507				_downEvents += ' mousedown';
2508				_upMoveEvents += ' mousemove mouseup';
2509				_globalEventHandlers.mousedown = _globalEventHandlers[_dragStartEvent];
2510				_globalEventHandlers.mousemove = _globalEventHandlers[_dragMoveEvent];
2511				_globalEventHandlers.mouseup = _globalEventHandlers[_dragEndEvent];
2512			}
2513
2514			if(!_likelyTouchDevice) {
2515				// don't allow pan to next slide from zoomed state on Desktop
2516				_options.allowPanToNext = false;
2517			}
2518		}
2519
2520	}
2521});
2522
2523
2524/*>>gestures*/
2525
2526/*>>show-hide-transition*/
2527/**
2528 * show-hide-transition.js:
2529 *
2530 * Manages initial opening or closing transition.
2531 *
2532 * If you're not planning to use transition for gallery at all,
2533 * you may set options hideAnimationDuration and showAnimationDuration to 0,
2534 * and just delete startAnimation function.
2535 * 
2536 */
2537
2538
2539var _showOrHideTimeout,
2540	_showOrHide = function(item, img, out, completeFn) {
2541
2542		if(_showOrHideTimeout) {
2543			clearTimeout(_showOrHideTimeout);
2544		}
2545
2546		_initialZoomRunning = true;
2547		_initialContentSet = true;
2548		
2549		// dimensions of small thumbnail {x:,y:,w:}.
2550		// Height is optional, as calculated based on large image.
2551		var thumbBounds; 
2552		if(item.initialLayout) {
2553			thumbBounds = item.initialLayout;
2554			item.initialLayout = null;
2555		} else {
2556			thumbBounds = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
2557		}
2558
2559		var duration = out ? _options.hideAnimationDuration : _options.showAnimationDuration;
2560
2561		var onComplete = function() {
2562			_stopAnimation('initialZoom');
2563			if(!out) {
2564				_applyBgOpacity(1);
2565				if(img) {
2566					img.style.display = 'block';
2567				}
2568				framework.addClass(template, 'pswp--animated-in');
2569				_shout('initialZoom' + (out ? 'OutEnd' : 'InEnd'));
2570			} else {
2571				self.template.removeAttribute('style');
2572				self.bg.removeAttribute('style');
2573			}
2574
2575			if(completeFn) {
2576				completeFn();
2577			}
2578			_initialZoomRunning = false;
2579		};
2580
2581		// if bounds aren't provided, just open gallery without animation
2582		if(!duration || !thumbBounds || thumbBounds.x === undefined) {
2583
2584			_shout('initialZoom' + (out ? 'Out' : 'In') );
2585
2586			_currZoomLevel = item.initialZoomLevel;
2587			_equalizePoints(_panOffset,  item.initialPosition );
2588			_applyCurrentZoomPan();
2589
2590			template.style.opacity = out ? 0 : 1;
2591			_applyBgOpacity(1);
2592
2593			if(duration) {
2594				setTimeout(function() {
2595					onComplete();
2596				}, duration);
2597			} else {
2598				onComplete();
2599			}
2600
2601			return;
2602		}
2603
2604		var startAnimation = function() {
2605			var closeWithRaf = _closedByScroll,
2606				fadeEverything = !self.currItem.src || self.currItem.loadError || _options.showHideOpacity;
2607			
2608			// apply hw-acceleration to image
2609			if(item.miniImg) {
2610				item.miniImg.style.webkitBackfaceVisibility = 'hidden';
2611			}
2612
2613			if(!out) {
2614				_currZoomLevel = thumbBounds.w / item.w;
2615				_panOffset.x = thumbBounds.x;
2616				_panOffset.y = thumbBounds.y - _initalWindowScrollY;
2617
2618				self[fadeEverything ? 'template' : 'bg'].style.opacity = 0.001;
2619				_applyCurrentZoomPan();
2620			}
2621
2622			_registerStartAnimation('initialZoom');
2623			
2624			if(out && !closeWithRaf) {
2625				framework.removeClass(template, 'pswp--animated-in');
2626			}
2627
2628			if(fadeEverything) {
2629				if(out) {
2630					framework[ (closeWithRaf ? 'remove' : 'add') + 'Class' ](template, 'pswp--animate_opacity');
2631				} else {
2632					setTimeout(function() {
2633						framework.addClass(template, 'pswp--animate_opacity');
2634					}, 30);
2635				}
2636			}
2637
2638			_showOrHideTimeout = setTimeout(function() {
2639
2640				_shout('initialZoom' + (out ? 'Out' : 'In') );
2641				
2642
2643				if(!out) {
2644
2645					// "in" animation always uses CSS transitions (instead of rAF).
2646					// CSS transition work faster here, 
2647					// as developer may also want to animate other things, 
2648					// like ui on top of sliding area, which can be animated just via CSS
2649					
2650					_currZoomLevel = item.initialZoomLevel;
2651					_equalizePoints(_panOffset,  item.initialPosition );
2652					_applyCurrentZoomPan();
2653					_applyBgOpacity(1);
2654
2655					if(fadeEverything) {
2656						template.style.opacity = 1;
2657					} else {
2658						_applyBgOpacity(1);
2659					}
2660
2661					_showOrHideTimeout = setTimeout(onComplete, duration + 20);
2662				} else {
2663
2664					// "out" animation uses rAF only when PhotoSwipe is closed by browser scroll, to recalculate position
2665					var destZoomLevel = thumbBounds.w / item.w,
2666						initialPanOffset = {
2667							x: _panOffset.x,
2668							y: _panOffset.y
2669						},
2670						initialZoomLevel = _currZoomLevel,
2671						initalBgOpacity = _bgOpacity,
2672						onUpdate = function(now) {
2673							
2674							if(now === 1) {
2675								_currZoomLevel = destZoomLevel;
2676								_panOffset.x = thumbBounds.x;
2677								_panOffset.y = thumbBounds.y  - _currentWindowScrollY;
2678							} else {
2679								_currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
2680								_panOffset.x = (thumbBounds.x - initialPanOffset.x) * now + initialPanOffset.x;
2681								_panOffset.y = (thumbBounds.y - _currentWindowScrollY - initialPanOffset.
2681y) * now + initialPanOffset.y;
2682							}
2683							
2684							_applyCurrentZoomPan();
2685							if(fadeEverything) {
2686								template.style.opacity = 1 - now;
2687							} else {
2688								_applyBgOpacity( initalBgOpacity - now * initalBgOpacity );
2689							}
2690						};
2691
2692					if(closeWithRaf) {
2693						_animateProp('initialZoom', 0, 1, duration, framework.easing.cubic.out, onUpdate, onComplete);
2694					} else {
2695						onUpdate(1);
2696						_showOrHideTimeout = setTimeout(onComplete, duration + 20);
2697					}
2698				}
2699			
2700			}, out ? 25 : 90); // Main purpose of this delay is to give browser time to paint and
2701					// create composite layers of PhotoSwipe UI parts (background, controls, caption, arrows).
2702					// Which avoids lag at the beginning of scale transition.
2703		};
2704		startAnimation();
2705
2706		
2707	};
2708
2709/*>>show-hide-transition*/
2710
2711/*>>items-controller*/
2712/**
2713*
2714* Controller manages gallery items, their dimensions, and their content. 
2715* 
2716*/
2717
2718var _items,
2719	_tempPanAreaSize = {},
2720	_imagesToAppendPool = [],
2721	_initialContentSet,
2722	_initialZoomRunning,
2723	_controllerDefaultOptions = {
2724		index: 0,
2725		//errorMsg: '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not just be loaded.</div>',
2726		//Johann
2727		errorMsg: '<iframe ID="ifrm" style="width: 100%;height: 70%;margin-top: 5%;border: 1px solid black;" src="%url%"></iframe>',
2728		forceProgressiveLoading: false, // TODO
2729		preload: [1,1],
2730		getNumItemsFn: function() {
2731			return _items.length;
2732		}
2733	};
2734
2735
2736var _getItemAt,
2737	_getNumItems,
2738	_initialIsLoop,
2739	_getZeroBounds = function() {
2740		return {
2741			center:{x:0,y:0}, 
2742			max:{x:0,y:0}, 
2743			min:{x:0,y:0}
2744		};
2745	},
2746	_calculateSingleItemPanBounds = function(item, realPanElementW, realPanElementH ) {
2747		var bounds = item.bounds;
2748
2749		// position of element when it's centered
2750		bounds.center.x = Math.round((_tempPanAreaSize.x - realPanElementW) / 2);
2751		bounds.center.y = Math.round((_tempPanAreaSize.y - realPanElementH) / 2) + item.vGap.top;
2752
2753		// maximum pan position
2754		bounds.max.x = (realPanElementW > _tempPanAreaSize.x) ? 
2755							Math.round(_tempPanAreaSize.x - realPanElementW) : 
2756							bounds.center.x;
2757		
2758		bounds.max.y = (realPanElementH > _tempPanAreaSize.y) ? 
2759							Math.round(_tempPanAreaSize.y - realPanElementH) + item.vGap.top : 
2760							bounds.center.y;
2761		
2762		// minimum pan position
2763		bounds.min.x = (realPanElementW > _tempPanAreaSize.x) ? 0 : bounds.center.x;
2764		bounds.min.y = (realPanElementH > _tempPanAreaSize.y) ? item.vGap.top : bounds.center.y;
2765	},
2766	_calculateItemSize = function(item, viewportSize, zoomLevel) {
2767
2768		if (item.src && !item.loadError) {
2769			var isInitial = !zoomLevel;
2770			
2771			if(isInitial) {
2772				if(!item.vGap) {
2773					item.vGap = {top:0,bottom:0};
2774				}
2775				// allows overriding vertical margin for individual items
2776				_shout('parseVerticalMargin', item);
2777			}
2778
2779
2780			_tempPanAreaSize.x = viewportSize.x;
2781			_tempPanAreaSize.y = viewportSize.y - item.vGap.top - item.vGap.bottom;
2782
2783			if (isInitial) {
2784				var hRatio = _tempPanAreaSize.x / item.w;
2785				var vRatio = _tempPanAreaSize.y / item.h;
2786
2787				item.fitRatio = hRatio < vRatio ? hRatio : vRatio;
2788				//item.fillRatio = hRatio > vRatio ? hRatio : vRatio;
2789
2790				var scaleMode = _options.scaleMode;
2791
2792				if (scaleMode === 'orig') {
2793					zoomLevel = 1;
2794				} else if (scaleMode === 'fit') {
2795					zoomLevel = item.fitRatio;
2796				}
2797
2798				if (zoomLevel > 1) {
2799					zoomLevel = 1;
2800				}
2801
2802				item.initialZoomLevel = zoomLevel;
2803				
2804				if(!item.bounds) {
2805					// reuse bounds object
2806					item.bounds = _getZeroBounds(); 
2807				}
2808			}
2809
2810			if(!zoomLevel) {
2811				return;
2812			}
2813
2814			_calculateSingleItemPanBounds(item, item.w * zoomLevel, item.h * zoomLevel);
2815
2816			if (isInitial && zoomLevel === item.initialZoomLevel) {
2817				item.initialPosition = item.bounds.center;
2818			}
2819
2820			return item.bounds;
2821		} else {
2822			item.w = item.h = 0;
2823			item.initialZoomLevel = item.fitRatio = 1;
2824			item.bounds = _getZeroBounds();
2825			item.initialPosition = item.bounds.center;
2826
2827			// if it's not image, we return zero bounds (content is not zoomable)
2828			return item.bounds;
2829		}
2830		
2831	},
2832
2833	
2834
2835
2836	_appendImage = function(index, item, baseDiv, img, preventAnimation, keepPlaceholder) {
2837
2838		if(item.loadError) {
2839			return;
2840		}
2841
2842		if(img) {
2843
2844			item.imageAppended = true;
2845			_setImageSize(item, img, (item === self.currItem && _renderMaxResolution) );
2846			
2847			baseDiv.appendChild(img);
2848
2849			if(keepPlaceholder) {
2850				setTimeout(function() {
2851					if(item && item.loaded && item.placeholder) {
2852						item.placeholder.style.display = 'none';
2853						item.placeholder = null;
2854					}
2855				}, 500);
2856			}
2857		}
2858	},
2859	
2860
2861
2862	_preloadImage = function(item) {
2863		item.loading = true;
2864		item.loaded = false;
2865		var img = item.img = framework.createEl('pswp__img', 'img');
2866		var onComplete = function() {
2867			item.loading = false;
2868			item.loaded = true;
2869
2870			if(item.loadComplete) {
2871				item.loadComplete(item);
2872			} else {
2873				item.img = null; // no need to store image object
2874			}
2875			img.onload = img.onerror = null;
2876			img = null;
2877		};
2878		img.onload = onComplete;
2879		img.onerror = function() {
2880			item.loadError = true;
2881			onComplete();
2882		};		
2883
2884		img.src = item.src;// + '?a=' + Math.random();
2885
2886		return img;
2887	},
2888	_checkForError = function(item, cleanUp) {
2889		if(item.src && item.loadError && item.container) {
2890
2891			if(cleanUp) {
2892				item.container.innerHTML = '';
2893			}
2894			// jOHANN
2895			item.container.innerHTML = _options.errorMsg.replace('%url%',  item.src.replace("www.kentridge.studio", "player.vimeo.com") );
2896//			item.container.innerHTML = _options.errorMsg;
2897			return true;
2898			
2899		}
2900	},
2901	_setImageSize = function(item, img, maxRes) {
2902		if(!item.src) {
2903			return;
2904		}
2905
2906		if(!img) {
2907			img = item.container.lastChild;
2908		}
2909
2910		var w = maxRes ? item.w : Math.round(item.w * item.fitRatio),
2911			h = maxRes ? item.h : Math.round(item.h * item.fitRatio);
2912		
2913		if(item.placeholder && !item.loaded) {
2914			item.placeholder.style.width = w + 'px';
2915			item.placeholder.style.height = h + 'px';
2916		}
2917
2918		img.style.width = w + 'px';
2919		img.style.height = h + 'px';
2920	},
2921	_appendImagesPool = function() {
2922
2923		if(_imagesToAppendPool.length) {
2924			var poolItem;
2925
2926			for(var i = 0; i < _imagesToAppendPool.length; i++) {
2927				poolItem = _imagesToAppendPool[i];
2928				if( poolItem.holder.index === poolItem.index ) {
2929					_appendImage(poolItem.index, poolItem.item, poolItem.baseDiv, poolItem.img, false, poolItem.clearPlaceholder);
2930				}
2931			}
2932			_imagesToAppendPool = [];
2933		}
2934	};
2935	
2936
2937
2938_registerModule('Controller', {
2939
2940	publicMethods: {
2941
2942		lazyLoadItem: function(index) {
2943			index = _getLoopedId(index);
2944			var item = _getItemAt(index);
2945
2946			if(!item || ((item.loaded || item.loading) && !_itemsNeedUpdate)) {
2947				return;
2948			}
2949
2950			_shout('gettingData', index, item);
2951
2952			if (!item.src) {
2953				return;
2954			}
2955
2956			_preloadImage(item);
2957		},
2958		initController: function() {
2959			framework.extend(_options, _controllerDefaultOptions, true);
2960			self.items = _items = items;
2961			_getItemAt = self.getItemAt;
2962			_getNumItems = _options.getNumItemsFn; //self.getNumItems;
2963
2964
2965
2966			_initialIsLoop = _options.loop;
2967			if(_getNumItems() < 3) {
2968				_options.loop = false; // disable loop if less then 3 items
2969			}
2970
2971			_listen('beforeChange', function(diff) {
2972
2973				var p = _options.preload,
2974					isNext = diff === null ? true : (diff >= 0),
2975					preloadBefore = Math.min(p[0], _getNumItems() ),
2976					preloadAfter = Math.min(p[1], _getNumItems() ),
2977					i;
2978
2979
2980				for(i = 1; i <= (isNext ? preloadAfter : preloadBefore); i++) {
2981					self.lazyLoadItem(_currentItemIndex+i);
2982				}
2983				for(i = 1; i <= (isNext ? preloadBefore : preloadAfter); i++) {
2984					self.lazyLoadItem(_currentItemIndex-i);
2985				}
2986			});
2987
2988			_listen('initialLayout', function() {
2989				self.currItem.initialLayout = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
2990			});
2991
2992			_listen('mainScrollAnimComplete', _appendImagesPool);
2993			_listen('initialZoomInEnd', _appendImagesPool);
2994
2995
2996
2997			_listen('destroy', function() {
2998				var item;
2999				for(var i = 0; i < _items.length; i++) {
3000					item = _items[i];
3001					// remove reference to DOM elements, for GC
3002					if(item.container) {
3003						item.container = null; 
3004					}
3005					if(item.placeholder) {
3006						item.placeholder = null;
3007					}
3008					if(item.img) {
3009						item.img = null;
3010					}
3011					if(item.preloader) {
3012						item.preloader = null;
3013					}
3014					if(item.loadError) {
3015						item.loaded = item.loadError = false;
3016					}
3017				}
3018				_imagesToAppendPool = null;
3019			});
3020		},
3021
3022
3023		getItemAt: function(index) {
3024			if (index >= 0) {
3025				return _items[index] !== undefined ? _items[index] : false;
3026			}
3027			return false;
3028		},
3029
3030		allowProgressiveImg: function() {
3031			// 1. Progressive image loading isn't working on webkit/blink 
3032			//    when hw-acceleration (e.g. translateZ) is applied to IMG element.
3033			//    That's why in PhotoSwipe parent element gets zoom transform, not image itself.
3034			//    
3035			// 2. Progressive image loading sometimes blinks in webkit/blink when applying animation to parent element.
3036			//    That's why it's disabled on touch devices (mainly because of swipe transition)
3037			//    
3038			// 3. Progressive image loading sometimes doesn't work in IE (up to 11).
3039
3040			// Don't allow progressive loading on non-large touch devices
3041			return _options.forceProgressiveLoading || !_likelyTouchDevice || _options.mouseUsed || screen.width > 1200; 
3042			// 1200 - to eliminate touch devices with large screen (like Chromebook Pixel)
3043		},
3044
3045		setContent: function(holder, index) {
3046
3047			if(_options.loop) {
3048				index = _getLoopedId(index);
3049			}
3050
3051			var prevItem = self.getItemAt(holder.index);
3052			if(prevItem) {
3053				prevItem.container = null;
3054			}
3055	
3056			var item = self.getItemAt(index),
3057				img;
3058			
3059			if(!item) {
3060				holder.el.innerHTML = '';
3061				return;
3062			}
3063
3064			// allow to override data
3065			_shout('gettingData', index, item);
3066
3067			holder.index = index;
3068			holder.item = item;
3069
3070			// base container DIV is created only once for each of 3 holders
3071			var baseDiv = item.container = framework.createEl('pswp__zoom-wrap'); 
3072
3073			
3074
3075			if(!item.src && item.html) {
3076				if(item.html.tagName) {
3077					baseDiv.appendChild(item.html);
3078				} else {
3079					baseDiv.innerHTML = item.html;
3080				}
3081			}
3082
3083			_checkForError(item);
3084
3085			_calculateItemSize(item, _viewportSize);
3086			
3087			if(item.src && !item.loadError && !item.loaded) {
3088
3089				item.loadComplete = function(item) {
3090
3091					// gallery closed before image finished loading
3092					if(!_isOpen) {
3093						return;
3094					}
3095
3096					// check if holder hasn't changed while image was loading
3097					if(holder && holder.index === index ) {
3098						if( _checkForError(item, true) ) {
3099							item.loadComplete = item.img = null;
3100							_calculateItemSize(item, _viewportSize);
3101							_applyZoomPanToItem(item);
3102
3103							if(holder.index === _currentItemIndex) {
3104								// recalculate dimensions
3105								self.updateCurrZoomItem();
3106							}
3107							return;
3108						}
3109						if( !item.imageAppended ) {
3110							if(_features.transform && (_mainScrollAnimating || _initialZoomRunning) ) {
3111								_imagesToAppendPool.push({
3112									item:item,
3113									baseDiv:baseDiv,
3114									img:item.img,
3115									index:index,
3116									holder:holder,
3117									clearPlaceholder:true
3118								});
3119							} else {
3120								_appendImage(index, item, baseDiv, item.img, _mainScrollAnimating || _initialZoomRunning, true);
3121							}
3122						} else {
3123							// remove preloader & mini-img
3124							if(!_initialZoomRunning && item.placeholder) {
3125								item.placeholder.style.display = 'none';
3126								item.placeholder = null;
3127							}
3128						}
3129					}
3130
3131					item.loadComplete = null;
3132					item.img = null; // no need to store image element after it's added
3133
3134					_shout('imageLoadComplete', index, item);
3135				};
3136
3137				if(framework.features.transform) {
3138					
3139					var placeholderClassName = 'pswp__img pswp__img--placeholder'; 
3140					placeholderClassName += (item.msrc ? '' : ' pswp__img--placeholder--blank');
3141
3142					var placeholder = framework.createEl(placeholderClassName, item.msrc ? 'img' : '');
3143					if(item.msrc) {
3144						placeholder.src = item.msrc;
3145					}
3146					
3147					_setImageSize(item, placeholder);
3148
3149					baseDiv.appendChild(placeholder);
3150					item.placeholder = placeholder;
3151
3152				}
3153				
3154
3155				
3156
3157				if(!item.loading) {
3158					_preloadImage(item);
3159				}
3160
3161
3162				if( self.allowProgressiveImg() ) {
3163					// just append image
3164					if(!_initialContentSet && _features.transform) {
3165						_imagesToAppendPool.push({
3166							item:item, 
3167							baseDiv:baseDiv, 
3168							img:item.img, 
3169							index:index, 
3170							holder:holder
3171						});
3172					} else {
3173						_appendImage(index, item, baseDiv, item.img, true, true);
3174					}
3175				}
3176				
3177			} else if(item.src && !item.loadError) {
3178				// image object is created every time, due to bugs of image loading & delay when switching images
3179				img = framework.createEl('pswp__img', 'img');
3180				img.style.opacity = 1;
3181				img.src = item.src;
3182				_setImageSize(item, img);
3183				_appendImage(index, item, baseDiv, img, true);
3184			}
3185			
3186
3187			if(!_initialContentSet && index === _currentItemIndex) {
3188				_currZoomElementStyle = baseDiv.style;
3189				_showOrHide(item, (img ||item.img) );
3190			} else {
3191				_applyZoomPanToItem(item);
3192			}
3193
3194			holder.el.innerHTML = '';
3195			holder.el.appendChild(baseDiv);
3196		},
3197
3198		cleanSlide: function( item ) {
3199			if(item.img ) {
3200				item.img.onload = item.img.onerror = null;
3201			}
3202			item.loaded = item.loading = item.img = item.imageAppended = false;
3203		}
3204
3205	}
3206});
3207
3208/*>>items-controller*/
3209
3210/*>>tap*/
3211/**
3212 * tap.js:
3213 *
3214 * Displatches tap and double-tap events.
3215 * 
3216 */
3217
3218var tapTimer,
3219	tapReleasePoint = {},
3220	_dispatchTapEvent = function(origEvent, releasePoint, pointerType) {		
3221		var e = document.createEvent( 'CustomEvent' ),
3222			eDetail = {
3223				origEvent:origEvent, 
3224				target:origEvent.target, 
3225				releasePoint: releasePoint, 
3226				pointerType:pointerType || 'touch'
3227			};
3228
3229		e.initCustomEvent( 'pswpTap', true, true, eDetail );
3230		origEvent.target.dispatchEvent(e);
3231	};
3232
3233_registerModule('Tap', {
3234	publicMethods: {
3235		initTap: function() {
3236			_listen('firstTouchStart', self.onTapStart);
3237			_listen('touchRelease', self.onTapRelease);
3238			_listen('destroy', function() {
3239				tapReleasePoint = {};
3240				tapTimer = null;
3241			});
3242		},
3243		onTapStart: function(touchList) {
3244			if(touchList.length > 1) {
3245				clearTimeout(tapTimer);
3246				tapTimer = null;
3247			}
3248		},
3249		onTapRelease: function(e, releasePoint) {
3250			if(!releasePoint) {
3251				return;
3252			}
3253
3254			if(!_moved && !_isMultitouch && !_numAnimations) {
3255				var p0 = releasePoint;
3256				if(tapTimer) {
3257					clearTimeout(tapTimer);
3258					tapTimer = null;
3259
3260					// Check if taped on the same place
3261					if ( _isNearbyPoints(p0, tapReleasePoint) ) {
3262						_shout('doubleTap', p0);
3263						return;
3264					}
3265				}
3266
3267				if(releasePoint.type === 'mouse') {
3268					_dispatchTapEvent(e, releasePoint, 'mouse');
3269					return;
3270				}
3271
3272				var clickedTagName = e.target.tagName.toUpperCase();
3273				// avoid double tap delay on buttons and elements that have class pswp__single-tap
3274				if(clickedTagName === 'BUTTON' || framework.hasClass(e.target, 'pswp__single-tap') ) {
3275					_dispatchTapEvent(e, releasePoint);
3276					return;
3277				}
3278
3279				_equalizePoints(tapReleasePoint, p0);
3280
3281				tapTimer = setTimeout(function() {
3282					_dispatchTapEvent(e, releasePoint);
3283					tapTimer = null;
3284				}, 300);
3285			}
3286		}
3287	}
3288});
3289
3290/*>>tap*/
3291
3292/*>>desktop-zoom*/
3293/**
3294 *
3295 * desktop-zoom.js:
3296 *
3297 * - Binds mousewheel event for paning zoomed image.
3298 * - Manages "dragging", "zoomed-in", "zoom-out" classes.
3299 *   (which are used for cursors and zoom icon)
3300 * - Adds toggleDesktopZoom function.
3301 * 
3302 */
3303
3304var _wheelDelta;
3305	
3306_registerModule('DesktopZoom', {
3307
3308	publicMethods: {
3309
3310		initDesktopZoom: function() {
3311
3312			if(_oldIE) {
3313				// no zoom for old IE (<=8)
3314				return;
3315			}
3316
3317			if(_likelyTouchDevice) {
3318				// if detected hardware touch support, we wait until mouse is used,
3319				// and only then apply desktop-zoom features
3320				_listen('mouseUsed', function() {
3321					self.setupDesktopZoom();
3322				});
3323			} else {
3324				self.setupDesktopZoom(true);
3325			}
3326
3327		},
3328
3329		setupDesktopZoom: function(onInit) {
3330
3331			_wheelDelta = {};
3332
3333			var events = 'wheel mousewheel DOMMouseScroll';
3334			
3335			_listen('bindEvents', function() {
3336				framework.bind(template, events,  self.handleMouseWheel);
3337			});
3338
3339			_listen('unbindEvents', function() {
3340				if(_wheelDelta) {
3341					framework.unbind(template, events, self.handleMouseWheel);
3342				}
3343			});
3344
3345			self.mouseZoomedIn = false;
3346
3347			var hasDraggingClass,
3348				updateZoomable = function() {
3349					if(self.mouseZoomedIn) {
3350						framework.removeClass(template, 'pswp--zoomed-in');
3351						self.mouseZoomedIn = false;
3352					}
3353					if(_currZoomLevel < 1) {
3354						framework.addClass(template, 'pswp--zoom-allowed');
3355					} else {
3356						framework.removeClass(template, 'pswp--zoom-allowed');
3357					}
3358					removeDraggingClass();
3359				},
3360				removeDraggingClass = function() {
3361					if(hasDraggingClass) {
3362						framework.removeClass(template, 'pswp--dragging');
3363						hasDraggingClass = false;
3364					}
3365				};
3366
3367			_listen('resize' , updateZoomable);
3368			_listen('afterChange' , updateZoomable);
3369			_listen('pointerDown', function() {
3370				if(self.mouseZoomedIn) {
3371					hasDraggingClass = true;
3372					framework.addClass(template, 'pswp--dragging');
3373				}
3374			});
3375			_listen('pointerUp', removeDraggingClass);
3376
3377			if(!onInit) {
3378				updateZoomable();
3379			}
3380			
3381		},
3382
3383		handleMouseWheel: function(e) {
3384
3385			if(_currZoomLevel <= self.currItem.fitRatio) {
3386				if( _options.modal ) {
3387
3388					if (!_options.closeOnScroll || _numAnimations || _isDragging) {
3389						e.preventDefault();
3390					} else if(_transformKey && Math.abs(e.deltaY) > 2) {
3391						// close PhotoSwipe
3392						// if browser supports transforms & scroll changed enough
3393						_closedByScroll = true;
3394						self.close();
3395					}
3396
3397				}
3398				return true;
3399			}
3400
3401			// allow just one event to fire
3402			e.stopPropagation();
3403
3404			// https://developer.mozilla.org/en-US/docs/Web/Events/wheel
3405			_wheelDelta.x = 0;
3406
3407			if('deltaX' in e) {
3408				if(e.deltaMode === 1 /* DOM_DELTA_LINE */) {
3409					// 18 - average line height
3410					_wheelDelta.x = e.deltaX * 18;
3411					_wheelDelta.y = e.deltaY * 18;
3412				} else {
3413					_wheelDelta.x = e.deltaX;
3414					_wheelDelta.y = e.deltaY;
3415				}
3416			} else if('wheelDelta' in e) {
3417				if(e.wheelDeltaX) {
3418					_wheelDelta.x = -0.16 * e.wheelDeltaX;
3419				}
3420				if(e.wheelDeltaY) {
3421					_wheelDelta.y = -0.16 * e.wheelDeltaY;
3422				} else {
3423					_wheelDelta.y = -0.16 * e.wheelDelta;
3424				}
3425			} else if('detail' in e) {
3426				_wheelDelta.y = e.detail;
3427			} else {
3428				return;
3429			}
3430
3431			_calculatePanBounds(_currZoomLevel, true);
3432
3433			var newPanX = _panOffset.x - _wheelDelta.x,
3434				newPanY = _panOffset.y - _wheelDelta.y;
3435
3436			// only prevent scrolling in nonmodal mode when not at edges
3437			if (_options.modal ||
3438				(
3439				newPanX <= _currPanBounds.min.x && newPanX >= _currPanBounds.max.x &&
3440				newPanY <= _currPanBounds.min.y && newPanY >= _currPanBounds.max.y
3441				) ) {
3442				e.preventDefault();
3443			}
3444
3445			// TODO: use rAF instead of mousewheel?
3446			self.panTo(newPanX, newPanY);
3447		},
3448
3449		toggleDesktopZoom: function(centerPoint) {
3450			centerPoint = centerPoint || {x:_viewportSize.x/2 + _offset.x, y:_viewportSize.y/2 + _offset.y };
3451
3452			var doubleTapZoomLevel = _options.getDoubleTapZoom(true, self.currItem);
3453			var zoomOut = _currZoomLevel === doubleTapZoomLevel;
3454			
3455			self.mouseZoomedIn = !zoomOut;
3456
3457			self.zoomTo(zoomOut ? self.currItem.initialZoomLevel : doubleTapZoomLevel, centerPoint, 333);
3458			framework[ (!zoomOut ? 'add' : 'remove') + 'Class'](template, 'pswp--zoomed-in');
3459		}
3460
3461	}
3462});
3463
3464
3465/*>>desktop-zoom*/
3466
3467/*>>history*/
3468/**
3469 *
3470 * history.js:
3471 *
3472 * - Back button to close gallery.
3473 * 
3474 * - Unique URL for each slide: example.com/&pid=1&gid=3
3475 *   (where PID is picture index, and GID and gallery index)
3476 *   
3477 * - Switch URL when slides change.
3478 * 
3479 */
3480
3481
3482var _historyDefaultOptions = {
3483	history: true,
3484	galleryUID: 1
3485};
3486
3487var _historyUpdateTimeout,
3488	_hashChangeTimeout,
3489	_hashAnimCheckTimeout,
3490	_hashChangedByScript,
3491	_hashChangedByHistory,
3492	_hashReseted,
3493	_initialHash,
3494	_historyChanged,
3495	_closedFromURL,
3496	_urlChangedOnce,
3497	_windowLoc,
3498
3499	_supportsPushState,
3500
3501	_getHash = function() {
3502		return _windowLoc.hash.substring(1);
3503	},
3504	_cleanHistoryTimeouts = function() {
3505
3506		if(_historyUpdateTimeout) {
3507			clearTimeout(_historyUpdateTimeout);
3508		}
3509
3510		if(_hashAnimCheckTimeout) {
3511			clearTimeout(_hashAnimCheckTimeout);
3512		}
3513	},
3514
3515	// pid - Picture index
3516	// [MODIFIED FOR JIG]
3517	_parseItemIndexFromURL = function() {
3518
3519		var hashTag = '',
3520			patt = /^!([\w\-[\]]+?\/(?:(\d+)(?=\/))?(https?:\/\/[^&]*)?([A-Z]{2}
3520\/[_\w]*)?)/im,
3521			hashMatches,
3522			hash = _getHash(),
3523			params = {};
3524		hash = hash.replace('%5B','[');
3525		hash = hash.replace('%5D',']');
3526		hash = hash.replace(/([^|])(poster=|videoplayer)/mg, "$1|$2");
3527		hashMatches = hash.match(patt);
3528		if(hashMatches !== null){
3529			hashTag = hashMatches[1];
3530			hashTag = hashTag.substring(hashTag.indexOf('/')+1,hashTag.length);
3531			if((/^([A-Z]{2}\/[_\w]*)$/im.test(hashTag))){
3532				hashTag = hashTag.replace("/","-");
3533			}
3534
3535			jQuery.each(items, function(index,item) {
3536				if (typeof item.contentID !== 'undefined' && item.contentID == 'jig-contentID-'+hashTag) {
3537					params.pid = index;
3538					return false;
3539				}else if(item.src == hashTag){
3540					params.pid = index;
3541					return false;
3542				}
3543			});
3544			params.pid = parseInt(params.pid,10);
3545		}else{
3546			params.pid = 0;
3547		}
3548		return params;
3549		
3550	},
3551	// [MODIFIED FOR JIG]
3552	_updateHash = function() {
3553
3554		if(_hashAnimCheckTimeout) {
3555			clearTimeout(_hashAnimCheckTimeout);
3556		}
3557
3558
3559		if(_numAnimations || _isDragging) {
3560			// changing browser URL forces layout/paint in some browsers, which causes noticable lag during animation
3561			// that's why we update hash only when no animations running
3562			_hashAnimCheckTimeout = setTimeout(_updateHash, 500);
3563			return;
3564		}
3565		
3566		if(_hashChangedByScript) {
3567			clearTimeout(_hashChangeTimeout);
3568		} else {
3569			_hashChangedByScript = true;
3570		}
3571		/* JIG mod */
3572		var newHash = '!'+_options.galleryUID+'/';
3573		if(typeof items[_currentItemIndex].contentID !== 'undefined'){
3574			var contentIDregex = /(?:jig-contentID)-(\w{2})-([^\s]*)/im,
3575				contentIDmatches = contentIDregex.exec(items[_currentItemIndex].contentID);
3576			if (contentIDmatches !== null) {
3577				newHash += contentIDmatches[1]+"/"+contentIDmatches[2];
3578			}
3579
3580		}else{
3581			newHash += items[_currentItemIndex].src;
3582		}
3583		/* /JIG mod */
3584
3585		if(!_historyChanged) {
3586			if(_windowLoc.hash.indexOf(newHash) === -1) {
3587
3588				// This protects against a major bug
3589				if(_windowLoc.hash.indexOf('%5B') === -1 && _windowLoc.hash.indexOf('%5D') === -1){
3590					_urlChangedOnce = true;
3591				}
3592			}
3593			_windowLoc.hash = newHash;
3594			// first time - add new hisory record, then just replace
3595		} else {
3596			var newURL = _windowLoc.href.split('#')[0] + '#' +  newHash;
3597
3598			_windowLoc.replace( newURL );
3599		}
3600		
3601
3602		_historyChanged = true;
3603		_hashChangeTimeout = setTimeout(function() {
3604			_hashChangedByScript = false;
3605		}, 60);
3606	};
3607
3608
3609
3610	
3611
3612_registerModule('History', {
3613
3614	
3615
3616	publicMethods: {
3617		// [MODIFIED FOR JIG]
3618		initHistory: function() {
3619
3620			framework.extend(_options, _historyDefaultOptions, true);
3621
3622			if( !_options.history ) {
3623				return;
3624			}
3625
3626
3627			_windowLoc = window.location;
3628			_urlChangedOnce = false;
3629			_closedFromURL = false;
3630			_historyChanged = false;
3631			_initialHash = '';
3632
3633
3634
3635			_listen('afterChange', self.updateURL);
3636			_listen('unbindEvents', function() {
3637				framework.unbind(window, 'hashchange', self.onHashChange);
3638			});
3639
3640
3641			var returnToOriginal = function() {
3642				_hashReseted = true;
3643				if(!_closedFromURL) {
3644					if(_urlChangedOnce) {
3645						history.back();
3646					} else {
3647						if ('pushState' in history) {
3648							// remove hash from url without refreshing it or scrolling to top
3649							history.pushState("", document.title, _windowLoc.pathname + _windowLoc.search);
3650						} else {
3651							_windowLoc.hash = "";
3652						}
3653					}
3654				}
3655
3656				_cleanHistoryTimeouts();
3657			};
3658
3659			_listen('unbindEvents', function() {
3660					if(_closedByScroll) {
3661						// if PhotoSwipe is closed by scroll, we go "back" before the closing animation starts
3662						// this is done to keep the scroll position
3663						returnToOriginal();
3664					}
3665				});
3666			_listen('destroy', function() {
3667				if(!_hashReseted) {
3668					returnToOriginal();
3669				}
3670			});
3671			_listen('firstUpdate', function() {
3672				_currentItemIndex = _parseItemIndexFromURL().pid;
3673			});
3674
3675			
3676
3677		
3678
3679			setTimeout(function() {
3680				if(_isOpen) { // hasn't destroyed yet
3681					framework.bind(window, 'hashchange', self.onHashChange);
3682				}
3683			}, 40);
3684			
3685		},
3686		// [MODIFIED FOR JIG]
3687		onHashChange: function() {
3688
3689			if(_getHash() === '') {
3690				_closedFromURL = true;
3691				self.close();
3692				return;
3693			}
3694			if(!_hashChangedByScript) {
3695				_hashChangedByHistory = true;
3696				self.goTo( _parseItemIndexFromURL().pid );
3697				_hashChangedByHistory = false;
3698			}
3699			
3700		},
3701		updateURL: function() {
3702
3703			// Delay the update of URL, to avoid lag during transition, 
3704			// and to not to trigger actions like "refresh page sound" or "blinking favicon" to often
3705			
3706			_cleanHistoryTimeouts();
3707			
3708
3709			if(_hashChangedByHistory) {
3710				return;
3711			}
3712
3713			if(!_historyChanged) {
3714				_updateHash(); // first time
3715			} else {
3716				_historyUpdateTimeout = setTimeout(_updateHash, 800);
3717			}
3718		}
3719	
3720	}
3721});
3722
3723
3724/*>>history*/
3725	framework.extend(self, publicMethods); };
3726	return PhotoSwipe;
3727});
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816/*! PhotoSwipe Default UI - 4.1.3 - 2019-01-08
3817* http://photoswipe.com
3818* Copyright (c) 2019 Dmitry Semenov; */
3819/**
3820*
3821* UI on top of main sliding area (caption, arrows, close button, etc.).
3822* Built just using public methods/properties of PhotoSwipe.
3823* 
3824*/
3825(function (root, factory) { 
3826	if (typeof define === 'function' && define.amd) {
3827		define(factory);
3828	} else if (typeof module !== 'undefined' && typeof exports === 'object') {
3829		module.exports = factory();
3830	} else {
3831		root.PhotoSwipeUI_Default = factory();
3832	}
3833})(this, function () {
3834
3835	'use strict';
3836
3837
3838
3839var PhotoSwipeUI_Default =
3840 function(pswp, framework) {
3841
3842	var ui = this;
3843	var _overlayUIUpdated = false,
3844		_controlsVisible = true,
3845		_fullscrenAPI,
3846		_controls,
3847		_captionContainer,
3848		_fakeCaptionContainer,
3849		_indexIndicator,
3850		_shareButton,
3851		_shareModal,
3852		_shareModalHidden = true,
3853		_initalCloseOnScrollValue,
3854		_isIdle,
3855		_listen,
3856
3857		_loadingIndicator,
3858		_loadingIndicatorHidden,
3859		_loadingIndicatorTimeout,
3860
3861		_galleryHasOneSlide,
3862
3863		_options,
3864		_defaultUIOptions = {
3865			barsSize: {top:44, bottom:'auto'},
3866			closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'], 
3867			timeToIdle: 4000, 
3868			timeToIdleOutside: 1000,
3869			loadingIndicatorDelay: 1000, // 2s
3870			
3871			addCaptionHTMLFn: function(item, captionEl /*, isFake */) {
3872				if(!item.title) {
3873					captionEl.children[0].innerHTML = '';
3874					return false;
3875				}
3876				captionEl.children[0].innerHTML = item.title;
3877				return true;
3878			},
3879
3880			closeEl:true,
3881			captionEl: true,
3882			fullscreenEl: true,
3883			zoomEl: true,
3884			shareEl: true,
3885			counterEl: true,
3886			arrowEl: true,
3887			preloaderEl: true,
3888
3889			tapToClose: false,
3890			tapToToggleControls: true,
3891
3892			clickToCloseNonZoomable: true,
3893
3894			shareButtons: [
3895				{id:'facebook', label:'Share on Facebook', url:'https://www.facebook.com/sharer/share-page/?u=https://www.kentridge.studio/share.php?image={{raw_image_url}}&text= &title={{title}}...'},
3896				{id:'twitter', label:'Tweet', url:'https://twitter.com/intent/tweet?text= &url=https://www.kentridge.studio/share.php?image={{raw_image_url}}&text= &title={{title}}...'},
3897				{id:'pinterest', label:'Pin it', url:'http://www.pinterest.com/pin/create/button/'+
3898													'?url={{url}}&media={{image_url}}&description={{description}}'},
3899				{id:'download', label:'Download image', url:'{{raw_image_url}}', download:true}
3900			],
3901			getImageURLForShare: function( /* shareButtonData */ ) {
3902				return pswp.currItem.src || '';
3903			},
3904			getPageURLForShare: function( /* shareButtonData */ ) {
3905				return window.location.href;
3906			},
3907			getTextForShare: function( /* shareButtonData */ ) {
3908				return pswp.currItem.title || '';
3909			},
3910				
3911			indexIndicatorSep: ' / ',
3912			fitControlsWidth: 1200
3913
3914		},
3915		_blockControlsTap,
3916		_blockControlsTapTimeout;
3917
3918
3919
3920	var _onControlsTap = function(e) {
3921			if(_blockControlsTap) {
3922				return true;
3923			}
3924
3925
3926			e = e || window.event;
3927
3928			if(_options.timeToIdle && _options.mouseUsed && !_isIdle) {
3929				// reset idle timer
3930				_onIdleMouseMove();
3931			}
3932
3933
3934			var target = e.target || e.srcElement,
3935				uiElement,
3936				clickedClass = target.getAttribute('class') || '',
3937				found;
3938
3939			for(var i = 0; i < _uiElements.length; i++) {
3940				uiElement = _uiElements[i];
3941				if(uiElement.onTap && clickedClass.indexOf('pswp__' + uiElement.name ) > -1 ) {
3942					uiElement.onTap();
3943					found = true;
3944
3945				}
3946			}
3947
3948			if(found) {
3949				if(e.stopPropagation) {
3950					e.stopPropagation();
3951				}
3952				_blockControlsTap = true;
3953
3954				// Some versions of Android don't prevent ghost click event 
3955				// when preventDefault() was called on touchstart and/or touchend.
3956				// 
3957				// This happens on v4.3, 4.2, 4.1, 
3958				// older versions strangely work correctly, 
3959				// but just in case we add delay on all of them)	
3960				var tapDelay = framework.features.isOldAndroid ? 600 : 30;
3961				_blockControlsTapTimeout = setTimeout(function() {
3962					_blockControlsTap = false;
3963				}, tapDelay);
3964			}
3965
3966		},
3967		_fitControlsInViewport = function() {
3968			return !pswp.likelyTouchDevice || _options.mouseUsed || screen.width > _options.fitControlsWidth;
3969		},
3970		_togglePswpClass = function(el, cName, add) {
3971			framework[ (add ? 'add' : 'remove') + 'Class' ](el, 'pswp__' + cName);
3972		},
3973
3974		// add class when there is just one item in the gallery
3975		// (by default it hides left/right arrows and 1ofX counter)
3976		_countNumItems = function() {
3977			var hasOneSlide = (_options.getNumItemsFn() === 1);
3978
3979			if(hasOneSlide !== _galleryHasOneSlide) {
3980				_togglePswpClass(_controls, 'ui--one-slide', hasOneSlide);
3981				_galleryHasOneSlide = hasOneSlide;
3982			}
3983		},
3984		_toggleShareModalClass = function() {
3985			_togglePswpClass(_shareModal, 'share-modal--hidden', _shareModalHidden);
3986		},
3987		_toggleShareModal = function() {
3988
3989			_shareModalHidden = !_shareModalHidden;
3990			
3991			
3992			if(!_shareModalHidden) {
3993				_toggleShareModalClass();
3994				setTimeout(function() {
3995					if(!_shareModalHidden) {
3996						framework.addClass(_shareModal, 'pswp__share-modal--fade-in');
3997					}
3998				}, 30);
3999			} else {
4000				framework.removeClass(_shareModal, 'pswp__share-modal--fade-in');
4001				setTimeout(function() {
4002					if(_shareModalHidden) {
4003						_toggleShareModalClass();
4004					}
4005				}, 300);
4006			}
4007			
4008			if(!_shareModalHidden) {
4009				_updateShareURLs();
4010			}
4011			return false;
4012		},
4013
4014		_openWindowPopup = function(e) {
4015			e = e || window.event;
4016			var target = e.target || e.srcElement;
4017
4018			pswp.shout('shareLinkClick', e, target);
4019
4020			if(!target.href) {
4021				return false;
4022			}
4023
4024			if( target.hasAttribute('download') ) {
4025				return true;
4026			}
4027
4028			window.open(target.href, 'pswp_share', 'scrollbars=yes,resizable=yes,toolbar=no,'+
4029										'location=yes,width=550,height=420,top=100,left=' + 
4030										(window.screen ? Math.round(screen.width / 2 - 275) : 100)  );
4031
4032			if(!_shareModalHidden) {
4033				_toggleShareModal();
4034			}
4035			
4036			return false;
4037		},
4038		// [MODIFIED FOR JIG]
4039		_updateShareURLs = function() {
4040			var shareButtonOut = '',
4041				shareButtons = pswp.options.shareButtons,
4042				shareButtonData,
4043				shareURL,
4044				URI,
4045				description;
4046
4047			if(location.hash){
4048				URI = decodeURI(document.location.href);
4049				var URIarray = URI.split('#!');
4050				URI = URIarray[0]+'#!'+URIarray[1];
4051			}else{
4052				URI = location.href;
4053			}
4054						
4055			if(typeof pswp.options.socialButtons !== 'undefined'){
4056				var socialButtons = pswp.options.socialButtons;
4057					shareButtons = [];
4058					
4059				socialButtons = socialButtons.toLowerCase();
4060				socialButtons = socialButtons.split("");
4061
4062				var facebookIndex = jQuery.inArray('f',socialButtons),
4063					twitterIndex = jQuery.inArray('t',socialButtons),
4064					pinterestIndex = jQuery.inArray('p',socialButtons),
4065					googleIndex = jQuery.inArray('g',socialButtons);
4066
4067				if (~facebookIndex)	shareButtons[facebookIndex] = pswp.options.shareButtons[0];
4068				if (~twitterIndex) shareButtons[twitterIndex] = pswp.options.shareButtons[1];
4069				if (~pinterestIndex) shareButtons[pinterestIndex] = pswp.options.shareButtons[2];
4070				if (~googleIndex) shareButtons[googleIndex] = pswp.options.shareButtons[3];
4071				if(typeof pswp.options.shareButtons[4] !== "undefined"){
4072					shareButtons.push(pswp.options.shareButtons[4]);
4073				}
4074			}
4075
4076				
4077			jQuery.each(shareButtons, function(i,shareButtonData){
4078				if(typeof shareButtonData !== 'undefined'){
4079					description = ((pswp.currItem.title || '')+(pswp.currItem.description ? (pswp.currItem.title ? ' ' : '')+pswp.currItem.description : '')) || document.title;
4080					console.log( '<div>'+'{{title}}'+'</div>' );
4081					var newtitle =  '<div>'+pswp.currItem.title+'</div>' ;
4082					
4083							//.replace('{{title}}', encodeURIComponent(jQuery(pswp.currItem.title.replace(/<\/div>/g,'####').replace(/<a(?:.+?)href=['"](.+?)['"](?:.+?>)(.+?)(?:<\/a>)/mg, "$2: $1")).text().replace(/####/g,'. ').substring(0, 80) ))
4084							//
4085							//
4086							//
4087					shareURL = shareButtonData.url.replace('{{url}}', encodeURIComponent(URI) )
4088								.replace("{{facebook_url}}",encodeURIComponent(URI.replace("#!", (URI.indexOf('?') === -1 ? "?" : "&")+'_escaped_fragment_=')))
4089								.replace('{{image_url}}', encodeURIComponent(pswp.currItem.src) )
4090								.replace('{{raw_image_url}}', pswp.currItem.src.replace(/ /g,'***') )
4091								.replace('{{title}}', encodeURIComponent(jQuery(newtitle.replace(/<\/div>/g,'####').replace(/<a(?:.+?)href=['"](.+?)['"](?:.+?>)(.+?)(?:<\/a>)/mg, "$2: $1")).text().replace(/####/g,'. ').substring(0, 80) ))
4092								.replace('{{description}}', encodeURIComponent(jQuery('<p>'+description.replace(/<a(?:.+?)href=['"](.+?)['"](?:.+?>)(.+?)(?:<\/a>)/mg, "$2: $1")+'</p>').text()));
4093
4094						shareButtonOut += '<a href="' + shareURL + '" target="_blank" class="pswp__share--' + shareButtonData.id + '"' + (shareButtonData.download ? 'download' : '') + '>' + shareButtonData.label + '</a>';
4095				}
4096			});
4097			_shareModal.children[0].innerHTML = shareButtonOut;
4098			_shareModal.children[0].onclick = _openWindowPopup;
4099
4100		},
4101		_hasCloseClass = function(target) {
4102			for(var  i = 0; i < _options.closeElClasses.length; i++) {
4103				if( framework.hasClass(target, 'pswp__' + _options.closeElClasses[i]) ) {
4104					return true;
4105				}
4106			}
4107		},
4108		_idleInterval,
4109		_idleTimer,
4110		_idleIncrement = 0,
4111		_onIdleMouseMove = function() {
4112			clearTimeout(_idleTimer);
4113			_idleIncrement = 0;
4114			if(_isIdle) {
4115				ui.setIdle(false);
4116			}
4117		},
4118		_onMouseLeaveWindow = function(e) {
4119			e = e ? e : window.event;
4120			var from = e.relatedTarget || e.toElement;
4121			if (!from || from.nodeName === 'HTML') {
4122				clearTimeout(_idleTimer);
4123				_idleTimer = setTimeout(function() {
4124					ui.setIdle(true);
4125				}, _options.timeToIdleOutside);
4126			}
4127		},
4128		_setupFullscreenAPI = function() {
4129			if(_options.fullscreenEl && !framework.features.isOldAndroid) {
4130				if(!_fullscrenAPI) {
4131					_fullscrenAPI = ui.getFullscreenAPI();
4132				}
4133				if(_fullscrenAPI) {
4134					framework.bind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
4135					ui.updateFullscreen();
4136					framework.addClass(pswp.template, 'pswp--supports-fs');
4137				} else {
4138					framework.removeClass(pswp.template, 'pswp--supports-fs');
4139				}
4140			}
4141		},
4142		_setupLoadingIndicator = function() {
4143			// Setup loading indicator
4144			if(_options.preloaderEl) {
4145			
4146				_toggleLoadingIndicator(true);
4147
4148				_listen('beforeChange', function() {
4149
4150					clearTimeout(_loadingIndicatorTimeout);
4151
4152					// display loading indicator with delay
4153					_loadingIndicatorTimeout = setTimeout(function() {
4154
4155						if(pswp.currItem && pswp.currItem.loading) {
4156
4157							if( !pswp.allowProgressiveImg() || (pswp.currItem.img && !pswp.currItem.img.naturalWi
4157dth)  ) {
4158								// show preloader if progressive loading is not enabled, 
4159								// or image width is not defined yet (because of slow connection)
4160								_toggleLoadingIndicator(false); 
4161								// items-controller.js function allowProgressiveImg
4162							}
4163							
4164						} else {
4165							_toggleLoadingIndicator(true); // hide preloader
4166						}
4167
4168					}, _options.loadingIndicatorDelay);
4169					
4170				});
4171				_listen('imageLoadComplete', function(index, item) {
4172					if(pswp.currItem === item) {
4173						_toggleLoadingIndicator(true);
4174					}
4175				});
4176
4177			}
4178		},
4179		_toggleLoadingIndicator = function(hide) {
4180			if( _loadingIndicatorHidden !== hide ) {
4181				_togglePswpClass(_loadingIndicator, 'preloader--active', !hide);
4182				_loadingIndicatorHidden = hide;
4183			}
4184		},
4185		_applyNavBarGaps = function(item) {
4186			var gap = item.vGap;
4187
4188			if( _fitControlsInViewport() ) {
4189				
4190				var bars = _options.barsSize; 
4191				if(_options.captionEl && bars.bottom === 'auto') {
4192					if(!_fakeCaptionContainer) {
4193						_fakeCaptionContainer = framework.createEl('pswp__caption pswp__caption--fake');
4194						_fakeCaptionContainer.appendChild( framework.createEl('pswp__caption__center') );
4195						_controls.insertBefore(_fakeCaptionContainer, _captionContainer);
4196						framework.addClass(_controls, 'pswp__ui--fit');
4197					}
4198					if( _options.addCaptionHTMLFn(item, _fakeCaptionContainer, true) ) {
4199
4200						var captionSize = _fakeCaptionContainer.clientHeight;
4201						gap.bottom = parseInt(captionSize,10) || 44;
4202					} else {
4203						gap.bottom = bars.top; // if no caption, set size of bottom gap to size of top
4204					}
4205				} else {
4206					gap.bottom = bars.bottom === 'auto' ? 0 : bars.bottom;
4207				}
4208				
4209				// height of top bar is static, no need to calculate it
4210				gap.top = bars.top;
4211			} else {
4212				gap.top = gap.bottom = 0;
4213			}
4214		},
4215		_setupIdle = function() {
4216			// Hide controls when mouse is used
4217			if(_options.timeToIdle) {
4218				_listen('mouseUsed', function() {
4219					
4220					framework.bind(document, 'mousemove', _onIdleMouseMove);
4221					framework.bind(document, 'mouseout', _onMouseLeaveWindow);
4222
4223					_idleInterval = setInterval(function() {
4224						_idleIncrement++;
4225						if(_idleIncrement === 2) {
4226							ui.setIdle(true);
4227						}
4228					}, _options.timeToIdle / 2);
4229				});
4230			}
4231		},
4232		_setupHidingControlsDuringGestures = function() {
4233
4234			// Hide controls on vertical drag
4235			_listen('onVerticalDrag', function(now) {
4236				if(_controlsVisible && now < 0.95) {
4237					ui.hideControls();
4238				} else if(!_controlsVisible && now >= 0.95) {
4239					ui.showControls();
4240				}
4241			});
4242
4243			// Hide controls when pinching to close
4244			var pinchControlsHidden;
4245			_listen('onPinchClose' , function(now) {
4246				if(_controlsVisible && now < 0.9) {
4247					ui.hideControls();
4248					pinchControlsHidden = true;
4249				} else if(pinchControlsHidden && !_controlsVisible && now > 0.9) {
4250					ui.showControls();
4251				}
4252			});
4253
4254			_listen('zoomGestureEnded', function() {
4255				pinchControlsHidden = false;
4256				if(pinchControlsHidden && !_controlsVisible) {
4257					ui.showControls();
4258				}
4259			});
4260
4261		};
4262
4263
4264
4265	var _uiElements = [
4266		{ 
4267			name: 'caption', 
4268			option: 'captionEl',
4269			onInit: function(el) {  
4270				_captionContainer = el; 
4271			} 
4272		},
4273		{ 
4274			name: 'share-modal', 
4275			option: 'shareEl',
4276			onInit: function(el) {  
4277				_shareModal = el;
4278			},
4279			onTap: function() {
4280				_toggleShareModal();
4281			} 
4282		},
4283		{ 
4284			name: 'button--share', 
4285			option: 'shareEl',
4286			onInit: function(el) { 
4287				_shareButton = el;
4288			},
4289			onTap: function() {
4290				_toggleShareModal();
4291			} 
4292		},
4293		{ 
4294			name: 'button--zoom', 
4295			option: 'zoomEl',
4296			onTap: pswp.toggleDesktopZoom
4297		},
4298		{ 
4299			name: 'counter', 
4300			option: 'counterEl',
4301			onInit: function(el) {  
4302				_indexIndicator = el;
4303			} 
4304		},
4305		{ 
4306			name: 'button--close', 
4307			option: 'closeEl',
4308			onTap: pswp.close
4309		},
4310		{ 
4311			name: 'button--arrow--left', 
4312			option: 'arrowEl',
4313			onTap: pswp.prev
4314		},
4315		{ 
4316			name: 'button--arrow--right', 
4317			option: 'arrowEl',
4318			onTap: pswp.next
4319		},
4320		{ 
4321			name: 'button--fs', 
4322			option: 'fullscreenEl',
4323			onTap: function() {  
4324				if(_fullscrenAPI.isFullscreen()) {
4325					_fullscrenAPI.exit();
4326				} else {
4327					_fullscrenAPI.enter();
4328				}
4329			} 
4330		},
4331		{ 
4332			name: 'preloader', 
4333			option: 'preloaderEl',
4334			onInit: function(el) {  
4335				_loadingIndicator = el;
4336			} 
4337		}
4338
4339	];
4340
4341	var _setupUIElements = function() {
4342		var item,
4343			classAttr,
4344			uiElement;
4345
4346		var loopThroughChildElements = function(sChildren) {
4347			if(!sChildren) {
4348				return;
4349			}
4350
4351			var l = sChildren.length;
4352			for(var i = 0; i < l; i++) {
4353				item = sChildren[i];
4354				classAttr = item.className;
4355
4356				for(var a = 0; a < _uiElements.length; a++) {
4357					uiElement = _uiElements[a];
4358
4359					if(classAttr.indexOf('pswp__' + uiElement.name) > -1  ) {
4360
4361						if( _options[uiElement.option] ) { // if element is not disabled from options
4362							
4363							framework.removeClass(item, 'pswp__element--disabled');
4364							if(uiElement.onInit) {
4365								uiElement.onInit(item);
4366							}
4367							
4368							//item.style.display = 'block';
4369						} else {
4370							framework.addClass(item, 'pswp__element--disabled');
4371							//item.style.display = 'none';
4372						}
4373					}
4374				}
4375			}
4376		};
4377		loopThroughChildElements(_controls.children);
4378
4379		var topBar =  framework.getChildByClass(_controls, 'pswp__top-bar');
4380		if(topBar) {
4381			loopThroughChildElements( topBar.children );
4382		}
4383	};
4384
4385
4386	
4387
4388	ui.init = function() {
4389
4390		// extend options
4391		framework.extend(pswp.options, _defaultUIOptions, true);
4392
4393		// create local link for fast access
4394		_options = pswp.options;
4395
4396		// find pswp__ui element
4397		_controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui');
4398
4399		// create local link
4400		_listen = pswp.listen;
4401
4402
4403		_setupHidingControlsDuringGestures();
4404
4405		// update controls when slides change
4406		_listen('beforeChange', ui.update);
4407
4408		// toggle zoom on double-tap
4409		_listen('doubleTap', function(point) {
4410			var initialZoomLevel = pswp.currItem.initialZoomLevel;
4411			if(pswp.getZoomLevel() !== initialZoomLevel) {
4412				pswp.zoomTo(initialZoomLevel, point, 333);
4413			} else {
4414				pswp.zoomTo(_options.getDoubleTapZoom(false, pswp.currItem), point, 333);
4415			}
4416		});
4417
4418		// Allow text selection in caption
4419		_listen('preventDragEvent', function(e, isDown, preventObj) {
4420			var t = e.target || e.srcElement;
4421			if(
4422				t && 
4423				t.getAttribute('class') && e.type.indexOf('mouse') > -1 && 
4424				( t.getAttribute('class').indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName) ) 
4425			) {
4426				preventObj.prevent = false;
4427			}
4428		});
4429
4430		// bind events for UI
4431		_listen('bindEvents', function() {
4432			framework.bind(_controls, 'pswpTap click', _onControlsTap);
4433			framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
4434
4435			if(!pswp.likelyTouchDevice) {
4436				framework.bind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);
4437			}
4438		});
4439
4440		// unbind events for UI
4441		_listen('unbindEvents', function() {
4442			if(!_shareModalHidden) {
4443				_toggleShareModal();
4444			}
4445
4446			if(_idleInterval) {
4447				clearInterval(_idleInterval);
4448			}
4449			framework.unbind(document, 'mouseout', _onMouseLeaveWindow);
4450			framework.unbind(document, 'mousemove', _onIdleMouseMove);
4451			framework.unbind(_controls, 'pswpTap click', _onControlsTap);
4452			framework.unbind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
4453			framework.unbind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);
4454
4455			if(_fullscrenAPI) {
4456				framework.unbind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
4457				if(_fullscrenAPI.isFullscreen()) {
4458					_options.hideAnimationDuration = 0;
4459					_fullscrenAPI.exit();
4460				}
4461				_fullscrenAPI = null;
4462			}
4463		});
4464
4465
4466		// clean up things when gallery is destroyed
4467		_listen('destroy', function() {
4468			if(_options.captionEl) {
4469				if(_fakeCaptionContainer) {
4470					_controls.removeChild(_fakeCaptionContainer);
4471				}
4472				framework.removeClass(_captionContainer, 'pswp__caption--empty');
4473			}
4474
4475			if(_shareModal) {
4476				_shareModal.children[0].onclick = null;
4477			}
4478			framework.removeClass(_controls, 'pswp__ui--over-close');
4479			framework.addClass( _controls, 'pswp__ui--hidden');
4480			ui.setIdle(false);
4481		});
4482		
4483
4484		if(!_options.showAnimationDuration) {
4485			framework.removeClass( _controls, 'pswp__ui--hidden');
4486		}
4487		_listen('initialZoomIn', function() {
4488			if(_options.showAnimationDuration) {
4489				framework.removeClass( _controls, 'pswp__ui--hidden');
4490			}
4491		});
4492		_listen('initialZoomOut', function() {
4493			framework.addClass( _controls, 'pswp__ui--hidden');
4494		});
4495
4496		_listen('parseVerticalMargin', _applyNavBarGaps);
4497		
4498		_setupUIElements();
4499
4500		if(_options.shareEl && _shareButton && _shareModal) {
4501			_shareModalHidden = true;
4502		}
4503
4504		_countNumItems();
4505
4506		_setupIdle();
4507
4508		_setupFullscreenAPI();
4509
4510		_setupLoadingIndicator();
4511	};
4512
4513	ui.setIdle = function(isIdle) {
4514		_isIdle = isIdle;
4515		_togglePswpClass(_controls, 'ui--idle', isIdle);
4516	};
4517	// [MODIFIED FOR JIG] fix for checking both title and description
4518	ui.update = function() {
4519		// Don't update UI if it's hidden
4520		if(_controlsVisible && pswp.currItem) {
4521			
4522			ui.updateIndexIndicator();
4523
4524			if(_options.captionEl) {
4525				_options.addCaptionHTMLFn(pswp.currItem, _captionContainer);
4526				_togglePswpClass(_captionContainer, 'caption--empty', !pswp.currItem.title && !pswp.currItem.description);
4527			}
4528
4529			_overlayUIUpdated = true;
4530
4531		} else {
4532			_overlayUIUpdated = false;
4533		}
4534
4535		if(!_shareModalHidden) {
4536			_toggleShareModal();
4537		}
4538
4539		_countNumItems();
4540	};
4541
4542	ui.updateFullscreen = function(e) {
4543
4544		if(e) {
4545			// some browsers change window scroll position during the fullscreen
4546			// so PhotoSwipe updates it just in case
4547			setTimeout(function() {
4548				pswp.setScrollOffset( 0, framework.getScrollY() );
4549			}, 50);
4550		}
4551		
4552		// toogle pswp--fs class on root element
4553		framework[ (_fullscrenAPI.isFullscreen() ? 'add' : 'remove') + 'Class' ](pswp.template, 'pswp--fs');
4554	};
4555
4556	ui.updateIndexIndicator = function() {
4557		if(_options.counterEl) {
4558			_indexIndicator.innerHTML = (pswp.getCurrentIndex()+1) + 
4559										_options.indexIndicatorSep + 
4560										_options.getNumItemsFn();
4561		}
4562	};
4563	
4564	ui.onGlobalTap = function(e) {
4565		e = e || window.event;
4566		var target = e.target || e.srcElement;
4567
4568		if(_blockControlsTap) {
4569			return;
4570		}
4571
4572		if(e.detail && e.detail.pointerType === 'mouse') {
4573
4574			// close gallery if clicked outside of the image
4575			if(_hasCloseClass(target)) {
4576				pswp.close();
4577				return;
4578			}
4579
4580			if(framework.hasClass(target, 'pswp__img')) {
4581				if(pswp.getZoomLevel() === 1 && pswp.getZoomLevel() <= pswp.currItem.fitRatio) {
4582					if(_options.clickToCloseNonZoomable) {
4583						pswp.close();
4584					}
4585				} else {
4586					pswp.toggleDesktopZoom(e.detail.releasePoint);
4587				}
4588			}
4589			
4590		} else {
4591
4592			// tap anywhere (except buttons) to toggle visibility of controls
4593			if(_options.tapToToggleControls) {
4594				if(_controlsVisible) {
4595					ui.hideControls();
4596				} else {
4597					ui.showControls();
4598				}
4599			}
4600
4601			// tap to close gallery
4602			if(_options.tapToClose && (framework.hasClass(target, 'pswp__img') || _hasCloseClass(target)) ) {
4603				pswp.close();
4604				return;
4605			}
4606			
4607		}
4608	};
4609	ui.onMouseOver = function(e) {
4610		e = e || window.event;
4611		var target = e.target || e.srcElement;
4612
4613		// add class when mouse is over an element that should close the gallery
4614		_togglePswpClass(_controls, 'ui--over-close', _hasCloseClass(target));
4615	};
4616
4617	ui.hideControls = function() {
4618		framework.addClass(_controls,'pswp__ui--hidden');
4619		_controlsVisible = false;
4620	};
4621
4622	ui.showControls = function() {
4623		_controlsVisible = true;
4624		if(!_overlayUIUpdated) {
4625			ui.update();
4626		}
4627		framework.removeClass(_controls,'pswp__ui--hidden');
4628	};
4629
4630	ui.supportsFullscreen = function() {
4631		var d = document;
4632		return !!(d.exitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen || d.msExitFullscreen);
4633	};
4634
4635	ui.getFullscreenAPI = function() {
4636		var dE = document.documentElement,
4637			api,
4638			tF = 'fullscreenchange';
4639
4640		if (dE.requestFullscreen) {
4641			api = {
4642				enterK: 'requestFullscreen',
4643				exitK: 'exitFullscreen',
4644				elementK: 'fullscreenElement',
4645				eventK: tF
4646			};
4647
4648		} else if(dE.mozRequestFullScreen ) {
4649			api = {
4650				enterK: 'mozRequestFullScreen',
4651				exitK: 'mozCancelFullScreen',
4652				elementK: 'mozFullScreenElement',
4653				eventK: 'moz' + tF
4654			};
4655
4656			
4657
4658		} else if(dE.webkitRequestFullscreen) {
4659			api = {
4660				enterK: 'webkitRequestFullscreen',
4661				exitK: 'webkitExitFullscreen',
4662				elementK: 'webkitFullscreenElement',
4663				eventK: 'webkit' + tF
4664			};
4665
4666		} else if(dE.msRequestFullscreen) {
4667			api = {
4668				enterK: 'msRequestFullscreen',
4669				exitK: 'msExitFullscreen',
4670				elementK: 'msFullscreenElement',
4671				eventK: 'MSFullscreenChange'
4672			};
4673		}
4674
4675		if(api) {
4676			api.enter = function() { 
4677				// disable close-on-scroll in fullscreen
4678				_initalCloseOnScrollValue = _options.closeOnScroll; 
4679				_options.closeOnScroll = false; 
4680
4681				if(this.enterK === 'webkitRequestFullscreen') {
4682					pswp.template[this.enterK]( Element.ALLOW_KEYBOARD_INPUT );
4683				} else {
4684					return pswp.template[this.enterK](); 
4685				}
4686			};
4687			api.exit = function() { 
4688				_options.closeOnScroll = _initalCloseOnScrollValue;
4689
4690				return document[this.exitK](); 
4691
4692			};
4693			api.isFullscreen = function() { return document[this.elementK]; };
4694		}
4695
4696		return api;
4697	};
4698
4699
4700
4701};
4702return PhotoSwipeUI_Default;
4703
4704
4705});
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746/*! JIGphotoSwipe(); jQuery wrapper made exclusively for JIG by Firsh
4747 		Additional changes to original PhotoSwipe:
4748			photoswipe-ui-default.js
4749				_updateShareURLs
4750				ui.update
4751			photoswipe.js - affected functions:
4752				publicMethods init (just an if at "add classes to root element of PhotoSwipe")
4753				init > _globalEventHandlers > _orientationChangeTimeout2
4754				History
4755					_parseItemIndexFromURL
4756					_updateHash
4757					publicMethods:
4758						initHistory
4759						onHashChange
4760*/
4761
4762(function ($){
4763	$.fn.JIGphotoSwipe = function(o){
4764		// set up default options 
4765		var elements = this,
4766			parent = $(elements[0]).closest(".justified-image-grid"),
4767			origOptions = $.extend(true, {}, o);
4768
4769		// base setup of settings, mouse interaction, images load event handler  [JIG REWRITE DONE - CHECK:NY]
4770		var init = function(){
4771
4772			if($('.pswp').length === 0){
4773				var captionClass = typeof o.captionAlign !== 'undefined' ? ' pswp__ca_'+o.captionAlign : ' pswp__ca_center';
4774				$('body').append('<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true"><div class="pswp__bg"></div><div class="pswp__scroll-wrap"><div class="pswp__container"><div class="pswp__item"></div><div class="pswp__item"></div><div class="pswp__item"></div></div><div class="pswp__ui pswp__ui--hidden'+captionClass+'"><div class="pswp__top-bar"><div class="pswp__counter"></div><span class="pswp__button pswp__button--close" title="Close (Esc)"></span><span class="pswp__button pswp__button--share" title="Share"></span><span class="pswp__button pswp__button--fs" title="Toggle fullscreen"></span><span class="pswp__button pswp__button--zoom" title="Zoom in/out"></span><div class="pswp__preloader"><div class="pswp__preloader__icn"><div class="pswp__preloader__cut"><div class="pswp__preloader__donut"></div></div></div></div></div><div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"><div class="pswp__share-tooltip"></div></div><span class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></span><span class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></span><div class="pswp__caption"><div class="pswp__caption__center"></div></div></div></div></div>');			
4775			}
4776
4777			// (re)bind click event
4778			parent.off('click.photoswipe').on('click.photoswipe','a.jig-link',onThumbnailsClick); 
4779
4780			// Parse URL and open gallery if it contains smart deeeplinking
4781			var hashData = photoswipeParseHash();
4782			if(hashData.theRel && hashData.contentID) {
4783				openPhotoSwipe(hashData.contentID, hashData.theRel, true );
4784			}
4785		}; // end of init
4786
4787
4788		// parse slide data (url, title, size ...) from DOM elements  [JIG REWRITE DONE - CHECK:NY]
4789		var parseThumbnailElements = function(rel) {
4790			var items = [];
4791			elements.filter('[rel~="'+rel+'"]').each(function(index){
4792				var el = $(this),
4793					img = el.find('img'),
4794					imgAlt = el.find('img').attr('alt'),
4795					linkTitle = el.attr('title'),
4796					wh = el.attr('data-wh').split('x'),
4797					contentIDbearerClass = el.parent().attr('class') == 'jig-overflow' ? el.closest('.jig-imageContainer').attr('class') : el.attr('class'),
4798					contentIDregex = /(?:jig-contentID)-(\w{2})-([^\s]*)/im,
4799					contentID = contentIDregex.exec(contentIDbearerClass),
4800					item = {
4801						src: el.attr('href'),
4802						w: parseInt(wh[0], 10),
4803						h: parseInt(wh[1], 10),
4804						msrc: img.attr('src')
4805					};
4806
4807				if (contentID != null) {
4808					item.contentID = contentID[0];
4809				}
4810
4811				if(imgAlt){
4812					item.title = imgAlt;
4813				}
4814				if(linkTitle){
4815					item.description = linkTitle;
4816				}
4817
4818				item.el = el[0]; // save link to element for getThumbBoundsFn
4819				items.push(item);
4820			});
4821
4822			return items;
4823		};
4824
4825
4826		// triggers when user clicks on thumbnail [JIG REWRITE DONE - CHECK:NY]
4827		var onThumbnailsClick = function(e) {
4828			e.preventDefault();
4829			// find contentID of clicked item
4830			var containerClass = $(e.target).closest('.jig-imageContainer').attr('class'),
4831				contentID,
4832				contentIDregex = /(?:jig-contentID)-(\w{2})-([^\s]*)/im,
4833				contentIDmatch = contentIDregex.exec(containerClass),
4834				theLink = $(e.target).closest('a.jig-link'),
4835				rel = theLink.attr('rel').split(" ").shift();
4836
4837			if (contentIDmatch != null) {
4838				contentID = contentIDmatch[0];
4839			}else{
4840				// In case there is no JIG ID (like RSS), just use the image URL (could be a hash though)
4841				contentID = theLink.attr('href');
4842			}
4843
4844			openPhotoSwipe(contentID, rel);
4845			
4846			return false;
4847		};
4848
4849		// parse contentID (or URL) and REL from hash
4850		var photoswipeParseHash = function() {
4851
4852			var hashTag = '',
4853				patt = /^#!([\w\-[\]]+?\/(?:(\d+)(?=\/))?(https?:\/\/[^&]*)?([A-Z]{2}
4853\/[_\w]*)?)/im,
4854				hashMatches,
4855				hash = location.hash,
4856				params = {};
4857			hash = hash.replace('%5B','[');
4858			hash = hash.replace('%5D',']');
4859			hash = hash.replace(/([^|])(poster=|videoplayer)/mg, "$1|$2");
4860			hashMatches = hash.match(patt);
4861			if(hashMatches !== null){
4862				hashTag = hashMatches[1];
4863				hashTag = hashTag.substring(hashTag.indexOf('/')+1,hashTag.length);
4864				if((/^([A-Z]{2}\/[_\w]*)$/im.test(hashTag))){
4865					hashTag = hashTag.replace("/","-");
4866				}
4867			}
4868
4869			params.contentID = 'jig-contentID-'+hashTag;
4870			params.theRel = hash.substring(2,hash.indexOf('/'));
4871
4872			return params;
4873
4874		};
4875
4876		// opens PS (by deeplink or click) [JIG REWRITE DONE - CHECK:NY]
4877		var openPhotoSwipe = function(contentID, rel, disableAnimation) {
4878
4879			var items = parseThumbnailElements(rel),
4880				putBackScroll,
4881				cropped = false;
4882
4883			if(items.length == 0){
4884				return false;
4885			}
4886
4887			$.each(items, function(index,item) {
4888				if (typeof item.contentID !== 'undefined' && item.contentID == contentID) {
4889					o.index = index;
4890					return false;
4891				}else if(item.src == contentID){
4892					o.index = index;
4893					return false;
4894				}
4895			});
4896
4897			o.galleryUID = rel;
4898			o.getThumbBoundsFn = function(index) {
4899				if($(items[index].el).parent().attr('class') == 'jig-overflow' ){
4900
4901				// See o -> getThumbBoundsFn section of docs for more info
4902					var thumbnail = items[index].el.getElementsByTagName('img')[0], // find thumbnail
4903						pageYScroll = $(window).scrollTop(),
4904						rect = thumbnail.getBoundingClientRect(); 
4905
4906					return {x:rect.left, y:rect.top + pageYScroll, w:rect.width};
4907				}else{
4908					// zoom-close hidden thumbs to the center of the viewport
4909					return {x:$(window).width()/2, y:$(window).height()/2+$(window).scrollTop(), w:0};
4910				}
4911				
4912			};
4913
4914			o.addCaptionHTMLFn = function(item, captionEl, isFake) {
4915				var fragments = [];
4916				if(typeof item.title !== 'undefined'){
4917					fragments.push(item.title);
4918				}
4919				if(typeof item.description !== 'undefined'){
4920					fragments.push('<small>'+item.description+'</small>');
4921				}
4922				if(fragments.length == 0){
4923					captionEl.children[0].innerText = '';
4924					return false;
4925				}else{
4926					captionEl.children[0].innerHTML = fragments.join('<br/>');
4927					return true;
4928				}			
4929			}
4930
4931			o.shareButtons = [
4932				{id:'facebook', label:'<i class="eicon-facebook"></i>  Share on Facebook', url:'https://www.facebook.com/sharer/sharer.php?u=https://www.kentridge.studio/share.php?image={{raw_image_url}}&text= &title={{title}}...'},
4933				{id:'twitter', label:'<i class="eicon-twitter"></i>  Tweet', url:'https://twitter.com/intent/tweet?text= &url=https://www.kentridge.studio/share.php?image={{raw_image_url}}&text= &title={{title}}...'},
4934				{id:'pinterest', label:'<i class="eicon-pinterest"></i>  Pin it', url:'http://www.pinterest.com/pin/create/button/?url={{url}}&media={{image_url}}&description={{description}}'},
4935			];
4936			if(typeof o.allowDownload !== 'undefined' && o.allowDownload === true){
4937				o.shareButtons.push({id:'download', label:'Download image', url:'{{raw_image_url}}', download:true});
4938			}
4939
4940		
4941			if(disableAnimation) {
4942				o.showAnimationDuration = 0;
4943				if(typeof origOptions.closeOnScroll == 'undefined' || origOptions.closeOnScroll == true){
4944					// Delay reacting to close on scroll, in case of the browser scrolls initially
4945					// It's in disableAnimation check as it's the same scenario (opening by URL hash)
4946					o.closeOnScroll = false;
4947					putBackScroll = true;
4948				}
4949			}else{
4950				o.showAnimationDuration = typeof origOptions.showAnimationDuration == 'undefined' ? 333 : origOptions.showAnimationDuration; // Put it back
4951			}
4952
4953
4954
4955			// detect JIG cropping
4956			var element = $(items[o.index].el);
4957			if(element.parent().attr('class') == 'jig-overflow' ){
4958				if(element.find('img').css('margin-left') !== '0px' || element.parent().parent().hasClass('differentAspectRatio')){
4959					cropped = true;
4960				}
4961			}
4962
4963			if(cropped || (typeof o.aspectRatioMismatch !== 'undefined' && o.aspectRatioMismatch === true)){
4964				o.showAnimationDuration = 0;
4965				o.showHideOpacity = typeof origOptions.showHideOpacity == 'undefined' ? true : origOptions.showHideOpacity;
4966				o.mainClass = "hidePsPlaceholder";
4967
4968			}else{
4969				o.showAnimationDuration = typeof origOptions.showAnimationDuration == 'undefined' ? 333 : origOptions.showAnimationDuration;
4970				o.showHideOpacity = typeof origOptions.showHideOpacity == 'undefined' ? false : origOptions.showHideOpacity;
4971				o.mainClass = typeof origOptions.mainClass == 'undefined' ? '' : origOptions.mainClass;
4972			}
4973
4974			// Pass data to PhotoSwipe and initialize it
4975			// this way disallows orhpan instances in the memory
4976			$('.pswp').removeAttr('style'); // Needed to clear element styles that remain after a differently animated PS
4977
4978				$(".pswp").removeClass(function(index, css){
4979					return (css.match (/pswp--\S+theme/g) || []).join(' ');
4980				});
4981				if(o.customTheme !== 'undefined'){
4982					$('.pswp').addClass('pswp--'+o.customTheme+'-theme');
4983				}
4984			
4985			$.fn.JIGphotoSwipe.gallery = new PhotoSwipe($('.pswp')[0], PhotoSwipeUI_Default, items, o);
4986			$.fn.JIGphotoSwipe.gallery.init();
4987			if(putBackScroll == true){
4988				// This puts back close on scroll feature
4989				setTimeout(function(){$.fn.JIGphotoSwipe.gallery.options.closeOnScroll = true;},2000);
4990			}
4991
4992		};
4993
4994		init();
4995
4996	}; // end of 'class'
4997
4998})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.