PageSourceSearch

https://wgs-gcvep.ru/local/templates/aspro_mshop/js/sly.js?178703800959910

js wgs-gcvep.ru collected 2026-10-06 16:47:19 UTC 59,910 bytes, 2,194 lines download raw bytes

1/*!
2 * sly 1.5.1 - 28th Apr 2015
3 * https://github.com/darsain/sly
4 *
5 * Licensed under the MIT license.
6 * http://opensource.org/licenses/MIT
7 */
8;(function ($, w, undefined) {
9	'use strict';
10
11	var pluginName = 'sly';
12	var className  = 'Sly';
13	var namespace  = pluginName;
14
15	// Local WindowAnimationTiming interface
16	var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame;
17	var rAF = w.requestAnimationFrame;
18
19	// Support indicators
20	var transform, gpuAcceleration;
21
22	// Other global values
23	var $doc = $(document);
24	var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace;
25	var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace;
26	var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace;
27	var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel.' : 'mousewheel.') + namespace;
28	var clickEvent = 'click.' + namespace;
29	var mouseDownEvent = 'mousedown.' + namespace;
30	var interactiveElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA'];
31	var tmpArray = [];
32	var time;
33
34	// Math shorthands
35	var abs = Math.abs;
36	var sqrt = Math.sqrt;
37	var pow = Math.pow;
38	var round = Math.round;
39	var max = Math.max;
40	var min = Math.min;
41
42	// Keep track of last fired global wheel event
43	var lastGlobalWheel = 0;
44	/*$doc.on(wheelEvent, function (event) {
45		var sly = event.originalEvent[namespace];
46		var time = +new Date();
47		// Update last global wheel time, but only when event didn't originate
48		// in Sly frame, or the origin was less than scrollHijack time ago
49		if (!sly || sly.options.scrollHijack < time - lastGlobalWheel) lastGlobalWheel = time;
50	});*/
51
52	/**
53	 * Sly.
54	 *
55	 * @class
56	 *
57	 * @param {Element} frame       DOM element of sly container.
58	 * @param {Object}  options     Object with options.
59	 * @param {Object}  callbackMap Callbacks map.
60	 */
61	function Sly(frame, options, callbackMap) {
62		// Extend options
63		var o = $.extend({}, Sly.defaults, options);
64
65		// Private variables
66		var self = this;
67		var parallax = isNumber(frame);
68
69		// Frame
70		var $frame = $(frame);
71		var $slidee = o.slidee ? $(o.slidee).eq(0) : $frame.children().eq(0);
72		var frameSize = 0;
73		var slideeSize = 0;
74		var pos = {
75			start: 0,
76			center: 0,
77			end: 0,
78			cur: 0,
79			dest: 0
80		};
81
82		// Scrollbar
83		var $sb = $(o.scrollBar).eq(0);
84		var $handle = $sb.children().eq(0);
85		var sbSize = 0;
86		var handleSize = 0;
87		var hPos = {
88			start: 0,
89			end: 0,
90			cur: 0
91		};
92
93		// Pagesbar
94		var $pb = $(o.pagesBar);
95		var $pages = 0;
96		var pages = [];
97
98		// Items
99		var $items = 0;
100		var items = [];
101		var rel = {
102			firstItem: 0,
103			lastItem: 0,
104			centerItem: 0,
105			activeItem: null,
106			activePage: 0
107		};
108
109		// Styles
110		var frameStyles = new StyleRestorer($frame[0]);
111		var slideeStyles = new StyleRestorer($slidee[0]);
112		var sbStyles = new StyleRestorer($sb[0]);
113		var handleStyles = new StyleRestorer($handle[0]);
114
115		// Navigation type booleans
116		var basicNav = o.itemNav === 'basic';
117		var forceCenteredNav = o.itemNav === 'forceCentered';
118		var centeredNav = o.itemNav === 'centered' || forceCenteredNav;
119		var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav);
120
121		// Miscellaneous
122		var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame;
123		var $dragSource = o.dragSource ? $(o.dragSource) : $frame;
124		var $forwardButton = $(o.forward);
125		var $backwardButton = $(o.backward);
126		var $prevButton = $(o.prev);
127		var $nextButton = $(o.next);
128		var $prevPageButton = $(o.prevPage);
129		var $nextPageButton = $(o.nextPage);
130		var callbacks = {};
131		var last = {};
132		var animation = {};
133		var move = {};
134		var dragging = {
135			released: 1
136		};
137		var scrolling = {
138			last: 0,
139			delta: 0,
140			resetTime: 200
141		};
142		var renderID = 0;
143		var historyID = 0;
144		var cycleID = 0;
145		var continuousID = 0;
146		var i, l;
147
148		// Normalizing frame
149		if (!parallax) {
150			frame = $frame[0];
151		}
152
153		// Expose properties
154		self.initialized = 0;
155		self.frame = frame;
156		self.slidee = $slidee[0];
157		self.pos = pos;
158		self.rel = rel;
159		self.items = items;
160		self.pages = pages;
161		self.isPaused = 0;
162		self.options = o;
163		self.dragging = dragging;
164
165		/**
166		 * Loading function.
167		 *
168		 * Populate arrays, set sizes, bind events, ...
169		 *
170		 * @param {Boolean} [isInit] Whether load is called from within self.init().
171		 * @return {Void}
172		 */
173		function load(isInit) {
174			// Local variables
175			var lastItemsCount = 0;
176			var lastPagesCount = pages.length;
177
178			// Save old position
179			pos.old = $.extend({}, pos);
180
181			// Reset global variables
182			frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height']();
183			sbSize = $sb[o.horizontal ? 'width' : 'height']();
184			slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight']();
185			pages.length = 0;
186
187			// Set position limits & relatives
188			pos.start = 0;
189			pos.end = max(slideeSize - frameSize, 0);
190
191			// Sizes & offsets for item based navigations
192			if (itemNav) {
193				// Save the number of current items
194				lastItemsCount = items.length;
195
196				// Reset itemNav related variables
197				$items = $slidee.children(o.itemSelector);
198				items.length = 0;
199
200				// Needed variables
201				var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop');
202				var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom');
203				var borderBox = $($items).css('boxSizing') === 'border-box';
204				var areFloated = $items.css('float') !== 'none';
205				var ignoredMargin = 0;
206				var lastItemIndex = $items.length - 1;
207				var lastItem;
208
209				// Reset slideeSize
210				slideeSize = 0;
211
212				// Iterate through items
213				$items.each(function (i, element) {
214					// Item
215					var $item = $(element);
216					var rect = element.getBoundingClientRect();
217					var itemSize = round(o.horizontal ? rect.width || rect.right - rect.left : rect.height || rect.bottom - rect.top);
218					var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop');
219					var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom');
220					if($item.parents('.frame.props').length){
221						if($item.find('td').eq(0).length){
222							itemSize -= $item.find('td').eq(0).outerWidth();
223						}
224					}
225					var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd;
226					var singleSpaced = !itemMarginStart || !itemMarginEnd;
227					var item = {};
228					item.el = element;
229					item.size = singleSpaced ? itemSize : itemSizeFull;
230					item.half = item.size / 2;
231					item.start = slideeSize + (singleSpaced ? itemMarginStart : 0);
232					item.center = item.start - round(frameSize / 2 - item.size / 2);
233					item.end = item.start - frameSize + item.size;
234
235					// Account for slidee padding
236					if (!i) {
237						slideeSize += paddingStart;
238					}
239
240					// Increment slidee size for size of the active element
241					slideeSize += itemSizeFull;
242
243					// Try to account for vertical margin collapsing in vertical mode
244					// It's not bulletproof, but should work in 99% of cases
245					if (!o.horizontal && !areFloated) {
246						// Subtract smaller margin, but only when top margin is not 0, and this is not the first element
247						if (itemMarginEnd && itemMarginStart && i > 0) {
248							slideeSize -= min(itemMarginStart, itemMarginEnd);
249						}
250					}
251
252					// Things to be done on last item
253					if (i === lastItemIndex) {
254						item.end += paddingEnd;
255						slideeSize += paddingEnd;
256						ignoredMargin = singleSpaced ? itemMarginEnd : 0;
257					}
258
259					// Add item object to items array
260					items.push(item);
261					lastItem = item;
262				});
263
264				// Resize SLIDEE to fit all items
265				$slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px';
266
267				// Adjust internal SLIDEE size for last margin
268				slideeSize -= ignoredMargin;
269
270				// Set limits
271				if (items.length) {
272					pos.start =  items[0][forceCenteredNav ? 'center' : 'start'];
273					pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start;
274				} else {
275					pos.start = pos.end = 0;
276				}
277			}
278
279			// Calculate SLIDEE center position
280			pos.center = round(pos.end / 2 + pos.start / 2);
281
282			// Update relative positions
283			updateRelatives();
284
285			// Scrollbar
286			if ($handle.length && sbSize > 0) {
287				// Stretch scrollbar handle to represent the visible area
288				if (o.dynamicHandle) {
289					handleSize = pos.start === pos.end ? sbSize : round(sbSize * frameSize / slideeSize);
290					handleSize = within(handleSize, o.minHandleSize, sbSize);
291					$handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px';
292					if(handleSize!=sbSize){
293						$($sb).parent().animate({'opacity': 1}, 50);
294						$($sb).fadeIn();
295						$($sb).closest('.wrapp_scrollbar').find('.slider_navigation').fadeIn();
296						if($($frame).hasClass('props')){
297							$($frame).addClass('border');
298						}
299					}else{
300						$($sb).parent().animate({'opacity': 0}, 50);
301						$($sb).fadeOut();
302						$($sb).closest('.wrapp_scrollbar').find('.slider_navigation').fadeOut();
303						if($($frame).hasClass('props')){
304							$($frame).removeClass('border');
305						}
306					}
307				} else {
308					handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight']();
309				}
310
311				hPos.end = sbSize - handleSize;
312
313				if (!renderID) {
314					syncScrollbar();
315				}
316			}
317
318			// Pages
319			if (!parallax && frameSize > 0) {
320				var tempPagePos = pos.start;
321				var pagesHtml = '';
322
323				// Populate pages array
324				if (itemNav) {
325					$.each(items, function (i, item) {
326						if (forceCenteredNav) {
327							pages.push(item.center);
328						} else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) {
329							tempPagePos = item.start;
330							pages.push(tempPagePos);
331							tempPagePos += frameSize;
332							if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) {
333								pages.push(pos.end);
334							}
335						}
336					});
337				} else {
338					while (tempPagePos - frameSize < pos.end) {
339						pages.push(tempPagePos);
340						tempPagePos += frameSize;
341					}
342				}
343
344				// Pages bar
345				if ($pb[0] && lastPagesCount !== pages.length) {
346					for (var i = 0; i < pages.length; i++) {
347						pagesHtml += o.pageBuilder.call(self, i);
348					}
349					$pages = $pb.html(pagesHtml).children();
350					$pages.eq(rel.activePage).addClass(o.activeClass);
351				}
352			}
353
354			// Extend relative variables object with some useful info
355			rel.slideeSize = slideeSize;
356			rel.frameSize = frameSize;
357			rel.sbSize = sbSize;
358			rel.handleSize = handleSize;
359
360			// Activate requested position
361			if (itemNav) {
362				if (isInit && o.startAt != null) {
363					activate(o.startAt);
364					self[centeredNav ? 'toCenter' : 'toStart'](o.startAt);
365				}
366				// Fix possible overflowing
367				var activeItem = items[rel.activeItem];
368				slideTo(centeredNav && activeItem ? activeItem.center : within(pos.dest, pos.start, pos.end));
369			} else {
370				if (isInit) {
371					if (o.startAt != null) slideTo(o.startAt, 1);
372				} else {
373					// Fix possible overflowing
374					slideTo(within(pos.dest, pos.start, pos.end));
375				}
376			}
377
378			// Trigger load event
379			trigger('load');
380		}
381		self.reload = function () { load(); };
382
383		/**
384		 * Animate to a position.
385		 *
386		 * @param {Int}  newPos    New position.
387		 * @param {Bool} immediate Reposition immediately without an animation.
388		 * @param {Bool}
388 dontAlign Do not align items, use the raw position passed in first argument.
389		 *
390		 * @return {Void}
391		 */
392		function slideTo(newPos, immediate, dontAlign) {
393			// Align items
394			/*if (itemNav && dragging.released && !dontAlign) {
395				var tempRel = getRelatives(newPos);
396				var isNotBordering = newPos > pos.start && newPos < pos.end;
397
398				if (centeredNav) {
399					if (isNotBordering) {
400						newPos = items[tempRel.centerItem].center;
401					}
402					if (forceCenteredNav && o.activateMiddle) {
403						activate(tempRel.centerItem);
404					}
405				} else if (isNotBordering) {
406					newPos = items[tempRel.firstItem].start;
407				}
408			}*/
409
410			// Handle overflowing position limits
411			if (dragging.init && dragging.slidee && o.elasticBounds) {
412				if (newPos > pos.end) {
413					newPos = pos.end + (newPos - pos.end) / 6;
414				} else if (newPos < pos.start) {
415					newPos = pos.start + (newPos - pos.start) / 6;
416				}
417			} else {
418				newPos = within(newPos, pos.start, pos.end);
419			}
420
421			// Update the animation object
422			animation.start = +new Date();
423			animation.time = 0;
424			animation.from = pos.cur;
425			animation.to = newPos;
426			animation.delta = newPos - pos.cur;
427			animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee;
428			animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed);
429
430			// Reset dragging tweesing request
431			dragging.tweese = 0;
432
433			// Start animation rendering
434			if (newPos !== pos.dest) {
435				pos.dest = newPos;
436				trigger('change');
437				if (!renderID) {
438					render();
439				}
440			}
441
442			// Reset next cycle timeout
443			resetCycle();
444
445			// Synchronize states
446			updateRelatives();
447			updateButtonsState();
448			syncPagesbar();
449		}
450
451		/**
452		 * Render animation frame.
453		 *
454		 * @return {Void}
455		 */
456		function render() {
457			if (!self.initialized) {
458				return;
459			}
460
461			// If first render call, wait for next animationFrame
462			if (!renderID) {
463				renderID = rAF(render);
464				if (dragging.released) {
465					trigger('moveStart');
466				}
467				return;
468			}
469
470			// If immediate repositioning is requested, don't animate.
471			if (animation.immediate) {
472				pos.cur = animation.to;
473			}
474			// Use tweesing for animations without known end point
475			else if (animation.tweesing) {
476				animation.tweeseDelta = animation.to - pos.cur;
477				// Fuck Zeno's paradox
478				/*if (abs(animation.tweeseDelta) < 0.1) {
479					pos.cur = animation.to;
480				} else {*/
481					pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed);
482				//}
483			}
484			// Use tweening for basic animations with known end point
485			else {
486				animation.time = min(+new Date() - animation.start, o.speed);
487				pos.cur = animation.from + animation.delta * $.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed);
488			}
489
490			// If there is nothing more to render break the rendering loop, otherwise request new animation frame.
491			if (animation.to === pos.cur) {
492				pos.cur = animation.to;
493				dragging.tweese = renderID = 0;
494			} else {
495				renderID = rAF(render);
496			}
497
498			trigger('move');
499			// Update SLIDEE position
500			if (!parallax) {
501				if (transform) {
502					$slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)';
503				} else {
504					$slidee[0].style[o.horizontal ? 'left' : 'top'] = -round(pos.cur) + 'px';
505				}
506			}
507
508			// When animation reached the end, and dragging is not active, trigger moveEnd
509			if (!renderID && dragging.released) {
510				trigger('moveEnd');
511			}
512
513			syncScrollbar();
514		}
515
516		/**
517		 * Synchronizes scrollbar with the SLIDEE.
518		 *
519		 * @return {Void}
520		 */
521		function syncScrollbar() {
522			if ($handle.length) {
523				hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end;
524				hPos.cur = within(round(hPos.cur), hPos.start, hPos.end);
525				if (last.hPos !== hPos.cur) {
526					last.hPos = hPos.cur;
527					if (transform) {
528						$handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)';
529					} else {
530						$handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px';
531					}
532				}
533			}
534		}
535
536		/**
537		 * Synchronizes pagesbar with SLIDEE.
538		 *
539		 * @return {Void}
540		 */
541		function syncPagesbar() {
542			if ($pages[0] && last.page !== rel.activePage) {
543				last.page = rel.activePage;
544				$pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass);
545				trigger('activePage', last.page);
546			}
547		}
548
549		/**
550		 * Returns the position object.
551		 *
552		 * @param {Mixed} item
553		 *
554		 * @return {Object}
555		 */
556		self.getPos = function (item) {
557			if (itemNav) {
558				var index = getIndex(item);
559				return index !== -1 ? items[index] : false;
560			} else {
561				var $item = $slidee.find(item).eq(0);
562
563				if ($item[0]) {
564					var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top;
565					var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight']();
566
567					return {
568						start: offset,
569						center: offset - frameSize / 2 + size / 2,
570						end: offset - frameSize + size,
571						size: size
572					};
573				} else {
574					return false;
575				}
576			}
577		};
578
579		/**
580		 * Continuous move in a specified direction.
581		 *
582		 * @param  {Bool} forward True for forward movement, otherwise it'll go backwards.
583		 * @param  {Int}  speed   Movement speed in pixels per frame. Overrides options.moveBy value.
584		 *
585		 * @return {Void}
586		 */
587		self.moveBy = function (speed) {
588			move.speed = speed;
589			// If already initiated, or there is nowhere to move, abort
590			/*if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
591				return;
592			}*/
593			// Initiate move object
594			move.lastTime = +new Date();
595			move.startPos = pos.cur;
596			// Set dragging as initiated
597			continuousInit('button');
598			dragging.init = 1;
599			// Start movement
600			trigger('moveStart');
601			cAF(continuousID);
602			moveLoop();
603		};
604
605		/**
606		 * Continuous movement loop.
607		 *
608		 * @return {Void}
609		 */
610		function moveLoop() {
611			// If there is nowhere to move anymore, stop
612			/*if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
613				self.stop();
614			}*/
615			
616			// Request new move loop if it hasn't been stopped
617			continuousID = dragging.init ? rAF(moveLoop) : 0;
618			// Update move object
619			move.now = +new Date();
620			move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed;
621			// Slide
622			slideTo(dragging.init ? move.pos : round(move.pos));
623			// Normally, this is triggered in render(), but if there
624			// is nothing to render, we have to do it manually here.
625			if (!dragging.init && pos.cur === pos.dest) {
626				trigger('moveEnd');
627			}
628			// Update times for future iteration
629			move.lastTime = move.now;
630		}
631
632		/**
633		 * Stops continuous movement.
634		 *
635		 * @return {Void}
636		 */
637		self.stop = function () {
638			if (dragging.source === 'button') {
639				dragging.init = 0;
640				dragging.released = 1;
641			}
642		};
643
644		/**
645		 * Activate previous item.
646		 *
647		 * @return {Void}
648		 */
649		self.prev = function () {
650			self.activate(rel.activeItem == null ? 0 : rel.activeItem - 1);
651		};
652
653		/**
654		 * Activate next item.
655		 *
656		 * @return {Void}
657		 */
658		self.next = function () {
659			self.activate(rel.activeItem == null ? 0 : rel.activeItem + 1);
660		};
661
662		/**
663		 * Activate previous page.
664		 *
665		 * @return {Void}
666		 */
667		self.prevPage = function () {
668			self.activatePage(rel.activePage - 1);
669		};
670
671		/**
672		 * Activate next page.
673		 *
674		 * @return {Void}
675		 */
676		self.nextPage = function () {
677			self.activatePage(rel.activePage + 1);
678		};
679
680		/**
681		 * Slide SLIDEE by amount of pixels.
682		 *
683		 * @param {Int}  delta     Pixels/Items. Positive means forward, negative means backward.
684		 * @param {Bool} immediate Reposition immediately without an animation.
685		 *
686		 * @return {Void}
687		 */
688		self.slideBy = function (delta, immediate) {
689			if (!delta) {
690				return;
691			}
692			if (itemNav) {
693				self[centeredNav ? 'toCenter' : 'toStart'](
694					within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length)
695				);
696			} else {
697				slideTo(pos.dest + delta, immediate);
698			}
699		};
700
701		/**
702		 * Animate SLIDEE to a specific position.
703		 *
704		 * @param {Int}  pos       New position.
705		 * @param {Bool} immediate Reposition immediately without an animation.
706		 *
707		 * @return {Void}
708		 */
709		self.slideTo = function (pos, immediate) {
710			slideTo(pos, immediate);
711		};
712
713		/**
714		 * Core method for handling `toLocation` methods.
715		 *
716		 * @param  {String} location
717		 * @param  {Mixed}  item
718		 * @param  {Bool}   immediate
719		 *
720		 * @return {Void}
721		 */
722		function to(location, item, immediate) {
723			// Optional arguments logic
724			if (type(item) === 'boolean') {
725				immediate = item;
726				item = undefined;
727			}
728
729			if (item === undefined) {
730				slideTo(pos[location], immediate);
731			} else {
732				// You can't align items to sides of the frame
733				// when centered navigation type is enabled
734				if (centeredNav && location !== 'center') {
735					return;
736				}
737
738				var itemPos = self.getPos(item);
739				if (itemPos) {
740					slideTo(itemPos[location], immediate, !centeredNav);
741				}
742			}
743		}
744
745		/**
746		 * Animate element or the whole SLIDEE to the start of the frame.
747		 *
748		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
749		 * @param {Bool}  immediate Reposition immediately without an animation.
750		 *
751		 * @return {Void}
752		 */
753		self.toStart = function (item, immediate) {
754			to('start', item, immediate);
755		};
756
757		/**
758		 * Animate element or the whole SLIDEE to the end of the frame.
759		 *
760		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
761		 * @param {Bool}  immediate Reposition immediately without an animation.
762		 *
763		 * @return {Void}
764		 */
765		self.toEnd = function (item, immediate) {
766			to('end', item, immediate);
767		};
768
769		/**
770		 * Animate element or the whole SLIDEE to the center of the frame.
771		 *
772		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
773		 * @param {Bool}  immediate Reposition immediately without an animation.
774		 *
775		 * @return {Void}
776		 */
777		self.toCenter = function (item, immediate) {
778			to('center', item, immediate);
779		};
780
781		/**
782		 * Get the index of an item in SLIDEE.
783		 *
784		 * @param {Mixed} item     Item DOM element.
785		 *
786		 * @return {Int}  Item index, or -1 if not found.
787		 */
788		function getIndex(item) {
789			return item != null ?
790					isNumber(item) ?
791						item >= 0 && item < items.length ? item : -1 :
792						$items.index(item) :
793					-1;
794		}
795		// Expose getIndex without lowering the compressibility of it,
796		// as it is used quite often throughout Sly.
797		self.getIndex = getIndex;
798
799		/**
800		 * Get index of an item in SLIDEE based on a variety of input types.
801		 *
802		 * @param  {Mixed} item DOM element, positive or negative integer.
803		 *
804		 * @return {Int}   Item index, or -1 if not found.
805		 */
806		function getRelativeIndex(item) {
807			return getIndex(isNumber(item) && item < 0 ? item + items.length : item);
808		}
809
810		/**
811		 * Activates an item.
812		 *
813		 * @param  {Mixed} item Item DOM element, or index starting at 0.
814		 *
815		 * @return {Mixed} Activated item index or false on fail.
816		 */
817		function activate(item, force) {
818			var index = getIndex(item);
819
820			if (!itemNav || index < 0) {
821				return false;
822			}
823
824			// Update classes, last active index, and trigger active event only when there
825			// has been a change. Otherwise just return the current active index.
826			if (last.active !== index || force) {
827				// Update classes
828				$items.eq(rel.activeItem).removeClass(o.activeClass);
829				$items.eq(index).addClass(o.activeClass);
830
831				last.active = rel.activeItem = index;
832
833				updateButtonsState();
834				trigger('active', index);
835			}
836
837			return index;
838		}
839
840		/**
841		 * Activates an item and helps with further navigation when o.smart is enabled.
842		 *
843		 * @param {Mixed} item      Item DOM element, or index starting at 0.
844		 * @param {Bool}  immediate Whether to reposition immediately in smart navigation.
845		 *
846		 * @return {Void}
847		 */
848		self.activate = function (item, immediate) {
849			var index = activate(item);
850
851			// Smart navigation
852			if (o.smart && index !== false) {
853				// When centeredNav is enabled, center the element.
854				// Otherwise, determine where to position the element based on its current position.
855				// If the element is currently on the far end side of the frame, assume that user is
856				// moving forward and animate it to the start of the visible frame, and vice versa.
857				if (centeredNav) {
858					self.toCenter(index, immediate);
859				} else if (index >= rel.lastItem) {
860					self.toStart(index, immediate);
861				} else if (index <= rel.firstItem) {
862					self.toEnd(index, immediate);
863				} else {
864					resetCycle();
865				}
866			}
867		};
868
869		/**
870		 * Activates a page.
871		 *
872		 * @param {Int}  index     Page index, starting from 0.
873		 * @param {Bool} immediate Whether to reposition immediately without animation.
874		 *
875		 * @return {Void}
876		 */
877		self.activatePage = function (index, immediate) {
878			if (isNumber(index)) {
879				slideTo(pages[within(index, 0, pages.length - 1)], immediate);
880			}
881		};
882
883		/**
884		 * Return relative positions of items based on their visibility within FRAME.
885		 *
886		 * @param {Int} slideePos Position of SLIDEE.
887		 *
888		 * @return {Void}
889		 */
890		function getRelatives(slideePos) {
891			slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end);
892
893			var relatives = {};
894			var centerOffset = forceCenteredNav ? 0 : frameSize / 2;
895
896			// Determine active page
897			if (!parallax) {
898				for (var p = 0, pl = pages.length; p < pl; p++) {
899					if (slideePos >= pos.end || p === pages.length - 1) {
900						relatives.activePage = pages.length - 1;
901						break;
902					}
903
904					if (slideePos <= pages[p] + centerOffset) {
905						relatives.activePage = p;
906						break;
907					}
908				}
909			}
910
911			// Relative item indexes
912			if (itemNav) {
913				var first = false;
914				var last = false;
915				var center = false;
916
917				// From start
918				for (var i = 0, il = items.length; i < il; i++) {
919					// First item
920					if (first === false && slideePos <= items[i].start + items[i].half) {
921						first = i;
922					}
923
924					// Center item
925					if (center === false && slideePos <= items[i].center + items[i].half) {
926						center = i;
927					}
928
929					// Last item
930					if (i === il - 1 || slideePos <= items[i].end + items[i].half) {
931						last = i;
932						break;
933					}
934				}
935
936				// Safe assignment, just to be sure the false won't be returned
937				relatives.firstItem = isNumber(first) ? first : 0;
938				relatives.centerItem = isNumber(center) ? center : relatives.firstItem;
939				relatives.lastItem = isNumber(last) ? last : relatives.centerItem;
940			}
941
942			return relatives;
943		}
944
945		/**
946		 * Update object with relative positions.
947		 *
948		 * @param {Int} newPos
949		 *
950		 * @return {Void}
951		 */
952		function updateRelatives(newPos) {
953			$.extend(rel, getRelatives(newPos));
954		}
955
956		/**
957		 * Disable navigation buttons when needed.
958		 *
959		 * Adds disabledClass, and when the button is <button> or <input>, activates :disabled state.
960		 *
961		 * @return {Void}
962		 */
963		function updateButtonsState() {
964			var isStart = pos.dest <= pos.start;
965			var isEnd = pos.dest >= pos.end;
966			var slideePosState = (isStart ? 1 : 0) | (isEnd ? 2 : 0);
967
968			// Update paging buttons only if there has been a change in SLIDEE position
969			if (last.slideePosState !== slideePosState) {
970				last.slideePosState = slideePosState;
971
972				if ($prevPageButton.is('button,input')) {
973					$prevPageButton.prop('disabled', isStart);
974				}
975
976				if ($nextPageButton.is('button,input')) {
977					$nextPageButton.prop('disabled', isEnd);
978				}
979
980				$prevPageButton.add($backwardButton)[isStart ? 'addClass' : 'removeClass'](o.disabledClass);
981				$nextPageButton.add($forwardButton)[isEnd ? 'addClass' : 'removeClass'](o.disabledClass);
982			}
983
984			// Forward & Backward buttons need a separate state caching because we cannot "property disable"
985			// them while they are being used, as disabled buttons stop emitting mouse events.
986			if (last.fwdbwdState !== slideePosState && dragging.released) {
987				last.fwdbwdState = slideePosState;
988
989				if ($backwardButton.is('button,input')) {
990					$backwardButton.prop('disabled', isStart);
991				}
992
993				if ($forwardButton.is('button,input')) {
994					$forwardButton.prop('disabled', isEnd);
995				}
996			}
997
998			// Item navigation
999			if (itemNav && rel.activeItem != null) {
1000				var isFirst = rel.activeItem === 0;
1001				var isLast = rel.activeItem >= items.length - 1;
1002				var itemsButtonState = (isFirst ? 1 : 0) | (isLast ? 2 : 0);
1003
1004				if (last.itemsButtonState !== itemsButtonState) {
1005					last.itemsButtonState = itemsButtonState;
1006
1007					if ($prevButton.is('button,input')) {
1008						$prevButton.prop('disabled', isFirst);
1009					}
1010
1011					if ($nextButton.is('button,input')) {
1012						$nextButton.prop('disabled', isLast);
1013					}
1014
1015					$prevButton[isFirst ? 'addClass' : 'removeClass'](o.disabledClass);
1016					$nextButton[isLast ? 'addClass' : 'removeClass'](o.disabledClass);
1017				}
1018			}
1019		}
1020
1021		/**
1022		 * Resume cycling.
1023		 *
1024		 * @param {Int} priority Resume pause with priority lower or equal than this. Used internally for pauseOnHover.
1025		 *
1026		 * @return {Void}
1027		 */
1028		self.resume = function (priority) {
1029			if (!o.cycleBy || !o.cycleInterval || o.cycleBy === 'items' && (!items[0] || rel.activeItem == null) || priority < self.isPaused) {
1030				return;
1031			}
1032
1033			self.isPaused = 0;
1034
1035			if (cycleID) {
1036				cycleID = clearTimeout(cycleID);
1037			} else {
1038				trigger('resume');
1039			}
1040
1041			cycleID = setTimeout(function () {
1042				trigger('cycle');
1043				switch (o.cycleBy) {
1044					case 'items':
1045						self.activate(rel.activeItem >= items.length - 1 ? 0 : rel.activeItem + 1);
1046						break;
1047
1048					case 'pages':
1049						self.activatePage(rel.activePage >
1049= pages.length - 1 ? 0 : rel.activePage + 1);
1050						break;
1051				}
1052			}, o.cycleInterval);
1053		};
1054
1055		/**
1056		 * Pause cycling.
1057		 *
1058		 * @param {Int} priority Pause priority. 100 is default. Used internally for pauseOnHover.
1059		 *
1060		 * @return {Void}
1061		 */
1062		self.pause = function (priority) {
1063			if (priority < self.isPaused) {
1064				return;
1065			}
1066
1067			self.isPaused = priority || 100;
1068
1069			if (cycleID) {
1070				cycleID = clearTimeout(cycleID);
1071				trigger('pause');
1072			}
1073		};
1074
1075		/**
1076		 * Toggle cycling.
1077		 *
1078		 * @return {Void}
1079		 */
1080		self.toggle = function () {
1081			self[cycleID ? 'pause' : 'resume']();
1082		};
1083
1084		/**
1085		 * Updates a signle or multiple option values.
1086		 *
1087		 * @param {Mixed} name  Name of the option that should be updated, or object that will extend the options.
1088		 * @param {Mixed} value New option value.
1089		 *
1090		 * @return {Void}
1091		 */
1092		self.set = function (name, value) {
1093			if ($.isPlainObject(name)) {
1094				$.extend(o, name);
1095			} else if (o.hasOwnProperty(name)) {
1096				o[name] = value;
1097			}
1098		};
1099
1100		/**
1101		 * Add one or multiple items to the SLIDEE end, or a specified position index.
1102		 *
1103		 * @param {Mixed} element Node element, or HTML string.
1104		 * @param {Int}   index   Index of a new item position. By default item is appended at the end.
1105		 *
1106		 * @return {Void}
1107		 */
1108		self.add = function (element, index) {
1109			var $element = $(element);
1110
1111			if (itemNav) {
1112				// Insert the element(s)
1113				if (index == null || !items[0] || index >= items.length) {
1114					$element.appendTo($slidee);
1115				} else if (items.length) {
1116					$element.insertBefore(items[index].el);
1117				}
1118
1119				// Adjust the activeItem index
1120				if (rel.activeItem != null && index <= rel.activeItem) {
1121					last.active = rel.activeItem += $element.length;
1122				}
1123			} else {
1124				$slidee.append($element);
1125			}
1126
1127			// Reload
1128			load();
1129		};
1130
1131		/**
1132		 * Remove an item from SLIDEE.
1133		 *
1134		 * @param {Mixed} element Item index, or DOM element.
1135		 * @param {Int}   index   Index of a new item position. By default item is appended at the end.
1136		 *
1137		 * @return {Void}
1138		 */
1139		self.remove = function (element) {
1140			if (itemNav) {
1141				var index = getRelativeIndex(element);
1142
1143				if (index > -1) {
1144					// Remove the element
1145					$items.eq(index).remove();
1146
1147					// If the current item is being removed, activate new one after reload
1148					var reactivate = index === rel.activeItem;
1149
1150					// Adjust the activeItem index
1151					if (rel.activeItem != null && index < rel.activeItem) {
1152						last.active = --rel.activeItem;
1153					}
1154
1155					// Reload
1156					load();
1157
1158					// Activate new item at the removed position
1159					if (reactivate) {
1160						last.active = null;
1161						self.activate(rel.activeItem);
1162					}
1163				}
1164			} else {
1165				$(element).remove();
1166				load();
1167			}
1168		};
1169
1170		/**
1171		 * Helps re-arranging items.
1172		 *
1173		 * @param  {Mixed} item     Item DOM element, or index starting at 0. Use negative numbers to select items from the end.
1174		 * @param  {Mixed} position Item insertion anchor. Accepts same input types as item argument.
1175		 * @param  {Bool}  after    Insert after instead of before the anchor.
1176		 *
1177		 * @return {Void}
1178		 */
1179		function moveItem(item, position, after) {
1180			item = getRelativeIndex(item);
1181			position = getRelativeIndex(position);
1182
1183			// Move only if there is an actual change requested
1184			if (item > -1 && position > -1 && item !== position && (!after || position !== item - 1) && (after || position !== item + 1)) {
1185				$items.eq(item)[after ? 'insertAfter' : 'insertBefore'](items[position].el);
1186
1187				var shiftStart = item < position ? item : (after ? position : position - 1);
1188				var shiftEnd = item > position ? item : (after ? position + 1 : position);
1189				var shiftsUp = item > position;
1190
1191				// Update activeItem index
1192				if (rel.activeItem != null) {
1193					if (item === rel.activeItem) {
1194						last.active = rel.activeItem = after ? (shiftsUp ? position + 1 : position) : (shiftsUp ? position : position - 1);
1195					} else if (rel.activeItem > shiftStart && rel.activeItem < shiftEnd) {
1196						last.active = rel.activeItem += shiftsUp ? 1 : -1;
1197					}
1198				}
1199
1200				// Reload
1201				load();
1202			}
1203		}
1204
1205		/**
1206		 * Move item after the target anchor.
1207		 *
1208		 * @param  {Mixed} item     Item to be moved. Can be DOM element or item index.
1209		 * @param  {Mixed} position Target position anchor. Can be DOM element or item index.
1210		 *
1211		 * @return {Void}
1212		 */
1213		self.moveAfter = function (item, position) {
1214			moveItem(item, position, 1);
1215		};
1216
1217		/**
1218		 * Move item before the target anchor.
1219		 *
1220		 * @param  {Mixed} item     Item to be moved. Can be DOM element or item index.
1221		 * @param  {Mixed} position Target position anchor. Can be DOM element or item index.
1222		 *
1223		 * @return {Void}
1224		 */
1225		self.moveBefore = function (item, position) {
1226			moveItem(item, position);
1227		};
1228
1229		/**
1230		 * Registers callbacks.
1231		 *
1232		 * @param  {Mixed}
1232 name  Event name, or callbacks map.
1233		 * @param  {Mixed} fn    Callback, or an array of callback functions.
1234		 *
1235		 * @return {Void}
1236		 */
1237		self.on = function (name, fn) {
1238			// Callbacks map
1239			if (type(name) === 'object') {
1240				for (var key in name) {
1241					if (name.hasOwnProperty(key)) {
1242						self.on(key, name[key]);
1243					}
1244				}
1245			// Callback
1246			} else if (type(fn) === 'function') {
1247				var names = name.split(' ');
1248				for (var n = 0, nl = names.length; n < nl; n++) {
1249					callbacks[names[n]] = callbacks[names[n]] || [];
1250					if (callbackIndex(names[n], fn) === -1) {
1251						callbacks[names[n]].push(fn);
1252					}
1253				}
1254			// Callbacks array
1255			} else if (type(fn) === 'array') {
1256				for (var f = 0, fl = fn.length; f < fl; f++) {
1257					self.on(name, fn[f]);
1258				}
1259			}
1260		};
1261
1262		/**
1263		 * Registers callbacks to be executed only once.
1264		 *
1265		 * @param  {Mixed} name  Event name, or callbacks map.
1266		 * @param  {Mixed} fn    Callback, or an array of callback functions.
1267		 *
1268		 * @return {Void}
1269		 */
1270		self.one = function (name, fn) {
1271			function proxy() {
1272				fn.apply(self, arguments);
1273				self.off(name, proxy);
1274			}
1275			self.on(name, proxy);
1276		};
1277
1278		/**
1279		 * Remove one or all callbacks.
1280		 *
1281		 * @param  {String} name Event name.
1282		 * @param  {Mixed}  fn   Callback, or an array of callback functions. Omit to remove all callbacks.
1283		 *
1284		 * @return {Void}
1285		 */
1286		self.off = function (name, fn) {
1287			if (fn instanceof Array) {
1288				for (var f = 0, fl = fn.length; f < fl; f++) {
1289					self.off(name, fn[f]);
1290				}
1291			} else {
1292				var names = name.split(' ');
1293				for (var n = 0, nl = names.length; n < nl; n++) {
1294					callbacks[names[n]] = callbacks[names[n]] || [];
1295					if (fn == null) {
1296						callbacks[names[n]].length = 0;
1297					} else {
1298						var index = callbackIndex(names[n], fn);
1299						if (index !== -1) {
1300							callbacks[names[n]].splice(index, 1);
1301						}
1302					}
1303				}
1304			}
1305		};
1306
1307		/**
1308		 * Returns callback array index.
1309		 *
1310		 * @param  {String}   name Event name.
1311		 * @param  {Function} fn   Function
1312		 *
1313		 * @return {Int} Callback array index, or -1 if isn't registered.
1314		 */
1315		function callbackIndex(name, fn) {
1316			for (var i = 0, l = callbacks[name].length; i < l; i++) {
1317				if (callbacks[name][i] === fn) {
1318					return i;
1319				}
1320			}
1321			return -1;
1322		}
1323
1324		/**
1325		 * Reset next cycle timeout.
1326		 *
1327		 * @return {Void}
1328		 */
1329		function resetCycle() {
1330			if (dragging.released && !self.isPaused) {
1331				self.resume();
1332			}
1333		}
1334
1335		/**
1336		 * Calculate SLIDEE representation of handle position.
1337		 *
1338		 * @param  {Int} handlePos
1339		 *
1340		 * @return {Int}
1341		 */
1342		function handleToSlidee(handlePos) {
1343			return round(within(handlePos, hPos.start, hPos.end) / hPos.end * (pos.end - pos.start)) + pos.start;
1344		}
1345
1346		/**
1347		 * Keeps track of a dragging delta history.
1348		 *
1349		 * @return {Void}
1350		 */
1351		function draggingHistoryTick() {
1352			// Looking at this, I know what you're thinking :) But as we need only 4 history states, doing it this way
1353			// as opposed to a proper loop is ~25 bytes smaller (when minified with GCC), a lot faster, and doesn't
1354			// generate garbage. The loop version would create 2 new variables on every tick. Unexaptable!
1355			dragging.history[0] = dragging.history[1];
1356			dragging.history[1] = dragging.history[2];
1357			dragging.history[2] = dragging.history[3];
1358			dragging.history[3] = dragging.delta;
1359		}
1360
1361		/**
1362		 * Initialize continuous movement.
1363		 *
1364		 * @return {Void}
1365		 */
1366		function continuousInit(source) {
1367			dragging.released = 0;
1368			dragging.source = source;
1369			dragging.slidee = source === 'slidee';
1370		}
1371
1372		/**
1373		 * Dragging initiator.
1374		 *
1375		 * @param  {Event} event
1376		 *
1377		 * @return {Void}
1378		 */
1379		function dragInit(event) {
1380			var isTouch = event.type === 'touchstart';
1381			var source = event.data.source;
1382			var isSlidee = source === 'slidee';
1383
1384			// Ignore when already in progress, or interactive element in non-touch navivagion
1385			if (dragging.init || !isTouch && isInteractive(event.target)) {
1386				return;
1387			}
1388
1389			// Handle dragging conditions
1390			if (source === 'handle' && (!o.dragHandle || hPos.start === hPos.end)) {
1391				return;
1392			}
1393
1394			// SLIDEE dragging conditions
1395			if (isSlidee && !(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) {
1396				return;
1397			}
1398
1399			if (!isTouch) {
1400				// prevents native image dragging in Firefox
1401				stopDefault(event);
1402			}
1403
1404			// Reset dragging object
1405			continuousInit(source);
1406
1407			// Properties used in dragHandler
1408			dragging.init = 0;
1409			dragging.$source = $(event.target);
1410			dragging.touch = isTouch;
1411			dragging.pointer = isTouch ? event.originalEvent.touches[0] : event;
1412			dragging.initX = dragging.pointer.pageX;
1413			dragging.initY = dragging.pointer.pageY;
1414			dragging.initPos = isSlidee ? pos.cur : hPos.cur;
1415			dragging.start = +new Date();
1416			dragging.time = 0;
1417			dragging.path = 0;
1418			dragging.delta = 0;
1419			dragging.locked = 0;
1420			dragging.history = [0, 0, 0, 0];
1421			dragging.pathToLock = isSlidee ? isTouch ? 30 : 10 : 0;
1422
1423			// Bind dragging events
1424			$doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler);
1425
1426			// Pause ongoing cycle
1427			self.pause(1);
1428
1429			// Add dragging class
1430			(isSlidee ? $slidee : $handle).addClass(o.draggedClass);
1431
1432			// Trigger moveStart event
1433			trigger('moveStart');
1434
1435			// Keep track of a dragging path history. This is later used in the
1436			// dragging release swing calculation when dragging SLIDEE.
1437			if (isSlidee) {
1438				historyID = setInterval(draggingHistoryTick, 10);
1439			}
1440		}
1441
1442		/**
1443		 * Handler for dragging scrollbar handle or SLIDEE.
1444		 *
1445		 * @param  {Event} event
1446		 *
1447		 * @return {Void}
1448		 */
1449		function dragHandler(event) {
1450			dragging.released = event.type === 'mouseup' || event.type === 'touchend';
1451			dragging.pointer = dragging.touch ? event.originalEvent[dragging.released ? 'changedTouches' : 'touches'][0] : event;
1452			dragging.pathX = dragging.pointer.pageX - dragging.initX;
1453			dragging.pathY = dragging.pointer.pageY - dragging.initY;
1454			dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2));
1455			dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY;
1456
1457			if (!dragging.released && dragging.path < 1) return;
1458
1459			if (!dragging.init) {
1460				if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) {
1461					dragging.init = 1;
1462				} else {
1463					return dragEnd();
1464				}
1465			}
1466
1467			stopDefault(event);
1468
1469			// Disable click on a source element, as it is unwelcome when dragging
1470			if (!dragging.locked && dragging.path > dragging.pathToLock && dragging.slidee) {
1471				dragging.locked = 1;
1472				dragging.$source.on(clickEvent, disableOneEvent);
1473			}
1474
1475			// Cancel dragging on release
1476			if (dragging.released) {
1477				dragEnd();
1478
1479				// Adjust path with a swing on mouse release
1480				if (o.releaseSwing && dragging.slidee) {
1481					dragging.swing = (dragging.delta - dragging.history[0]) / 40 * 300;
1482					dragging.delta += dragging.swing;
1483					dragging.tweese = abs(dragging.swing) > 10;
1484				}
1485			}
1486
1487			slideTo(dragging.slidee ? round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta));
1488		}
1489
1490		/**
1491		 * Stops dragging and cleans up after it.
1492		 *
1493		 * @return {Void}
1494		 */
1495		function dragEnd() {
1496			clearInterval(historyID);
1497			dragging.released = true;
1498			$doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler);
1499			(dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass);
1500
1501			// Make sure that disableOneEvent is not active in next tick.
1502			setTimeout(function () {
1503				dragging.$source.off(clickEvent, disableOneEvent);
1504			});
1505
1506			// Normally, this is triggered in render(), but if there
1507			// is nothing to render, we have to do it manually here.
1508			if (pos.cur === pos.dest && dragging.init) {
1509				trigger('moveEnd');
1510			}
1511
1512			// Resume ongoing cycle
1513			self.resume(1);
1514
1515			dragging.init = 0;
1516		}
1517
1518		/**
1519		 * Check whether element is interactive.
1520		 *
1521		 * @return {Boolean}
1522		 */
1523		function isInteractive(element) {
1524			return ~$.inArray(element.nodeName, interactiveElements) || $(element).is(o.interactive);
1525		}
1526
1527		/**
1528		 * Continuous movement cleanup on mouseup.
1529		 *
1530		 * @return {Void}
1531		 */
1532		function movementReleaseHandler() {
1533			self.stop();
1534			//$doc.off('mouseup', movementReleaseHandler);
1535		}
1536
1537		/**
1538		 * Buttons navigation handler.
1539		 *
1540		 * @param  {Event} event
1541		 *
1542		 * @return {Void}
1543		 */
1544		function buttonsHandler(event) {
1545			/*jshint validthis:true */
1546			stopDefault(event);
1547			switch (this) {
1548				case $forwardButton[0]:
1549				case $backwardButton[0]:
1550					self.moveBy($forwardButton.is(this) ? o.moveBy : -o.moveBy);
1551					$doc.on('mouseup', movementReleaseHandler);
1552					break;
1553
1554				case $prevButton[0]:
1555					self.prev();
1556					break;
1557
1558				case $nextButton[0]:
1559					self.next();
1560					break;
1561
1562				case $prevPageButton[0]:
1563					self.prevPage();
1564					break;
1565
1566				case $nextPageButton[0]:
1567					self.nextPage();
1568					break;
1569			}
1570		}
1571
1572		/**
1573		 * Mouse wheel delta normalization.
1574		 *
1575		 * @param  {Event} event
1576		 *
1577		 * @return {Int}
1578		 */
1579		function normalizeWheelDelta(event) {
1580			// wheelDelta needed only for IE8-
1581			scrolling.curDelta = ((o.horizontal ? event.deltaY || event.deltaX : event.deltaY) || -event.wheelDelta);
1582			scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100;
1583			if (!itemNav) {
1584				return scrolling.curDelta;
1585			}
1586			time = +new Date();
1587			if (scrolling.last < time - scrolling.resetTime) {
1588				scrolling.delta = 0;
1589			}
1590			scrolling.last = time;
1591			scrolling.delta += scrolling.curDelta;
1592			if (abs(scrolling.delta) < 1) {
1593				scrolling.finalDelta = 0;
1594			} else {
1595				scrolling.finalDelta = round(scrolling.delta / 1);
1596				scrolling.delta %= 1;
1597			}
1598			return scrolling.finalDelta;
1599		}
1600
1601		/**
1602		 * Mouse scrolling handler.
1603		 *
1604		 * @param  {Event} event
1605		 *
1606		 * @return {Void}
1607		 */
1608		function scrollHandler(event) {
1609			// Mark event as originating in a Sly instance
1610			event.originalEvent[namespace] = self;
1611			// Don't hijack global scrolling
1612			var time = +new Date();
1613			if (lastGlobalWheel + o.scrollHijack > time && $scrollSource[0] !== document && $scrollSource[0] !== window) {
1614				lastGlobalWheel = time;
1615				return;
1616			}
1617			// Ignore if there is no scrolling to be done
1618			if (!o.scrollBy || pos.start === pos.end) {
1619				return;
1620			}
1621			var delta = normalizeWheelDelta(event.originalEvent);
1622			// Trap scrolling only when necessary and/or requested
1623			if (o.scrollTrap || delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) {
1624				stopDefault(event, 1);
1625			}
1626			self.slideBy(o.scrollBy * delta);
1627		}
1628
1629		/**
1630		 * Scrollbar click handler.
1631		 *
1632		 * @param  {Event} event
1633		 *
1634		 * @return {Void}
1635		 */
1636		function scrollbarHandler(event) {
1637			// Only clicks on scroll bar. Ignore the handle.
1638			if (o.clickBar && event.target === $sb[0]) {
1639				stopDefault(event);
1640				// Calculate new handle position and sync SLIDEE to it
1641				slideTo(handleToSlidee((o.horizontal ? event.pageX - $sb.offset().left : event.pageY - $sb.offset().top) - handleSize / 2));
1642			}
1643		}
1644
1645		/**
1646		 * Keyboard input handler.
1647		 *
1648		 * @param  {Event} event
1649		 *
1650		 * @return {Void}
1651		 */
1652		function keyboardHandler(event) {
1653			if (!o.keyboardNavBy) {
1654				return;
1655			}
1656
1657			switch (event.which) {
1658				// Left or Up
1659				case o.horizontal ? 37 : 38:
1660					stopDefault(event);
1661					self[o.keyboardNavBy === 'pages' ? 'prevPage' : 'prev']();
1662					break;
1663
1664				// Right or Down
1665				case o.horizontal ? 39 : 40:
1666					stopDefault(event);
1667					self[o.keyboardNavBy === 'pages' ? 'nextPage' : 'next']();
1668					break;
1669			}
1670		}
1671
1672		/**
1673		 * Click on item activation handler.
1674		 *
1675		 * @param  {Event} event
1676		 *
1677		 * @return {Void}
1678		 */
1679		function activateHandler(event) {
1680			/*jshint validthis:true */
1681
1682			// Ignore clicks on interactive elements.
1683			if (isInteractive(this)) {
1684				event.originalEvent[namespace + 'ignore'] = true;
1685				return;
1686			}
1687
1688			// Ignore events that:
1689			// - are not originating from direct SLIDEE children
1690			// - originated from interactive elements
1691			if (this.parentNode !== $slidee[0] || event.originalEvent[namespace + 'ignore']) return;
1692
1693			self.activate(this);
1694		}
1695
1696		/**
1697		 * Click on page button handler.
1698		 *
1699		 * @param {Event} event
1700		 *
1701		 * @return {Void}
1702		 */
1703		function activatePageHandler() {
1704			/*jshint validthis:true */
1705			// Accept only events from direct pages bar children.
1706			if (this.parentNode === $pb[0]) {
1707				self.activatePage($pages.index(this));
1708			}
1709		}
1710
1711		/**
1712		 * Pause on hover handler.
1713		 *
1714		 * @param  {Event} event
1715		 *
1716		 * @return {Void}
1717		 */
1718		function pauseOnHoverHandler(event) {
1719			if (o.pauseOnHover) {
1720				self[event.type === 'mouseenter' ? 'pause' : 'resume'](2);
1721			}
1722		}
1723
1724		/**
1725		 * Trigger callbacks for event.
1726		 *
1727		 * @param  {String} name Event name.
1728		 * @param  {Mixed}  argX Arguments passed to callbacks.
1729		 *
1730		 * @return {Void}
1731		 */
1732		function trigger(name, arg1) {
1733			if (callbacks[name]) {
1734				l = callbacks[name].length;
1735				// Callbacks will be stored and executed from a temporary array to not
1736				// break the execution queue when one of the callbacks unbinds itself.
1737				tmpArray.length = 0;
1738				for (i = 0; i < l; i++) {
1739					tmpArray.push(callbacks[name][i]);
1740				}
1741				// Execute the callbacks
1742				for (i = 0; i < l; i++) {
1743					tmpArray[i].call(self, name, arg1);
1744				}
1745			}
1746		}
1747
1748		/**
1749		 * Destroys instance and everything it created.
1750		 *
1751		 * @return {Void}
1752		 */
1753		self.destroy = function () {
1754			// Unbind all events
1755			$scrollSource
1756				.add($handle)
1757				.add($sb)
1758				.add($pb)
1759				.add($forwardButton)
1760				.add($backwardButton)
1761				.add($prevButton)
1762				.add($nextButton)
1763				.add($prevPageButton)
1764				.add($nextPageButton)
1765				.off('.' + namespace);
1766
1767			// Unbinding specifically as to not nuke out other instances
1768			$doc.off('keydown', keyboardHandler);
1769
1770			// Remove classes
1771			$prevButton
1772				.add($nextButton)
1773				.add($prevPageButton)
1774				.add($nextPageButton)
1775				.removeClass(o.disabledClass);
1776
1777			if ($items && rel.activeItem != null) {
1778				$items.eq(rel.activeItem).removeClass(o.activeClass);
1779			}
1780
1781			// Remove page items
1782			$pb.empty();
1783
1784			if (!parallax) {
1785				// Unbind events from frame
1786				$frame.off('.' + namespace);
1787				// Restore original styles
1788				frameStyles.restore();
1789				slideeStyles.restore();
1790				sbStyles.restore();
1791				handleStyles.restore();
1792				// Remove the instance from element data storage
1793				$.removeData(frame, namespace);
1794			}
1795
1796			// Clean up collections
1797			items.length = pages.length = 0;
1798			last = {};
1799
1800			// Reset initialized status and return the instance
1801			self.initialized = 0;
1802			return self;
1803		};
1804
1805		/**
1806		 * Initialize.
1807		 *
1808		 * @return {Object}
1809		 */
1810		self.init = function () {
1811			if (self.initialized) {
1812				return;
1813			}
1814
1815			// Register callbacks map
1816			self.on(callbackMap);
1817
1818			// Save styles
1819			var holderProps = ['overflow', 'position'];
1820			var movableProps = ['position', 'webkitTransform', 'msTransform', 'transform', 'left', 'top', 'width', 'height'];
1821			frameStyles.save.apply(frameStyles, holderProps);
1822			sbStyles.save.apply(sbStyles, holderProps);
1823			slideeStyles.save.apply(slideeStyles, movableProps);
1824			handleStyles.save.apply(handleStyles, movableProps);
1825
1826			// Set required styles
1827			var $movables = $handle;
1828			if (!parallax) {
1829				$movables = $movables.add($slidee);
1830				$frame.css('overflow', 'hidden');
1831				if (!transform && $frame.css('position') === 'static') {
1832					$frame.css('position', 'relative');
1833				}
1834			}
1835			if (transform) {
1836				if (gpuAcceleration) {
1837					$movables.css(transform, gpuAcceleration);
1838				}
1839			} else {
1840				if ($sb.css('position') === 'static') {
1841					$sb.css('position', 'relative');
1842				}
1843				$movables.css({ position: 'absolute' });
1844			}
1845
1846			// Navigation buttons
1847			if (o.forward) {
1848				$forwardButton.on(mouseDownEvent, buttonsHandler);
1849			}
1850			if (o.backward) {
1851				$backwardButton.on(mouseDownEvent, buttonsHandler);
1852			}
1853			if (o.prev) {
1854				$prevButton.on(clickEvent, buttonsHandler);
1855			}
1856			if (o.next) {
1857				$nextButton.on(clickEvent, buttonsHandler);
1858			}
1859			if (o.prevPage) {
1860				$prevPageButton.on(clickEvent, buttonsHandler);
1861			}
1862			if (o.nextPage) {
1863				$nextPageButton.on(clickEvent, buttonsHandler);
1864			}
1865
1866			// Scrolling navigation
1867			//$scrollSource.on(wheelEvent, scrollHandler);
1868
1869			// Clicking on scrollbar navigation
1870			if ($sb[0]) {
1871				$sb.on(clickEvent, scrollbarHandler);
1872			}
1873
1874			// Click on items navigation
1875			if (itemNav && o.activateOn) {
1876				$frame.on(o.activateOn + '.' + namespace, '*', activateHandler);
1877			}
1878
1879			// Pages navigation
1880			if ($pb[0] && o.activatePageOn) {
1881				$pb.on(o.activatePageOn + '.' + namespace, '*', activatePageHandler);
1882			}
1883
1884			// Dragging navigation
1885			$dragSource.on(dragInitEvents, { source: 'slidee' }, dragInit);
1886
1887			// Scrollbar dragging navigation
1888			if ($handle) {
1889				$handle.on(dragInitEvents, { source: 'handle' }, dragInit);
1890			}
1891
1892			// Keyboard navigation
1893			$doc.on('keydown', keyboardHandler);
1894
1895			if (!parallax) {
1896				// Pause on hover
1897				$frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler);
1898				// Reset native FRAME element scroll
1899				$frame.on('scroll.' + namespace, resetScroll);
1900			}
1901
1902			// Mark instance as initialized
1903			self.initialized = 1;
1904
1905			// Load
1906			load(true);
1907
1908			// Initiate automatic cycling
1909			if (o.cycleBy && !parallax) {
1910				self[o.startPaused ? 'pause' : 'resume']();
1911			}
1912
1913			// Return instance
1914			return self;
1915		};
1916	}
1917
1918	/**
1919	 * Return type of the value.
1920	 *
1921	 * @param  {Mixed} value
1922	 *
1923	 * @return {String}
1924	 */
1925	function type(value) {
1926		if (value == null) {
1927			return String(value);
1928		}
1929
1930		if (typeof value === 'object' || typeof value === 'function') {
1931			return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object';
1932		}
1933
1934		return typeof value;
1935	}
1936
1937	/**
1938	 * Event preventDefault & stopPropagation helper.
1939	 *
1940	 * @param {Event} event     Event object.
1941	 * @param {Bool}  noBubbles Cancel event bubbling.
1942	 *
1943	 * @return {Void}
1944	 */
1945	function stopDefault(event, noBubbles) {
1946		event.preventDefault();
1947		if (noBubbles) {
1948			event.stopPropagation();
1949		}
1950	}
1951
1952	/**
1953	 * Disables an event it was triggered on and unbinds itself.
1954	 *
1955	 * @param  {Event} event
1956	 *
1957	 * @return {Void}
1958	 */
1959	function disableOneEvent(event) {
1960		/*jshint validthis:true */
1961		stopDefault(event, 1);
1962		$(this).off(event.type, disableOneEvent);
1963	}
1964
1965	/**
1966	 * Resets native element scroll values to 0.
1967	 *
1968	 * @return {Void}
1969	 */
1970	function resetScroll() {
1971		/*jshint validthis:true */
1972		this.scrollLeft = 0;
1973		this.scrollTop = 0;
1974	}
1975
1976	/**
1977	 * Check if variable is a number.
1978	 *
1979	 * @param {Mixed} value
1980	 *
1981	 * @return {Boolean}
1982	 */
1983	function isNumber(value) {
1984		return !isNaN(parseFloat(value)) && isFinite(value);
1985	}
1986
1987	/**
1988	 * Parse style to pixels.
1989	 *
1990	 * @param {Object}   $item    jQuery object with element.
1991	 * @param {Property} property CSS property to get the pixels from.
1992	 *
1993	 * @return {Int}
1994	 */
1995	function getPx($item, property) {
1996		return 0 | round(String($item.css(property)).replace(/[^\-0-9.]/g, ''));
1997	}
1998
1999	/**
2000	 * Make sure that number is within the limits.
2001	 *
2002	 * @param {Number} number
2003	 * @param {Number} min
2004	 * @param {Number} max
2005	 *
2006	 * @return {Number}
2007	 */
2008	function within(number, min, max) {
2009		return number < min ? min : number > max ? max : number;
2010	}
2011
2012	/**
2013	 * Saves element styles for later restoration.
2014	 *
2015	 * Example:
2016	 *   var styles = new StyleRestorer(frame);
2017	 *   styles.save('position');
2018	 *   element.style.position = 'absolute';
2019	 *   styles.restore(); // restores to state before the assignment above
2020	 *
2021	 * @param {Element} element
2022	 */
2023	function StyleRestorer(element) {
2024		var self = {};
2025		self.style = {};
2026		self.save = function () {
2027			if (!element || !element.nodeType) return;
2028			for (var i = 0; i < arguments.length; i++) {
2029				self.style[arguments[i]] = element.style[arguments[i]];
2030			}
2031			return self;
2032		};
2033		self.restore = function () {
2034			if (!element || !element.nodeType) return;
2035			for (var prop in self.style) {
2036				if (self.style.hasOwnProperty(prop)) element.style[prop] = self.style[prop];
2037			}
2038			return self;
2039		};
2040		return self;
2041	}
2042
2043	// Local WindowAnimationTiming interface polyfill
2044	(function (w) {
2045		rAF = w.requestAnimationFrame
2046			|| w.webkitRequestAnimationFrame
2047			|| fallback;
2048
2049		/**
2050		* Fallback implementation.
2051		*/
2052		var prev = new Date().getTime();
2053		function fallback(fn) {
2054			var curr = new Date().getTime();
2055			var ms = Math.max(0, 16 - (curr - prev));
2056			var req = setTimeout(fn, ms);
2057			prev = curr;
2058			return req;
2059		}
2060
2061		/**
2062		* Cancel.
2063		*/
2064		var cancel = w.cancelAnimationFrame
2065			|| w.webkitCancelAnimationFrame
2066			|| w.clearTimeout;
2067
2068		cAF = function(id){
2069			cancel.call(w, id);
2070		};
2071	}(window));
2072
2073	// Feature detects
2074	(function () {
2075		var prefixes = ['', 'webkit', 'moz', 'ms', 'o'];
2076		var el = document.createElement('div');
2077
2078		function testProp(prop) {
2079			for (var p = 0, pl = prefixes.length; p < pl; p++) {
2080				var prefixedProp = prefixes[p] ? prefixes[p] + prop.charAt(0).toUpperCase() + prop.slice(1) : prop;
2081				if (el.style[prefixedProp] != null) {
2082					return prefixedProp;
2083				}
2084			}
2085		}
2086
2087		// Global support indicators
2088		transform = testProp('transform');
2089		gpuAcceleration = testProp('perspective') ? 'translateZ(0) ' : '';
2090	}());
2091
2092	// Expose class globally
2093	w[className] = Sly;
2094
2095	// jQuery proxy
2096	$.fn[pluginName] = function (options, callbackMap) {
2097		var method, methodArgs;
2098
2099		// Attributes logic
2100		if (!$.isPlainObject(options)) {
2101			if (type(options) === 'string' || options === false) {
2102				method = options === false ? 'destroy' : options;
2103				methodArgs = Array.prototype.slice.call(arguments, 1);
2104			}
2105			options = {};
2106		}
2107
2108		// Apply to all elements
2109		return this.each(function (i, element) {
2110			// Call with prevention against multiple instantiations
2111			var plugin = $.data(element, namespace);
2112
2113			if (!plugin && !method) {
2114				// Create a new object if it doesn't exist yet
2115				plugin = $.data(element, namespace, new Sly(element, options, callbackMap).init());
2116			} else if (plugin && method) {
2117				// Call method
2118				if (plugin[method]) {
2119					plugin[method].apply(plugin, methodArgs);
2120				}
2121			}
2122		});
2123	};
2124
2125	// Default options
2126	Sly.defaults = {
2127		slidee:     null,  // Selector, DOM element, or jQuery object with DOM element representing SLIDEE.
2128		horizontal: false, // Switch to horizontal mode.
2129
2130		// Item based navigation
2131		itemNav:        null,  // Item navigation type. Can be: 'basic', 'centered', 'forceCentered'.
2132		itemSelector:   null,  // Select only items that match this selector.
2133		smart:          false, // Repositions the activated item to help with further navigation.
2134		activateOn:     null,  // Activate an item on this event. Can be: 'click', 'mouseenter', ...
2135		activateMiddle: false, // Always activate the item in the middle of the FRAME. forceCentered only.
2136
2137		// Scrolling
2138		scrollSource: null,  // Element for catching the mouse wheel scrolling. Default is FRAME.
2139		scrollBy:     0,     // Pixels or items to move per one mouse scroll. 0 to disable scrolling.
2140		scrollHijack: 300,   // Milliseconds since last wheel event after which it is acceptable to hijack global scroll.
2141		scrollTrap:   false, // Don't bubble scrolling when hitting scrolling limits.
2142
2143		// Dragging
2144		dragSource:    null,  // Selector or DOM element for catching dragging events. Default is FRAME.
2145		mouseDragging: false, // Enable navigation by dragging the SLIDEE with mouse cursor.
2146		touchDragging: false, // Enable navigation by dragging the SLIDEE with touch events.
2147		releaseSwing:  false, // Ease out on dragging swing release.
2148		swingSpeed:    0.2,   // Swing synchronization speed, where: 1 = instant, 0 = infinite.
2149		elasticBounds: false, // Stretch SLIDEE position limits when dragging past FRAME boundaries.
2150		interactive:   null,  // Selector for special interactive elements.
2151
2152		// Scrollbar
2153		scrollBar:     null,  // Selector or DOM element for scrollbar container.
2154		dragHandle:    false, // Whether the scrollbar handle should be draggable.
2155		dynamicHandle: false, // Scrollbar handle represents the ratio between hidden and visible content.
2156		minHandleSize: 50,    // Minimal height or width (depends on sly direction) of a handle in pixels.
2157		clickBar:      false, // Enable navigation by clicking on scrollbar.
2158		syncSpeed:     0.5,   // Handle =>
2158 SLIDEE synchronization speed, where: 1 = instant, 0 = infinite.
2159
2160		// Pagesbar
2161		pagesBar:       null, // Selector or DOM element for pages bar container.
2162		activatePageOn: null, // Event used to activate page. Can be: click, mouseenter, ...
2163		pageBuilder:          // Page item generator.
2164			function (index) {
2165				return '<li>' + (index + 1) + '</li>';
2166			},
2167
2168		// Navigation buttons
2169		forward:  null, // Selector or DOM element for "forward movement" button.
2170		backward: null, // Selector or DOM element for "backward movement" button.
2171		prev:     null, // Selector or DOM element for "previous item" button.
2172		next:     null, // Selector or DOM element for "next item" button.
2173		prevPage: null, // Selector or DOM element for "previous page" button.
2174		nextPage: null, // Selector or DOM element for "next page" button.
2175
2176		// Automated cycling
2177		cycleBy:       null,  // Enable automatic cycling by 'items' or 'pages'.
2178		cycleInterval: 5000,  // Delay between cycles in milliseconds.
2179		pauseOnHover:  false, // Pause cycling when mouse hovers over the FRAME.
2180		startPaused:   false, // Whether to start in paused sate.
2181
2182		// Mixed options
2183		moveBy:        300,     // Speed in pixels per second used by forward and backward buttons.
2184		speed:         0,       // Animations speed in milliseconds. 0 to disable animations.
2185		easing:        'swing', // Easing for duration based (tweening) animations.
2186		startAt:       null,    // Starting offset in pixels or items.
2187		keyboardNavBy: null,    // Enable keyboard navigation by 'items' or 'pages'.
2188
2189		// Classes
2190		draggedClass:  'dragged', // Class for dragged elements (like SLIDEE or scrollbar handle).
2191		activeClass:   'active',  // Class for active items and pages.
2192		disabledClass: 'disabled' // Class for disabled navigation elements.
2193	};
2194}(jQuery, window));

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.