// SmoothScroll for websites v1.2.1

// Licensed under the terms of the MIT license.



// People involved

//  - Balazs Galambosi (maintainer)  

//  - Michael Herf     (Pulse Algorithm)



(function(){

  

// Scroll Variables (tweakable)

var defaultOptions = {



    // Scrolling Core

    frameRate        : 150, // [Hz]

    animationTime    : 800, // [px]

    stepSize         : 140, // [px]



    // Pulse (less tweakable)

    // ratio of "tail" to "acceleration"

    pulseAlgorithm   : true,

    pulseScale       : 6,

    pulseNormalize   : 1,



    // Acceleration

    accelerationDelta : 20,  // 20

    accelerationMax   : 1,   // 1



    // Keyboard Settings

    keyboardSupport   : true,  // option

    arrowScroll       : 50,     // [px]



    // Other

    touchpadSupport   : true,

    fixedBackground   : true, 

    excluded          : ""    

};



var options = defaultOptions;





// Other Variables

var isExcluded = false;

var isFrame = false;

var direction = { x: 0, y: 0 };

var initDone  = false;

var root = document.documentElement;

var activeElement;

var observer;

var deltaBuffer = [ 120, 120, 120 ];



var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 

            pageup: 33, pagedown: 34, end: 35, home: 36 };





/***********************************************

 * SETTINGS

 ***********************************************/



var options = defaultOptions;





/***********************************************

 * INITIALIZE

 ***********************************************/



/**

 * Tests if smooth scrolling is allowed. Shuts down everything if not.

 */

function initTest() {



    var disableKeyboard = false; 

    

    // disable keyboard support if anything above requested it

    if (disableKeyboard) {

        removeEvent("keydown", keydown);

    }



    if (options.keyboardSupport && !disableKeyboard) {

        addEvent("keydown", keydown);

    }

}



/**

 * Sets up scrolls array, determines if frames are involved.

 */

function init() {

  

    if (!document.body) return;



    var body = document.body;

    var html = document.documentElement;

    var windowHeight = window.innerHeight; 

    var scrollHeight = body.scrollHeight;

    

    // check compat mode for root element

    root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;

    activeElement = body;

    

    initTest();

    initDone = true;



    // Checks if this script is running in a frame

    if (top != self) {

        isFrame = true;

    }



    /**

     * This fixes a bug where the areas left and right to 

     * the content does not trigger the onmousewheel event

     * on some pages. e.g.: html, body { height: 100% }

     */

    else if (scrollHeight > windowHeight &&

            (body.offsetHeight <= windowHeight || 

             html.offsetHeight <= windowHeight)) {



        html.style.height = 'auto';

        //setTimeout(refresh, 10);



        // clearfix

        if (root.offsetHeight <= windowHeight) {

            var underlay = document.createElement("div"); 	

            underlay.style.clear = "both";

            body.appendChild(underlay);

        }

    }



    // disable fixed background

    if (!options.fixedBackground && !isExcluded) {

        body.style.backgroundAttachment = "scroll";

        html.style.backgroundAttachment = "scroll";

    }

}





/************************************************

 * SCROLLING 

 ************************************************/

 

var que = [];

var pending = false;

var lastScroll = +new Date;



/**

 * Pushes scroll actions to the scrolling queue.

 */

function scrollArray(elem, left, top, delay) {

    

    delay || (delay = 1000);

    directionCheck(left, top);



    if (options.accelerationMax != 1) {

        var now = +new Date;

        var elapsed = now - lastScroll;

        if (elapsed < options.accelerationDelta) {

            var factor = (1 + (30 / elapsed)) / 2;

            if (factor > 1) {

                factor = Math.min(factor, options.accelerationMax);

                left *= factor;

                top  *= factor;

            }

        }

        lastScroll = +new Date;

    }          

    

    // push a scroll command

    que.push({

        x: left, 

        y: top, 

        lastX: (left < 0) ? 0.99 : -0.99,

        lastY: (top  < 0) ? 0.99 : -0.99, 

        start: +new Date

    });

        

    // don't act if there's a pending queue

    if (pending) {

        return;

    }  



    var scrollWindow = (elem === document.body);

    

    var step = function (time) {

        

        var now = +new Date;

        var scrollX = 0;

        var scrollY = 0; 

    

        for (var i = 0; i < que.length; i++) {

            

            var item = que[i];

            var elapsed  = now - item.start;

            var finished = (elapsed >= options.animationTime);

            

            // scroll position: [0, 1]

            var position = (finished) ? 1 : elapsed / options.animationTime;

            

            // easing [optional]

            if (options.pulseAlgorithm) {

                position = pulse(position);

            }

            

            // only need the difference

            var x = (item.x * position - item.lastX) >> 0;

            var y = (item.y * position - item.lastY) >> 0;

            

            // add this to the total scrolling

            scrollX += x;

            scrollY += y;            

            

            // update last values

            item.lastX += x;

            item.lastY += y;

        

            // delete and step back if it's over

            if (finished) {

                que.splice(i, 1); i--;

            }           

        }



        // scroll left and top

        if (scrollWindow) {

            window.scrollBy(scrollX, scrollY);

        } 

        else {

            if (scrollX) elem.scrollLeft += scrollX;

            if (scrollY) elem.scrollTop  += scrollY;                    

        }

        

        // clean up if there's nothing left to do

        if (!left && !top) {

            que = [];

        }

        

        if (que.length) { 

            requestFrame(step, elem, (delay / options.frameRate + 1)); 

        } else { 

            pending = false;

        }

    };

    

    // start a new queue of actions

    requestFrame(step, elem, 0);

    pending = true;

}





/***********************************************

 * EVENTS

 ***********************************************/



/**

 * Mouse wheel handler.

 * @param {Object} event

 */

function wheel(event) {



    if (!initDone) {

        init();

    }

    

    var target = event.target;

    var overflowing = overflowingAncestor(target);

    

    // use default if there's no overflowing

    // element or default action is prevented    

    if (!overflowing || event.defaultPrevented ||

        isNodeName(activeElement, "embed") ||

       (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {

        return true;

    }



    var deltaX = event.wheelDeltaX || 0;

    var deltaY = event.wheelDeltaY || 0;

    

    // use wheelDelta if deltaX/Y is not available

    if (!deltaX && !deltaY) {

        deltaY = event.wheelDelta || 0;

    }



    // check if it's a touchpad scroll that should be ignored

    if (!options.touchpadSupport && isTouchpad(deltaY)) {

        return true;

    }



    // scale by step size

    // delta is 120 most of the time

    // synaptics seems to send 1 sometimes

    if (Math.abs(deltaX) > 1.2) {

        deltaX *= options.stepSize / 120;

    }

    if (Math.abs(deltaY) > 1.2) {

        deltaY *= options.stepSize / 120;

    }

    

    scrollArray(overflowing, -deltaX, -deltaY);

    event.preventDefault();

}



/**

 * Keydown event handler.

 * @param {Object} event

 */

function keydown(event) {



    var target   = event.target;

    var modifier = event.ctrlKey || event.altKey || event.metaKey || 

                  (event.shiftKey && event.keyCode !== key.spacebar);

    

    // do nothing if user is editing text

    // or using a modifier key (except shift)

    // or in a dropdown

    if ( /input|textarea|select|embed/i.test(target.nodeName) ||

         target.isContentEditable || 

         event.defaultPrevented   ||

         modifier ) {

      return true;

    }

    // spacebar should trigger button press

    if (isNodeName(target, "button") &&

        event.keyCode === key.spacebar) {

      return true;

    }

    

    var shift, x = 0, y = 0;

    var elem = overflowingAncestor(activeElement);

    var clientHeight = elem.clientHeight;



    if (elem == document.body) {

        clientHeight = window.innerHeight;

    }



    switch (event.keyCode) {

        case key.up:

            y = -options.arrowScroll;

            break;

        case key.down:

            y = options.arrowScroll;

            break;         

        case key.spacebar: // (+ shift)

            shift = event.shiftKey ? 1 : -1;

            y = -shift * clientHeight * 0.9;

            break;

        case key.pageup:

            y = -clientHeight * 0.9;

            break;

        case key.pagedown:

            y = clientHeight * 0.9;

            break;

        case key.home:

            y = -elem.scrollTop;

            break;

        case key.end:

            var damt = elem.scrollHeight - elem.scrollTop - clientHeight;

            y = (damt > 0) ? damt+10 : 0;

            break;

        case key.left:

            x = -options.arrowScroll;

            break;

        case key.right:

            x = options.arrowScroll;

            break;            

        default:

            return true; // a key we don't care about

    }



    scrollArray(elem, x, y);

    event.preventDefault();

}



/**

 * Mousedown event only for updating activeElement

 */

function mousedown(event) {

    activeElement = event.target;

}





/***********************************************

 * OVERFLOW

 ***********************************************/

 

var cache = {}; // cleared out every once in while

setInterval(function () { cache = {}; }, 10 * 1000);



var uniqueID = (function () {

    var i = 0;

    return function (el) {

        return el.uniqueID || (el.uniqueID = i++);

    };

})();



function setCache(elems, overflowing) {

    for (var i = elems.length; i--;)

        cache[uniqueID(elems[i])] = overflowing;

    return overflowing;

}



function overflowingAncestor(el) {

    var elems = [];

    var rootScrollHeight = root.scrollHeight;

    do {

        var cached = cache[uniqueID(el)];

        if (cached) {

            return setCache(elems, cached);

        }

        elems.push(el);

        if (rootScrollHeight === el.scrollHeight) {

            if (!isFrame || root.clientHeight + 10 < rootScrollHeight) {

                return setCache(elems, document.body); // scrolling root in WebKit

            }

        } else if (el.clientHeight + 10 < el.scrollHeight) {

            overflow = getComputedStyle(el, "").getPropertyValue("overflow-y");

            if (overflow === "scroll" || overflow === "auto") {

                return setCache(elems, el);

            }

        }

    } while (el = el.parentNode);

}





/***********************************************

 * HELPERS

 ***********************************************/



function addEvent(type, fn, bubble) {

    window.addEventListener(type, fn, (bubble||false));

}



function removeEvent(type, fn, bubble) {

    window.removeEventListener(type, fn, (bubble||false));  

}



function isNodeName(el, tag) {

    return (el.nodeName||"").toLowerCase() === tag.toLowerCase();

}



function directionCheck(x, y) {

    x = (x > 0) ? 1 : -1;

    y = (y > 0) ? 1 : -1;

    if (direction.x !== x || direction.y !== y) {

        direction.x = x;

        direction.y = y;

        que = [];

        lastScroll = 0;

    }

}



var deltaBufferTimer;



function isTouchpad(deltaY) {

    if (!deltaY) return;

    deltaY = Math.abs(deltaY)

    deltaBuffer.push(deltaY);

    deltaBuffer.shift();

    clearTimeout(deltaBufferTimer);



    var allEquals    = (deltaBuffer[0] == deltaBuffer[1] && 

                        deltaBuffer[1] == deltaBuffer[2]);

    var allDivisable = (isDivisible(deltaBuffer[0], 120) &&

                        isDivisible(deltaBuffer[1], 120) &&

                        isDivisible(deltaBuffer[2], 120));

    return !(allEquals || allDivisable);

} 



function isDivisible(n, divisor) {

    return (Math.floor(n / divisor) == n / divisor);

}



var requestFrame = (function () {

      return  window.requestAnimationFrame       || 

              window.webkitRequestAnimationFrame || 

              function (callback, element, delay) {

                  window.setTimeout(callback, delay || (1000/60));

              };

})();





/***********************************************

 * PULSE

 ***********************************************/

 

/**

 * Viscous fluid with a pulse for part and decay for the rest.

 * - Applies a fixed force over an interval (a damped acceleration), and

 * - Lets the exponential bleed away the velocity over a longer interval

 * - Michael Herf, http://stereopsis.com/stopping/

 */

function pulse_(x) {

    var val, start, expx;

    // test

    x = x * options.pulseScale;

    if (x < 1) { // acceleartion

        val = x - (1 - Math.exp(-x));

    } else {     // tail

        // the previous animation ended here:

        start = Math.exp(-1);

        // simple viscous drag

        x -= 1;

        expx = 1 - Math.exp(-x);

        val = start + (expx * (1 - start));

    }

    return val * options.pulseNormalize;

}



function pulse(x) {

    if (x >= 1) return 1;

    if (x <= 0) return 0;



    if (options.pulseNormalize == 1) {

        options.pulseNormalize /= pulse_(1);

    }

    return pulse_(x);

}



var isChrome = /chrome/i.test(window.navigator.userAgent);

var isMouseWheelSupported = 'onmousewheel' in document; 



if (isMouseWheelSupported && isChrome) {

	addEvent("mousedown", mousedown);

	addEvent("mousewheel", wheel);

	addEvent("load", init);

};



})();