PageSourceSearch

https://b2b.vimis.ru/local/templates/aspro-priority2/js/sly.js?176482770061252

js vimis.ru collected 2026-10-07 00:35:55 UTC 61,252 bytes, 2,236 lines download raw bytes

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