Page MenuHomeDevCentral

No OneTemporary

diff --git a/css/app.css b/css/app.css
index debeed7..28873f2 100644
--- a/css/app.css
+++ b/css/app.css
@@ -1,29 +1,167 @@
@charset "utf-8";
/* -------------------------------------------------------------
Launch Nasqueron
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Author: Dereckson
+ Author: Sébastien Santoro aka Dereckson, Tomas Sardyha
Filename: app.css
Version: 1.0
Licence: Creative Commons BY 3.0, BSD-2-Clause
------------------------------------------------------------- */
/* -------------------------------------------------------------
Table of contents
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
:: Main styles
+ :: Cards
+ :: Cards content
+ :: Horizontal scrollbar
+ :: Cards slider selector
+ :: Utilities classes
*/
/* -------------------------------------------------------------
Main styles
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
body {
margin-top: 5vh;
min-height: 150vh;
background: #44484D url("../img/mountains.svg") repeat-x scroll bottom;
}
+
+/* -------------------------------------------------------------
+ Cards
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+.wrap {
+ position: relative;
+ margin: 3em 5vw;
+}
+
+@media screen and (max-width: 1239px) {
+ section {
+ width: 78vw;
+ }
+}
+
+@media screen and (min-width: 1240px) {
+ section {
+ width: 39vw;
+ }
+}
+
+section {
+ float: left;
+ display: inline-block;
+
+ min-height: 70vh;
+ padding: 1em !important;
+ margin: 0 5vw 0 0;
+
+ border: solid 5px #2b3441;
+}
+
+.section-odd {
+ background-color: #5587A2;
+ color: black;
+}
+
+.section-even {
+ background-color: #67947D;
+ color: black;
+}
+
+.section-third {
+ background-color: #F6D258;
+ color: black;
+}
+
+/* -------------------------------------------------------------
+ Cards content
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+section .paragraph {
+ margin-bottom: 2.5em;
+}
+
+section p {
+ -webkit-margin-before: 0;
+}
+
+.icon {
+ color: white;
+}
+
+.section-third .icon {
+ color: #2B3441;
+}
+
+/* -------------------------------------------------------------
+ Horizontal scrollbar
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+.scrollbar {
+ margin: 0 0 1em 0;
+ height: 2px;
+ background: #ccc;
+ line-height: 0;
+}
+
+.scrollbar .handle {
+ width: 100px;
+ height: 100%;
+ background: #292a33;
+ cursor: pointer;
+}
+
+.scrollbar .handle .mousearea {
+ position: absolute;
+ top: -9px;
+ left: 0;
+ width: 100%;
+ height: 20px;
+}
+
+/* -------------------------------------------------------------
+ Cards slider selector
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+.pages {
+ list-style: none;
+ margin: 20px 0;
+ padding: 0;
+ text-align: center;
+}
+
+.pages li {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ margin: 0 4px;
+ text-indent: -999px;
+ border-radius: 10px;
+ cursor: pointer;
+ overflow: hidden;
+ background: #fff;
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,.2);
+}
+
+.pages li:hover {
+ background: #aaa;
+}
+
+.pages li.active {
+ background: #666;
+}
+
+/* -------------------------------------------------------------
+ Utilities classes
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+.text-center {
+ text-align: center;
+}
diff --git a/humans.txt b/humans.txt
index b1d9c94..564c878 100644
--- a/humans.txt
+++ b/humans.txt
@@ -1,33 +1,36 @@
# humanstxt.org/
# The humans responsible & technology colophon
# TEAM
Sébastien Santoro -- Lead developer -- @Dereckson
# THANKS
# TECHNOLOGY COLOPHON
CSS3, HTML5
jQuery, Modernizr, Normalize.css
Git, Phabricator
+# JAVASCRIPT LIBRARIES
-# MANIFESTO
+ Sly — Tomas Sardyha
+
+# NASQUERON MANIFESTO
Nasqueron is a budding community of creative people, writers, developers
and thinkers.
You'll find here like-minded people to connect to, hang out,
and build projects.
We focus on free culture, ethics and to be a positive change.
Our software is open source our datasources and content are licensed under
CC-BY-SA or CC-BY license. We share values like respect, justice and equity.
We like experiments, originality and to discover new thing.
We are Nasqueron.
diff --git a/img/logo-eglide-133px.png b/img/logo-eglide-133px.png
new file mode 100644
index 0000000..fdec601
Binary files /dev/null and b/img/logo-eglide-133px.png differ
diff --git a/img/logo-main-133px.png b/img/logo-main-133px.png
new file mode 100644
index 0000000..9de7ad7
Binary files /dev/null and b/img/logo-main-133px.png differ
diff --git a/index.html b/index.html
index f61c653..426a317 100644
--- a/index.html
+++ b/index.html
@@ -1,31 +1,175 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
- <title></title>
- <meta name="description" content="">
+ <title>Launch Nasqueron</title>
+ <meta name="description" content="Some explanations about Nasqueron">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<!-- Place favicon.ico in the root directory -->
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/app.css">
<script src="js/vendor/modernizr-2.8.3.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
- <!-- Add your site or application content here -->
- <p>Hello world! This is HTML5 Boilerplate.</p>
+ <div class="pagespan">
+ <div class="wrap">
+ <div class="scrollbar">
+ <div class="handle">
+ <div class="mousearea"></div>
+ </div>
+ </div>
+
+ <div id="cards" class="frame">
+ <div class="slidee" class="clearfix">
+ <section id="manifesto" class="section-odd">
+ <p class="text-center"><img src="img/logo-main-133px.png" height="131" width="133" alt="Nasqueron logo" /></p>
+
+ <h2 class="text-center">Our values</h2>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">🏴</div>
+ <p>Nasqueron is a budding community of creative people, writers, developers and thinkers.</p>
+ </div>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">🈁</div>
+ <p>You'll find here like-minded people to connect to, hang out, and build projects.</p>
+ </div>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">📃</div>
+ <p>We focus on free culture, ethics and to be a positive change. Our software is open source our datasources and content are licensed under CC-BY-SA or CC-BY license. We share values like respect, justice and equity.</p>
+ </div>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">❤️</div>
+ <p>We like experiments, originality and to discover new thing.</p>
+ </div>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">👽</div>
+ <p>We are Nasqueron.</p>
+ </div>
+ </section>
+
+ <section id="infrastructure" class="section-even">
+ <p class="text-center"><img src="img/logo-main-133px.png" height="131" width="133" alt="Nasqueron logo" /></p>
+
+ <h2 class="text-center">Infrastructure</h2>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">💎</div>
+ <p>In 2016, we've focused to build a great infrastructure. Servers, continuous integration, places to communicate, notifications, operations workflows.</p>
+ <p>We believe this infrastructure first provides a great added value to any project: everything is in place and polished.</p>
+ <p>The infrastructure is open and managed by the Operations special interest group. A configuration-as-code repository is used, so it's reliable, well documented and open source.</p>
+ </div>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">🔨</div>
+ <p>To communicate, we've deployed mailing lists, Etherpad, a forum, a wiki, IRC bots with a great glue notifications tooling.</p>
+ <p>To craft software, we've Phabricator and Jenkins instances. Did we tell you we've notifications sorted?</p>
+ <p>To host apps, we've a PaaS built on Docker. And an server dedicated to IRC. And a development and staging server.</p>
+ </div>
+
+ <div class="paragraph paragraph-withicon">
+ <div class="icon text-center">🏺</div>
+ <p>So whatever project you wish to start, we're here to support you and provide you with an awesome infrastructure.</p>
+ </div>
+ </section>
+
+ <section id="software" class="section-third">
+ <p class="text-center"><img src="img/logo-main-133px.png" height="131" width="133" alt="Nasqueron logo" /></p>
+
+ <h2 class="text-center">Software we develop</h2>
+
+ <h3>Tasacora</h3>
+ <p>A cartography solution to build a vectorial thematic map from a blank map and a dataset.</p>
+ <p>Lead developer: <em>Rama</em> — Lead geographer: <em>Kumkum</em></p>
+
+ <h3>Notifications center</h3>
+ <p>A gateway to accept webhooks and payloads from GitHub, DockerHub, Jenkins, Phabricator and other CI products.</p>
+ <p>It then standardizes notifications into an unified format.<br />Emits them to an AMQP broker.</p>
+ <p>Intelligent bus with <em>if this then that</em> behavior.</p>
+ <p>Lead developer: <em>Dereckson</em></p>
+
+ <h3>Docker images for Phabricator</h3>
+ <p>We maintain state of art Docker images to run Phabricator, Aphlict or Arcanist in Docker.</p>
+ <p>Team: <em>Sandlayth</em> — <em>Dereckson</em></p>
+
+ <h3>DynBridge</h3>
+ <p>TCL ⋄ Python bridge</p>
+ <p>Lead developer: <em>Xavier Combelle</em></p>
+ </section>
+
+ <section id="eglide" class="section-odd">
+ <p class="text-center"><img src="img/logo-eglide-133px.png" height="131" width="133" alt="Eglide logo" /></p>
+
+ <h2 class="text-center">Eglide</h2>
+
+ <p>We're baking a great shell hosting service. Claim a little space for your files, an IRC connection for you or your bot, a website. Something where to code, experiment, connect with like-minded people.</p>
+
+ <p>Für die Freikultur. Eglide is a free culture project. Everyone is welcome to participate or use it.</p>
+
+ <h2 class="text-center">Our involvement</h2>
+
+ <p>The service is managed by Nasqueron.</p>
+
+ <p>Nasqueron is committed to provide ITC management at operations and devops level to free culture projects.</p>
+ </section>
+
+ <section id="Governance" class="section-even">
+ <p class="text-center"><img src="img/logo-main-133px.png" height="131" width="133" alt="Nasqueron logo" /></p>
+
+ <h2 class="text-center">Governance</h2>
+ <p>Nasqueron is organised in special interest groups (SIG). Each group is autonomous and self managed. Decisions for the project as a whole are taken by consensus.</p>
+
+ <p>Some SIGs take responsibilities for the project as a whole, for example the Operations SIG build and maintain our infrastructure. Others can represent the project to third parties like the IRC SIG to the Freenode network.</p>
+
+ <p>We aren't incorporated: a de facto status is currently enough for our needs. If we decide to incorporate, it will be as a non-profit foundation.</p>
+
+
+ <h2 class="text-center">Covenant</h2>
+
+ <p>We as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.</p>
+ </section>
+
+ <section id="Extra" class="section-third">
+ <p class="text-center"><img src="img/logo-main-133px.png" height="131" width="133" alt="Nasqueron logo" /></p>
+
+ <h2 class="text-center">More information?</h2>
+
+ <div class="icon text-center">📧</div>
+ <p>You can join our Launch mailing list. Send a mail to launch-subscribe (at) lists.nasqueron.org.</p>
+
+ <div class="icon text-center">💬</div>
+ <p>You can also say hello on IRC. We meet in Freenode #nasqueron.</p>
+
+ <div class="icon text-center">🈴</div>
+ <p>If you consider yourself a part of Nasqueron, <a href="https://agora.nasqueron.org/We_are_Nasqueron">follow this guide</a> to join.</p>
+
+ <div class="icon text-center">📛</div>
+ <p>Finally, if you come to the FOSDEM 2017 edition, you can meet some members.</p>
+ </section>
+ </div>
+ </div>
+
+ <ul class="pages"></ul>
+ </div>
+ </div>
<script src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.12.0.min.js"><\/script>')</script>
<script src="js/plugins.js"></script>
+ <script src="js/vendor/sly.js"></script>
<script src="js/main.js"></script>
</body>
</html>
diff --git a/js/main.js b/js/main.js
index e69de29..715caf8 100644
--- a/js/main.js
+++ b/js/main.js
@@ -0,0 +1,34 @@
+jQuery(function($){
+ 'use strict';
+
+ // -------------------------------------------------------------
+ // Basic Navigation
+ // -------------------------------------------------------------
+ (function () {
+ var frame = $('#cards');
+ var wrap = frame.parent();
+
+ // Call Sly on frame
+ frame.sly({
+ horizontal: 1,
+ itemNav: 'basic',
+ smart: 1,
+ activateOn: 'click',
+ mouseDragging: 1,
+ touchDragging: 1,
+ releaseSwing: 1,
+ startAt: 0,
+ scrollBar: wrap.find('.scrollbar'),
+ scrollBy: 1,
+ pagesBar: wrap.find('.pages'),
+ activatePageOn: 'click',
+ speed: 300,
+ elasticBounds: 1,
+ easing: 'easeOutExpo',
+ dragHandle: 1,
+ dynamicHandle: 1,
+ clickBar: 1,
+ });
+ }());
+
+});
diff --git a/js/plugins.js b/js/plugins.js
index f887480..4453920 100644
--- a/js/plugins.js
+++ b/js/plugins.js
@@ -1,24 +1,32 @@
// Avoid `console` errors in browsers that lack a console.
(function() {
var method;
var noop = function () {};
var methods = [
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
];
var length = methods.length;
var console = (window.console = window.console || {});
while (length--) {
method = methods[length];
// Only stub undefined methods.
if (!console[method]) {
console[method] = noop;
}
}
}());
-// Place any jQuery/helper plugins in here.
+// jQuery easing 1.3
+jQuery.easing.jswing=jQuery.easing.swing;
+jQuery.extend(jQuery.easing,{def:"easeOutQuad",swing:function(e,a,c,b,d){return jQuery.easing[jQuery.easing.def](e,a,c,b,d)},easeInQuad:function(e,a,c,b,d){return b*(a/=d)*a+c},easeOutQuad:function(e,a,c,b,d){return-b*(a/=d)*(a-2)+c},easeInOutQuad:function(e,a,c,b,d){return 1>(a/=d/2)?b/2*a*a+c:-b/2*(--a*(a-2)-1)+c},easeInCubic:function(e,a,c,b,d){return b*(a/=d)*a*a+c},easeOutCubic:function(e,a,c,b,d){return b*((a=a/d-1)*a*a+1)+c},easeInOutCubic:function(e,a,c,b,d){return 1>(a/=d/2)?b/2*a*a*a+c:
+ b/2*((a-=2)*a*a+2)+c},easeInQuart:function(e,a,c,b,d){return b*(a/=d)*a*a*a+c},easeOutQuart:function(e,a,c,b,d){return-b*((a=a/d-1)*a*a*a-1)+c},easeInOutQuart:function(e,a,c,b,d){return 1>(a/=d/2)?b/2*a*a*a*a+c:-b/2*((a-=2)*a*a*a-2)+c},easeInQuint:function(e,a,c,b,d){return b*(a/=d)*a*a*a*a+c},easeOutQuint:function(e,a,c,b,d){return b*((a=a/d-1)*a*a*a*a+1)+c},easeInOutQuint:function(e,a,c,b,d){return 1>(a/=d/2)?b/2*a*a*a*a*a+c:b/2*((a-=2)*a*a*a*a+2)+c},easeInSine:function(e,a,c,b,d){return-b*Math.cos(a/
+ d*(Math.PI/2))+b+c},easeOutSine:function(e,a,c,b,d){return b*Math.sin(a/d*(Math.PI/2))+c},easeInOutSine:function(e,a,c,b,d){return-b/2*(Math.cos(Math.PI*a/d)-1)+c},easeInExpo:function(e,a,c,b,d){return 0==a?c:b*Math.pow(2,10*(a/d-1))+c},easeOutExpo:function(e,a,c,b,d){return a==d?c+b:b*(-Math.pow(2,-10*a/d)+1)+c},easeInOutExpo:function(e,a,c,b,d){return 0==a?c:a==d?c+b:1>(a/=d/2)?b/2*Math.pow(2,10*(a-1))+c:b/2*(-Math.pow(2,-10*--a)+2)+c},easeInCirc:function(e,a,c,b,d){return-b*(Math.sqrt(1-(a/=d)*
+ a)-1)+c},easeOutCirc:function(e,a,c,b,d){return b*Math.sqrt(1-(a=a/d-1)*a)+c},easeInOutCirc:function(e,a,c,b,d){return 1>(a/=d/2)?-b/2*(Math.sqrt(1-a*a)-1)+c:b/2*(Math.sqrt(1-(a-=2)*a)+1)+c},easeInElastic:function(e,a,c,b,d){var e=1.70158,f=0,g=b;if(0==a)return c;if(1==(a/=d))return c+b;f||(f=0.3*d);g<Math.abs(b)?(g=b,e=f/4):e=f/(2*Math.PI)*Math.asin(b/g);return-(g*Math.pow(2,10*(a-=1))*Math.sin((a*d-e)*2*Math.PI/f))+c},easeOutElastic:function(e,a,c,b,d){var e=1.70158,f=0,g=b;if(0==a)return c;if(1==
+ (a/=d))return c+b;f||(f=0.3*d);g<Math.abs(b)?(g=b,e=f/4):e=f/(2*Math.PI)*Math.asin(b/g);return g*Math.pow(2,-10*a)*Math.sin((a*d-e)*2*Math.PI/f)+b+c},easeInOutElastic:function(e,a,c,b,d){var e=1.70158,f=0,g=b;if(0==a)return c;if(2==(a/=d/2))return c+b;f||(f=d*0.3*1.5);g<Math.abs(b)?(g=b,e=f/4):e=f/(2*Math.PI)*Math.asin(b/g);return 1>a?-0.5*g*Math.pow(2,10*(a-=1))*Math.sin((a*d-e)*2*Math.PI/f)+c:0.5*g*Math.pow(2,-10*(a-=1))*Math.sin((a*d-e)*2*Math.PI/f)+b+c},easeInBack:function(e,a,c,b,d,f){void 0==
+f&&(f=1.70158);return b*(a/=d)*a*((f+1)*a-f)+c},easeOutBack:function(e,a,c,b,d,f){void 0==f&&(f=1.70158);return b*((a=a/d-1)*a*((f+1)*a+f)+1)+c},easeInOutBack:function(e,a,c,b,d,f){void 0==f&&(f=1.70158);return 1>(a/=d/2)?b/2*a*a*(((f*=1.525)+1)*a-f)+c:b/2*((a-=2)*a*(((f*=1.525)+1)*a+f)+2)+c},easeInBounce:function(e,a,c,b,d){return b-jQuery.easing.easeOutBounce(e,d-a,0,b,d)+c},easeOutBounce:function(e,a,c,b,d){return(a/=d)<1/2.75?b*7.5625*a*a+c:a<2/2.75?b*(7.5625*(a-=1.5/2.75)*a+0.75)+c:a<2.5/2.75?
+ b*(7.5625*(a-=2.25/2.75)*a+0.9375)+c:b*(7.5625*(a-=2.625/2.75)*a+0.984375)+c},easeInOutBounce:function(e,a,c,b,d){return a<d/2?0.5*jQuery.easing.easeInBounce(e,2*a,0,b,d)+c:0.5*jQuery.easing.easeOutBounce(e,2*a-d,0,b,d)+0.5*b+c}});
diff --git a/js/vendor/sly.js b/js/vendor/sly.js
new file mode 100644
index 0000000..112110e
--- /dev/null
+++ b/js/vendor/sly.js
@@ -0,0 +1,2205 @@
+;(function ($, w, undefined) {
+ 'use strict';
+
+ var pluginName = 'sly';
+ var className = 'Sly';
+ var namespace = pluginName;
+
+ // Local WindowAnimationTiming interface
+ var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame;
+ var rAF = w.requestAnimationFrame;
+
+ // Support indicators
+ var transform, gpuAcceleration;
+
+ // Other global values
+ var $doc = $(document);
+ var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace;
+ var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace;
+ var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace;
+ var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel.' : 'mousewheel.') + namespace;
+ var clickEvent = 'click.' + namespace;
+ var mouseDownEvent = 'mousedown.' + namespace;
+ var tmpArray = [];
+ var time;
+
+ // Math shorthands
+ var abs = Math.abs;
+ var sqrt = Math.sqrt;
+ var pow = Math.pow;
+ var round = Math.round;
+ var max = Math.max;
+ var min = Math.min;
+
+ // Keep track of last fired global wheel event
+ var lastGlobalWheel = 0;
+ $doc.on(wheelEvent, function (event) {
+ var sly = event.originalEvent[namespace];
+ var time = +new Date();
+ // Update last global wheel time, but only when event didn't originate
+ // in Sly frame, or the origin was less than scrollHijack time ago
+ if (!sly || sly.options.scrollHijack < time - lastGlobalWheel) lastGlobalWheel = time;
+ });
+
+ /**
+ * Sly.
+ *
+ * @class
+ *
+ * @param {Element} frame DOM element of sly container.
+ * @param {Object} options Object with options.
+ * @param {Object} callbackMap Callbacks map.
+ */
+ function Sly(frame, options, callbackMap) {
+ if (!(this instanceof Sly)) return new Sly(frame, options, callbackMap);
+
+ // Extend options
+ var o = $.extend({}, Sly.defaults, options);
+
+ // Private variables
+ var self = this;
+ var parallax = isNumber(frame);
+
+ // Frame
+ var $frame = $(frame);
+ var $slidee = o.slidee ? $(o.slidee).eq(0) : $frame.children().eq(0);
+ var frameSize = 0;
+ var slideeSize = 0;
+ var pos = {
+ start: 0,
+ center: 0,
+ end: 0,
+ cur: 0,
+ dest: 0
+ };
+
+ // Scrollbar
+ var $sb = $(o.scrollBar).eq(0);
+ var $handle = $sb.children().eq(0);
+ var sbSize = 0;
+ var handleSize = 0;
+ var hPos = {
+ start: 0,
+ end: 0,
+ cur: 0
+ };
+
+ // Pagesbar
+ var $pb = $(o.pagesBar);
+ var $pages = 0;
+ var pages = [];
+
+ // Items
+ var $items = 0;
+ var items = [];
+ var rel = {
+ firstItem: 0,
+ lastItem: 0,
+ centerItem: 0,
+ activeItem: null,
+ activePage: 0
+ };
+
+ // Styles
+ var frameStyles = new StyleRestorer($frame[0]);
+ var slideeStyles = new StyleRestorer($slidee[0]);
+ var sbStyles = new StyleRestorer($sb[0]);
+ var handleStyles = new StyleRestorer($handle[0]);
+
+ // Navigation type booleans
+ var basicNav = o.itemNav === 'basic';
+ var forceCenteredNav = o.itemNav === 'forceCentered';
+ var centeredNav = o.itemNav === 'centered' || forceCenteredNav;
+ var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav);
+
+ // Miscellaneous
+ var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame;
+ var $dragSource = o.dragSource ? $(o.dragSource) : $frame;
+ var $forwardButton = $(o.forward);
+ var $backwardButton = $(o.backward);
+ var $prevButton = $(o.prev);
+ var $nextButton = $(o.next);
+ var $prevPageButton = $(o.prevPage);
+ var $nextPageButton = $(o.nextPage);
+ var callbacks = {};
+ var last = {};
+ var animation = {};
+ var move = {};
+ var dragging = {
+ released: 1
+ };
+ var scrolling = {
+ last: 0,
+ delta: 0,
+ resetTime: 200
+ };
+ var renderID = 0;
+ var historyID = 0;
+ var cycleID = 0;
+ var continuousID = 0;
+ var i, l;
+
+ // Normalizing frame
+ if (!parallax) {
+ frame = $frame[0];
+ }
+
+ // Expose properties
+ self.initialized = 0;
+ self.frame = frame;
+ self.slidee = $slidee[0];
+ self.pos = pos;
+ self.rel = rel;
+ self.items = items;
+ self.pages = pages;
+ self.isPaused = 0;
+ self.options = o;
+ self.dragging = dragging;
+
+ /**
+ * Loading function.
+ *
+ * Populate arrays, set sizes, bind events, ...
+ *
+ * @param {Boolean} [isInit] Whether load is called from within self.init().
+ * @return {Void}
+ */
+ function load(isInit) {
+ // Local variables
+ var lastItemsCount = 0;
+ var lastPagesCount = pages.length;
+
+ // Save old position
+ pos.old = $.extend({}, pos);
+
+ // Reset global variables
+ frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height']();
+ sbSize = $sb[o.horizontal ? 'width' : 'height']();
+ slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight']();
+ pages.length = 0;
+
+ // Set position limits & relatives
+ pos.start = 0;
+ pos.end = max(slideeSize - frameSize, 0);
+
+ // Sizes & offsets for item based navigations
+ if (itemNav) {
+ // Save the number of current items
+ lastItemsCount = items.length;
+
+ // Reset itemNav related variables
+ $items = $slidee.children(o.itemSelector);
+ items.length = 0;
+
+ // Needed variables
+ var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop');
+ var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom');
+ var borderBox = $($items).css('boxSizing') === 'border-box';
+ var areFloated = $items.css('float') !== 'none';
+ var ignoredMargin = 0;
+ var lastItemIndex = $items.length - 1;
+ var lastItem;
+
+ // Reset slideeSize
+ slideeSize = 0;
+
+ // Iterate through items
+ $items.each(function (i, element) {
+ // Item
+ var $item = $(element);
+ var rect = element.getBoundingClientRect();
+ var itemSize = (o.horizontal ? rect.width || rect.right - rect.left : rect.height || rect.bottom - rect.top);
+ var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop');
+ var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom');
+ var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd;
+ var singleSpaced = !itemMarginStart || !itemMarginEnd;
+ var item = {};
+ item.el = element;
+ item.size = singleSpaced ? itemSize : itemSizeFull;
+ item.half = item.size / 2;
+ item.start = slideeSize + (singleSpaced ? itemMarginStart : 0);
+ item.center = item.start - round(frameSize / 2 - item.size / 2);
+ item.end = item.start - frameSize + item.size;
+
+ // Account for slidee padding
+ if (!i) {
+ slideeSize += paddingStart;
+ }
+
+ // Increment slidee size for size of the active element
+ slideeSize += itemSizeFull;
+
+ // Try to account for vertical margin collapsing in vertical mode
+ // It's not bulletproof, but should work in 99% of cases
+ if (!o.horizontal && !areFloated) {
+ // Subtract smaller margin, but only when top margin is not 0, and this is not the first element
+ if (itemMarginEnd && itemMarginStart && i > 0) {
+ slideeSize -= min(itemMarginStart, itemMarginEnd);
+ }
+ }
+
+ // Things to be done on last item
+ if (i === lastItemIndex) {
+ item.end += paddingEnd;
+ slideeSize += paddingEnd;
+ ignoredMargin = singleSpaced ? itemMarginEnd : 0;
+ }
+
+ // Add item object to items array
+ items.push(item);
+ lastItem = item;
+ });
+
+ // Resize SLIDEE to fit all items
+ $slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px';
+
+ // Adjust internal SLIDEE size for last margin
+ slideeSize -= ignoredMargin;
+
+ // Set limits
+ if (items.length) {
+ pos.start = items[0][forceCenteredNav ? 'center' : 'start'];
+ pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start;
+ } else {
+ pos.start = pos.end = 0;
+ }
+ }
+
+ // Calculate SLIDEE center position
+ pos.center = round(pos.end / 2 + pos.start / 2);
+
+ // Update relative positions
+ updateRelatives();
+
+ // Scrollbar
+ if ($handle.length && sbSize > 0) {
+ // Stretch scrollbar handle to represent the visible area
+ if (o.dynamicHandle) {
+ handleSize = pos.start === pos.end ? sbSize : round(sbSize * frameSize / slideeSize);
+ handleSize = within(handleSize, o.minHandleSize, sbSize);
+ $handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px';
+ } else {
+ handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight']();
+ }
+
+ hPos.end = sbSize - handleSize;
+
+ if (!renderID) {
+ syncScrollbar();
+ }
+ }
+
+ // Pages
+ if (!parallax && frameSize > 0) {
+ var tempPagePos = pos.start;
+ var pagesHtml = '';
+
+ // Populate pages array
+ if (itemNav) {
+ $.each(items, function (i, item) {
+ if (forceCenteredNav) {
+ pages.push(item.center);
+ } else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) {
+ tempPagePos = item.start;
+ pages.push(tempPagePos);
+ tempPagePos += frameSize;
+ if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) {
+ pages.push(pos.end);
+ }
+ }
+ });
+ } else {
+ while (tempPagePos - frameSize < pos.end) {
+ pages.push(tempPagePos);
+ tempPagePos += frameSize;
+ }
+ }
+
+ // Pages bar
+ if ($pb[0] && lastPagesCount !== pages.length) {
+ for (var i = 0; i < pages.length; i++) {
+ pagesHtml += o.pageBuilder.call(self, i);
+ }
+ $pages = $pb.html(pagesHtml).children();
+ $pages.eq(rel.activePage).addClass(o.activeClass);
+ }
+ }
+
+ // Extend relative variables object with some useful info
+ rel.slideeSize = slideeSize;
+ rel.frameSize = frameSize;
+ rel.sbSize = sbSize;
+ rel.handleSize = handleSize;
+
+ // Activate requested position
+ if (itemNav) {
+ if (isInit && o.startAt != null) {
+ activate(o.startAt);
+ self[centeredNav ? 'toCenter' : 'toStart'](o.startAt);
+ }
+ // Fix possible overflowing
+ var activeItem = items[rel.activeItem];
+ slideTo(centeredNav && activeItem ? activeItem.center : within(pos.dest, pos.start, pos.end));
+ } else {
+ if (isInit) {
+ if (o.startAt != null) slideTo(o.startAt, 1);
+ } else {
+ // Fix possible overflowing
+ slideTo(within(pos.dest, pos.start, pos.end));
+ }
+ }
+
+ // Trigger load event
+ trigger('load');
+ }
+ self.reload = function () { load(); };
+
+ /**
+ * Animate to a position.
+ *
+ * @param {Int} newPos New position.
+ * @param {Bool} immediate Reposition immediately without an animation.
+ * @param {Bool} dontAlign Do not align items, use the raw position passed in first argument.
+ *
+ * @return {Void}
+ */
+ function slideTo(newPos, immediate, dontAlign) {
+ // Align items
+ if (itemNav && dragging.released && !dontAlign) {
+ var tempRel = getRelatives(newPos);
+ var isNotBordering = newPos > pos.start && newPos < pos.end;
+
+ if (centeredNav) {
+ if (isNotBordering) {
+ newPos = items[tempRel.centerItem].center;
+ }
+ if (forceCenteredNav && o.activateMiddle) {
+ activate(tempRel.centerItem);
+ }
+ } else if (isNotBordering) {
+ newPos = items[tempRel.firstItem].start;
+ }
+ }
+
+ // Handle overflowing position limits
+ if (dragging.init && dragging.slidee && o.elasticBounds) {
+ if (newPos > pos.end) {
+ newPos = pos.end + (newPos - pos.end) / 6;
+ } else if (newPos < pos.start) {
+ newPos = pos.start + (newPos - pos.start) / 6;
+ }
+ } else {
+ newPos = within(newPos, pos.start, pos.end);
+ }
+
+ // Update the animation object
+ animation.start = +new Date();
+ animation.time = 0;
+ animation.from = pos.cur;
+ animation.to = newPos;
+ animation.delta = newPos - pos.cur;
+ animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee;
+ animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed);
+
+ // Reset dragging tweesing request
+ dragging.tweese = 0;
+
+ // Start animation rendering
+ if (newPos !== pos.dest) {
+ pos.dest = newPos;
+ trigger('change');
+ if (!renderID) {
+ render();
+ }
+ }
+
+ // Reset next cycle timeout
+ resetCycle();
+
+ // Synchronize states
+ updateRelatives();
+ updateButtonsState();
+ syncPagesbar();
+ }
+
+ /**
+ * Render animation frame.
+ *
+ * @return {Void}
+ */
+ function render() {
+ if (!self.initialized) {
+ return;
+ }
+
+ // If first render call, wait for next animationFrame
+ if (!renderID) {
+ renderID = rAF(render);
+ if (dragging.released) {
+ trigger('moveStart');
+ }
+ return;
+ }
+
+ // If immediate repositioning is requested, don't animate.
+ if (animation.immediate) {
+ pos.cur = animation.to;
+ }
+ // Use tweesing for animations without known end point
+ else if (animation.tweesing) {
+ animation.tweeseDelta = animation.to - pos.cur;
+ // Fuck Zeno's paradox
+ if (abs(animation.tweeseDelta) < 0.1) {
+ pos.cur = animation.to;
+ } else {
+ pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed);
+ }
+ }
+ // Use tweening for basic animations with known end point
+ else {
+ animation.time = min(+new Date() - animation.start, o.speed);
+ pos.cur = animation.from + animation.delta * $.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed);
+ }
+
+ // If there is nothing more to render break the rendering loop, otherwise request new animation frame.
+ if (animation.to === pos.cur) {
+ pos.cur = animation.to;
+ dragging.tweese = renderID = 0;
+ } else {
+ renderID = rAF(render);
+ }
+
+ trigger('move');
+
+ // Update SLIDEE position
+ if (!parallax) {
+ if (transform) {
+ $slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)';
+ } else {
+ $slidee[0].style[o.horizontal ? 'left' : 'top'] = -round(pos.cur) + 'px';
+ }
+ }
+
+ // When animation reached the end, and dragging is not active, trigger moveEnd
+ if (!renderID && dragging.released) {
+ trigger('moveEnd');
+ }
+
+ syncScrollbar();
+ }
+
+ /**
+ * Synchronizes scrollbar with the SLIDEE.
+ *
+ * @return {Void}
+ */
+ function syncScrollbar() {
+ if ($handle.length) {
+ hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end;
+ hPos.cur = within(round(hPos.cur), hPos.start, hPos.end);
+ if (last.hPos !== hPos.cur) {
+ last.hPos = hPos.cur;
+ if (transform) {
+ $handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)';
+ } else {
+ $handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px';
+ }
+ }
+ }
+ }
+
+ /**
+ * Synchronizes pagesbar with SLIDEE.
+ *
+ * @return {Void}
+ */
+ function syncPagesbar() {
+ if ($pages[0] && last.page !== rel.activePage) {
+ last.page = rel.activePage;
+ $pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass);
+ trigger('activePage', last.page);
+ }
+ }
+
+ /**
+ * Returns the position object.
+ *
+ * @param {Mixed} item
+ *
+ * @return {Object}
+ */
+ self.getPos = function (item) {
+ if (itemNav) {
+ var index = getIndex(item);
+ return index !== -1 ? items[index] : false;
+ } else {
+ var $item = $slidee.find(item).eq(0);
+
+ if ($item[0]) {
+ var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top;
+ var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight']();
+
+ return {
+ start: offset,
+ center: offset - frameSize / 2 + size / 2,
+ end: offset - frameSize + size,
+ size: size
+ };
+ } else {
+ return false;
+ }
+ }
+ };
+
+ /**
+ * Continuous move in a specified direction.
+ *
+ * @param {Bool} forward True for forward movement, otherwise it'll go backwards.
+ * @param {Int} speed Movement speed in pixels per frame. Overrides options.moveBy value.
+ *
+ * @return {Void}
+ */
+ self.moveBy = function (speed) {
+ move.speed = speed;
+ // If already initiated, or there is nowhere to move, abort
+ if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
+ return;
+ }
+ // Initiate move object
+ move.lastTime = +new Date();
+ move.startPos = pos.cur;
+ // Set dragging as initiated
+ continuousInit('button');
+ dragging.init = 1;
+ // Start movement
+ trigger('moveStart');
+ cAF(continuousID);
+ moveLoop();
+ };
+
+ /**
+ * Continuous movement loop.
+ *
+ * @return {Void}
+ */
+ function moveLoop() {
+ // If there is nowhere to move anymore, stop
+ if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
+ self.stop();
+ }
+ // Request new move loop if it hasn't been stopped
+ continuousID = dragging.init ? rAF(moveLoop) : 0;
+ // Update move object
+ move.now = +new Date();
+ move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed;
+ // Slide
+ slideTo(dragging.init ? move.pos : round(move.pos));
+ // Normally, this is triggered in render(), but if there
+ // is nothing to render, we have to do it manually here.
+ if (!dragging.init && pos.cur === pos.dest) {
+ trigger('moveEnd');
+ }
+ // Update times for future iteration
+ move.lastTime = move.now;
+ }
+
+ /**
+ * Stops continuous movement.
+ *
+ * @return {Void}
+ */
+ self.stop = function () {
+ if (dragging.source === 'button') {
+ dragging.init = 0;
+ dragging.released = 1;
+ }
+ };
+
+ /**
+ * Activate previous item.
+ *
+ * @return {Void}
+ */
+ self.prev = function () {
+ self.activate(rel.activeItem == null ? 0 : rel.activeItem - 1);
+ };
+
+ /**
+ * Activate next item.
+ *
+ * @return {Void}
+ */
+ self.next = function () {
+ self.activate(rel.activeItem == null ? 0 : rel.activeItem + 1);
+ };
+
+ /**
+ * Activate previous page.
+ *
+ * @return {Void}
+ */
+ self.prevPage = function () {
+ self.activatePage(rel.activePage - 1);
+ };
+
+ /**
+ * Activate next page.
+ *
+ * @return {Void}
+ */
+ self.nextPage = function () {
+ self.activatePage(rel.activePage + 1);
+ };
+
+ /**
+ * Slide SLIDEE by amount of pixels.
+ *
+ * @param {Int} delta Pixels/Items. Positive means forward, negative means backward.
+ * @param {Bool} immediate Reposition immediately without an animation.
+ *
+ * @return {Void}
+ */
+ self.slideBy = function (delta, immediate) {
+ if (!delta) {
+ return;
+ }
+ if (itemNav) {
+ self[centeredNav ? 'toCenter' : 'toStart'](
+ within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length)
+ );
+ } else {
+ slideTo(pos.dest + delta, immediate);
+ }
+ };
+
+ /**
+ * Animate SLIDEE to a specific position.
+ *
+ * @param {Int} pos New position.
+ * @param {Bool} immediate Reposition immediately without an animation.
+ *
+ * @return {Void}
+ */
+ self.slideTo = function (pos, immediate) {
+ slideTo(pos, immediate);
+ };
+
+ /**
+ * Core method for handling `toLocation` methods.
+ *
+ * @param {String} location
+ * @param {Mixed} item
+ * @param {Bool} immediate
+ *
+ * @return {Void}
+ */
+ function to(location, item, immediate) {
+ // Optional arguments logic
+ if (type(item) === 'boolean') {
+ immediate = item;
+ item = undefined;
+ }
+
+ if (item === undefined) {
+ slideTo(pos[location], immediate);
+ } else {
+ // You can't align items to sides of the frame
+ // when centered navigation type is enabled
+ if (centeredNav && location !== 'center') {
+ return;
+ }
+
+ var itemPos = self.getPos(item);
+ if (itemPos) {
+ slideTo(itemPos[location], immediate, !centeredNav);
+ }
+ }
+ }
+
+ /**
+ * Animate element or the whole SLIDEE to the start of the frame.
+ *
+ * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
+ * @param {Bool} immediate Reposition immediately without an animation.
+ *
+ * @return {Void}
+ */
+ self.toStart = function (item, immediate) {
+ to('start', item, immediate);
+ };
+
+ /**
+ * Animate element or the whole SLIDEE to the end of the frame.
+ *
+ * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
+ * @param {Bool} immediate Reposition immediately without an animation.
+ *
+ * @return {Void}
+ */
+ self.toEnd = function (item, immediate) {
+ to('end', item, immediate);
+ };
+
+ /**
+ * Animate element or the whole SLIDEE to the center of the frame.
+ *
+ * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
+ * @param {Bool} immediate Reposition immediately without an animation.
+ *
+ * @return {Void}
+ */
+ self.toCenter = function (item, immediate) {
+ to('center', item, immediate);
+ };
+
+ /**
+ * Get the index of an item in SLIDEE.
+ *
+ * @param {Mixed} item Item DOM element.
+ *
+ * @return {Int} Item index, or -1 if not found.
+ */
+ function getIndex(item) {
+ return item != null ?
+ isNumber(item) ?
+ item >= 0 && item < items.length ? item : -1 :
+ $items.index(item) :
+ -1;
+ }
+ // Expose getIndex without lowering the compressibility of it,
+ // as it is used quite often throughout Sly.
+ self.getIndex = getIndex;
+
+ /**
+ * Get index of an item in SLIDEE based on a variety of input types.
+ *
+ * @param {Mixed} item DOM element, positive or negative integer.
+ *
+ * @return {Int} Item index, or -1 if not found.
+ */
+ function getRelativeIndex(item) {
+ return getIndex(isNumber(item) && item < 0 ? item + items.length : item);
+ }
+
+ /**
+ * Activates an item.
+ *
+ * @param {Mixed} item Item DOM element, or index starting at 0.
+ *
+ * @return {Mixed} Activated item index or false on fail.
+ */
+ function activate(item, force) {
+ var index = getIndex(item);
+
+ if (!itemNav || index < 0) {
+ return false;
+ }
+
+ // Update classes, last active index, and trigger active event only when there
+ // has been a change. Otherwise just return the current active index.
+ if (last.active !== index || force) {
+ // Update classes
+ $items.eq(rel.activeItem).removeClass(o.activeClass);
+ $items.eq(index).addClass(o.activeClass);
+
+ last.active = rel.activeItem = index;
+
+ updateButtonsState();
+ trigger('active', index);
+ }
+
+ return index;
+ }
+
+ /**
+ * Activates an item and helps with further navigation when o.smart is enabled.
+ *
+ * @param {Mixed} item Item DOM element, or index starting at 0.
+ * @param {Bool} immediate Whether to reposition immediately in smart navigation.
+ *
+ * @return {Void}
+ */
+ self.activate = function (item, immediate) {
+ var index = activate(item);
+
+ // Smart navigation
+ if (o.smart && index !== false) {
+ // When centeredNav is enabled, center the element.
+ // Otherwise, determine where to position the element based on its current position.
+ // If the element is currently on the far end side of the frame, assume that user is
+ // moving forward and animate it to the start of the visible frame, and vice versa.
+ if (centeredNav) {
+ self.toCenter(index, immediate);
+ } else if (index >= rel.lastItem) {
+ self.toStart(index, immediate);
+ } else if (index <= rel.firstItem) {
+ self.toEnd(index, immediate);
+ } else {
+ resetCycle();
+ }
+ }
+ };
+
+ /**
+ * Activates a page.
+ *
+ * @param {Int} index Page index, starting from 0.
+ * @param {Bool} immediate Whether to reposition immediately without animation.
+ *
+ * @return {Void}
+ */
+ self.activatePage = function (index, immediate) {
+ if (isNumber(index)) {
+ slideTo(pages[within(index, 0, pages.length - 1)], immediate);
+ }
+ };
+
+ /**
+ * Return relative positions of items based on their visibility within FRAME.
+ *
+ * @param {Int} slideePos Position of SLIDEE.
+ *
+ * @return {Void}
+ */
+ function getRelatives(slideePos) {
+ slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end);
+
+ var relatives = {};
+ var centerOffset = forceCenteredNav ? 0 : frameSize / 2;
+
+ // Determine active page
+ if (!parallax) {
+ for (var p = 0, pl = pages.length; p < pl; p++) {
+ if (slideePos >= pos.end || p === pages.length - 1) {
+ relatives.activePage = pages.length - 1;
+ break;
+ }
+
+ if (slideePos <= pages[p] + centerOffset) {
+ relatives.activePage = p;
+ break;
+ }
+ }
+ }
+
+ // Relative item indexes
+ if (itemNav) {
+ var first = false;
+ var last = false;
+ var center = false;
+
+ // From start
+ for (var i = 0, il = items.length; i < il; i++) {
+ // First item
+ if (first === false && slideePos <= items[i].start + items[i].half) {
+ first = i;
+ }
+
+ // Center item
+ if (center === false && slideePos <= items[i].center + items[i].half) {
+ center = i;
+ }
+
+ // Last item
+ if (i === il - 1 || slideePos <= items[i].end + items[i].half) {
+ last = i;
+ break;
+ }
+ }
+
+ // Safe assignment, just to be sure the false won't be returned
+ relatives.firstItem = isNumber(first) ? first : 0;
+ relatives.centerItem = isNumber(center) ? center : relatives.firstItem;
+ relatives.lastItem = isNumber(last) ? last : relatives.centerItem;
+ }
+
+ return relatives;
+ }
+
+ /**
+ * Update object with relative positions.
+ *
+ * @param {Int} newPos
+ *
+ * @return {Void}
+ */
+ function updateRelatives(newPos) {
+ $.extend(rel, getRelatives(newPos));
+ }
+
+ /**
+ * Disable navigation buttons when needed.
+ *
+ * Adds disabledClass, and when the button is <button> or <input>, activates :disabled state.
+ *
+ * @return {Void}
+ */
+ function updateButtonsState() {
+ var isStart = pos.dest <= pos.start;
+ var isEnd = pos.dest >= pos.end;
+ var slideePosState = (isStart ? 1 : 0) | (isEnd ? 2 : 0);
+
+ // Update paging buttons only if there has been a change in SLIDEE position
+ if (last.slideePosState !== slideePosState) {
+ last.slideePosState = slideePosState;
+
+ if ($prevPageButton.is('button,input')) {
+ $prevPageButton.prop('disabled', isStart);
+ }
+
+ if ($nextPageButton.is('button,input')) {
+ $nextPageButton.prop('disabled', isEnd);
+ }
+
+ $prevPageButton.add($backwardButton)[isStart ? 'addClass' : 'removeClass'](o.disabledClass);
+ $nextPageButton.add($forwardButton)[isEnd ? 'addClass' : 'removeClass'](o.disabledClass);
+ }
+
+ // Forward & Backward buttons need a separate state caching because we cannot "property disable"
+ // them while they are being used, as disabled buttons stop emitting mouse events.
+ if (last.fwdbwdState !== slideePosState && dragging.released) {
+ last.fwdbwdState = slideePosState;
+
+ if ($backwardButton.is('button,input')) {
+ $backwardButton.prop('disabled', isStart);
+ }
+
+ if ($forwardButton.is('button,input')) {
+ $forwardButton.prop('disabled', isEnd);
+ }
+ }
+
+ // Item navigation
+ if (itemNav && rel.activeItem != null) {
+ var isFirst = rel.activeItem === 0;
+ var isLast = rel.activeItem >= items.length - 1;
+ var itemsButtonState = (isFirst ? 1 : 0) | (isLast ? 2 : 0);
+
+ if (last.itemsButtonState !== itemsButtonState) {
+ last.itemsButtonState = itemsButtonState;
+
+ if ($prevButton.is('button,input')) {
+ $prevButton.prop('disabled', isFirst);
+ }
+
+ if ($nextButton.is('button,input')) {
+ $nextButton.prop('disabled', isLast);
+ }
+
+ $prevButton[isFirst ? 'addClass' : 'removeClass'](o.disabledClass);
+ $nextButton[isLast ? 'addClass' : 'removeClass'](o.disabledClass);
+ }
+ }
+ }
+
+ /**
+ * Resume cycling.
+ *
+ * @param {Int} priority Resume pause with priority lower or equal than this. Used internally for pauseOnHover.
+ *
+ * @return {Void}
+ */
+ self.resume = function (priority) {
+ if (!o.cycleBy || !o.cycleInterval || o.cycleBy === 'items' && (!items[0] || rel.activeItem == null) || priority < self.isPaused) {
+ return;
+ }
+
+ self.isPaused = 0;
+
+ if (cycleID) {
+ cycleID = clearTimeout(cycleID);
+ } else {
+ trigger('resume');
+ }
+
+ cycleID = setTimeout(function () {
+ trigger('cycle');
+ switch (o.cycleBy) {
+ case 'items':
+ self.activate(rel.activeItem >= items.length - 1 ? 0 : rel.activeItem + 1);
+ break;
+
+ case 'pages':
+ self.activatePage(rel.activePage >= pages.length - 1 ? 0 : rel.activePage + 1);
+ break;
+ }
+ }, o.cycleInterval);
+ };
+
+ /**
+ * Pause cycling.
+ *
+ * @param {Int} priority Pause priority. 100 is default. Used internally for pauseOnHover.
+ *
+ * @return {Void}
+ */
+ self.pause = function (priority) {
+ if (priority < self.isPaused) {
+ return;
+ }
+
+ self.isPaused = priority || 100;
+
+ if (cycleID) {
+ cycleID = clearTimeout(cycleID);
+ trigger('pause');
+ }
+ };
+
+ /**
+ * Toggle cycling.
+ *
+ * @return {Void}
+ */
+ self.toggle = function () {
+ self[cycleID ? 'pause' : 'resume']();
+ };
+
+ /**
+ * Updates a signle or multiple option values.
+ *
+ * @param {Mixed} name Name of the option that should be updated, or object that will extend the options.
+ * @param {Mixed} value New option value.
+ *
+ * @return {Void}
+ */
+ self.set = function (name, value) {
+ if ($.isPlainObject(name)) {
+ $.extend(o, name);
+ } else if (o.hasOwnProperty(name)) {
+ o[name] = value;
+ }
+ };
+
+ /**
+ * Add one or multiple items to the SLIDEE end, or a specified position index.
+ *
+ * @param {Mixed} element Node element, or HTML string.
+ * @param {Int} index Index of a new item position. By default item is appended at the end.
+ *
+ * @return {Void}
+ */
+ self.add = function (element, index) {
+ var $element = $(element);
+
+ if (itemNav) {
+ // Insert the element(s)
+ if (index == null || !items[0] || index >= items.length) {
+ $element.appendTo($slidee);
+ } else if (items.length) {
+ $element.insertBefore(items[index].el);
+ }
+
+ // Adjust the activeItem index
+ if (rel.activeItem != null && index <= rel.activeItem) {
+ last.active = rel.activeItem += $element.length;
+ }
+ } else {
+ $slidee.append($element);
+ }
+
+ // Reload
+ load();
+ };
+
+ /**
+ * Remove an item from SLIDEE.
+ *
+ * @param {Mixed} element Item index, or DOM element.
+ * @param {Int} index Index of a new item position. By default item is appended at the end.
+ *
+ * @return {Void}
+ */
+ self.remove = function (element) {
+ if (itemNav) {
+ var index = getRelativeIndex(element);
+
+ if (index > -1) {
+ // Remove the element
+ $items.eq(index).remove();
+
+ // If the current item is being removed, activate new one after reload
+ var reactivate = index === rel.activeItem;
+
+ // Adjust the activeItem index
+ if (rel.activeItem != null && index < rel.activeItem) {
+ last.active = --rel.activeItem;
+ }
+
+ // Reload
+ load();
+
+ // Activate new item at the removed position
+ if (reactivate) {
+ last.active = null;
+ self.activate(rel.activeItem);
+ }
+ }
+ } else {
+ $(element).remove();
+ load();
+ }
+ };
+
+ /**
+ * Helps re-arranging items.
+ *
+ * @param {Mixed} item Item DOM element, or index starting at 0. Use negative numbers to select items from the end.
+ * @param {Mixed} position Item insertion anchor. Accepts same input types as item argument.
+ * @param {Bool} after Insert after instead of before the anchor.
+ *
+ * @return {Void}
+ */
+ function moveItem(item, position, after) {
+ item = getRelativeIndex(item);
+ position = getRelativeIndex(position);
+
+ // Move only if there is an actual change requested
+ if (item > -1 && position > -1 && item !== position && (!after || position !== item - 1) && (after || position !== item + 1)) {
+ $items.eq(item)[after ? 'insertAfter' : 'insertBefore'](items[position].el);
+
+ var shiftStart = item < position ? item : (after ? position : position - 1);
+ var shiftEnd = item > position ? item : (after ? position + 1 : position);
+ var shiftsUp = item > position;
+
+ // Update activeItem index
+ if (rel.activeItem != null) {
+ if (item === rel.activeItem) {
+ last.active = rel.activeItem = after ? (shiftsUp ? position + 1 : position) : (shiftsUp ? position : position - 1);
+ } else if (rel.activeItem > shiftStart && rel.activeItem < shiftEnd) {
+ last.active = rel.activeItem += shiftsUp ? 1 : -1;
+ }
+ }
+
+ // Reload
+ load();
+ }
+ }
+
+ /**
+ * Move item after the target anchor.
+ *
+ * @param {Mixed} item Item to be moved. Can be DOM element or item index.
+ * @param {Mixed} position Target position anchor. Can be DOM element or item index.
+ *
+ * @return {Void}
+ */
+ self.moveAfter = function (item, position) {
+ moveItem(item, position, 1);
+ };
+
+ /**
+ * Move item before the target anchor.
+ *
+ * @param {Mixed} item Item to be moved. Can be DOM element or item index.
+ * @param {Mixed} position Target position anchor. Can be DOM element or item index.
+ *
+ * @return {Void}
+ */
+ self.moveBefore = function (item, position) {
+ moveItem(item, position);
+ };
+
+ /**
+ * Registers callbacks.
+ *
+ * @param {Mixed} name Event name, or callbacks map.
+ * @param {Mixed} fn Callback, or an array of callback functions.
+ *
+ * @return {Void}
+ */
+ self.on = function (name, fn) {
+ // Callbacks map
+ if (type(name) === 'object') {
+ for (var key in name) {
+ if (name.hasOwnProperty(key)) {
+ self.on(key, name[key]);
+ }
+ }
+ // Callback
+ } else if (type(fn) === 'function') {
+ var names = name.split(' ');
+ for (var n = 0, nl = names.length; n < nl; n++) {
+ callbacks[names[n]] = callbacks[names[n]] || [];
+ if (callbackIndex(names[n], fn) === -1) {
+ callbacks[names[n]].push(fn);
+ }
+ }
+ // Callbacks array
+ } else if (type(fn) === 'array') {
+ for (var f = 0, fl = fn.length; f < fl; f++) {
+ self.on(name, fn[f]);
+ }
+ }
+ };
+
+ /**
+ * Registers callbacks to be executed only once.
+ *
+ * @param {Mixed} name Event name, or callbacks map.
+ * @param {Mixed} fn Callback, or an array of callback functions.
+ *
+ * @return {Void}
+ */
+ self.one = function (name, fn) {
+ function proxy() {
+ fn.apply(self, arguments);
+ self.off(name, proxy);
+ }
+ self.on(name, proxy);
+ };
+
+ /**
+ * Remove one or all callbacks.
+ *
+ * @param {String} name Event name.
+ * @param {Mixed} fn Callback, or an array of callback functions. Omit to remove all callbacks.
+ *
+ * @return {Void}
+ */
+ self.off = function (name, fn) {
+ if (fn instanceof Array) {
+ for (var f = 0, fl = fn.length; f < fl; f++) {
+ self.off(name, fn[f]);
+ }
+ } else {
+ var names = name.split(' ');
+ for (var n = 0, nl = names.length; n < nl; n++) {
+ callbacks[names[n]] = callbacks[names[n]] || [];
+ if (fn == null) {
+ callbacks[names[n]].length = 0;
+ } else {
+ var index = callbackIndex(names[n], fn);
+ if (index !== -1) {
+ callbacks[names[n]].splice(index, 1);
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Returns callback array index.
+ *
+ * @param {String} name Event name.
+ * @param {Function} fn Function
+ *
+ * @return {Int} Callback array index, or -1 if isn't registered.
+ */
+ function callbackIndex(name, fn) {
+ for (var i = 0, l = callbacks[name].length; i < l; i++) {
+ if (callbacks[name][i] === fn) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Reset next cycle timeout.
+ *
+ * @return {Void}
+ */
+ function resetCycle() {
+ if (dragging.released && !self.isPaused) {
+ self.resume();
+ }
+ }
+
+ /**
+ * Calculate SLIDEE representation of handle position.
+ *
+ * @param {Int} handlePos
+ *
+ * @return {Int}
+ */
+ function handleToSlidee(handlePos) {
+ return round(within(handlePos, hPos.start, hPos.end) / hPos.end * (pos.end - pos.start)) + pos.start;
+ }
+
+ /**
+ * Keeps track of a dragging delta history.
+ *
+ * @return {Void}
+ */
+ function draggingHistoryTick() {
+ // Looking at this, I know what you're thinking :) But as we need only 4 history states, doing it this way
+ // as opposed to a proper loop is ~25 bytes smaller (when minified with GCC), a lot faster, and doesn't
+ // generate garbage. The loop version would create 2 new variables on every tick. Unexaptable!
+ dragging.history[0] = dragging.history[1];
+ dragging.history[1] = dragging.history[2];
+ dragging.history[2] = dragging.history[3];
+ dragging.history[3] = dragging.delta;
+ }
+
+ /**
+ * Initialize continuous movement.
+ *
+ * @return {Void}
+ */
+ function continuousInit(source) {
+ dragging.released = 0;
+ dragging.source = source;
+ dragging.slidee = source === 'slidee';
+ }
+
+ /**
+ * Dragging initiator.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function dragInit(event) {
+ var isTouch = event.type === 'touchstart';
+ var source = event.data.source;
+ var isSlidee = source === 'slidee';
+
+ // Ignore when already in progress, or interactive element in non-touch navivagion
+ if (dragging.init || !isTouch && isInteractive(event.target)) {
+ return;
+ }
+
+ // Handle dragging conditions
+ if (source === 'handle' && (!o.dragHandle || hPos.start === hPos.end)) {
+ return;
+ }
+
+ // SLIDEE dragging conditions
+ if (isSlidee && !(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) {
+ return;
+ }
+
+ if (!isTouch) {
+ // prevents native image dragging in Firefox
+ stopDefault(event);
+ }
+
+ // Reset dragging object
+ continuousInit(source);
+
+ // Properties used in dragHandler
+ dragging.init = 0;
+ dragging.$source = $(event.target);
+ dragging.touch = isTouch;
+ dragging.pointer = isTouch ? event.originalEvent.touches[0] : event;
+ dragging.initX = dragging.pointer.pageX;
+ dragging.initY = dragging.pointer.pageY;
+ dragging.initPos = isSlidee ? pos.cur : hPos.cur;
+ dragging.start = +new Date();
+ dragging.time = 0;
+ dragging.path = 0;
+ dragging.delta = 0;
+ dragging.locked = 0;
+ dragging.history = [0, 0, 0, 0];
+ dragging.pathToLock = isSlidee ? isTouch ? 30 : 10 : 0;
+
+ // Bind dragging events
+ $doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler);
+
+ // Pause ongoing cycle
+ self.pause(1);
+
+ // Add dragging class
+ (isSlidee ? $slidee : $handle).addClass(o.draggedClass);
+
+ // Trigger moveStart event
+ trigger('moveStart');
+
+ // Keep track of a dragging path history. This is later used in the
+ // dragging release swing calculation when dragging SLIDEE.
+ if (isSlidee) {
+ historyID = setInterval(draggingHistoryTick, 10);
+ }
+ }
+
+ /**
+ * Handler for dragging scrollbar handle or SLIDEE.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function dragHandler(event) {
+ dragging.released = event.type === 'mouseup' || event.type === 'touchend';
+ dragging.pointer = dragging.touch ? event.originalEvent[dragging.released ? 'changedTouches' : 'touches'][0] : event;
+ dragging.pathX = dragging.pointer.pageX - dragging.initX;
+ dragging.pathY = dragging.pointer.pageY - dragging.initY;
+ dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2));
+ dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY;
+
+ if (!dragging.released && dragging.path < 1) return;
+
+ // We haven't decided whether this is a drag or not...
+ if (!dragging.init) {
+ // If the drag path was very short, maybe it's not a drag?
+ if (dragging.path < o.dragThreshold) {
+ // If the pointer was released, the path will not become longer and it's
+ // definitely not a drag. If not released yet, decide on next iteration
+ return dragging.released ? dragEnd() : undefined;
+ }
+ else {
+ // If dragging path is sufficiently long we can confidently start a drag
+ // if drag is in different direction than scroll, ignore it
+ if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) {
+ dragging.init = 1;
+ } else {
+ return dragEnd();
+ }
+ }
+ }
+
+ stopDefault(event);
+
+ // Disable click on a source element, as it is unwelcome when dragging
+ if (!dragging.locked && dragging.path > dragging.pathToLock && dragging.slidee) {
+ dragging.locked = 1;
+ dragging.$source.on(clickEvent, disableOneEvent);
+ }
+
+ // Cancel dragging on release
+ if (dragging.released) {
+ dragEnd();
+
+ // Adjust path with a swing on mouse release
+ if (o.releaseSwing && dragging.slidee) {
+ dragging.swing = (dragging.delta - dragging.history[0]) / 40 * 300;
+ dragging.delta += dragging.swing;
+ dragging.tweese = abs(dragging.swing) > 10;
+ }
+ }
+
+ slideTo(dragging.slidee ? round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta));
+ }
+
+ /**
+ * Stops dragging and cleans up after it.
+ *
+ * @return {Void}
+ */
+ function dragEnd() {
+ clearInterval(historyID);
+ dragging.released = true;
+ $doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler);
+ (dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass);
+
+ // Make sure that disableOneEvent is not active in next tick.
+ setTimeout(function () {
+ dragging.$source.off(clickEvent, disableOneEvent);
+ });
+
+ // Normally, this is triggered in render(), but if there
+ // is nothing to render, we have to do it manually here.
+ if (pos.cur === pos.dest && dragging.init) {
+ trigger('moveEnd');
+ }
+
+ // Resume ongoing cycle
+ self.resume(1);
+
+ dragging.init = 0;
+ }
+
+ /**
+ * Check whether element is interactive.
+ *
+ * @return {Boolean}
+ */
+ function isInteractive(element) {
+ return ~$.inArray(element.nodeName, o.interactiveElements) || $(element).is(o.interactive);
+ }
+
+ /**
+ * Continuous movement cleanup on mouseup.
+ *
+ * @return {Void}
+ */
+ function movementReleaseHandler() {
+ self.stop();
+ $doc.off('mouseup', movementReleaseHandler);
+ }
+
+ /**
+ * Buttons navigation handler.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function buttonsHandler(event) {
+ /*jshint validthis:true */
+ stopDefault(event);
+ switch (this) {
+ case $forwardButton[0]:
+ case $backwardButton[0]:
+ self.moveBy($forwardButton.is(this) ? o.moveBy : -o.moveBy);
+ $doc.on('mouseup', movementReleaseHandler);
+ break;
+
+ case $prevButton[0]:
+ self.prev();
+ break;
+
+ case $nextButton[0]:
+ self.next();
+ break;
+
+ case $prevPageButton[0]:
+ self.prevPage();
+ break;
+
+ case $nextPageButton[0]:
+ self.nextPage();
+ break;
+ }
+ }
+
+ /**
+ * Mouse wheel delta normalization.
+ *
+ * @param {Event} event
+ *
+ * @return {Int}
+ */
+ function normalizeWheelDelta(event) {
+ // wheelDelta needed only for IE8-
+ scrolling.curDelta = ((o.horizontal ? event.deltaY || event.deltaX : event.deltaY) || -event.wheelDelta);
+ scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100;
+ if (!itemNav) {
+ return scrolling.curDelta;
+ }
+ time = +new Date();
+ if (scrolling.last < time - scrolling.resetTime) {
+ scrolling.delta = 0;
+ }
+ scrolling.last = time;
+ scrolling.delta += scrolling.curDelta;
+ if (abs(scrolling.delta) < 1) {
+ scrolling.finalDelta = 0;
+ } else {
+ scrolling.finalDelta = round(scrolling.delta / 1);
+ scrolling.delta %= 1;
+ }
+ return scrolling.finalDelta;
+ }
+
+ /**
+ * Mouse scrolling handler.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function scrollHandler(event) {
+ // Mark event as originating in a Sly instance
+ event.originalEvent[namespace] = self;
+ // Don't hijack global scrolling
+ var time = +new Date();
+ if (lastGlobalWheel + o.scrollHijack > time && $scrollSource[0] !== document && $scrollSource[0] !== window) {
+ lastGlobalWheel = time;
+ return;
+ }
+ // Ignore if there is no scrolling to be done
+ if (!o.scrollBy || pos.start === pos.end) {
+ return;
+ }
+ var delta = normalizeWheelDelta(event.originalEvent);
+ // Trap scrolling only when necessary and/or requested
+ if (o.scrollTrap || delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) {
+ stopDefault(event, 1);
+ }
+ self.slideBy(o.scrollBy * delta);
+ }
+
+ /**
+ * Scrollbar click handler.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function scrollbarHandler(event) {
+ // Only clicks on scroll bar. Ignore the handle.
+ if (o.clickBar && event.target === $sb[0]) {
+ stopDefault(event);
+ // Calculate new handle position and sync SLIDEE to it
+ slideTo(handleToSlidee((o.horizontal ? event.pageX - $sb.offset().left : event.pageY - $sb.offset().top) - handleSize / 2));
+ }
+ }
+
+ /**
+ * Keyboard input handler.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function keyboardHandler(event) {
+ if (!o.keyboardNavBy) {
+ return;
+ }
+
+ switch (event.which) {
+ // Left or Up
+ case o.horizontal ? 37 : 38:
+ stopDefault(event);
+ self[o.keyboardNavBy === 'pages' ? 'prevPage' : 'prev']();
+ break;
+
+ // Right or Down
+ case o.horizontal ? 39 : 40:
+ stopDefault(event);
+ self[o.keyboardNavBy === 'pages' ? 'nextPage' : 'next']();
+ break;
+ }
+ }
+
+ /**
+ * Click on item activation handler.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function activateHandler(event) {
+ /*jshint validthis:true */
+
+ // Ignore clicks on interactive elements.
+ if (isInteractive(this)) {
+ event.originalEvent[namespace + 'ignore'] = true;
+ return;
+ }
+
+ // Ignore events that:
+ // - are not originating from direct SLIDEE children
+ // - originated from interactive elements
+ if (this.parentNode !== $slidee[0] || event.originalEvent[namespace + 'ignore']) return;
+
+ self.activate(this);
+ }
+
+ /**
+ * Click on page button handler.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function activatePageHandler() {
+ /*jshint validthis:true */
+ // Accept only events from direct pages bar children.
+ if (this.parentNode === $pb[0]) {
+ self.activatePage($pages.index(this));
+ }
+ }
+
+ /**
+ * Pause on hover handler.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function pauseOnHoverHandler(event) {
+ if (o.pauseOnHover) {
+ self[event.type === 'mouseenter' ? 'pause' : 'resume'](2);
+ }
+ }
+
+ /**
+ * Trigger callbacks for event.
+ *
+ * @param {String} name Event name.
+ * @param {Mixed} argX Arguments passed to callbacks.
+ *
+ * @return {Void}
+ */
+ function trigger(name, arg1) {
+ if (callbacks[name]) {
+ l = callbacks[name].length;
+ // Callbacks will be stored and executed from a temporary array to not
+ // break the execution queue when one of the callbacks unbinds itself.
+ tmpArray.length = 0;
+ for (i = 0; i < l; i++) {
+ tmpArray.push(callbacks[name][i]);
+ }
+ // Execute the callbacks
+ for (i = 0; i < l; i++) {
+ tmpArray[i].call(self, name, arg1);
+ }
+ }
+ }
+
+ /**
+ * Destroys instance and everything it created.
+ *
+ * @return {Void}
+ */
+ self.destroy = function () {
+ // Remove the reference to itself
+ Sly.removeInstance(frame);
+
+ // Unbind all events
+ $scrollSource
+ .add($handle)
+ .add($sb)
+ .add($pb)
+ .add($forwardButton)
+ .add($backwardButton)
+ .add($prevButton)
+ .add($nextButton)
+ .add($prevPageButton)
+ .add($nextPageButton)
+ .off('.' + namespace);
+
+ // Unbinding specifically as to not nuke out other instances
+ $doc.off('keydown', keyboardHandler);
+
+ // Remove classes
+ $prevButton
+ .add($nextButton)
+ .add($prevPageButton)
+ .add($nextPageButton)
+ .removeClass(o.disabledClass);
+
+ if ($items && rel.activeItem != null) {
+ $items.eq(rel.activeItem).removeClass(o.activeClass);
+ }
+
+ // Remove page items
+ $pb.empty();
+
+ if (!parallax) {
+ // Unbind events from frame
+ $frame.off('.' + namespace);
+ // Restore original styles
+ frameStyles.restore();
+ slideeStyles.restore();
+ sbStyles.restore();
+ handleStyles.restore();
+ // Remove the instance from element data storage
+ $.removeData(frame, namespace);
+ }
+
+ // Clean up collections
+ items.length = pages.length = 0;
+ last = {};
+
+ // Reset initialized status and return the instance
+ self.initialized = 0;
+ return self;
+ };
+
+ /**
+ * Initialize.
+ *
+ * @return {Object}
+ */
+ self.init = function () {
+ if (self.initialized) {
+ return;
+ }
+
+ // Disallow multiple instances on the same element
+ if (Sly.getInstance(frame)) throw new Error('There is already a Sly instance on this element');
+
+ // Store the reference to itself
+ Sly.storeInstance(frame, self);
+
+ // Register callbacks map
+ self.on(callbackMap);
+
+ // Save styles
+ var holderProps = ['overflow', 'position'];
+ var movableProps = ['position', 'webkitTransform', 'msTransform', 'transform', 'left', 'top', 'width', 'height'];
+ frameStyles.save.apply(frameStyles, holderProps);
+ sbStyles.save.apply(sbStyles, holderProps);
+ slideeStyles.save.apply(slideeStyles, movableProps);
+ handleStyles.save.apply(handleStyles, movableProps);
+
+ // Set required styles
+ var $movables = $handle;
+ if (!parallax) {
+ $movables = $movables.add($slidee);
+ $frame.css('overflow', 'hidden');
+ if (!transform && $frame.css('position') === 'static') {
+ $frame.css('position', 'relative');
+ }
+ }
+ if (transform) {
+ if (gpuAcceleration) {
+ $movables.css(transform, gpuAcceleration);
+ }
+ } else {
+ if ($sb.css('position') === 'static') {
+ $sb.css('position', 'relative');
+ }
+ $movables.css({ position: 'absolute' });
+ }
+
+ // Navigation buttons
+ if (o.forward) {
+ $forwardButton.on(mouseDownEvent, buttonsHandler);
+ }
+ if (o.backward) {
+ $backwardButton.on(mouseDownEvent, buttonsHandler);
+ }
+ if (o.prev) {
+ $prevButton.on(clickEvent, buttonsHandler);
+ }
+ if (o.next) {
+ $nextButton.on(clickEvent, buttonsHandler);
+ }
+ if (o.prevPage) {
+ $prevPageButton.on(clickEvent, buttonsHandler);
+ }
+ if (o.nextPage) {
+ $nextPageButton.on(clickEvent, buttonsHandler);
+ }
+
+ // Scrolling navigation
+ $scrollSource.on(wheelEvent, scrollHandler);
+
+ // Clicking on scrollbar navigation
+ if ($sb[0]) {
+ $sb.on(clickEvent, scrollbarHandler);
+ }
+
+ // Click on items navigation
+ if (itemNav && o.activateOn) {
+ $frame.on(o.activateOn + '.' + namespace, '*', activateHandler);
+ }
+
+ // Pages navigation
+ if ($pb[0] && o.activatePageOn) {
+ $pb.on(o.activatePageOn + '.' + namespace, '*', activatePageHandler);
+ }
+
+ // Dragging navigation
+ $dragSource.on(dragInitEvents, { source: 'slidee' }, dragInit);
+
+ // Scrollbar dragging navigation
+ if ($handle) {
+ $handle.on(dragInitEvents, { source: 'handle' }, dragInit);
+ }
+
+ // Keyboard navigation
+ $doc.on('keydown', keyboardHandler);
+
+ if (!parallax) {
+ // Pause on hover
+ $frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler);
+ // Reset native FRAME element scroll
+ $frame.on('scroll.' + namespace, resetScroll);
+ }
+
+ // Mark instance as initialized
+ self.initialized = 1;
+
+ // Load
+ load(true);
+
+ // Initiate automatic cycling
+ if (o.cycleBy && !parallax) {
+ self[o.startPaused ? 'pause' : 'resume']();
+ }
+
+ // Return instance
+ return self;
+ };
+ }
+
+ Sly.getInstance = function (element) {
+ return $.data(element, namespace);
+ };
+
+ Sly.storeInstance = function (element, sly) {
+ return $.data(element, namespace, sly);
+ };
+
+ Sly.removeInstance = function (element) {
+ return $.removeData(element, namespace);
+ };
+
+ /**
+ * Return type of the value.
+ *
+ * @param {Mixed} value
+ *
+ * @return {String}
+ */
+ function type(value) {
+ if (value == null) {
+ return String(value);
+ }
+
+ if (typeof value === 'object' || typeof value === 'function') {
+ return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object';
+ }
+
+ return typeof value;
+ }
+
+ /**
+ * Event preventDefault & stopPropagation helper.
+ *
+ * @param {Event} event Event object.
+ * @param {Bool} noBubbles Cancel event bubbling.
+ *
+ * @return {Void}
+ */
+ function stopDefault(event, noBubbles) {
+ event.preventDefault();
+ if (noBubbles) {
+ event.stopPropagation();
+ }
+ }
+
+ /**
+ * Disables an event it was triggered on and unbinds itself.
+ *
+ * @param {Event} event
+ *
+ * @return {Void}
+ */
+ function disableOneEvent(event) {
+ /*jshint validthis:true */
+ stopDefault(event, 1);
+ $(this).off(event.type, disableOneEvent);
+ }
+
+ /**
+ * Resets native element scroll values to 0.
+ *
+ * @return {Void}
+ */
+ function resetScroll() {
+ /*jshint validthis:true */
+ this.scrollLeft = 0;
+ this.scrollTop = 0;
+ }
+
+ /**
+ * Check if variable is a number.
+ *
+ * @param {Mixed} value
+ *
+ * @return {Boolean}
+ */
+ function isNumber(value) {
+ return !isNaN(parseFloat(value)) && isFinite(value);
+ }
+
+ /**
+ * Parse style to pixels.
+ *
+ * @param {Object} $item jQuery object with element.
+ * @param {Property} property CSS property to get the pixels from.
+ *
+ * @return {Int}
+ */
+ function getPx($item, property) {
+ return 0 | round(String($item.css(property)).replace(/[^\-0-9.]/g, ''));
+ }
+
+ /**
+ * Make sure that number is within the limits.
+ *
+ * @param {Number} number
+ * @param {Number} min
+ * @param {Number} max
+ *
+ * @return {Number}
+ */
+ function within(number, min, max) {
+ return number < min ? min : number > max ? max : number;
+ }
+
+ /**
+ * Saves element styles for later restoration.
+ *
+ * Example:
+ * var styles = new StyleRestorer(frame);
+ * styles.save('position');
+ * element.style.position = 'absolute';
+ * styles.restore(); // restores to state before the assignment above
+ *
+ * @param {Element} element
+ */
+ function StyleRestorer(element) {
+ var self = {};
+ self.style = {};
+ self.save = function () {
+ if (!element || !element.nodeType) return;
+ for (var i = 0; i < arguments.length; i++) {
+ self.style[arguments[i]] = element.style[arguments[i]];
+ }
+ return self;
+ };
+ self.restore = function () {
+ if (!element || !element.nodeType) return;
+ for (var prop in self.style) {
+ if (self.style.hasOwnProperty(prop)) element.style[prop] = self.style[prop];
+ }
+ return self;
+ };
+ return self;
+ }
+
+ // Local WindowAnimationTiming interface polyfill
+ (function (w) {
+ rAF = w.requestAnimationFrame
+ || w.webkitRequestAnimationFrame
+ || fallback;
+
+ /**
+ * Fallback implementation.
+ */
+ var prev = new Date().getTime();
+ function fallback(fn) {
+ var curr = new Date().getTime();
+ var ms = Math.max(0, 16 - (curr - prev));
+ var req = setTimeout(fn, ms);
+ prev = curr;
+ return req;
+ }
+
+ /**
+ * Cancel.
+ */
+ var cancel = w.cancelAnimationFrame
+ || w.webkitCancelAnimationFrame
+ || w.clearTimeout;
+
+ cAF = function(id){
+ cancel.call(w, id);
+ };
+ }(window));
+
+ // Feature detects
+ (function () {
+ var prefixes = ['', 'Webkit', 'Moz', 'ms', 'O'];
+ var el = document.createElement('div');
+
+ function testProp(prop) {
+ for (var p = 0, pl = prefixes.length; p < pl; p++) {
+ var prefixedProp = prefixes[p] ? prefixes[p] + prop.charAt(0).toUpperCase() + prop.slice(1) : prop;
+ if (el.style[prefixedProp] != null) {
+ return prefixedProp;
+ }
+ }
+ }
+
+ // Global support indicators
+ transform = testProp('transform');
+ gpuAcceleration = testProp('perspective') ? 'translateZ(0) ' : '';
+ }());
+
+ // Expose class globally
+ w[className] = Sly;
+
+ // jQuery proxy
+ $.fn[pluginName] = function (options, callbackMap) {
+ var method, methodArgs;
+
+ // Attributes logic
+ if (!$.isPlainObject(options)) {
+ if (type(options) === 'string' || options === false) {
+ method = options === false ? 'destroy' : options;
+ methodArgs = Array.prototype.slice.call(arguments, 1);
+ }
+ options = {};
+ }
+
+ // Apply to all elements
+ return this.each(function (i, element) {
+ // Call with prevention against multiple instantiations
+ var plugin = Sly.getInstance(element);
+
+ if (!plugin && !method) {
+ // Create a new object if it doesn't exist yet
+ plugin = new Sly(element, options, callbackMap).init();
+ } else if (plugin && method) {
+ // Call method
+ if (plugin[method]) {
+ plugin[method].apply(plugin, methodArgs);
+ }
+ }
+ });
+ };
+
+ // Default options
+ Sly.defaults = {
+ slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE.
+ horizontal: false, // Switch to horizontal mode.
+
+ // Item based navigation
+ itemNav: null, // Item navigation type. Can be: 'basic', 'centered', 'forceCentered'.
+ itemSelector: null, // Select only items that match this selector.
+ smart: false, // Repositions the activated item to help with further navigation.
+ activateOn: null, // Activate an item on this event. Can be: 'click', 'mouseenter', ...
+ activateMiddle: false, // Always activate the item in the middle of the FRAME. forceCentered only.
+
+ // Scrolling
+ scrollSource: null, // Element for catching the mouse wheel scrolling. Default is FRAME.
+ scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling.
+ scrollHijack: 300, // Milliseconds since last wheel event after which it is acceptable to hijack global scroll.
+ scrollTrap: false, // Don't bubble scrolling when hitting scrolling limits.
+
+ // Dragging
+ dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME.
+ mouseDragging: false, // Enable navigation by dragging the SLIDEE with mouse cursor.
+ touchDragging: false, // Enable navigation by dragging the SLIDEE with touch events.
+ releaseSwing: false, // Ease out on dragging swing release.
+ swingSpeed: 0.2, // Swing synchronization speed, where: 1 = instant, 0 = infinite.
+ elasticBounds: false, // Stretch SLIDEE position limits when dragging past FRAME boundaries.
+ dragThreshold: 3, // Distance in pixels before Sly recognizes dragging.
+ interactive: null, // Selector for special interactive elements.
+ interactiveElements : ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA'],
+ // Scrollbar
+ scrollBar: null, // Selector or DOM element for scrollbar container.
+ dragHandle: false, // Whether the scrollbar handle should be draggable.
+ dynamicHandle: false, // Scrollbar handle represents the ratio between hidden and visible content.
+ minHandleSize: 50, // Minimal height or width (depends on sly direction) of a handle in pixels.
+ clickBar: false, // Enable navigation by clicking on scrollbar.
+ syncSpeed: 0.5, // Handle => SLIDEE synchronization speed, where: 1 = instant, 0 = infinite.
+
+ // Pagesbar
+ pagesBar: null, // Selector or DOM element for pages bar container.
+ activatePageOn: null, // Event used to activate page. Can be: click, mouseenter, ...
+ pageBuilder: // Page item generator.
+ function (index) {
+ return '<li>' + (index + 1) + '</li>';
+ },
+
+ // Navigation buttons
+ forward: null, // Selector or DOM element for "forward movement" button.
+ backward: null, // Selector or DOM element for "backward movement" button.
+ prev: null, // Selector or DOM element for "previous item" button.
+ next: null, // Selector or DOM element for "next item" button.
+ prevPage: null, // Selector or DOM element for "previous page" button.
+ nextPage: null, // Selector or DOM element for "next page" button.
+
+ // Automated cycling
+ cycleBy: null, // Enable automatic cycling by 'items' or 'pages'.
+ cycleInterval: 5000, // Delay between cycles in milliseconds.
+ pauseOnHover: false, // Pause cycling when mouse hovers over the FRAME.
+ startPaused: false, // Whether to start in paused sate.
+
+ // Mixed options
+ moveBy: 300, // Speed in pixels per second used by forward and backward buttons.
+ speed: 0, // Animations speed in milliseconds. 0 to disable animations.
+ easing: 'swing', // Easing for duration based (tweening) animations.
+ startAt: null, // Starting offset in pixels or items.
+ keyboardNavBy: null, // Enable keyboard navigation by 'items' or 'pages'.
+
+ // Classes
+ draggedClass: 'dragged', // Class for dragged elements (like SLIDEE or scrollbar handle).
+ activeClass: 'active', // Class for active items and pages.
+ disabledClass: 'disabled' // Class for disabled navigation elements.
+ };
+ //Export module for nodejs browserify
+ if (typeof exports === 'object') {
+ module.exports = Sly;
+ }
+}(jQuery, window));

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 24, 19:42 (6 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2256338
Default Alt Text
(98 KB)

Event Timeline