PageSourceSearch

https://nntk.net/rw_common/themes/Geometric/js/elixir.js?rwcache=759240085

js nntk.net collected 2026-10-02 14:21:53 UTC 90,569 bytes, 2,154 lines download raw bytes

1
2/*!
3 * skrollr core
4 *
5 * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr
6 *
7 * Free to use under terms of MIT license
8 */
9(function(window, document, undefined) {
10    'use strict';
11
12    /*
13     * Global api.
14     */
15    var skrollr = {
16        get: function() {
17            return _instance;
18        },
19        //Main entry point.
20        init: function(options) {
21            return _instance || new Skrollr(options);
22        },
23        VERSION: '0.6.22'
24    };
25
26    //Minify optimization.
27    var hasProp = Object.prototype.hasOwnProperty;
28    var Math = window.Math;
29    var getStyle = window.getComputedStyle;
30
31    //They will be filled when skrollr gets initialized.
32    var documentElement;
33    var body;
34
35    var EVENT_TOUCHSTART = 'touchstart';
36    var EVENT_TOUCHMOVE = 'touchmove';
37    var EVENT_TOUCHCANCEL = 'touchcancel';
38    var EVENT_TOUCHEND = 'touchend';
39
40    var SKROLLABLE_CLASS = 'skrollable';
41    var SKROLLABLE_BEFORE_CLASS = SKROLLABLE_CLASS + '-before';
42    var SKROLLABLE_BETWEEN_CLASS = SKROLLABLE_CLASS + '-between';
43    var SKROLLABLE_AFTER_CLASS = SKROLLABLE_CLASS + '-after';
44
45    var SKROLLR_CLASS = 'skrollr';
46    var NO_SKROLLR_CLASS = 'no-' + SKROLLR_CLASS;
47    var SKROLLR_DESKTOP_CLASS = SKROLLR_CLASS + '-desktop';
48    var SKROLLR_MOBILE_CLASS = SKROLLR_CLASS + '-mobile';
49
50    var DEFAULT_EASING = 'linear';
51    var DEFAULT_DURATION = 1000;//ms
52    var DEFAULT_MOBILE_DECELERATION = 0.004;//pixel/ms²
53
54    var DEFAULT_SMOOTH_SCROLLING_DURATION = 200;//ms
55
56    var ANCHOR_START = 'start';
57    var ANCHOR_END = 'end';
58    var ANCHOR_CENTER = 'center';
59    var ANCHOR_BOTTOM = 'bottom';
60
61    //The property which will be added to the DOM element to hold the ID of the skrollable.
62    var SKROLLABLE_ID_DOM_PROPERTY = '___skrollable_id';
63
64    var rxTouchIgnoreTags = /^(?:input|textarea|button|select)$/i;
65
66    var rxTrim = /^\s+|\s+$/g;
67
68    //Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor].
69    var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d*\.?\d+p?))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/;
70
71    var rxPropValue = /\s*([\w\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi;
72
73    //Easing function names follow the property in square brackets.
74    var rxPropEasing = /^([a-z\-]+)\[(\w+)\]$/;
75
76    var rxCamelCase = /-([a-z])/g;
77    var rxCamelCaseFn = function(str, letter) {
78        return letter.toUpperCase();
79    };
80
81    //Numeric values with optional sign.
82    var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g;
83
84    //Used to replace occurences of {?} with a number.
85    var rxInterpolateString = /\{\?\}/g;
86
87    //Finds rgb(a) colors, which don't use the percentage notation.
88    var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g;
89
90    //Finds all gradients.
91    var rxGradient = /[a-z\-]+-gradient/g;
92
93    //Vendor prefix. Will be set once skrollr gets initialized.
94    var theCSSPrefix = '';
95    var theDashedCSSPrefix = '';
96
97    //Will be called once (when skrollr gets initialized).
98    var detectCSSPrefix = function() {
99        //Only relevant prefixes. May be extended.
100        //Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so.
101        var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/;
102
103        //Detect prefix for current browser by finding the first property using a prefix.
104        if(!getStyle) {
105            return;
106        }
107
108        var style = getStyle(body, null);
109
110        for(var k in style) {
111            //We check the key and if the key is a number, we check the value as well, because safari's getComputedStyle returns some weird array-like thingy.
112            theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes)));
113
114            if(theCSSPrefix) {
115                break;
116            }
117        }
118
119        //Did we even detect a prefix?
120        if(!theCSSPrefix) {
121            theCSSPrefix = theDashedCSSPrefix = '';
122
123            return;
124        }
125
126        theCSSPrefix = theCSSPrefix[0];
127
128        //We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff.
129        if(theCSSPrefix.slice(0,1) === '-') {
130            theDashedCSSPrefix = theCSSPrefix;
131
132            //There's no logic behind these. Need a look up.
133            theCSSPrefix = ({
134                '-webkit-': 'webkit',
135                '-moz-': 'Moz',
136                '-ms-': 'ms',
137                '-o-': 'O'
138            })[theCSSPrefix];
139        } else {
140            theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-';
141        }
142    };
143
144    var polyfillRAF = function() {
145        var requestAnimFrame = window.requestAnimationFrame || window[theCSSPrefix.toLowerCase() + 'RequestAnimationFrame'];
146
147        var lastTime = _now();
148
149        if(_isMobile || !requestAnimFrame) {
150            requestAnimFrame = function(callback) {
151                //How long did it take to render?
152                var deltaTime = _now() - lastTime;
153                var delay = Math.max(0, 1000 / 60 - deltaTime);
154
155                return window.setTimeout(function() {
156                    lastTime = _now();
157                    callback();
158                }, delay);
159            };
160        }
161
162        return requestAnimFrame;
163    };
164
165    var polyfillCAF = function() {
166        var cancelAnimFrame = window.cancelAnimationFrame || window[theCSSPrefix.toLowerCase() + 'CancelAnimationFrame'];
167
168        if(_isMobile || !cancelAnimFrame) {
169            cancelAnimFrame = function(timeout) {
170                return window.clearTimeout(timeout);
171            };
172        }
173
174        return cancelAnimFrame;
175    };
176
177    //Built-in easing functions.
178    var easings = {
179        begin: function() {
180            return 0;
181        },
182        end: function() {
183            return 1;
184        },
185        linear: function(p) {
186            return p;
187        },
188        quadratic: function(p) {
189            return p * p;
190        },
191        cubic: function(p) {
192            return p * p * p;
193        },
194        swing: function(p) {
195            return (-Math.cos(p * Math.PI) / 2) + 0.5;
196        },
197        sqrt: function(p) {
198            return Math.sqrt(p);
199        },
200        outCubic: function(p) {
201            return (Math.pow((p - 1), 3) + 1);
202        },
203        //see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this
204        bounce: function(p) {
205            var a;
206
207            if(p <= 0.5083) {
208                a = 3;
209            } else if(p <= 0.8489) {
210                a = 9;
211            } else if(p <= 0.96208) {
212                a = 27;
213            } else if(p <= 0.99981) {
214                a = 91;
215            } else {
216                return 1;
217            }
218
219            return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a);
220        }
221    };
222
223    /**
224     * Constructor.
225     */
226    function Skrollr(options) {
227        documentElement = document.documentElement;
228        body = document.body;
229
230        detectCSSPrefix();
231
232        _instance = this;
233
234        options = options || {};
235
236        _constants = options.constants || {};
237
238        //We allow defining custom easings or overwrite existing.
239        if(options.easing) {
240            for(var e in options.easing) {
241                easings[e] = options.easing[e];
242            }
243        }
244
245        _edgeStrategy = options.edgeStrategy || 'set';
246
247        _listeners = {
248            //Function to be called right before rendering.
249            beforerender: options.beforerender,
250
251            //Function to be called right after finishing rendering.
252            render: options.render
253        };
254
255        //forceHeight is true by default
256        _forceHeight = options.forceHeight !== false;
257
258        if(_forceHeight) {
259            _scale = options.scale || 1;
260        }
261
262        _mobileDeceleration = options.mobileDeceleration || DEFAULT_MOBILE_DECELERATION;
263
264        _smoothScrollingEnabled = options.smoothScrolling !== false;
265        _smoothScrollingDuration = options.smoothScrollingDuration || DEFAULT_SMOOTH_SCROLLING_DURATION;
266
267        //Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated.
268        _smoothScrolling = {
269            targetTop: _instance.getScrollTop()
270        };
271
272        //A custom check function may be passed.
273        _isMobile = ((options.mobileCheck || function() {
274            return (/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera);
275        })());
276
277        if(_isMobile) {
278            _skrollrBody = document.getElementById('skrollr-body');
279
280            //Detect 3d transform if there's a skrollr-body (only needed for #skrollr-body).
281            if(_skrollrBody) {
282                _detect3DTransforms();
283            }
284
285            _initMobile();
286            _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_MOBILE_CLASS], [NO_SKROLLR_CLASS]);
287        } else {
288            _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS], [NO_SKROLLR_CLASS]);
289        }
290
291        //Triggers parsing of elements and a first reflow.
292        _instance.refresh();
293
294        _addEvent(window, 'resize orientationchange', function() {
295            var width = documentElement.clientWidth;
296            var height = documentElement.clientHeight;
297
298            //Only reflow if the size actually changed (#271).
299            if(height !== _lastViewportHeight || width !== _lastViewportWidth) {
300                _lastViewportHeight = height;
301                _lastViewportWidth = width;
302
303                _requestReflow = true;
304            }
305        });
306
307        var requestAnimFrame = polyfillRAF();
308
309        //Let's go.
310        (function animloop(){
311            _render();
312            _animFrame = requestAnimFrame(animloop);
313        }());
314
315        return _instance;
316    }
317
318    /**
319     * (Re)parses some or all elements.
320     */
321    Skrollr.prototype.refresh = function(elements) {
322        var elementIndex;
323        var elementsLength;
324        var ignoreID = false;
325
326        //Completely reparse anything without argument.
327        if(elements === undefined) {
328            //Ignore that some elements may already have a skrollable ID.
329            ignoreID = true;
330
331            _skrollables = [];
332            _skrollableIdCounter = 0;
333
334            elements = document.getElementsByTagName('*');
335        } else if(elements.length === undefined) {
336            //We also accept a single element as parameter.
337            elements = [elements];
338        }
339
340        elementIndex = 0;
341        elementsLength = elements.length;
342
343        for(; elementIndex < elementsLength; elementIndex++) {
344            var el = elements[elementIndex];
345            var anchorTarget = el;
346            var keyFrames = [];
347
348            //If this particular element should be smooth scrolled.
349            var smoothScrollThis = _smoothScrollingEnabled;
350
351            //The edge strategy for this particular element.
352            var edgeStrategy = _edgeStrategy;
353
354            if(!el.attributes) {
355                continue;
356            }
357
358            //Iterate over all attributes and search for key frame attributes.
359            var attributeIndex = 0;
360            var attributesLength = el.attributes.length;
361
362            for (; attributeIndex < attributesLength; attributeIndex++) {
363                var attr = el.attributes[attributeIndex];
364
365                if(attr.name === 'data-anchor-target') {
366                    anchorTarget = document.querySelector(attr.value);
367
368                    if(anchorTarget === null) {
369                        throw 'Unable to find anchor target "' + attr.value + '"';
370                    }
371
372                    continue;
373                }
374
375                //Global smooth scrolling can be overridden by the element attribute.
376                if(attr.name === 'data-smooth-scrolling') {
377                    smoothScrollThis = attr.value !== 'off';
378
379                    continue;
380                }
381
382                //Global edge strategy can be overridden by the element attribute.
383                if(attr.name === 'data-edge-strategy') {
384                    edgeStrategy = attr.value;
385
386                    continue;
387                }
388
389                var match = attr.name.match(rxKeyframeAttribute);
390
391                if(match === null) {
392                    continue;
393                }
394
395                var kf = {
396                    props: attr.value,
397                    //Point back to the element as well.
398                    element: el
399                };
400
401                keyFrames.push(kf);
402
403                var constant = match[1];
404
405                if(constant) {
406                    //Strip the underscore prefix.
407                    kf.constant = constant.substr(1);
408                }
409
410                //Get the key frame offset.
411                var offset = match[2];
412
413                //Is it a percentage offset?
414                if(/p$/.test(offset)) {
415                    kf.isPercentage = true;
416                    kf.offset = (offset.slice(0, -1) | 0) / 100;
417                } else {
418                    kf.offset = (offset | 0);
419                }
420
421                var anchor1 = match[3];
422
423                //If second anchor is not set, the first will be taken for both.
424                var anchor2 = match[4] || anchor1;
425
426                //"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
427                if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
428                    kf.mode = 'absolute';
429
430                    //data-end needs to be calculated after all key frames are known.
431                    if(anchor1 === ANCHOR_END) {
432                        kf.isEnd = true;
433                    } else if(!kf.isPercentage) {
434                        //For data-start we can already set the key frame w/
434o calculations.
435                        //#59: "scale" options should only affect absolute mode.
436                        kf.offset = kf.offset * _scale;
437                    }
438                }
439                //"relative" mode, where numbers are relative to anchors.
440                else {
441                    kf.mode = 'relative';
442                    kf.anchors = [anchor1, anchor2];
443                }
444            }
445
446            //Does this element have key frames?
447            if(!keyFrames.length) {
448                continue;
449            }
450
451            //Will hold the original style and class attributes before we controlled the element (see #80).
452            var styleAttr, classAttr;
453
454            var id;
455
456            if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
457                //We already have this element under control. Grab the corresponding skrollable id.
458                id = el[SKROLLABLE_ID_DOM_PROPERTY];
459                styleAttr = _skrollables[id].styleAttr;
460                classAttr = _skrollables[id].classAttr;
461            } else {
462                //It's an unknown element. Asign it a new skrollable id.
463                id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++);
464                styleAttr = el.style.cssText;
465                classAttr = _getClass(el);
466            }
467
468            _skrollables[id] = {
469                element: el,
470                styleAttr: styleAttr,
471                classAttr: classAttr,
472                anchorTarget: anchorTarget,
473                keyFrames: keyFrames,
474                smoothScrolling: smoothScrollThis,
475                edgeStrategy: edgeStrategy
476            };
477
478            _updateClass(el, [SKROLLABLE_CLASS], []);
479        }
480
481        //Reflow for the first time.
482        _reflow();
483
484        //Now that we got all key frame numbers right, actually parse the properties.
485        elementIndex = 0;
486        elementsLength = elements.length;
487
488        for(; elementIndex < elementsLength; elementIndex++) {
489            var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]];
490
491            if(sk === undefined) {
492                continue;
493            }
494
495            //Parse the property string to objects
496            _parseProps(sk);
497
498            //Fill key frames with missing properties from left and right
499            _fillProps(sk);
500        }
501
502        return _instance;
503    };
504
505    /**
506     * Transform "relative" mode to "absolute" mode.
507     * That is, calculate anchor position and offset of element.
508     */
509    Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) {
510        var viewportHeight = documentElement.clientHeight;
511        var box = element.getBoundingClientRect();
512        var absolute = box.top;
513
514        //#100: IE doesn't supply "height" with getBoundingClientRect.
515        var boxHeight = box.bottom - box.top;
516
517        if(viewportAnchor === ANCHOR_BOTTOM) {
518            absolute -= viewportHeight;
519        } else if(viewportAnchor === ANCHOR_CENTER) {
520            absolute -= viewportHeight / 2;
521        }
522
523        if(elementAnchor === ANCHOR_BOTTOM) {
524            absolute += boxHeight;
525        } else if(elementAnchor === ANCHOR_CENTER) {
526            absolute += boxHeight / 2;
527        }
528
529        //Compensate scrolling since getBoundingClientRect is relative to viewport.
530        absolute += _instance.getScrollTop();
531
532        return (absolute + 0.5) | 0;
533    };
534
535    /**
536     * Animates scroll top to new position.
537     */
538    Skrollr.prototype.animateTo = function(top, options) {
539        options = options || {};
540
541        var now = _now();
542        var scrollTop = _instance.getScrollTop();
543
544        //Setting this to a new value will automatically cause the current animation to stop, if any.
545        _scrollAnimation = {
546            startTop: scrollTop,
547            topDiff: top - scrollTop,
548            targetTop: top,
549            duration: options.duration || DEFAULT_DURATION,
550            startTime: now,
551            endTime: now + (options.duration || DEFAULT_DURATION),
552            easing: easings[options.easing || DEFAULT_EASING],
553            done: options.done
554        };
555
556        //Don't queue the animation if there's nothing to animate.
557        if(!_scrollAnimation.topDiff) {
558            if(_scrollAnimation.done) {
559                _scrollAnimation.done.call(_instance, false);
560            }
561
562            _scrollAnimation = undefined;
563        }
564
565        return _instance;
566    };
567
568    /**
569     * Stops animateTo animation.
570     */
571    Skrollr.prototype.stopAnimateTo = function() {
572        if(_scrollAnimation && _scrollAnimation.done) {
573            _scrollAnimation.done.call(_instance, true);
574        }
575
576        _scrollAnimation = undefined;
577    };
578
579    /**
580     * Returns if an animation caused by animateTo is currently running.
581     */
582    Skrollr.prototype.isAnimatingTo = function() {
583        return !!_scrollAnimation;
584    };
585
586    Skrollr.prototype.setScrollTop = function(top, force) {
587        _forceRender = (force === true);
588
589        if(_isMobile) {
590            _mobileOffset = Math.min(Math.max(top, 0), _maxKeyFrame);
591        } else {
592            window.scrollTo(0, top);
593        }
594
595        return _instance;
596    };
597
598    Skrollr.prototype.getScrollTop = function() {
599        if(_isMobile) {
600            return _mobileOffset;
601        } else {
602            return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
603        }
604    };
605
606    Skrollr.prototype.getMaxScrollTop = function() {
607        return _maxKeyFrame;
608    };
609
610    Skrollr.prototype.on = function(name, fn) {
611        _listeners[name] = fn;
612
613        return _instance;
614    };
615
616    Skrollr.prototype.off = function(name) {
617        delete _listeners[name];
618
619        return _instance;
620    };
621
622    Skrollr.prototype.destroy = function() {
623        var cancelAnimFrame = polyfillCAF();
624        cancelAnimFrame(_animFrame);
625        _removeAllEvents();
626
627        _updateClass(documentElement, [NO_SKROLLR_CLASS], [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS, SKROLLR_MOBILE_CLASS]);
628
629        var skrollableIndex = 0;
630        var skrollablesLength = _skrollables.length;
631
632        for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
633            _reset(_skrollables[skrollableIndex].element);
634        }
635
636        documentElement.style.overflow = body.style.overflow = 'auto';
637        documentElement.style.height = body.style.height = 'auto';
638
639        if(_skrollrBody) {
640            skrollr.setStyle(_skrollrBody, 'transform', 'none');
641        }
642
643        _instance = undefined;
644        _skrollrBody = undefined;
645        _listeners = undefined;
646        _forceHeight = undefined;
647        _maxKeyFrame = 0;
648        _scale = 1;
649        _constants = undefined;
650        _mobileDeceleration = undefined;
651        _direction = 'down';
652        _lastTop = -1;
653        _lastViewportWidth = 0;
654        _lastViewportHeight = 0;
655        _requestReflow = false;
656        _scrollAnimation = undefined;
657        _smoothScrollingEnabled = undefined;
658        _smoothScrollingDuration = undefined;
659        _smoothScrolling = undefined;
660        _forceRender = undefined;
661        _skrollableIdCounter = 0;
662        _edgeStrategy = undefined;
663        _isMobile = false;
664        _mobileOffset = 0;
665        _translateZ = undefined;
666    };
667
668    /*
669        Private methods.
670    */
671
672    var _initMobile = function() {
673        var initialElement;
674        var initialTouchY;
675        var initialTouchX;
676        var currentElement;
677        var currentTouchY;
678        var currentTouchX;
679        var lastTouchY;
680        var deltaY;
681
682        var initialTouchTime;
683        var currentTouchTime;
684        var lastTouchTime;
685        var deltaTime;
686
687        _addEvent(documentElement, [EVENT_TOUCHSTART, EVENT_TOUCHMOVE, EVENT_TOUCHCANCEL, EVENT_TOUCHEND].join(' '), function(e) {
688            var touch = e.changedTouches[0];
689
690            currentElement = e.target;
691
692            //We don't want text nodes.
693            while(currentElement.nodeType === 3) {
694                currentElement = currentElement.parentNode;
695            }
696
697            currentTouchY = touch.clientY;
698            currentTouchX = touch.clientX;
699            currentTouchTime = e.timeStamp;
700
701            if(!rxTouchIgnoreTags.test(currentElement.tagName)) {
702                e.preventDefault();
703            }
704
705            switch(e.type) {
706                case EVENT_TOUCHSTART:
707                    //The last element we tapped on.
708                    if(initialElement) {
709                        initialElement.blur();
710                    }
711
712                    _instance.stopAnimateTo();
713
714                    initialElement = currentElement;
715
716                    initialTouchY = lastTouchY = currentTouchY;
717                    initialTouchX = currentTouchX;
718                    initialTouchTime = currentTouchTime;
719
720                    break;
721                case EVENT_TOUCHMOVE:
722                    //Prevent default event on touchIgnore elements in case they don't have focus yet.
723                    if(rxTouchIgnoreTags.test(currentElement.tagName) && document.activeElement !== currentElement) {
724                        e.preventDefault();
725                    }
726
727                    deltaY = currentTouchY - lastTouchY;
728                    deltaTime = currentTouchTime - lastTouchTime;
729
730                    _instance.setScrollTop(_mobileOffset - deltaY, true);
731
732                    lastTouchY = currentTouchY;
733                    lastTouchTime = currentTouchTime;
734                    break;
735                default:
736                case EVENT_TOUCHCANCEL:
737                case EVENT_TOUCHEND:
738                    var distanceY = initialTouchY - currentTouchY;
739                    var distanceX = initialTouchX - currentTouchX;
740                    var distance2 = distanceX * distanceX + distanceY * distanceY;
741
742                    //Check if it was more like a tap (moved less than 7px).
743                    if(distance2 < 49) {
744                        if(!rxTouchIgnoreTags.test(initialElement.tagName)) {
745                            initialElement.focus();
746
747                            //It was a tap, click the element.
748                            var clickEvent = document.createEvent('MouseEvents');
749                            clickEvent.initMouseEvent('click', true, true, e.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null);
750                            initialElement.dispatchEvent(clickEvent);
751                        }
752
753                        return;
754                    }
755
756                    initialElement = undefined;
757
758                    var speed = deltaY / deltaTime;
759
760                    //Cap speed at 3 pixel/ms.
761                    speed = Math.max(Math.min(speed, 3), -3);
762
763                    var duration = Math.abs(speed / _mobileDeceleration);
764                    var targetOffset = speed * duration + 0.5 * _mobileDeceleration * duration * duration;
765                    var targetTop = _instance.getScrollTop() - targetOffset;
766
767                    //Relative duration change for when scrolling above bounds.
768                    var targetRatio = 0;
769
770                    //Change duration proportionally when scrolling would leave bounds.
771                    if(targetTop > _maxKeyFrame) {
772                        targetRatio = (_maxKeyFrame - targetTop) / targetOffset;
773
774                        targetTop = _maxKeyFrame;
775                    } else if(targetTop < 0) {
776                        targetRatio = -targetTop / targetOffset;
777
778                        targetTop = 0;
779                    }
780
781                    duration = duration * (1 - targetRatio);
782
783                    _instance.animateTo((targetTop + 0.5) | 0, {easing: 'outCubic', duration: duration});
784                    break;
785            }
786        });
787
788        //Just in case there has already been some native scrolling, reset it.
789        window.scrollTo(0, 0);
790        documentElement.style.overflow = body.style.overflow = 'hidden';
791    };
792
793    /**
794     * Updates key frames which depend on others / need to be updated on resize.
795     * That is "end" in "absolute" mode and all key frames in "relative" mode.
796     * Also handles constants, because they may change on resize.
797     */
798    var _updateDependentKeyFrames = function() {
799        var viewportHeight = documentElement.clientHeight;
800        var processedConstants = _processConstants();
801        var skrollable;
802        var element;
803        var anchorTarget;
804        var keyFrames;
805        var keyFrameIndex;
806        var keyFramesLength;
807        var kf;
808        var skrollableIndex;
809        var skrollablesLength;
810        var offset;
811        var constantValue;
812
813        //First process all relative-mode elements and find the max key frame.
814        skrollableIndex = 0;
815        skrollablesLength = _skrollables.length;
816
817        for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
818            skrollable = _skrollables[skrollableIndex];
819            element = skrollable.element;
820            anchorTarget = skrollable.anchorTarget;
821            keyFrames = skrollable.keyFrames;
822
823            keyFrameIndex = 0;
824            keyFramesLength = keyFrames.length;
825
826            for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
827                kf = keyFrames[keyFrameIndex];
828
829                offset = kf.offset;
830                constantValue = processedConstants[kf.constant] || 0;
831
832                kf.frame = offset;
833
834                if(kf.isPercentage) {
835                    //Convert the offset to percentage of the viewport height.
836                    offset = offset * viewportHeight;
837
838                    //Absolute + percentage mode.
839                    kf.frame = offset;
840                }
841
842                if(kf.mode === 'relative') {
843                    _reset(element);
844
845                    kf.frame = _instance.relativeToAbsolute(anchorTarget, kf.anchors[0], kf.anchors[1]) - offset;
846
847                    _reset(element, true);
848                }
849
850                kf.frame += constantValue;
851
852                //Only search for max key frame when forceHeight is enabled.
853                if(_forceHeight) {
854                    //Find the max key frame, but don't use one of the data-end ones for comparison.
855                    if(!kf.isEnd && kf.frame > _maxKeyFrame) {
856                        _maxKeyFrame = kf.frame;
857                    }
858                }
859            }
860        }
861
862        //#133: The document can be larger than the maxKeyFrame we found.
863        _maxKeyFrame = Math.max(_maxKeyFrame, _getDocumentHeight());
864
865        //Now process all data-end keyframes.
866        skrollableIndex = 0;
867        skrollablesLength = _skrollables.length;
868
869        for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
870            skrollable = _skrollables[skrollableIndex];
871            keyFrames = skrollable.keyFrames;
872
873            keyFrameIndex = 0;
874            keyFramesLength = keyFrames.length;
875
876            for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
877                kf = keyFrames[keyFrameIndex];
878
879                constantValue = processedConstants[kf.constant] || 0;
880
881                if(kf.isEnd) {
882                    kf.frame = _maxKeyFrame - kf.offset + constantValue;
883                }
884            }
885
886            skrollable.keyFrames.sort(_keyFrameComparator);
887        }
888    };
889
890    /**
891     * Calculates and sets the style properties for the element at the given frame.
892     * @param fakeFrame The frame to render at when smooth scrolling is enabled.
893     * @param actualFrame The actual frame we are at.
894     */
895    var _calcSteps = function(fakeFrame, actualFrame) {
896        //Iterate over all skrollables.
897        var skrollableIndex = 0;
898        var skrollablesLength = _skrollables.length;
899
900        for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
901            var skrollable = _skrollables[skrollableIndex];
902            var element = skrollable.element;
903            var frame = skrollable.smoothScrolling ? fakeFrame : actualFrame;
904            var frames = skrollable.keyFrames;
905            var firstFrame = frames[0].frame;
906            var lastFrame = frames[frames.length - 1].frame;
907            var beforeFirst = frame < firstFrame;
908            var afterLast = frame > lastFrame;
909            var firstOrLastFrame = frames[beforeFirst ? 0 : frames.length - 1];
910            var key;
911            var value;
912
913            //If we are before/after the first/last frame, set the styles according to the given edge strategy.
914            if(beforeFirst || afterLast) {
915                //Check if we already handled this edge case last time.
916                //Note: using setScrollTop it's possible that we jumped from one edge to the other.
917                if(beforeFirst && skrollable.edge === -1 || afterLast && skrollable.edge === 1) {
918                    continue;
919                }
920
921                //Add the skrollr-before or -after class.
922                _updateClass(element, [beforeFirst ? SKROLLABLE_BEFORE_CLASS : SKROLLABLE_AFTER_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_BETWEEN_CLASS, SKROLLABLE_AFTER_CLASS]);
923
924                //Remember that we handled the edge case (before/after the first/last keyframe).
925                skrollable.edge = beforeFirst ? -1 : 1;
926
927                switch(skrollable.edgeStrategy) {
928                    case 'reset':
929                        _reset(element);
930                        continue;
931                    case 'ease':
932                        //Handle this case like it would be exactly at first/last keyframe and just pass it on.
933                        frame = firstOrLastFrame.frame;
934                        break;
935                    default:
936                    case 'set':
937                        var props = firstOrLastFrame.props;
938
939                        for(key in props) {
940                            if(hasProp.call(props, key)) {
941                                value = _interpolateString(props[key].value);
942
943                                skrollr.setStyle(element, key, value);
944                            }
945                        }
946
947                        continue;
948                }
949            } else {
950                //Did we handle an edge last time?
951                if(skrollable.edge !== 0) {
952                    _updateClass(element, [SKROLLABLE_CLASS, SKROLLABLE_BETWEEN_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_AFTER_CLASS]);
953                    skrollable.edge = 0;
954                }
955            }
956
957            //Find out between which two key frames we are right now.
958            var keyFrameIndex = 0;
959            var framesLength = frames.length - 1;
960
961            for(; keyFrameIndex < framesLength; keyFrameIndex++) {
962                if(frame >= frames[keyFrameIndex].frame && frame <= frames[keyFrameIndex + 1].frame) {
963                    var left = frames[keyFrameIndex];
964                    var right = frames[keyFrameIndex + 1];
965
966                    for(key in left.props) {
967                        if(hasProp.call(left.props, key)) {
968                            var progress = (frame - left.frame) / (right.frame - left.frame);
969
970                            //Transform the current progress using the given easing function.
971                            progress = left.props[key].easing(progress);
972
973                            //Interpolate between the two values
974                            value = _calcInterpolation(left.props[key].value, right.props[key].value, progress);
975
976                            value = _interpolateString(value);
977
978                            skrollr.setStyle(element, key, value);
979                        }
980                    }
981
982                    break;
983                }
984            }
985        }
986    };
987
988    /**
989     * Renders all elements.
990     */
991    var _render = function() {
992        if(_requestReflow) {
993            _requestReflow = false;
994            _reflow();
995        }
996
997        //We may render something else than the actual scrollbar position.
998        var renderTop = _instance.getScrollTop();
999
1000        //If there's an animation, which ends in current render call, call the callback after rendering.
1001        var afterAnimationCallback;
1002        var now = _now();
1003        var progress;
1004
1005        //Before actually rendering handle the scroll animation, if any.
1006        if(_scrollAnimation) {
1007            //It's over
1008            if(now >= _scrollAnimation.endTime) {
1009                renderTop = _scrollAnimation.targetTop;
1010                afterAnimationCallback = _scrollAnimation.done;
1011                _scrollAnimation = undefined;
1012            } else {
1013                //Map the current progress to the new progress using given easing function.
1014                progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration);
1015
1016                renderTop = (_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0;
1017            }
1018
1019            _instance.setScrollTop(renderTop, true);
1020        }
1021        //Smooth scrolling only if there's no animation running and if we're not forcing the rendering.
1022        else if(!_forceRender) {
1023            var smoothScrollingDiff = _smoothScrolling.targetTop - renderTop;
1024
1025            //The user scrolled, start new smooth scrolling.
1026            if(smoothScrollingDiff) {
1027                _smoothScrolling = {
1028                    startTop: _lastTop,
1029                    topDiff: renderTop - _lastTop,
1030                    targetTop: renderTop,
1031                    startTime: _lastRenderCall,
1032                    endTime: _lastRenderCall + _smoothScrollingDuration
1033                };
1034            }
1035
1036            //Interpolate the internal scroll position (not the actual scrollbar).
1037            if(now <= _smoothScrolling.endTime) {
1038                //Map the current progress to the new progress using easing function.
1039                progress = easings.sqrt((now - _smoothScrolling.startTime) / _smoothScrollingDuration);
1040
1041                renderTop = (_smoothScrolling.startTop + progress * _smoothScrolling.to
1041pDiff) | 0;
1042            }
1043        }
1044
1045        //That's were we actually "scroll" on mobile.
1046        if(_isMobile && _skrollrBody) {
1047            //Set the transform ("scroll it").
1048            skrollr.setStyle(_skrollrBody, 'transform', 'translate(0, ' + -(_mobileOffset) + 'px) ' + _translateZ);
1049        }
1050
1051        //Did the scroll position even change?
1052        if(_forceRender || _lastTop !== renderTop) {
1053            //Remember in which direction are we scrolling?
1054            _direction = (renderTop > _lastTop) ? 'down' : (renderTop < _lastTop ? 'up' : _direction);
1055
1056            _forceRender = false;
1057
1058            var listenerParams = {
1059                curTop: renderTop,
1060                lastTop: _lastTop,
1061                maxTop: _maxKeyFrame,
1062                direction: _direction
1063            };
1064
1065            //Tell the listener we are about to render.
1066            var continueRendering = _listeners.beforerender && _listeners.beforerender.call(_instance, listenerParams);
1067
1068            //The beforerender listener function is able the cancel rendering.
1069            if(continueRendering !== false) {
1070                //Now actually interpolate all the styles.
1071                _calcSteps(renderTop, _instance.getScrollTop());
1072
1073                //Remember when we last rendered.
1074                _lastTop = renderTop;
1075
1076                if(_listeners.render) {
1077                    _listeners.render.call(_instance, listenerParams);
1078                }
1079            }
1080
1081            if(afterAnimationCallback) {
1082                afterAnimationCallback.call(_instance, false);
1083            }
1084        }
1085
1086        _lastRenderCall = now;
1087    };
1088
1089    /**
1090     * Parses the properties for each key frame of the given skrollable.
1091     */
1092    var _parseProps = function(skrollable) {
1093        //Iterate over all key frames
1094        var keyFrameIndex = 0;
1095        var keyFramesLength = skrollable.keyFrames.length;
1096
1097        for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
1098            var frame = skrollable.keyFrames[keyFrameIndex];
1099            var easing;
1100            var value;
1101            var prop;
1102            var props = {};
1103
1104            var match;
1105
1106            while((match = rxPropValue.exec(frame.props)) !== null) {
1107                prop = match[1];
1108                value = match[2];
1109
1110                easing = prop.match(rxPropEasing);
1111
1112                //Is there an easing specified for this prop?
1113                if(easing !== null) {
1114                    prop = easing[1];
1115                    easing = easing[2];
1116                } else {
1117                    easing = DEFAULT_EASING;
1118                }
1119
1120                //Exclamation point at first position forces the value to be taken literal.
1121                value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)];
1122
1123                //Save the prop for this key frame with his value and easing function
1124                props[prop] = {
1125                    value: value,
1126                    easing: easings[easing]
1127                };
1128            }
1129
1130            frame.props = props;
1131        }
1132    };
1133
1134    /**
1135     * Parses a value extracting numeric values and generating a format string
1136     * for later interpolation of the new values in old string.
1137     *
1138     * @param val The CSS value to be parsed.
1139     * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7]
1140     * where the first element is the format string later used
1141     * and all following elements are the numeric value.
1142     */
1143    var _parseProp = function(val) {
1144        var numbers = [];
1145
1146        //One special case, where floats don't work.
1147        //We replace all occurences of rgba colors
1148        //which don't use percentage notation with the percentage notation.
1149        rxRGBAIntegerColor.lastIndex = 0;
1150        val = val.replace(rxRGBAIntegerColor, function(rgba) {
1151            return rgba.replace(rxNumericValue, function(n) {
1152                return n / 255 * 100 + '%';
1153            });
1154        });
1155
1156        //Handle prefixing of "gradient" values.
1157        //For now only the prefixed value will be set. Unprefixed isn't supported anyway.
1158        if(theDashedCSSPrefix) {
1159            rxGradient.lastIndex = 0;
1160            val = val.replace(rxGradient, function(s) {
1161                return theDashedCSSPrefix + s;
1162            });
1163        }
1164
1165        //Now parse ANY number inside this string and create a format str
1165ing.
1166        val = val.replace(rxNumericValue, function(n) {
1167            numbers.push(+n);
1168            return '{?}';
1169        });
1170
1171        //Add the formatstring as first value.
1172        numbers.unshift(val);
1173
1174        return numbers;
1175    };
1176
1177    /**
1178     * Fills the key frames with missing left and right hand properties.
1179     * If key frame 1 has property X and key frame 2 is missing X,
1180     * but key frame 3 has X again, then we need to assign X to key frame 2 too.
1181     *
1182     * @param sk A skrollable.
1183     */
1184    var _fillProps = function(sk) {
1185        //Will collect the properties key frame by key frame
1186        var propList = {};
1187        var keyFrameIndex;
1188        var keyFramesLength;
1189
1190        //Iterate over all key frames from left to right
1191        keyFrameIndex = 0;
1192        keyFramesLength = sk.keyFrames.length;
1193
1194        for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
1195            _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
1196        }
1197
1198        //Now do the same from right to fill the last gaps
1199
1200        propList = {};
1201
1202        //Iterate over all key frames from right to left
1203        keyFrameIndex = sk.keyFrames.length - 1;
1204
1205        for(; keyFrameIndex >= 0; keyFrameIndex--) {
1206            _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
1207        }
1208    };
1209
1210    var _fillPropForFrame = function(frame, propList) {
1211        var key;
1212
1213        //For each key frame iterate over all right hand properties and assign them,
1214        //but only if the current key frame doesn't have the property by itself
1215        for(key in propList) {
1216            //The current frame misses this property, so assign it.
1217            if(!hasProp.call(frame.props, key)) {
1218                frame.props[key] = propList[key];
1219            }
1220        }
1221
1222        //Iterate over all props of the current frame and collect them
1223        for(key in frame.props) {
1224            propList[key] = frame.props[key];
1225        }
1226    };
1227
1228    /**
1229     * Calculates the new values for two given values array.
1230     */
1231    var _calcInterpolation = function(val1, val2, progress) {
1232        var valueIndex;
1233        var val1Length = val1.length;
1234
1235        //They both need to have the same length
1236        if(val1Length !== val2.length) {
1237            throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"';
1238        }
1239
1240        //Add the format string as first element.
1241        var interpolated = [val1[0]];
1242
1243        valueIndex = 1;
1244
1245        for(; valueIndex < val1Length; valueIndex++) {
1246            //That's the line where the two numbers are actually interpolated.
1247            interpolated[valueIndex] = val1[valueIndex] + ((val2[valueIndex] - val1[valueIndex]) * progress);
1248        }
1249
1250        return interpolated;
1251    };
1252
1253    /**
1254     * Interpolates the numeric values into the format string.
1255     */
1256    var _interpolateString = function(val) {
1257        var valueIndex = 1;
1258
1259        rxInterpolateString.lastIndex = 0;
1260
1261        return val[0].replace(rxInterpolateString, function() {
1262            return val[valueIndex++];
1263        });
1264    };
1265
1266    /**
1267     * Resets the class and style attribute to what it was before skrollr manipulated the element.
1268     * Also remembers the values it had before reseting, in order to undo the reset.
1269     */
1270    var _reset = function(elements, undo) {
1271        //We accept a single element or an array of elements.
1272        elements = [].concat(elements);
1273
1274        var skrollable;
1275        var element;
1276        var elementsIndex = 0;
1277        var elementsLength = elements.length;
1278
1279        for(; elementsIndex < elementsLength; elementsIndex++) {
1280            element = elements[elementsIndex];
1281            skrollable = _skrollables[element[SKROLLABLE_ID_DOM_PROPERTY]];
1282
1283            //Couldn't find the skrollable for this DOM element.
1284            if(!skrollable) {
1285                continue;
1286            }
1287
1288            if(undo) {
1289                //Reset class and style to the "dirty" (set by skrollr) values.
1290                element.style.cssText = skrollable.dirtyStyleAttr;
1291                _updateClass(element, skrollable.dirtyClassAttr);
1292            } else {
1293                //Remember the "dirty" (set by skrollr) class and style.
1294                skrollable.dirtyStyleAttr = element.style.cssText;
1295                skrollable.dirtyClassAttr = _getClass(element);
1296
1297                //Reset class and style to what it originally was.
1298                element.style.cssText = skrollable.styleAttr;
1299                _updateClass(element, skrollable.classAttr);
1300            }
1301        }
1302    };
1303
1304    /**
1305     * Detects support for 3d transforms by applying it to the skrollr-body.
1306     */
1307    var _detect3DTransforms = function() {
1308        _translateZ = 'translateZ(0)';
1309        skrollr.setStyle(_skrollrBody, 'transform', _translateZ);
1310
1311        var computedStyle = getStyle(_skrollrBody);
1312        var computedTransform = computedStyle.getPropertyValue('transform');
1313        var computedTransformWithPrefix = computedStyle.getPropertyValue(theDashedCSSPrefix + 'transform');
1314        var has3D = (computedTransform && computedTransform !== 'none') || (computedTransformWithPrefix && computedTransformWithPrefix !== 'none');
1315
1316        if(!has3D) {
1317            _translateZ = '';
1318        }
1319    };
1320
1321    /**
1322     * Set the CSS property on the given element. Sets prefixed properties as well.
1323     */
1324    skrollr.setStyle = function(el, prop, val) {
1325        var style = el.style;
1326
1327        //Camel case.
1328        prop = prop.replace(rxCamelCase, rxCamelCaseFn).replace('-', '');
1329
1330        //Make sure z-index gets a <integer>.
1331        //This is the only <integer> case we need to handle.
1332        if(prop === 'zIndex') {
1333            if(isNaN(val)) {
1334                //If it's not a number, don't touch it.
1335                //It could for example be "auto" (#351).
1336                style[prop] = val;
1337            } else {
1338                //Floor the number.
1339                style[prop] = '' + (val | 0);
1340            }
1341        }
1342        //#64: "float" can't be set across browsers. Needs to use "cssFloat" for all except IE.
1343        else if(prop === 'float') {
1344            style.styleFloat = style.cssFloat = val;
1345        }
1346        else {
1347            //Need try-catch for old IE.
1348            try {
1349                //Set prefixed property if there's a prefix.
1350                if(theCSSPrefix) {
1351                    style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val;
1352                }
1353
1354                //Set unprefixed.
1355                style[prop] = val;
1356            } catch(ignore) {}
1357        }
1358    };
1359
1360    /**
1361     * Cross browser event handling.
1362     */
1363    var _addEvent = skrollr.addEvent = function(element, names, callback) {
1364        var intermediate = function(e) {
1365            //Normalize IE event stuff.
1366            e = e || window.event;
1367
1368            if(!e.target) {
1369                e.target = e.srcElement;
1370            }
1371
1372            if(!e.preventDefault) {
1373                e.preventDefault = function() {
1374                    e.returnValue = false;
1375                };
1376            }
1377
1378            return callback.call(this, e);
1379        };
1380
1381        names = names.split(' ');
1382
1383        var name;
1384        var nameCounter = 0;
1385        var namesLength = names.length;
1386
1387        for(; nameCounter < namesLength; nameCounter++) {
1388            name = names[nameCounter];
1389
1390            if(element.addEventListener) {
1391                element.addEventListener(name, callback, false);
1392            } else {
1393                element.attachEvent('on' + name, intermediate);
1394            }
1395
1396            //Remember the events to be able to flush them later.
1397            _registeredEvents.push({
1398                element: element,
1399                name: name,
1400                listener: callback
1401            });
1402        }
1403    };
1404
1405    var _removeEvent = skrollr.removeEvent = function(element, names, callback) {
1406        names = names.split(' ');
1407
1408        var nameCounter = 0;
1409        var namesLength = names.length;
1410
1411        for(; nameCounter < namesLength; nameCounter++) {
1412            if(element.removeEventListener) {
1413                element.removeEventListener(names[nameCounter], callback, false);
1414            } else {
1415                element.detachEvent('on' + names[nameCounter], callback);
1416            }
1417        }
1418    };
1419
1420    var _removeAllEvents = function() {
1421        var eventData;
1422        var eventCounter = 0;
1423        var eventsLength = _registeredEvents.length;
1424
1425        for(; eventCounter < eventsLength; eventCounter++) {
1426            eventData = _registeredEvents[eventCounter];
1427
1428            _removeEvent(eventData.element, eventData.name, eventData.listener);
1429        }
1430
1431        _registeredEvents = [];
1432    };
1433
1434    var _reflow = function() {
1435        var pos = _instance.getScrollTop();
1436
1437        //Will be recalculated by _updateDependentKeyFrames.
1438        _maxKeyFrame = 0;
1439
1440        if(_forceHeight && !_isMobile) {
1441            //un-"force" the height to not mess with the calculations in _updateDependentKeyFrames (#216).
1442            body.style.height = 'auto';
1443        }
1444
1445        _updateDependentKeyFrames();
1446
1447        if(_forceHeight && !_isMobile) {
1448            //"force" the height.
1449            body.style.height = (_maxKeyFrame + documentElement.clientHeight) + 'px';
1450        }
1451
1452        //The scroll offset may now be larger than needed (on desktop the browser/os prevents scrolling farther than the bottom).
1453        if(_isMobile) {
1454            _instance.setScrollTop(Math.min(_instance.getScrollTop(), _maxKeyFrame));
1455        } else {
1456            //Remember and reset the scroll pos (#217).
1457            _instance.setScrollTop(pos, true);
1458        }
1459
1460        _forceRender = true;
1461    };
1462
1463    /*
1464     * Returns a copy of the constants object where all functions and strings have been evaluated.
1465     */
1466    var _processConstants = function() {
1467        var viewportHeight = documentElement.clientHeight;
1468        var copy = {};
1469        var prop;
1470        var value;
1471
1472        for(prop in _constants) {
1473            value = _constants[prop];
1474
1475            if(typeof value === 'function') {
1476                value = value.call(_instance);
1477            }
1478            //Percentage offset.
1479            else if((/p$/).test(value)) {
1480                value = (value.slice(0, -1) / 100) * viewportHeight;
1481            }
1482
1483            copy[prop] = value;
1484        }
1485
1486        return copy;
1487    };
1488
1489    /*
1490     * Returns the height of the document.
1491     */
1492    var _getDocumentHeight = function() {
1493        var skrollrBodyHeight = (_skrollrBody && _skrollrBody.offsetHeight || 0);
1494        var bodyHeight = Math.max(skrollrBodyHeight, body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight);
1495
1496        return bodyHeight - documentElement.clientHeight;
1497    };
1498
1499    /**
1500     * Returns a string of space separated classnames for the current element.
1501     * Works with SVG as well.
1502     */
1503    var _getClass = function(element) {
1504        var prop = 'className';
1505
1506        //SVG support by using className.baseVal instead of just className.
1507        if(window.SVGElement && element instanceof window.SVGElement) {
1508            element = element[prop];
1509            prop = 'baseVal';
1510        }
1511
1512        return element[prop];
1513    };
1514
1515    /**
1516     * Adds and removes a CSS classes.
1517     * Works with SVG as well.
1518     * add and remove are arrays of strings,
1519     * or if remove is ommited add is a string and overwrites all classes.
1520     */
1521    var _updateClass = function(element, add, remove) {
1522        var prop = 'className';
1523
1524        //SVG support by using className.baseVal instead of just className.
1525        if(window.SVGElement && element instanceof window.SVGElement) {
1526            element = element[prop];
1527            prop = 'baseVal';
1528        }
1529
1530        //When remove is ommited, we want to overwrite/set the classes.
1531        if(remove === undefined) {
1532            element[prop] = add;
1533            return;
1534        }
1535
1536        //Cache current classes. We will work on a string before passing back to DOM.
1537        var val = element[prop];
1538
1539        //All classes to be removed.
1540        var classRemoveIndex = 0;
1541        var removeLength = remove.length;
1542
1543        for(; classRemoveIndex < removeLength; classRemoveIndex++) {
1544            val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' ');
1545        }
1546
1547        val = _trim(val);
1548
1549        //All classes to be added.
1550        var classAddIndex = 0;
1551        var addLength = add.length;
1552
1553        for(; classAddIndex < addLength; classAddIndex++) {
1554            //Only add if el not already has class.
1555            if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) {
1556                val += ' ' + add[classAddIndex];
1557            }
1558        }
1559
1560        element[prop] = _trim(val);
1561    };
1562
1563    var _trim = function(a) {
1564        return a.replace(rxTrim, '');
1565    };
1566
1567    /**
1568     * Adds a space before and after the string.
1569     */
1570    var _untrim = function(a) {
1571        return ' ' + a + ' ';
1572    };
1573
1574    var _now = Date.now || function() {
1575        return +new Date();
1576    };
1577
1578    var _keyFrameComparator = function(a, b) {
1579        return a.frame - b.frame;
1580    };
1581
1582    /*
1583     * Private variables.
1584     */
1585
1586    //Singleton
1587    var _instance;
1588
1589    /*
1590        A list of all elements which should be animated associated with their the metadata.
1591        Exmaple skrollable with two key frames animating from 100px width to 20px:
1592
1593        skrollable = {
1594            element: <the DOM element>,
1595            styleAttr: <style attribute of the element before skrollr>,
1596            classAttr: <class attribute of the element before skrollr>,
1597            keyFrames: [
1598                {
1599                    frame: 100,
1600                    props: {
1601                        width: {
1602                            value: ['{?}px', 100],
1603                            easing: <reference to easing function>
1604                        }
1605                    },
1606                    mode: "absolute"
1607                },
1608                {
1609                    frame: 200,
1610                    props: {
1611                        width: {
1612                            value: ['{?}px', 20],
1613                            easing: <reference to easing function>
1614                        }
1615                    },
1616                    mode: "absolute"
1617                }
1618            ]
1619        };
1620    */
1621    var _skrollables;
1622
1623    var _skrollrBody;
1624
1625    var _listeners;
1626    var _forceHeight;
1627    var _maxKeyFrame = 0;
1628
1629    var _scale = 1;
1630    var _constants;
1631
1632    var _mobileDeceleration;
1633
1634    //Current direction (up/down).
1635    var _direction = 'down';
1636
1637    //The last top offset value. Needed to determine direction.
1638    var _lastTop = -1;
1639
1640    //The last time we called the render method (doesn't mean we rendered!).
1641    var _lastRenderCall = _now();
1642
1643    //For detecting if it actually resized (#271).
1644    var _lastViewportWidth = 0;
1645    var _lastViewportHeight = 0;
1646
1647    var _requestReflow = false;
1648
1649    //Will contain data about a running scrollbar animation, if any.
1650    var _scrollAnimation;
1651
1652    var _smoothScrollingEnabled;
1653
1654    var _smoothScrollingDuration;
1655
1656    //Will contain settins for smooth scrolling if enabled.
1657    var _smoothScrolling;
1658
1659    //Can be set by any operation/event to force rendering even if the scrollbar didn't move.
1660    var _forceRender;
1661
1662    //Each skrollable gets an unique ID incremented for each skrollable.
1663    //The ID is the index in the _skrollables array.
1664    var _skrollableIdCounter = 0;
1665
1666    var _edgeStrategy;
1667
1668
1669    //Mobile specific vars. Will be stripped by UglifyJS when not in use.
1670    var _isMobile = false;
1671
1672    //The virtual scroll offset when using mobile scrolling.
1673    var _mobileOffset = 0;
1674
1675    //If the browser supports 3d transforms, this will be filled with 'translateZ(0)' (empty string otherwise).
1676    var _translateZ;
1677
1678    //Will contain data about registered events by skrollr.
1679    var _registeredEvents = [];
1680
1681    //Animation frame id returned by RequestAnimationFrame (or timeout when RAF is not supported).
1682    var _animFrame;
1683
1684    //Expose skrollr as either a global variable or a require.js module
1685    if(typeof define === 'function' && define.amd) {
1686        define('skrollr', function () {
1687            return skrollr;
1688        });
1689    } else {
1690        window.skrollr = skrollr;
1691    }
1692
1693}(window, document));
1694
1695
1696
1697
1698
1699
1700
1701
1702// Magnific Popup v0.9.9 by Dmitry Semenov
1703// http://bit.ly/magnific-popup#build=inline+image+gallery+retina+imagezoom+fastclick
1704(function(a){var b="Close",c="BeforeClose",d="AfterClose",e="BeforeAppend",f="MarkupParse",g="Open",h="Change",i="mfp",j="."+i,k="mfp-ready",l="mfp-removing",m="mfp-prevent-close",n,o=function(){},p=!!window.jQuery,q,r=a(window),s,t,u,v,w,x=function(a,b){n.ev.on(i+a+j,b)},y=function(b,c,d,e){var f=document.createElement("div");return f.className="mfp-"+b,d&&(f.innerHTML=d),e?c&&c.appendChild(f):(f=a(f),c&&f.appendTo(c)),f},z=function(b,c){n.ev.triggerHandler(i+b,c),n.st.callbacks&&(b=b.charAt(0).toLowerCase()+b.slice(1),n.st.callbacks[b]&&n.st.callbacks[b].apply(n,a.isArray(c)?c:[c]))},A=function(b){if(b!==w||!n.currTemplate.closeBtn)n.currTemplate.closeBtn=a(n.st.closeMarkup.replace("%title%",n.st.tClose)),w=b;return n.currTemplate.closeBtn},B=function(){a.magnificPopup.instance||(n=new o,n.init(),a.magnificPopup.instance=n)},C=function(){var a=document.createElement("p").style,b=["ms","O","Moz","Webkit"];if(a.transition!==undefined)return!0;while(b.length)if(b.pop()+"Transition"in a)return!0;return!1};o.prototype={constructor:o,init:function(){var b=navigator.appVersion;n.isIE7=b.indexOf("MSIE 7.")!==-1,n.isIE8=b.indexOf("MSIE 8.")!==-1,n.isLowIE=n.isIE7||n.isIE8,n.isAndroid=/android/gi.test(b),n.isIOS=/iphone|ipad|ipod/gi.test(b),n.supportsTransition=C(),n.probablyMobile=n.isAndroid||n.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),t=a(document),n.popupsCache={}},open:function(b){s||(s=a(document.body));var c;if(b.isObj===!1){n.items=b.items.toArray(),n.index=0;var d=b.items,e;for(c=0;c<d.length;c++){e=d[c],e.parsed&&(e=e.el[0]);if(e===b.el[0]){n.index=c;break}}}else n.items=a.isArray(b.items)?b.items:[b.items],n.index=b.index||0;if(n.isOpen){n.updateItemHTML();return}n.types=[],v="",b.mainEl&&b.mainEl.length?n.ev=b.mainEl.eq(0):n.ev=t,b.key?(n.popupsCache[b.key]||(n.popupsCache[b.key]={}),n.currTemplate=n.popupsCache[b.key]):n.currTemplate={},n.st=a.extend(!0,{},a.magnificPopup.defaults,b),n.fixedContentPos=n.st.fixedContentPos==="auto"?!n.probablyMobile:n.st.fixedContentPos,n.st.modal&&(n.st.closeOnContentClick=!1,n.st.closeOnBgClick=!1,n.st.showCloseBtn=!1,n.st.enableEscapeKey=!1),n.bgOverlay||(n.bgOverlay=y("bg").on("click"+j,function(){n.close()}),n.wrap=y("wrap").attr("tabindex",-1).on("click"+j,function(a){n._checkIfClose(a.target)&&n.close()}),n.container=y("container",n.wrap)),n.contentContainer=y("content"),n.st.preloader&&(n.preloader=y("preloader",n.container,n.st.tLoading));var h=a.magnificPopup.modules;for(c=0;c<h.length;c++){var i=h[c];i=i.charAt(0).toUpperCase()+i.slice(1),n["init"+i].call(n)}z("BeforeOpen"),n.st.showCloseBtn&&(n.st.closeBtnInside?(x(f,function(a,b,c,d){c.close_replaceWith=A(d.type)}),v+=" mfp-close-btn-in"):n.wrap.append(A())),n.st.alignTop&&(v+=" mfp-align-top"),n.fixedContentPos?n.wrap.css({overflow:n.st.overflowY,overflowX:"hidden",overflowY:n.st.overflowY}):n.wrap.css({top:r.scrollTop(),position:"absolute"}),(n.st.fixedBgPos===!1||n.st.fixedBgPos==="auto"&&!n.fixedContentPos)&&n.bgOverlay.css({height:t.height(),position:"absolute"}),n.st.enableEscapeKey&&t.on("keyup"+j,function(a){a.keyCode===27&&n.close()}),r.on("resize"+j,function(){n.updateSize()}),n.st.closeOnContentClick||(v+=" mfp-auto-cursor"),v&&n.wrap.addClass(v);var l=n.wH=r.height(),m={};if(n.fixedContentPos&&n._hasScrollBar(l)){var o=n._getScrollbarSize();o&&(m.marginRight=o)}n.fixedContentPos&&(n.isIE7?a("body, html").css("overflow","hidden"):m.overflow="hidden");var p=n.st.mainClass;return n.isIE7&&(p+=" mfp-ie7"),p&&n._addClassToMFP(p),n.updateItemHTML(),z("BuildControls"),a("html").css(m),n.bgOverlay.add(n.wrap).prependTo(n.st.prepe
1704ndTo||s),n._lastFocusedEl=document.activeElement,setTimeout(function(){n.content?(n._addClassToMFP(k),n._setFocus()):n.bgOverlay.addClass(k),t.on("focusin"+j,n._onFocusIn)},16),n.isOpen=!0,n.updateSize(l),z(g),b},close:function(){if(!n.isOpen)return;z(c),n.isOpen=!1,n.st.removalDelay&&!n.isLowIE&&n.supportsTransition?(n._addClassToMFP(l),setTimeout(function(){n._close()},n.st.removalDelay)):n._close()},_close:function(){z(b);var c=l+" "+k+" ";n.bgOverlay.detach(),n.wrap.detach(),n.container.empty(),n.st.mainClass&&(c+=n.st.mainClass+" "),n._removeClassFromMFP(c);if(n.fixedContentPos){var e={marginRight:""};n.isIE7?a("body, html").css("overflow",""):e.overflow="",a("html").css(e)}t.off("keyup"+j+" focusin"+j),n.ev.off(j),n.wrap.attr("class","mfp-wrap").removeAttr("style"),n.bgOverlay.attr("class","mfp-bg"),n.container.attr("class","mfp-container"),n.st.showCloseBtn&&(!n.st.closeBtnInside||n.currTemplate[n.currItem.type]===!0)&&n.currTemplate.closeBtn&&n.currTemplate.closeBtn.detach(),n._lastFocusedEl&&a(n._lastFocusedEl).focus(),n.currItem=null,n.content=null,n.currTemplate=null,n.prevHeight=0,z(d)},updateSize:function(a){if(n.isIOS){var b=document.documentElement.clientWidth/window.innerWidth,c=window.innerHeight*b;n.wrap.css("height",c),n.wH=c}else n.wH=a||r.height();n.fixedContentPos||n.wrap.css("height",n.wH),z("Resize")},updateItemHTML:function(){var b=n.items[n.index];n.contentContainer.detach(),n.content&&n.content.detach(),b.parsed||(b=n.parseEl(n.index));var c=b.type;z("BeforeChange",[n.currItem?n.currItem.type:"",c]),n.currItem=b;if(!n.currTemplate[c]){var d=n.st[c]?n.st[c].markup:!1;z("FirstMarkupParse",d),d?n.currTemplate[c]=a(d):n.currTemplate[c]=!0}u&&u!==b.type&&n.container.removeClass("mfp-"+u+"-holder");var e=n["get"+c.charAt(0).toUpperCase()+c.slice(1)](b,n.currTemplate[c]);n.appendContent(e,c),b.preloaded=!0,z(h,b),u=b.type,n.container.prepend(n.contentContainer),z("AfterChange")},appendContent:function(a,b){n.content=a,a?n.st.showCloseBtn&&n.st.closeBtnInside&&n.currTemplate[b]===!0?n.content.find(".mfp-close").length||n.content.append(A()):n.content=a:n.content="",z(e),n.container.addClass("mfp-"+b+"-holder"),n.contentContainer.append(n.content)},parseEl:function(b){var c=n.items[b],d;c.tagName?c={el:a(c)}:(d=c.type,c={data:c,src:c.src});if(c.el){var e=n.types;for(var f=0;f<e.length;f++)if(c.el.hasClass("mfp-"+e[f])){d=e[f];break}c.src=c.el.attr("data-mfp-src"),c.src||(c.src=c.el.attr("href"))}return c.type=d||n.st.type||"inline",c.index=b,c.parsed=!0,n.items[b]=c,z("ElementParse",c),n.items[b]},addGroup:function(a,b){var c=function(c){c.mfpEl=this,n._openClick(c,a,b)};b||(b={});var d="click.magnificPopup";b.mainEl=a,b.items?(b.isObj=!0,a.off(d).on(d,c)):(b.isObj=!1,b.delegate?a.off(d).on(d,b.delegate,c):(b.items=a,a.off(d).on(d,c)))},_openClick:function(b,c,d){var e=d.midClick!==undefined?d.midClick:a.magnificPopup.defaults.midClick;if(!e&&(b.which===2||b.ctrlKey||b.metaKey))return;var f=d.disableOn!==undefined?d.disableOn:a.magnificPopup.defaults.disableOn;if(f)if(a.isFunction(f)){if(!f.call(n))return!0}else if(r.width()<f)return!0;b.type&&(b.preventDefault(),n.isOpen&&b.stopPropagation()),d.el=a(b.mfpEl),d.delegate&&(d.items=c.find(d.delegate)),n.open(d)},updateStatus:function(a,b){if(n.preloader){q!==a&&n.container.removeClass("mfp-s-"+q),!b&&a==="loading"&&(b=n.st.tLoading);var c={status:a,text:b};z("UpdateStatus",c),a=c.status,b=c.text,n.preloader.html(b),n.preloader.find("a").on("click",function(a){a.stopImmediatePropagation()}),n.container.addClass("mfp-s-"+a),q=a}},_checkIfClose:function(b){if(a(b).hasClass(m))return;var c=n.st.closeOnContentClick,d=n.st.closeOnBgClick;if(c&&d)return!0;if(!n.content||a(b).hasClass("mfp-close")||n.preloader&&b===n.preloader[0])return!0;if(b!==n.content[0]&&!a.contains(n.content[0],b)){if(d&&a.contains(document,b))return!0}else if(c)return!0;return!1},_addClassToMFP:function(a){n.bgOverlay.addClass(a),n.wrap.addClass(a)},_removeClassFromMFP:function(a){this.bgOverlay.removeClass(a),n.wrap.removeClass(a)},_hasScrollBar:function(a){return(n.isIE7?t.height():document.body.scrollHeight)>
1704(a||r.height())},_setFocus:function(){(n.st.focus?n.content.find(n.st.focus).eq(0):n.wrap).focus()},_onFocusIn:function(b){if(b.target!==n.wrap[0]&&!a.contains(n.wrap[0],b.target))return n._setFocus(),!1},_parseMarkup:function(b,c,d){var e;d.data&&(c=a.extend(d.data,c)),z(f,[b,c,d]),a.each(c,function(a,c){if(c===undefined||c===!1)return!0;e=a.split("_");if(e.length>1){var d=b.find(j+"-"+e[0]);if(d.length>0){var f=e[1];f==="replaceWith"?d[0]!==c[0]&&d.replaceWith(c):f==="img"?d.is("img")?d.attr("src",c):d.replaceWith('<img src="'+c+'" class="'+d.attr("class")+'" />'):d.attr(e[1],c)}}else b.find(j+"-"+a).html(c)})},_getScrollbarSize:function(){if(n.scrollbarSize===undefined){var a=document.createElement("div");a.id="mfp-sbm",a.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(a),n.scrollbarSize=a.offsetWidth-a.clientWidth,document.body.removeChild(a)}return n.scrollbarSize}},a.magnificPopup={instance:null,proto:o.prototype,modules:[],open:function(b,c){return B(),b?b=a.extend(!0,{},b):b={},b.isObj=!0,b.index=c||0,this.instance.open(b)},close:function(){return a.magnificPopup.instance&&a.magnificPopup.instance.close()},registerModule:function(b,c){c.options&&(a.magnificPopup.defaults[b]=c.options),a.extend(this.proto,c.proto),this.modules.push(b)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'<button title="%title%" type="button" class="mfp-close">&times;</button>',tClose:"Close (Esc)",tLoading:"Loading..."}},a.fn.magnificPopup=function(b){B();var c=a(this);if(typeof b=="string")if(b==="open"){var d,e=p?c.data("magnificPopup"):c[0].magnificPopup,f=parseInt(arguments[1],10)||0;e.items?d=e.items[f]:(d=c,e.delegate&&(d=d.find(e.delegate)),d=d.eq(f)),n._openClick({mfpEl:d},c,e)}else n.isOpen&&n[b].apply(n,Array.prototype.slice.call(arguments,1));else b=a.extend(!0,{},b),p?c.data("magnificPopup",b):c[0].magnificPopup=b,n.addGroup(c,b);return c};var D="inline",E,F,G,H=function(){G&&(F.after(G.addClass(E)).detach(),G=null)};a.magnificPopup.registerModule(D,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){n.types.push(D),x(b+"."+D,function(){H()})},getInline:function(b,c){H();if(b.src){var d=n.st.inline,e=a(b.src);if(e.length){var f=e[0].parentNode;f&&f.tagName&&(F||(E=d.hiddenClass,F=y(E),E="mfp-"+E),G=e.after(F).detach().removeClass(E)),n.updateStatus("ready")}else n.updateStatus("error",d.tNotFound),e=a("<div>");return b.inlineElement=e,e}return n.updateStatus("ready"),n._parseMarkup(c,{},b),c}}});var I,J=function(b){if(b.data&&b.data.title!==undefined)return b.data.title;var c=n.st.image.titleSrc;if(c){if(a.isFunction(c))return c.call(n,b);if(b.el)return b.el.attr(c)||""}return""};a.magnificPopup.registerModule("image",{options:{markup:'<div class="mfp-figure"><div class="mfp-close"></div><figure><div class="mfp-img"></div><figcaption><div class="mfp-bottom-bar"><div class="mfp-title"></div><div class="mfp-counter"></div></div></figcaption></figure></div>',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'<a href="%url%">The image</a> could not be loaded.'},proto:{initImage:function(){var a=n.st.image,c=".image";n.types.push("image"),x(g+c,function(){n.currItem.type==="image"&&a.cursor&&s.addClass(a.cursor)}),x(b+c,function(){a.cursor&&s.removeClass(a.cursor),r.off("resize"+j)}),x("Resize"+c,n.resizeImage),n.isLowIE&&x("AfterChange",n.resizeImage)},resizeImage:function(){var a=n.currItem;if(!a||!a.img)return;if(n.st.image.verticalFit){var b=0;n.isLowIE&&(b=parseInt(a.img.css("padding-top"),10)+parseInt(a.img.css("padding-bottom"),10)),a.img.css("max-height",n.wH-b)}},_onImageHasSize:function(a){a.img&&(a.hasSize=!0,I&&clearInterval(I),a.isCheckingImgSize=!1,z("ImageHasSize",a),a.imgHidden&&(n.content&&n.content.removeClass("mfp-loading"),a.imgHidden=!1))},findImageSize:function(a){var b=0,c=a.img[0],d=function(e){I&&clearInterval(I),I=setInterval(function(){if(c.naturalWi
1704dth>0){n._onImageHasSize(a);return}b>200&&clearInterval(I),b++,b===3?d(10):b===40?d(50):b===100&&d(500)},e)};d(1)},getImage:function(b,c){var d=0,e=function(){b&&(b.img[0].complete?(b.img.off(".mfploader"),b===n.currItem&&(n._onImageHasSize(b),n.updateStatus("ready")),b.hasSize=!0,b.loaded=!0,z("ImageLoadComplete")):(d++,d<200?setTimeout(e,100):f()))},f=function(){b&&(b.img.off(".mfploader"),b===n.currItem&&(n._onImageHasSize(b),n.updateStatus("error",g.tError.replace("%url%",b.src))),b.hasSize=!0,b.loaded=!0,b.loadError=!0)},g=n.st.image,h=c.find(".mfp-img");if(h.length){var i=document.createElement("img");i.className="mfp-img",b.img=a(i).on("load.mfploader",e).on("error.mfploader",f),i.src=b.src,h.is("img")&&(b.img=b.img.clone()),i=b.img[0],i.naturalWidth>0?b.hasSize=!0:i.width||(b.hasSize=!1)}return n._parseMarkup(c,{title:J(b),img_replaceWith:b.img},b),n.resizeImage(),b.hasSize?(I&&clearInterval(I),b.loadError?(c.addClass("mfp-loading"),n.updateStatus("error",g.tError.replace("%url%",b.src))):(c.removeClass("mfp-loading"),n.updateStatus("ready")),c):(n.updateStatus("loading"),b.loading=!0,b.hasSize||(b.imgHidden=!0,c.addClass("mfp-loading"),n.findImageSize(b)),c)}}});var K,L=function(){return K===undefined&&(K=document.createElement("p").style.MozTransform!==undefined),K};a.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(a){return a.is("img")?a:a.find("img")}},proto:{initZoom:function(){var a=n.st.zoom,d=".zoom",e;if(!a.enabled||!n.supportsTransition)return;var f=a.duration,g=function(b){var c=b.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),d="all "+a.duration/1e3+"s "+a.easing,e={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},f="transition";return e["-webkit-"+f]=e["-moz-"+f]=e["-o-"+f]=e[f]=d,c.css(e),c},h=function(){n.content.css("visibility","visible")},i,j;x("BuildControls"+d,function(){if(n._allowZoom()){clearTimeout(i),n.content.css("visibility","hidden"),e=n._getItemToZoom();if(!e){h();return}j=g(e),j.css(n._getOffset()),n.wrap.append(j),i=setTimeout(function(){j.css(n._getOffset(!0)),i=setTimeout(function(){h(),setTimeout(function(){j.remove(),e=j=null,z("ZoomAnimationEnded")},16)},f)},16)}}),x(c+d,function(){if(n._allowZoom()){clearTimeout(i),n.st.removalDelay=f;if(!e){e=n._getItemToZoom();if(!e)return;j=g(e)}j.css(n._getOffset(!0)),n.wrap.append(j),n.content.css("visibility","hidden"),setTimeout(function(){j.css(n._getOffset())},16)}}),x(b+d,function(){n._allowZoom()&&(h(),j&&j.remove(),e=null)})},_allowZoom:function(){return n.currItem.type==="image"},_getItemToZoom:function(){return n.currItem.hasSize?n.currItem.img:!1},_getOffset:function(b){var c;b?c=n.currItem.img:c=n.st.zoom.opener(n.currItem.el||n.currItem);var d=c.offset(),e=parseInt(c.css("padding-top"),10),f=parseInt(c.css("padding-bottom"),10);d.top-=a(window).scrollTop()-e;var g={width:c.width(),height:(p?c.innerHeight():c[0].offsetHeight)-f-e};return L()?g["-moz-transform"]=g.transform="translate("+d.left+"px,"+d.top+"px)":(g.left=d.left,g.top=d.top),g}}});var M=function(a){var b=n.items.length;return a>b-1?a-b:a<0?b+a:a},N=function(a,b,c){return a.replace(/%curr%/gi,b+1).replace(/%total%/gi,c)};a.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var c=n.st.gallery,d=".mfp-gallery",e=Boolean(a.fn.mfpFastClick);n.direction=!0;if(!c||!c.enabled)return!1;v+=" mfp-gallery",x(g+d,function(){c.navigateByImgClick&&n.wrap.on("click"+d,".mfp-img",function(){if(n.items.length>1)return n.next(),!1}),t.on("keydown"+d,function(a){a.keyCode===37?n.prev():a.keyCode===39&&n.next()})}),x("UpdateStatus"+d,function(a,b){b.text&&(b.text=N(b.text,n.currItem.index,n.items.length))}),x(f+d,function(a,b,d,e){var f=n.items.length;d.counter=f>1?N(c.tCounter,e.index,f):""}),x("BuildControls"+d,function(){if(n.items.length>1&&c.arrows&&!n.arrowLeft){var b=c.arrowMarkup,d=n.arrowLeft=a(b.replace(/%title%/gi,c.tPrev).replace(/%dir%/gi,"left")).addClass(m),f=n.arrowRight=a(b.replace(/%title%/gi,c.tNext).replace(/%dir%/gi,"right")).addClass(m),g=e?"mfpFastClick":"click";d[g](function(){n.prev()}),f[g](function(){n.next()}),n.isIE7&&(y("b",d[0],!1,!0),y("a",d[0],!1,!0),y("b",f[0],!1,!0),y("a",f[0],!1,!0)),n.container.append(d.add(f))}}),x(h+d,function(){n._preloadTimeout&&clearTimeout(n._preloadTimeout),n._preloadTimeout=setTimeout(function(){n.preloadNearbyImages(),n._preloadTimeout=null},16)}
1704),x(b+d,function(){t.off(d),n.wrap.off("click"+d),n.arrowLeft&&e&&n.arrowLeft.add(n.arrowRight).destroyMfpFastClick(),n.arrowRight=n.arrowLeft=null})},next:function(){n.direction=!0,n.index=M(n.index+1),n.updateItemHTML()},prev:function(){n.direction=!1,n.index=M(n.index-1),n.updateItemHTML()},goTo:function(a){n.direction=a>=n.index,n.index=a,n.updateItemHTML()},preloadNearbyImages:function(){var a=n.st.gallery.preload,b=Math.min(a[0],n.items.length),c=Math.min(a[1],n.items.length),d;for(d=1;d<=(n.direction?c:b);d++)n._preloadItem(n.index+d);for(d=1;d<=(n.direction?b:c);d++)n._preloadItem(n.index-d)},_preloadItem:function(b){b=M(b);if(n.items[b].preloaded)return;var c=n.items[b];c.parsed||(c=n.parseEl(b)),z("LazyLoad",c),c.type==="image"&&(c.img=a('<img class="mfp-img" />').on("load.mfploader",function(){c.hasSize=!0}).on("error.mfploader",function(){c.hasSize=!0,c.loadError=!0,z("LazyLoadError",c)}).attr("src",c.src)),c.preloaded=!0}}});var O="retina";a.magnificPopup.registerModule(O,{options:{replaceSrc:function(a){return a.src.replace(/\.\w+$/,function(a){return"@2x"+a})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var a=n.st.retina,b=a.ratio;b=isNaN(b)?b():b,b>1&&(x("ImageHasSize."+O,function(a,c){c.img.css({"max-width":c.img[0].naturalWidth/b,width:"100%"})}),x("ElementParse."+O,function(c,d){d.src=a.replaceSrc(d,b)}))}}}}),function(){var b=1e3,c="ontouchstart"in window,d=function(){r.off("touchmove"+f+" touchend"+f)},e="mfpFastClick",f="."+e;a.fn.mfpFastClick=function(e){return a(this).each(function(){var g=a(this),h;if(c){var i,j,k,l,m,n;g.on("touchstart"+f,function(a){l=!1,n=1,m=a.originalEvent?a.originalEvent.touches[0]:a.touches[0],j=m.clientX,k=m.clientY,r.on("touchmove"+f,function(a){m=a.originalEvent?a.originalEvent.touches:a.touches,n=m.length,m=m[0];if(Math.abs(m.clientX-j)>10||Math.abs(m.clientY-k)>10)l=!0,d()}).on("touchend"+f,function(a){d();if(l||n>1)return;h=!0,a.preventDefault(),clearTimeout(i),i=setTimeout(function(){h=!1},b),e()})})}g.on("click"+f,function(){h||e()})})},a.fn.destroyMfpFastClick=function(){a(this).off("touchstart"+f+" click"+f),c&&r.off("touchmove"+f+" touchend"+f)}}(),B()})(window.jQuery||window.Zepto)
1705
1706
1707
1708
1709/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
1710
1711/* Begin Elixir theme specific javascript */
1712
1713/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
1714
1715// 'elixir' is the global object for the elixir RapidWeaver theme
1716var elixir = {};
1717
1718// reduce potential conflicts with other scripts on the page
1719elixir.jQuery = jQuery.noConflict(true);
1720var $elixir = elixir.jQuery;
1721
1722// Create unique object and namespace for theme functions
1723elixir.themeFunctions = {};
1724
1725// Define a closure
1726elixir.themeFunctions = (function() {
1727    // When jQuery is used it will be available as $ and jQuery but only
1728    // inside the closure.
1729    var jQuery = elixir.jQuery;
1730    var $ = jQuery;
1731	var $elixir = jQuery.noConflict();
1732
1733
1734
1735	///////////////////////////////
1736	// Mobile Detection
1737	///////////////////////////////
1738
1739	function isMobile(){
1740	    return (
1741	    (navigator.userAgent.match(/Android/i)) ||
1742			(navigator.userAgent.match(/webOS/i)) ||
1743			(navigator.userAgent.match(/iPhone/i)) ||
1744			(navigator.userAgent.match(/iPod/i)) ||
1745			(navigator.userAgent.match(/iPad/i)) ||
1746			(navigator.userAgent.match(/BlackBerry/))
1747	    );
1748	}
1749
1750
1751	function extraContent() {
1752		/*
1753			# ExtraContent (jQuery) #
1754
1755			AUTHOR:	Adam Merrifield <http://adam.merrifield.ca>
1756					Giuseppe Caruso <http://www.bonsai-studio.net/>
1757			VERSION: r1.4.2
1758			DATE: 12-16-10 09:40
1759
1760			USAGE:
1761			- be sure to include a copy of the jQuery libraray in the <head>
1762				before the inclusion of this function
1763			- noConflict mode is optional but highly recommended
1764			- call this script in the <head>
1765			- change the value of ecValue to match the number of ExtraContent
1766				areas in your theme
1767		*/
1768		$elixir(document).ready(function(){
1769			var extraContent =  (function() {
1770				// change ecValue to suit your theme
1771				var ecValue = 4;
1772				for (i=1;i<=ecValue;i++)
1773				{
1774					$elixir('#myExtraContent'+i+' script').remove();
1775					$elixir('#myExtraContent'+i).appendTo('#extraContainer'+i).show();
1776				}
1777			})();
1778		});
1779
1780		// Automatically show ExtraContent areas if they contain content.
1781
1782		if ($elixir('#myExtraContent1').length > 0) {
1783			$elixir('#extraContent1').show();
1784		}
1785
1786		if ($elixir('#myExtraContent2').length > 0) {
1787			$elixir('#extraContent2').show();
1788		}
1789
1790		if ($elixir('#myExtraContent3').length > 0) {
1791            $elixir('#extraContent3').show();
1792		}
1793
1794		if ($elixir('#myExtraContent4').length > 0) {
1795			$elixir('#extraContent4').show();
1796            $elixir('#inner_footer').css({'margin-top' : '20px'})
1797        }
1798	}
1799
1800	function blogEntryTopper() {
1801		/*
1802
1803		-=-= BLOG ENTRY TOPPER =-=-
1804
1805		DESCRIPTION: 	Handles the blog entry topper images. Finds image in blog entries with an ALT tag
1806						of 'blogEntryTopper' and then moves image to the top of the entry. The theme's
1807						CSS autosizes the image to fit the blog entry's width.
1808
1809		AUTHOR: Adam Shiver  /  Elixir Graphics
1810		VERSION: 1.0b
1811		DATE: May 9, 2011
1812
1813		*/
1814
1815			blogEntryTopperImg = $elixir('.blog-entry img[alt^=blogEntryTopper]');
1816			blogEntryTopperImg.hide();
1817			blogEntryTopperImg.each(function() {
1818				/* Applies special class to the image */
1819				$elixir(this).addClass('blogEntryTopperImg');
1820				/* Moves topper image to the top of the blog entry and wraps it in a div with a class of blogEntryTopper_wrapper  */
1821				$elixir(this).prependTo($elixir(this).parent().parent()).show().wrap('<div class="blogEntryTopper_wrapper" />');
1822			});
1823	}
1824
1825
1826	function generalThemeFunctions() {
1827		//
1828		// Add class to last blog entry on a page
1829		//
1830		$elixir('.blog-entry').last().addClass('lastBlogChild');
1831
1832		//
1833		// Display the Breadcrumb trail only if it is enabled and has content
1834		//
1835		if ( $elixir.trim( $elixir('#breadcrumb').html() ).length ) {
1836		 $elixir('#breadcrumb').show();
1837        }
1838
1839
1840        //
1841        // Move social badges to their appropriate locations.
1842        //
1843        $elixir('a.myBadge').appendTo('#social_badges');
1844
1845	}
1846
1847	function navigation() {
1848
1849        if ($elixir('nav#secondary_navigation ul ul li').length > 0) {
1850            $elixir('nav#secondary_navigation ul ul').show();
1851        }
1852
1853    	$elixir('nav#main_navigation li, nav#mobile_navigation li').has('ul').addClass('hasChild');
1854
1855	}
1856
1857
1858	function photoAlbum() {
1859        //
1860        // Handles lightbox for Photo Album page
1861        //
1862		$elixir('.thumbnail-frame').each(function(){
1863			var thumbnailCaption = $elixir('.thumbnail-caption', this).text();
1864			$elixir('a',this).attr({
1865				'href' : $elixir('a img',this).attr('src').replace(/thumb/i,'full'),
1866				'title' : thumbnailCaption,
1867			});
1868		});
1869
1870		$elixir('.thumbnail-frame').magnificPopup({
1871		  delegate: 'a', // child items selector, by clicking on it popup will open
1872		  type: 'image',
1873		  titleSrc: 'title',
1874		  gallery:{
1875		    enabled: true,
1876	      navigateByImgClick: false,
1877				preload: [0,2]
1878		  },
1879
1880		  closeOnContentClick: true,
1881
1882		  mainClass: 'mfp-with-zoom', // this class is for CSS animation below
1883
1884		  zoom: {
1885		    enabled: true, // By default it's false, so don't forget to enable it
1886
1887		    duration: 300, // duration of the effect, in milliseconds
1888		    easing: 'ease-in-out', // CSS transition easing function
1889
1890		    // The "opener" function should return the element from which popup will be zoomed in
1891		    // and to which popup will be scaled down
1892		    // By defailt it looks for an image tag:
1893		    opener: function(openerElement) {
1894		      // openerElement is the element on which popup was initialized, in this case its <a> tag
1895		      // you don't need to add "opener" option if this code matches your needs, it's defailt one.
1896		      return openerElement.is('img') ? openerElement : openerElement.find('img');
1897		    }
1898		  }
1899
1900		});
1901
1902        //
1903        // Hanldes caption spacing when image captions are turned on for thumbnails.
1904        //
1905		if ($elixir('.thumbnail-wrap .thumbnail-caption').length > 0) {
1906			$elixir('.thumbnail-wrap').css({'margin-bottom' : '80px'});
1907		}
1908	}
1909
1910	$elixir(document).ready(function() {
1911		generalThemeFunctions();
1912		navigation();
1913        photoAlbum();
1914        extraContent();
1915        blogEntryTopper();
1916	});
1917
1918    $elixir(window).load(function() {
1919        banner_height = $elixir('#banner img').height();
1920        $elixir('#alignment_wrapper .items li .outerContainer').css({'height':banner_height});
1921    });
1922
1923    $elixir(window).resize(function() {
1924        banner_height = $elixir('#banner img').height();
1925        $elixir('#alignment_wrapper .items li .outerContainer').css({'height':banner_height});
1926    });
1927
1928/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
1929
1930/* End Elixir theme specific javascript */
1931
1932/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
1933
1934
1935
1936
1937
1938/*
1939 * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
1940 *
1941 * Uses the built in easing capabilities added In jQuery 1.1
1942 * to offer multiple easing options
1943 *
1944 * TERMS OF USE - jQuery Easing
1945 *
1946 * Open source under the BSD License.
1947 *
1948 * Copyright © 2008 George McGinley Smith
1949 * All rights reserved.
1950 *
1951 * Redistribution and use in source and binary forms, with or without modification,
1952 * are permitted provided that the following conditions are met:
1953 *
1954 * Redistributions of source code must retain the above copyright notice, this list of
1955 * conditions and the following disclaimer.
1956 * Redistributions in binary form must reproduce the above copyright notice, this list
1957 * of conditions and the following disclaimer in the documentation and/or other materials
1958 * provided with the distribution.
1959 *
1960 * Neither the name of the author nor the names of contributors may be used to endorse
1961 * or promote products derived from this software without specific prior written permission.
1962 *
1963 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
1964 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1965 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
1966 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
vendor: 5,087 bytes, lines 1967-2112
1967 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
1968 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
1969 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
1970 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
1971 * OF THE POSSIBILITY OF SUCH DAMAGE.
1972 *
1973*/
1974
1975// t: current time, b: begInnIng value, c: change In value, d: duration
1976jQuery.easing['jswing'] = jQuery.easing['swing'];
1977
1978jQuery.extend( jQuery.easing,
1979{
1980	def: 'easeOutQuad',
1981	swing: function (x, t, b, c, d) {
1982		//alert(jQuery.easing.default);
1983		return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
1984	},
1985	easeInQuad: function (x, t, b, c, d) {
1986		return c*(t/=d)*t + b;
1987	},
1988	easeOutQuad: function (x, t, b, c, d) {
1989		return -c *(t/=d)*(t-2) + b;
1990	},
1991	easeInOutQuad: function (x, t, b, c, d) {
1992		if ((t/=d/2) < 1) return c/2*t*t + b;
1993		return -c/2 * ((--t)*(t-2) - 1) + b;
1994	},
1995	easeInCubic: function (x, t, b, c, d) {
1996		return c*(t/=d)*t*t + b;
1997	},
1998	easeOutCubic: function (x, t, b, c, d) {
1999		return c*((t=t/d-1)*t*t + 1) + b;
2000	},
2001	easeInOutCubic: function (x, t, b, c, d) {
2002		if ((t/=d/2) < 1) return c/2*t*t*t + b;
2003		return c/2*((t-=2)*t*t + 2) + b;
2004	},
2005	easeInQuart: function (x, t, b, c, d) {
2006		return c*(t/=d)*t*t*t + b;
2007	},
2008	easeOutQuart: function (x, t, b, c, d) {
2009		return -c * ((t=t/d-1)*t*t*t - 1) + b;
2010	},
2011	easeInOutQuart: function (x, t, b, c, d) {
2012		if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
2013		return -c/2 * ((t-=2)*t*t*t - 2) + b;
2014	},
2015	easeInQuint: function (x, t, b, c, d) {
2016		return c*(t/=d)*t*t*t*t + b;
2017	},
2018	easeOutQuint: function (x, t, b, c, d) {
2019		return c*((t=t/d-1)*t*t*t*t + 1) + b;
2020	},
2021	easeInOutQuint: function (x, t, b, c, d) {
2022		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
2023		return c/2*((t-=2)*t*t*t*t + 2) + b;
2024	},
2025	easeInSine: function (x, t, b, c, d) {
2026		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
2027	},
2028	easeOutSine: function (x, t, b, c, d) {
2029		return c * Math.sin(t/d * (Math.PI/2)) + b;
2030	},
2031	easeInOutSine: function (x, t, b, c, d) {
2032		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
2033	},
2034	easeInExpo: function (x, t, b, c, d) {
2035		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
2036	},
2037	easeOutExpo: function (x, t, b, c, d) {
2038		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
2039	},
2040	easeInOutExpo: function (x, t, b, c, d) {
2041		if (t==0) return b;
2042		if (t==d) return b+c;
2043		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
2044		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
2045	},
2046	easeInCirc: function (x, t, b, c, d) {
2047		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
2048	},
2049	easeOutCirc: function (x, t, b, c, d) {
2050		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
2051	},
2052	easeInOutCirc: function (x, t, b, c, d) {
2053		if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
2054		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
2055	},
2056	easeInElastic: function (x, t, b, c, d) {
2057		var s=1.70158;var p=0;var a=c;
2058		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
2059		if (a < Math.abs(c)) { a=c; var s=p/4; }
2060		else var s = p/(2*Math.PI) * Math.asin (c/a);
2061		return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
2062	},
2063	easeOutElastic: function (x, t, b, c, d) {
2064		var s=1.70158;var p=0;var a=c;
2065		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
2066		if (a < Math.abs(c)) { a=c; var s=p/4; }
2067		else var s = p/(2*Math.PI) * Math.asin (c/a);
2068		return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
2069	},
2070	easeInOutElastic: function (x, t, b, c, d) {
2071		var s=1.70158;var p=0;var a=c;
2072		if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
2073		if (a < Math.abs(c)) { a=c; var s=p/4; }
2074		else var s = p/(2*Math.PI) * Math.asin (c/a);
2075		if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
2076		return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
2077	},
2078	easeInBack: function (x, t, b, c, d, s) {
2079		if (s == undefined) s = 1.70158;
2080		return c*(t/=d)*t*((s+1)*t - s) + b;
2081	},
2082	easeOutBack: function (x, t, b, c, d, s) {
2083		if (s == undefined) s = 1.70158;
2084		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
2085	},
2086	easeInOutBack: function (x, t, b, c, d, s) {
2087		if (s == undefined) s = 1.70158;
2088		if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
2089		return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
2090	},
2091	easeInBounce: function (x, t, b, c, d) {
2092		return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
2093	},
2094	easeOutBounce: function (x, t, b, c, d) {
2095		if ((t/=d) < (1/2.75)) {
2096			return c*(7.5625*t*t) + b;
2097		} else if (t < (2/2.75)) {
2098			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
2099		} else if (t < (2.5/2.75)) {
2100			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
2101		} else {
2102			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
2103		}
2104	},
2105	easeInOutBounce: function (x, t, b, c, d) {
2106		if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
2107		return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
2108	}
2109});
2110
2111/*
2112 *
2113 * TERMS OF USE - EASING EQUATIONS
2114 *
2115 * Open source under the BSD License.
2116 *
2117 * Copyright © 2001 Robert Penner
2118 * All rights reserved.
2119 *
2120 * Redistribution and use in source and binary forms, with or without modification,
2121 * are permitted provided that the following conditions are met:
2122 *
2123 * Redistributions of source code must retain the above copyright notice, this list of
2124 * conditions and the following disclaimer.
2125 * Redistributions in binary form must reproduce the above copyright notice, this list
2126 * of conditions and the following disclaimer in the documentation and/or other materials
2127 * provided with the distribution.
2128 *
2129 * Neither the name of the author nor the names of contributors may be used to endorse
2130 * or promote products derived from this software without specific prior written permission.
2131 *
2132 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
2133 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
2134 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
2135 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
2136 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
2137 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
2138 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
2139 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2140 * OF THE POSSIBILITY OF SUCH DAMAGE.
2141 *
2142 */
2143
2144
2145/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
2146
2147
2148
2149/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
2150
2151
2152
2153
2154})(elixir.themeFunctions);

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.