let tns = (function() {
    let win = window;

    let raf = win.requestAnimationFrame ||
        win.webkitRequestAnimationFrame ||
        win.mozRequestAnimationFrame ||
        win.msRequestAnimationFrame ||

        function(cb) { return setTimeout(cb, 16); };

    let win$1 = window;

    let caf = win$1.cancelAnimationFrame ||
        win$1.mozCancelAnimationFrame ||

        function(id) { clearTimeout(id); };

    function extend() {
        let obj, name, copy,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length;

        for (; i < length; i++) {
            if ((obj = arguments[i]) !== null) {
                for (name in obj) {
                    copy = obj[name];

                    if (target === copy) {
                        continue;
                    } else if (copy !== undefined) {
                        target[name] = copy;
                    }
                }
            }
        }
        return target;
    }

    function checkStorageValue(value) {
        return ['true', 'false'].indexOf(value) >= 0 ? JSON.parse(value) : value;
    }

    function setLocalStorage(storage, key, value, access) {
        if (access) {
            try { storage.setItem(key, value); } catch (e) {}
        }
        return value;
    }

    function getSlideId() {
        let id = window.tnsId;
        window.tnsId = !id ? 1 : id + 1;

        return 'tns' + window.tnsId;
    }

    function getBody() {
        let doc = document,
            body = doc.body;

        if (!body) {
            body = doc.createElement('body');
            body.fake = true;
        }

        return body;
    }

    let docElement = document.documentElement;

    function setFakeBody(body) {
        let docOverflow = '';
        if (body.fake) {
            docOverflow = docElement.style.overflow;
            //avoid crashing IE8, if background image is used
            body.style.background = '';
            //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
            body.style.overflow = docElement.style.overflow = 'hidden';
            docElement.appendChild(body);
        }

        return docOverflow;
    }

    function resetFakeBody(body, docOverflow) {
        if (body.fake) {
            body.remove();
            docElement.style.overflow = docOverflow;
            // Trigger layout so kinetic scrolling isn't disabled in iOS6+
            // eslint-disable-next-line
            docElement.offsetHeight;
        }
    }

    // get css-calc 

    function calc() {
        let doc = document,
            body = getBody(),
            docOverflow = setFakeBody(body),
            div = doc.createElement('div'),
            result = false;

        body.appendChild(div);
        try {
            let str = '(10px * 10)',
                vals = ['calc' + str, '-moz-calc' + str, '-webkit-calc' + str],
                val;
            for (let i = 0; i < 3; i++) {
                val = vals[i];
                div.style.width = val;
                if (div.offsetWidth === 100) {
                    result = val.replace(str, '');
                    break;
                }
            }
        } catch (e) {}

        body.fake ? resetFakeBody(body, docOverflow) : div.remove();

        return result;
    }

    // get subpixel support value

    function percentageLayout() {
        // check subpixel layout supporting
        let doc = document,
            body = getBody(),
            docOverflow = setFakeBody(body),
            wrapper = doc.createElement('div'),
            outer = doc.createElement('div'),
            str = '',
            count = 70,
            perPage = 3,
            supported = false;

        wrapper.className = "tns-t-subp2";
        outer.className = "tns-t-ct";

        for (let i = 0; i < count; i++) {
            str += '<div></div>';
        }

        outer.innerHTML = str;
        wrapper.appendChild(outer);
        body.appendChild(wrapper);

        supported = Math.abs(wrapper.getBoundingClientRect().left - outer.children[count - perPage].getBoundingClientRect().left) < 2;

        body.fake ? resetFakeBody(body, docOverflow) : wrapper.remove();

        return supported;
    }

    function mediaquerySupport() {
        if (window.matchMedia || window.msMatchMedia) {
            return true;
        }

        let doc = document,
            body = getBody(),
            docOverflow = setFakeBody(body),
            div = doc.createElement('div'),
            style = doc.createElement('style'),
            rule = '@media all and (min-width:1px){.tns-mq-test{position:absolute}}',
            position;

        style.type = 'text/css';
        div.className = 'tns-mq-test';

        body.appendChild(style);
        body.appendChild(div);

        if (style.styleSheet) {
            style.styleSheet.cssText = rule;
        } else {
            style.appendChild(doc.createTextNode(rule));
        }

        position = window.getComputedStyle ? window.getComputedStyle(div).position : div.currentStyle['position'];

        body.fake ? resetFakeBody(body, docOverflow) : div.remove();

        return position === "absolute";
    }

    // create and append style sheet
    function createStyleSheet(media, nonce) {
        // Create the <style> tag
        var style = document.createElement("style");
        // style.setAttribute("type", "text/css");

        // Add a media (and/or media query) here if you'd like!
        // style.setAttribute("media", "screen")
        // style.setAttribute("media", "only screen and (max-width : 1024px)")
        if (media) { style.setAttribute("media", media); }

        // Add nonce attribute for Content Security Policy
        if (nonce) { style.setAttribute("nonce", nonce); }

        // WebKit hack :(
        // style.appendChild(document.createTextNode(""));

        // Add the <style> element to the page
        document.querySelector('head').appendChild(style);

        return style.sheet ? style.sheet : style.styleSheet;
    }

    // cross browsers addRule method
    function addCSSRule(sheet, selector, rules, index) {
        // return raf(function() {
        'insertRule' in sheet ?
            sheet.insertRule(selector + '{' + rules + '}', index) :
            sheet.addRule(selector, rules, index);
        // });
    }

    // cross browsers addRule method
    function removeCSSRule(sheet, index) {
        // return raf(function() {
        'deleteRule' in sheet ?
            sheet.deleteRule(index) :
            sheet.removeRule(index);
        // });
    }

    function getCssRulesLength(sheet) {
        let rule = ('insertRule' in sheet) ? sheet.cssRules : sheet.rules;
        return rule.length;
    }

    function toDegree(y, x) {
        return Math.atan2(y, x) * (180 / Math.PI);
    }

    function getTouchDirection(angle, range) {
        let direction = false,
            gap = Math.abs(90 - Math.abs(angle));

        if (gap >= 90 - range) {
            direction = 'horizontal';
        } else if (gap <= range) {
            direction = 'vertical';
        }

        return direction;
    }

    // https://toddmotto.com/ditch-the-array-foreach-call-nodelist-hack/
    function forEach(arr, callback, scope) {
        for (let i = 0, l = arr.length; i < l; i++) {
            callback.call(scope, arr[i], i);
        }
    }

    let classListSupport = 'classList' in document.createElement('_');

    let hasClass = classListSupport ?
        function(el, str) { return el.classList.contains(str); } :
        function(el, str) { return el.className.indexOf(str) >= 0; };

    let addClass = classListSupport ?
        function(el, str) {
            if (!hasClass(el, str)) { el.classList.add(str); }
        } :
        function(el, str) {
            if (!hasClass(el, str)) { el.className += ' ' + str; }
        };

    let removeClass = classListSupport ?
        function(el, str) {
            if (hasClass(el, str)) { el.classList.remove(str); }
        } :
        function(el, str) {
            if (hasClass(el, str)) { el.className = el.className.replace(str, ''); }
        };

    function hasAttr(el, attr) {
        return el.hasAttribute(attr);
    }

    function getAttr(el, attr) {
        return el.getAttribute(attr);
    }

    function isNodeList(el) {
        // Only NodeList has the "item()" function
        return typeof el.item !== "undefined";
    }

    function setAttrs(els, attrs) {
        els = (isNodeList(els) || els instanceof Array) ? els : [els];
        if (Object.prototype.toString.call(attrs) !== '[object Object]') { return; }

        for (let i = els.length; i--;) {
            for (let key in attrs) {
                els[i].setAttribute(key, attrs[key]);
            }
        }
    }

    function removeAttrs(els, attrs) {
        els = (isNodeList(els) || els instanceof Array) ? els : [els];
        attrs = (attrs instanceof Array) ? attrs : [attrs];

        let attrLength = attrs.length;
        for (let i = els.length; i--;) {
            for (let j = attrLength; j--;) {
                els[i].removeAttribute(attrs[j]);
            }
        }
    }

    function arrayFromNodeList(nl) {
        let arr = [];
        for (let i = 0, l = nl.length; i < l; i++) {
            arr.push(nl[i]);
        }
        return arr;
    }

    function hideElement(el, forceHide) {
        if (el.style.display !== 'none') { el.style.display = 'none'; }
    }

    function showElement(el, forceHide) {
        if (el.style.display === 'none') { el.style.display = ''; }
    }

    function isVisible(el) {
        return window.getComputedStyle(el).display !== 'none';
    }

    function whichProperty(props) {
        if (typeof props === 'string') {
            let arr = [props],
                Props = props.charAt(0).toUpperCase() + props.substr(1),
                prefixes = ['Webkit', 'Moz', 'ms', 'O'];

            prefixes.forEach(function(prefix) {
                if (prefix !== 'ms' || props === 'transform') {
                    arr.push(prefix + Props);
                }
            });

            props = arr;
        }

        let el = document.createElement('fakeelement'),
            len = props.length;
        for (let i = 0; i < props.length; i++) {
            let prop = props[i];
            if (el.style[prop] !== undefined) { return prop; }
        }

        return false; // explicit for ie9-
    }

    function has3DTransforms(tf) {
        if (!tf) { return false; }
        if (!window.getComputedStyle) { return false; }

        let doc = document,
            body = getBody(),
            docOverflow = setFakeBody(body),
            el = doc.createElement('p'),
            has3d,
            cssTF = tf.length > 9 ? '-' + tf.slice(0, -9).toLowerCase() + '-' : '';

        cssTF += 'transform';

        // Add it to the body to get the computed style
        body.insertBefore(el, null);

        el.style[tf] = 'translate3d(1px,1px,1px)';
        has3d = window.getComputedStyle(el).getPropertyValue(cssTF);

        body.fake ? resetFakeBody(body, docOverflow) : el.remove();

        return (has3d !== undefined && has3d.length > 0 && has3d !== "none");
    }

    // get transitionend, animationend based on transitionDuration
    // @propin: string
    // @propOut: string, first-letter uppercase
    // Usage: getEndProperty('WebkitTransitionDuration', 'Transition') => webkitTransitionEnd
    function getEndProperty(propIn, propOut) {
        let endProp = false;
        if (/^Webkit/.test(propIn)) {
            endProp = 'webkit' + propOut + 'End';
        } else if (/^O/.test(propIn)) {
            endProp = 'o' + propOut + 'End';
        } else if (propIn) {
            endProp = propOut.toLowerCase() + 'end';
        }
        return endProp;
    }

    // Test via a getter in the options object to see if the passive property is accessed
    let supportsPassive = false;
    try {
        let opts = Object.defineProperty({}, 'passive', {
            get: function() {
                supportsPassive = true;
            }
        });
        window.addEventListener("test", null, opts);
    } catch (e) {}
    let passiveOption = supportsPassive ? { passive: true } : false;

    function addEvents(el, obj, preventScrolling) {
        for (let prop in obj) {
            let option = ['touchstart', 'touchmove'].indexOf(prop) >= 0 && !preventScrolling ? passiveOption : false;
            el.addEventListener(prop, obj[prop], option);
        }
    }

    function removeEvents(el, obj) {
        for (let prop in obj) {
            let option = ['touchstart', 'touchmove'].indexOf(prop) >= 0 ? passiveOption : false;
            el.removeEventListener(prop, obj[prop], option);
        }
    }

    function Events() {
        return {
            topics: {},
            on: function(eventName, fn) {
                this.topics[eventName] = this.topics[eventName] || [];
                this.topics[eventName].push(fn);
            },
            off: function(eventName, fn) {
                if (this.topics[eventName]) {
                    for (let i = 0; i < this.topics[eventName].length; i++) {
                        if (this.topics[eventName][i] === fn) {
                            this.topics[eventName].splice(i, 1);
                            break;
                        }
                    }
                }
            },
            emit: function(eventName, data) {
                data.type = eventName;
                if (this.topics[eventName]) {
                    this.topics[eventName].forEach(function(fn) {
                        fn(data, eventName);
                    });
                }
            }
        };
    }

    function jsTransform(element, attr, prefix, postfix, to, duration, callback) {
        let tick = Math.min(duration, 10),
            unit = (to.indexOf('%') >= 0) ? '%' : 'px',
            to = to.replace(unit, ''),
            from = Number(element.style[attr].replace(prefix, '').replace(postfix, '').replace(unit, '')),
            positionTick = (to - from) / duration * tick,
            running;

        setTimeout(moveElement, tick);

        function moveElement() {
            duration -= tick;
            from += positionTick;
            element.style[attr] = prefix + from + unit + postfix;
            if (duration > 0) {
                setTimeout(moveElement, tick);
            } else {
                callback();
            }
        }
    }

    // Object.keys
    if (!Object.keys) {
        Object.keys = function(object) {
            let keys = [];
            for (let name in object) {
                if (Object.prototype.hasOwnProperty.call(object, name)) {
                    keys.push(name);
                }
            }
            return keys;
        };
    }

    // ChildNode.remove
    if (!("remove" in Element.prototype)) {
        Element.prototype.remove = function() {
            if (this.parentNode) {
                this.parentNode.removeChild(this);
            }
        };
    }

    let tns = function(options) {
        options = extend({
            container: '.slider',
            mode: 'carousel',
            axis: 'horizontal',
            items: 1,
            gutter: 0,
            edgePadding: 0,
            fixedWidth: false,
            autoWidth: false,
            viewportMax: false,
            slideBy: 1,
            center: false,
            controls: true,
            controlsPosition: 'top',
            controlsText: ['prev', 'next'],
            controlsContainer: false,
            prevButton: false,
            nextButton: false,
            nav: true,
            navPosition: 'top',
            navContainer: false,
            navAsThumbnails: false,
            arrowKeys: false,
            speed: 300,
            autoplay: false,
            autoplayPosition: 'top',
            autoplayTimeout: 5000,
            autoplayDirection: 'forward',
            autoplayText: ['start', 'stop'],
            autoplayHoverPause: false,
            autoplayButton: false,
            autoplayButtonOutput: true,
            autoplayResetOnVisibility: true,
            animateIn: 'tns-fadeIn',
            animateOut: 'tns-fadeOut',
            animateNormal: 'tns-normal',
            animateDelay: false,
            loop: true,
            rewind: false,
            autoHeight: false,
            responsive: false,
            lazyload: false,
            lazyloadSelector: '.tns-lazy-img',
            touch: true,
            mouseDrag: false,
            swipeAngle: 15,
            nested: false,
            preventActionWhenRunning: false,
            preventScrollOnTouch: false,
            freezable: true,
            onInit: false,
            useLocalStorage: true,
            nonce: false
        }, options || {});

        let doc = document,
            win = window,
            KEYS = {
                ENTER: 13,
                SPACE: 32,
                LEFT: 37,
                RIGHT: 39
            },
            tnsStorage = {},
            localStorageAccess = options.useLocalStorage;

        if (localStorageAccess) {
            // check browser version and local storage access
            let browserInfo = navigator.userAgent;
            let uid = new Date;

            try {
                tnsStorage = win.localStorage;
                if (tnsStorage) {
                    tnsStorage.setItem(uid, uid);
                    localStorageAccess = tnsStorage.getItem(uid) == uid;
                    tnsStorage.removeItem(uid);
                } else {
                    localStorageAccess = false;
                }
                if (!localStorageAccess) { tnsStorage = {}; }
            } catch (e) {
                localStorageAccess = false;
            }

            if (localStorageAccess) {
                // remove storage when browser version changes
                if (tnsStorage['tnsApp'] && tnsStorage['tnsApp'] !== browserInfo) {
                    ['tC', 'tPL', 'tMQ', 'tTf', 't3D', 'tTDu', 'tTDe', 'tADu', 'tADe', 'tTE', 'tAE'].forEach(function(item) { tnsStorage.removeItem(item); });
                }
                // update browserInfo
                localStorage['tnsApp'] = browserInfo;
            }
        }

        let CALC = tnsStorage['tC'] ? checkStorageValue(tnsStorage['tC']) : setLocalStorage(tnsStorage, 'tC', calc(), localStorageAccess),
            PERCENTAGELAYOUT = tnsStorage['tPL'] ? checkStorageValue(tnsStorage['tPL']) : setLocalStorage(tnsStorage, 'tPL', percentageLayout(), localStorageAccess),
            CSSMQ = tnsStorage['tMQ'] ? checkStorageValue(tnsStorage['tMQ']) : setLocalStorage(tnsStorage, 'tMQ', mediaquerySupport(), localStorageAccess),
            TRANSFORM = tnsStorage['tTf'] ? checkStorageValue(tnsStorage['tTf']) : setLocalStorage(tnsStorage, 'tTf', whichProperty('transform'), localStorageAccess),
            HAS3DTRANSFORMS = tnsStorage['t3D'] ? checkStorageValue(tnsStorage['t3D']) : setLocalStorage(tnsStorage, 't3D', has3DTransforms(TRANSFORM), localStorageAccess),
            TRANSITIONDURATION = tnsStorage['tTDu'] ? checkStorageValue(tnsStorage['tTDu']) : setLocalStorage(tnsStorage, 'tTDu', whichProperty('transitionDuration'), localStorageAccess),
            TRANSITIONDELAY = tnsStorage['tTDe'] ? checkStorageValue(tnsStorage['tTDe']) : setLocalStorage(tnsStorage, 'tTDe', whichProperty('transitionDelay'), localStorageAccess),
            ANIMATIONDURATION = tnsStorage['tADu'] ? checkStorageValue(tnsStorage['tADu']) : setLocalStorage(tnsStorage, 'tADu', whichProperty('animationDuration'), localStorageAccess),
            ANIMATIONDELAY = tnsStorage['tADe'] ? checkStorageValue(tnsStorage['tADe']) : setLocalStorage(tnsStorage, 'tADe', whichProperty('animationDelay'), localStorageAccess),
            TRANSITIONEND = tnsStorage['tTE'] ? checkStorageValue(tnsStorage['tTE']) : setLocalStorage(tnsStorage, 'tTE', getEndProperty(TRANSITIONDURATION, 'Transition'), localStorageAccess),
            ANIMATIONEND = tnsStorage['tAE'] ? checkStorageValue(tnsStorage['tAE']) : setLocalStorage(tnsStorage, 'tAE', getEndProperty(ANIMATIONDURATION, 'Animation'), localStorageAccess);

        // get element nodes from selectors
        let supportConsoleWarn = win.console && typeof win.console.warn === "function",
            tnsList = ['container', 'controlsContainer', 'prevButton', 'nextButton', 'navContainer', 'autoplayButton'],
            optionsElements = {};

        tnsList.forEach(function(item) {
            if (typeof options[item] === 'string') {
                let str = options[item],
                    el = doc.querySelector(str);
                optionsElements[item] = str;

                if (el && el.nodeName) {
                    options[item] = el;
                } else {
                    if (supportConsoleWarn) { console.warn('Can\'t find', options[item]); }
                    return;
                }
            }
        });

        // make sure at least 1 slide
        if (options.container.children.length < 1) {
            if (supportConsoleWarn) { console.warn('No slides found in', options.container); }
            return;
        }

        // update options
        let responsive = options.responsive,
            nested = options.nested,
            carousel = options.mode === 'carousel' ? true : false;

        if (responsive) {
            // apply responsive[0] to options and remove it
            if (0 in responsive) {
                options = extend(options, responsive[0]);
                delete responsive[0];
            }

            let responsiveTem = {};
            for (let key in responsive) {
                let val = responsive[key];
                // update responsive
                // from: 300: 2
                // to:
                //   300: {
                //     items: 2
                //   }
                val = typeof val === 'number' ? { items: val } : val;
                responsiveTem[key] = val;
            }
            responsive = responsiveTem;
            responsiveTem = null;
        }

        // update options
        function updateOptions(obj) {
            for (let key in obj) {
                if (!carousel) {
                    if (key === 'slideBy') { obj[key] = 'page'; }
                    if (key === 'edgePadding') { obj[key] = false; }
                    if (key === 'autoHeight') { obj[key] = false; }
                }

                // update responsive options
                if (key === 'responsive') { updateOptions(obj[key]); }
            }
        }
        if (!carousel) { updateOptions(options); }


        // === define and set variables ===
        if (!carousel) {
            options.axis = 'horizontal';
            options.slideBy = 'page';
            options.edgePadding = false;

            let animateIn = options.animateIn,
                animateOut = options.animateOut,
                animateDelay = options.animateDelay,
                animateNormal = options.animateNormal;
        }

        let horizontal = options.axis === 'horizontal' ? true : false,
            outerWrapper = doc.createElement('div'),
            innerWrapper = doc.createElement('div'),
            middleWrapper,
            container = options.container,
            containerParent = container.parentNode,
            containerHTML = container.outerHTML,
            slideItems = container.children,
            slideCount = slideItems.length,
            breakpointZone,
            windowWidth = getWindowWidth(),
            isOn = false;
        if (responsive) { setBreakpointZone(); }
        if (carousel) { container.className += ' tns-vpfix'; }

        // fixedWidth: viewport > rightBoundary > indexMax
        let autoWidth = options.autoWidth,
            fixedWidth = getOption('fixedWidth'),
            edgePadding = getOption('edgePadding'),
            gutter = getOption('gutter'),
            viewport = getViewportWidth(),
            center = getOption('center'),
            items = !autoWidth ? Math.floor(getOption('items')) : 1,
            slideBy = getOption('slideBy'),
            viewportMax = options.viewportMax || options.fixedWidthViewportWidth,
            arrowKeys = getOption('arrowKeys'),
            speed = getOption('speed'),
            rewind = options.rewind,
            loop = rewind ? false : options.loop,
            autoHeight = getOption('autoHeight'),
            controls = getOption('controls'),
            controlsText = getOption('controlsText'),
            nav = getOption('nav'),
            touch = getOption('touch'),
            mouseDrag = getOption('mouseDrag'),
            autoplay = getOption('autoplay'),
            autoplayTimeout = getOption('autoplayTimeout'),
            autoplayText = getOption('autoplayText'),
            autoplayHoverPause = getOption('autoplayHoverPause'),
            autoplayResetOnVisibility = getOption('autoplayResetOnVisibility'),
            sheet = createStyleSheet(null, getOption('nonce')),
            lazyload = options.lazyload,
            lazyloadSelector = options.lazyloadSelector,
            slidePositions, // collection of slide positions
            slideItemsOut = [],
            cloneCount = loop ? getCloneCountForLoop() : 0,
            slideCountNew = !carousel ? slideCount + cloneCount : slideCount + cloneCount * 2,
            hasRightDeadZone = (fixedWidth || autoWidth) && !loop ? true : false,
            rightBoundary = fixedWidth ? getRightBoundary() : null,
            updateIndexBeforeTransform = (!carousel || !loop) ? true : false,
            // transform
            transformAttr = horizontal ? 'left' : 'top',
            transformPrefix = '',
            transformPostfix = '',
            // index
            getIndexMax = (function() {
                if (fixedWidth) {
                    return function() { return center && !loop ? slideCount - 1 : Math.ceil(-rightBoundary / (fixedWidth + gutter)); };
                } else if (autoWidth) {
                    return function() {
                        for (let i = 0; i < slideCountNew; i++) {
                            if (slidePositions[i] >= -rightBoundary) { return i; }
                        }
                    };
                } else {
                    return function() {
                        if (center && carousel && !loop) {
                            return slideCount - 1;
                        } else {
                            return loop || carousel ? Math.max(0, slideCountNew - Math.ceil(items)) : slideCountNew - 1;
                        }
                    };
                }
            })(),
            index = getStartIndex(getOption('startIndex')),
            indexCached = index,
            displayIndex = getCurrentSlide(),
            indexMin = 0,
            indexMax = !autoWidth ? getIndexMax() : null,
            // resize
            resizeTimer,
            preventActionWhenRunning = options.preventActionWhenRunning,
            swipeAngle = options.swipeAngle,
            moveDirectionExpected = swipeAngle ? '?' : true,
            running = false,
            onInit = options.onInit,
            events = new Events(),
            // id, class
            newContainerClasses = ' tns-slider tns-' + options.mode,
            slideId = container.id || getSlideId(),
            disable = getOption('disable'),
            disabled = false,
            freezable = options.freezable,
            freeze = freezable && !autoWidth ? getFreeze() : false,
            frozen = false,
            controlsEvents = {
                'click': onControlsClick,
                'keydown': onControlsKeydown
            },
            navEvents = {
                'click': onNavClick,
                'keydown': onNavKeydown
            },
            hoverEvents = {
                'mouseover': mouseoverPause,
                'mouseout': mouseoutRestart
            },
            visibilityEvent = { 'visibilitychange': onVisibilityChange },
            docmentKeydownEvent = { 'keydown': onDocumentKeydown },
            touchEvents = {
                'touchstart': onPanStart,
                'touchmove': onPanMove,
                'touchend': onPanEnd,
                'touchcancel': onPanEnd
            },
            dragEvents = {
                'mousedown': onPanStart,
                'mousemove': onPanMove,
                'mouseup': onPanEnd,
                'mouseleave': onPanEnd
            },
            hasControls = hasOption('controls'),
            hasNav = hasOption('nav'),
            navAsThumbnails = autoWidth ? true : options.navAsThumbnails,
            hasAutoplay = hasOption('autoplay'),
            hasTouch = hasOption('touch'),
            hasMouseDrag = hasOption('mouseDrag'),
            slideActiveClass = 'tns-slide-active',
            slideClonedClass = 'tns-slide-cloned',
            imgCompleteClass = 'tns-complete',
            imgEvents = {
                'load': onImgLoaded,
                'error': onImgFailed
            },
            imgsComplete,
            liveregionCurrent,
            preventScroll = options.preventScrollOnTouch === 'force' ? true : false;

        // controls
        if (hasControls) {
            var controlsContainer = options.controlsContainer,
                controlsContainerHTML = options.controlsContainer ? options.controlsContainer.outerHTML : '',
                prevButton = options.prevButton,
                nextButton = options.nextButton,
                prevButtonHTML = options.prevButton ? options.prevButton.outerHTML : '',
                nextButtonHTML = options.nextButton ? options.nextButton.outerHTML : '',
                prevIsButton,
                nextIsButton;
        }

        // nav
        if (hasNav) {
            var navContainer = options.navContainer,
                navContainerHTML = options.navContainer ? options.navContainer.outerHTML : '',
                navItems,
                pages = autoWidth ? slideCount : getPages(),
                pagesCached = 0,
                navClicked = -1,
                navCurrentIndex = getCurrentNavIndex(),
                navCurrentIndexCached = navCurrentIndex,
                navActiveClass = 'tns-nav-active',
                navStr = 'Carousel Page ',
                navStrCurrent = ' (Current Slide)';
        }

        // autoplay
        if (hasAutoplay) {
            var autoplayDirection = options.autoplayDirection === 'forward' ? 1 : -1,
                autoplayButton = options.autoplayButton,
                autoplayButtonHTML = options.autoplayButton ? options.autoplayButton.outerHTML : '',
                autoplayHtmlStrings = ['<span class=\'tns-visually-hidden\'>', ' animation</span>'],
                autoplayTimer,
                animating,
                autoplayHoverPaused,
                autoplayUserPaused,
                autoplayVisibilityPaused;
        }

        if (hasTouch || hasMouseDrag) {
            let initPosition = {},
                lastPosition = {},
                translateInit,
                disX,
                disY,
                panStart = false,
                rafIndex,
                getDist = horizontal ?
                function(a, b) { return a.x - b.x; } :
                function(a, b) { return a.y - b.y; };
        }

        // disable slider when slidecount <= items
        if (!autoWidth) { resetVariblesWhenDisable(disable || freeze); }

        if (TRANSFORM) {
            transformAttr = TRANSFORM;
            transformPrefix = 'translate';

            if (HAS3DTRANSFORMS) {
                transformPrefix += horizontal ? '3d(' : '3d(0px, ';
                transformPostfix = horizontal ? ', 0px, 0px)' : ', 0px)';
            } else {
                transformPrefix += horizontal ? 'X(' : 'Y(';
                transformPostfix = ')';
            }

        }

        if (carousel) { container.className = container.className.replace('tns-vpfix', ''); }
        initStructure();
        initSheet();
        initSliderTransform();

        // === COMMON FUNCTIONS === //
        function resetVariblesWhenDisable(condition) {
            if (condition) {
                controls = nav = touch = mouseDrag = arrowKeys = autoplay = autoplayHoverPause = autoplayResetOnVisibility = false;
            }
        }

        function getCurrentSlide() {
            let tem = carousel ? index - cloneCount : index;
            while (tem < 0) { tem += slideCount; }
            return tem % slideCount + 1;
        }

        function getStartIndex(ind) {
            ind = ind ? Math.max(0, Math.min(loop ? slideCount - 1 : slideCount - items, ind)) : 0;
            return carousel ? ind + cloneCount : ind;
        }

        function getAbsIndex(i) {
            if (i == null) { i = index; }

            if (carousel) { i -= cloneCount; }
            while (i < 0) { i += slideCount; }

            return Math.floor(i % slideCount);
        }

        function getCurrentNavIndex() {
            let absIndex = getAbsIndex(),
                result;

            result = navAsThumbnails ? absIndex :
                fixedWidth || autoWidth ? Math.ceil((absIndex + 1) * pages / slideCount - 1) :
                Math.floor(absIndex / items);

            // set active nav to the last one when reaches the right edge
            if (!loop && carousel && index === indexMax) { result = pages - 1; }

            return result;
        }

        function getItemsMax() {
            // fixedWidth or autoWidth while viewportMax is not available
            if (autoWidth || (fixedWidth && !viewportMax)) {
                return slideCount - 1;
                // most cases
            } else {
                let str = fixedWidth ? 'fixedWidth' : 'items',
                    arr = [];

                if (fixedWidth || options[str] < slideCount) { arr.push(options[str]); }

                if (responsive) {
                    for (let bp in responsive) {
                        let tem = responsive[bp][str];
                        if (tem && (fixedWidth || tem < slideCount)) { arr.push(tem); }
                    }
                }

                if (!arr.length) { arr.push(0); }

                return Math.ceil(fixedWidth ? viewportMax / Math.min.apply(null, arr) : Math.max.apply(null, arr));
            }
        }

        function getCloneCountForLoop() {
            let itemsMax = getItemsMax(),
                result = carousel ? Math.ceil((itemsMax * 5 - slideCount) / 2) : (itemsMax * 4 - slideCount);
            result = Math.max(itemsMax, result);

            return hasOption('edgePadding') ? result + 1 : result;
        }

        function getWindowWidth() {
            return win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth;
        }

        function getInsertPosition(pos) {
            return pos === 'top' ? 'afterbegin' : 'beforeend';
        }

        function getClientWidth(el) {
            if (el == null) { return; }
            let div = doc.createElement('div'),
                rect, width;
            el.appendChild(div);
            rect = div.getBoundingClientRect();
            width = rect.right - rect.left;
            div.remove();
            return width || getClientWidth(el.parentNode);
        }

        function getViewportWidth() {
            let gap = edgePadding ? edgePadding * 2 - gutter : 0;
            return getClientWidth(containerParent) - gap;
        }

        function hasOption(item) {
            if (options[item]) {
                return true;
            } else {
                if (responsive) {
                    for (let bp in responsive) {
                        if (responsive[bp][item]) { return true; }
                    }
                }
                return false;
            }
        }

        // get option:
        // fixed width: viewport, fixedWidth, gutter => items
        // others: window width => all variables
        // all: items => slideBy
        function getOption(item, ww) {
            if (ww == null) { ww = windowWidth; }

            if (item === 'items' && fixedWidth) {
                return Math.floor((viewport + gutter) / (fixedWidth + gutter)) || 1;

            } else {
                let result = options[item];

                if (responsive) {
                    for (let bp in responsive) {
                        // bp: convert string to number
                        if (ww >= parseInt(bp)) {
                            if (item in responsive[bp]) { result = responsive[bp][item]; }
                        }
                    }
                }

                if (item === 'slideBy' && result === 'page') { result = getOption('items'); }
                if (!carousel && (item === 'slideBy' || item === 'items')) { result = Math.floor(result); }

                return result;
            }
        }

        function getSlideMarginLeft(i) {
            return CALC ?
                CALC + '(' + i * 100 + '% / ' + slideCountNew + ')' :
                i * 100 / slideCountNew + '%';
        }

        function getInnerWrapperStyles(edgePaddingTem, gutterTem, fixedWidthTem, speedTem, autoHeightBP) {
            let str = '';

            if (edgePaddingTem !== undefined) {
                let gap = edgePaddingTem;
                if (gutterTem) { gap -= gutterTem; }
                str = horizontal ?
                    'margin: 0 ' + gap + 'px 0 ' + edgePaddingTem + 'px;' :
                    'margin: ' + edgePaddingTem + 'px 0 ' + gap + 'px 0;';
            } else if (gutterTem && !fixedWidthTem) {
                let gutterTemUnit = '-' + gutterTem + 'px',
                    dir = horizontal ? gutterTemUnit + ' 0 0' : '0 ' + gutterTemUnit + ' 0';
                str = 'margin: 0 ' + dir + ';';
            }

            if (!carousel && autoHeightBP && TRANSITIONDURATION && speedTem) { str += getTransitionDurationStyle(speedTem); }
            return str;
        }

        function getContainerWidth(fixedWidthTem, gutterTem, itemsTem) {
            if (fixedWidthTem) {
                return (fixedWidthTem + gutterTem) * slideCountNew + 'px';
            } else {
                return CALC ?
                    CALC + '(' + slideCountNew * 100 + '% / ' + itemsTem + ')' :
                    slideCountNew * 100 / itemsTem + '%';
            }
        }

        function getSlideWidthStyle(fixedWidthTem, gutterTem, itemsTem) {
            let width;

            if (fixedWidthTem) {
                width = (fixedWidthTem + gutterTem) + 'px';
            } else {
                if (!carousel) { itemsTem = Math.floor(itemsTem); }
                let dividend = carousel ? slideCountNew : itemsTem;
                width = CALC ?
                    CALC + '(100% / ' + dividend + ')' :
                    100 / dividend + '%';
            }

            width = 'width:' + width;

            // inner slider: overwrite outer slider styles
            return nested !== 'inner' ? width + ';' : width + ' !important;';
        }

        function getSlideGutterStyle(gutterTem) {
            let str = '';

            // gutter maybe interger || 0
            // so can't use 'if (gutter)'
            if (gutterTem !== false) {
                let prop = horizontal ? 'padding-' : 'margin-',
                    dir = horizontal ? 'right' : 'bottom';
                str = prop + dir + ': ' + gutterTem + 'px;';
            }

            return str;
        }

        function getCSSPrefix(name, num) {
            let prefix = name.substring(0, name.length - num).toLowerCase();
            if (prefix) { prefix = '-' + prefix + '-'; }

            return prefix;
        }

        function getTransitionDurationStyle(speed) {
            return getCSSPrefix(TRANSITIONDURATION, 18) + 'transition-duration:' + speed / 1000 + 's;';
        }

        function getAnimationDurationStyle(speed) {
            return getCSSPrefix(ANIMATIONDURATION, 17) + 'animation-duration:' + speed / 1000 + 's;';
        }

        function initStructure() {
            let classOuter = 'tns-outer',
                classInner = 'tns-inner',
                hasGutter = hasOption('gutter');

            outerWrapper.className = classOuter;
            innerWrapper.className = classInner;
            outerWrapper.id = slideId + '-ow';
            innerWrapper.id = slideId + '-iw';

            // set container properties
            if (container.id === '') { container.id = slideId; }
            newContainerClasses += PERCENTAGELAYOUT || autoWidth ? ' tns-subpixel' : ' tns-no-subpixel';
            newContainerClasses += CALC ? ' tns-calc' : ' tns-no-calc';
            if (autoWidth) { newContainerClasses += ' tns-autowidth'; }
            newContainerClasses += ' tns-' + options.axis;
            container.className += newContainerClasses;

            // add constrain layer for carousel
            if (carousel) {
                middleWrapper = doc.createElement('div');
                middleWrapper.id = slideId + '-mw';
                middleWrapper.className = 'tns-ovh';

                outerWrapper.appendChild(middleWrapper);
                middleWrapper.appendChild(innerWrapper);
            } else {
                outerWrapper.appendChild(innerWrapper);
            }

            if (autoHeight) {
                let wp = middleWrapper ? middleWrapper : innerWrapper;
                wp.className += ' tns-ah';
            }

            containerParent.insertBefore(outerWrapper, container);
            innerWrapper.appendChild(container);

            // add id, class, aria attributes
            // before clone slides
            forEach(slideItems, function(item, i) {
                addClass(item, 'tns-item');
                if (!item.id) { item.id = slideId + '-item' + i; }
                if (!carousel && animateNormal) { addClass(item, animateNormal); }
                setAttrs(item, {
                    'aria-hidden': 'true',
                    'tabindex': '-1'
                });
            });

            // ## clone slides
            // carousel: n + slides + n
            // gallery:      slides + n
            if (cloneCount) {
                let fragmentBefore = doc.createDocumentFragment(),
                    fragmentAfter = doc.createDocumentFragment();

                for (let j = cloneCount; j--;) {
                    let num = j % slideCount,
                        cloneFirst = slideItems[num].cloneNode(true);
                    addClass(cloneFirst, slideClonedClass);
                    removeAttrs(cloneFirst, 'id');
                    fragmentAfter.insertBefore(cloneFirst, fragmentAfter.firstChild);

                    if (carousel) {
                        let cloneLast = slideItems[slideCount - 1 - num].cloneNode(true);
                        addClass(cloneLast, slideClonedClass);
                        removeAttrs(cloneLast, 'id');
                        fragmentBefore.appendChild(cloneLast);
                    }
                }

                container.insertBefore(fragmentBefore, container.firstChild);
                container.appendChild(fragmentAfter);
                slideItems = container.children;
            }

        }

        function initSliderTransform() {
            // ## images loaded/failed
            if (hasOption('autoHeight') || autoWidth || !horizontal) {
                let imgs = container.querySelectorAll('img');

                // add img load event listener
                forEach(imgs, function(img) {
                    let src = img.src;

                    if (!lazyload) {
                        // not data img
                        if (src && src.indexOf('data:image') < 0) {
                            img.src = '';
                            addEvents(img, imgEvents);
                            addClass(img, 'loading');

                            img.src = src;
                            // data img
                        } else {
                            imgLoaded(img);
                        }
                    }
                });

                // set imgsComplete
                raf(function() { imgsLoadedCheck(arrayFromNodeList(imgs), function() { imgsComplete = true; }); });

                // reset imgs for auto height: check visible imgs only
                if (hasOption('autoHeight')) { imgs = getImageArray(index, Math.min(index + items - 1, slideCountNew - 1)); }

                lazyload ? initSliderTransformStyleCheck() : raf(function() { imgsLoadedCheck(arrayFromNodeList(imgs), initSliderTransformStyleCheck); });

            } else {
                // set container transform property
                if (carousel) { doContainerTransformSilent(); }

                // update slider tools and events
                initTools();
                initEvents();
            }
        }

        function initSliderTransformStyleCheck() {
            if (autoWidth && slideCount > 1) {
                // check styles application
                let num = loop ? index : slideCount - 1;

                (function stylesApplicationCheck() {
                    let left = slideItems[num].getBoundingClientRect().left;
                    let right = slideItems[num - 1].getBoundingClientRect().right;

                    (Math.abs(left - right) <= 1) ?
                    initSliderTransformCore():
                        setTimeout(function() { stylesApplicationCheck(); }, 16);
                })();

            } else {
                initSliderTransformCore();
            }
        }


        function initSliderTransformCore() {
            // run Fn()s which are rely on image loading
            if (!horizontal || autoWidth) {
                setSlidePositions();

                if (autoWidth) {
                    rightBoundary = getRightBoundary();
                    if (freezable) { freeze = getFreeze(); }
                    indexMax = getIndexMax(); // <= slidePositions, rightBoundary <=
                    resetVariblesWhenDisable(disable || freeze);
                } else {
                    updateContentWrapperHeight();
                }
            }

            // set container transform property
            if (carousel) { doContainerTransformSilent(); }

            // update slider tools and events
            initTools();
            initEvents();
        }

        function initSheet() {
            // gallery:
            // set animation classes and left value for gallery slider
            if (!carousel) {
                for (let i = index, l = index + Math.min(slideCount, items); i < l; i++) {
                    let item = slideItems[i];
                    item.style.left = (i - index) * 100 / items + '%';
                    addClass(item, animateIn);
                    removeClass(item, animateNormal);
                }
            }

            // #### LAYOUT

            // ## INLINE-BLOCK VS FLOAT

            // ## PercentageLayout:
            // slides: inline-block
            // remove blank space between slides by set font-size: 0

            // ## Non PercentageLayout:
            // slides: float
            //         margin-right: -100%
            //         margin-left: ~

            // Resource: https://docs.google.com/spreadsheets/d/147up245wwTXeQYve3BRSAD4oVcvQmuGsFteJOeA5xNQ/edit?usp=sharing
            if (horizontal) {
                if (PERCENTAGELAYOUT || autoWidth) {
                    addCSSRule(sheet, '#' + slideId + ' > .tns-item', 'font-size:' + win.getComputedStyle(slideItems[0]).fontSize + ';', getCssRulesLength(sheet));
                    addCSSRule(sheet, '#' + slideId, 'font-size:0;', getCssRulesLength(sheet));
                } else if (carousel) {
                    forEach(slideItems, function(slide, i) {
                        slide.style.marginLeft = getSlideMarginLeft(i);
                    });
                }
            }


            // ## BASIC STYLES
            if (CSSMQ) {
                // middle wrapper style
                if (TRANSITIONDURATION) {
                    let str = middleWrapper && options.autoHeight ? getTransitionDurationStyle(options.speed) : '';
                    addCSSRule(sheet, '#' + slideId + '-mw', str, getCssRulesLength(sheet));
                }

                // inner wrapper styles
                str = getInnerWrapperStyles(options.edgePadding, options.gutter, options.fixedWidth, options.speed, options.autoHeight);
                addCSSRule(sheet, '#' + slideId + '-iw', str, getCssRulesLength(sheet));

                // container styles
                if (carousel) {
                    str = horizontal && !autoWidth ? 'width:' + getContainerWidth(options.fixedWidth, options.gutter, options.items) + ';' : '';
                    if (TRANSITIONDURATION) { str += getTransitionDurationStyle(speed); }
                    addCSSRule(sheet, '#' + slideId, str, getCssRulesLength(sheet));
                }

                // slide styles
                str = horizontal && !autoWidth ? getSlideWidthStyle(options.fixedWidth, options.gutter, options.items) : '';
                if (options.gutter) { str += getSlideGutterStyle(options.gutter); }
                // set gallery items transition-duration
                if (!carousel) {
                    if (TRANSITIONDURATION) { str += getTransitionDurationStyle(speed); }
                    if (ANIMATIONDURATION) { str += getAnimationDurationStyle(speed); }
                }
                if (str) { addCSSRule(sheet, '#' + slideId + ' > .tns-item', str, getCssRulesLength(sheet)); }

                // non CSS mediaqueries: IE8
                // ## update inner wrapper, container, slides if needed
                // set inline styles for inner wrapper & container
                // insert stylesheet (one line) for slides only (since slides are many)
            } else {
                // middle wrapper styles
                update_carousel_transition_duration();

                // inner wrapper styles
                innerWrapper.style.cssText = getInnerWrapperStyles(edgePadding, gutter, fixedWidth, autoHeight);

                // container styles
                if (carousel && horizontal && !autoWidth) {
                    container.style.width = getContainerWidth(fixedWidth, gutter, items);
                }

                // slide styles
                let str = horizontal && !autoWidth ? getSlideWidthStyle(fixedWidth, gutter, items) : '';
                if (gutter) { str += getSlideGutterStyle(gutter); }

                // append to the last line
                if (str) { addCSSRule(sheet, '#' + slideId + ' > .tns-item', str, getCssRulesLength(sheet)); }
            }

            // ## MEDIAQUERIES
            if (responsive && CSSMQ) {
                for (let bp in responsive) {
                    // bp: convert string to number
                    bp = parseInt(bp);

                    let opts = responsive[bp],
                        str = '',
                        middleWrapperStr = '',
                        innerWrapperStr = '',
                        containerStr = '',
                        slideStr = '',
                        itemsBP = !autoWidth ? getOption('items', bp) : null,
                        fixedWidthBP = getOption('fixedWidth', bp),
                        speedBP = getOption('speed', bp),
                        edgePaddingBP = getOption('edgePadding', bp),
                        autoHeightBP = getOption('autoHeight', bp),
                        gutterBP = getOption('gutter', bp);

                    // middle wrapper string
                    if (TRANSITIONDURATION && middleWrapper && getOption('autoHeight', bp) && 'speed' in opts) {
                        middleWrapperStr = '#' + slideId + '-mw{' + getTransitionDurationStyle(speedBP) + '}';
                    }

                    // inner wrapper string
                    if ('edgePadding' in opts || 'gutter' in opts) {
                        innerWrapperStr = '#' + slideId + '-iw{' + getInnerWrapperStyles(edgePaddingBP, gutterBP, fixedWidthBP, speedBP, autoHeightBP) + '}';
                    }

                    // container string
                    if (carousel && horizontal && !autoWidth && ('fixedWidth' in opts || 'items' in opts || (fixedWidth && 'gutter' in opts))) {
                        containerStr = 'width:' + getContainerWidth(fixedWidthBP, gutterBP, itemsBP) + ';';
                    }
                    if (TRANSITIONDURATION && 'speed' in opts) {
                        containerStr += getTransitionDurationStyle(speedBP);
                    }
                    if (containerStr) {
                        containerStr = '#' + slideId + '{' + containerStr + '}';
                    }

                    // slide string
                    if ('fixedWidth' in opts || (fixedWidth && 'gutter' in opts) || !carousel && 'items' in opts) {
                        slideStr += getSlideWidthStyle(fixedWidthBP, gutterBP, itemsBP);
                    }
                    if ('gutter' in opts) {
                        slideStr += getSlideGutterStyle(gutterBP);
                    }
                    // set gallery items transition-duration
                    if (!carousel && 'speed' in opts) {
                        if (TRANSITIONDURATION) { slideStr += getTransitionDurationStyle(speedBP); }
                        if (ANIMATIONDURATION) { slideStr += getAnimationDurationStyle(speedBP); }
                    }
                    if (slideStr) { slideStr = '#' + slideId + ' > .tns-item{' + slideStr + '}'; }

                    // add up
                    str = middleWrapperStr + innerWrapperStr + containerStr + slideStr;

                    if (str) {
                        sheet.insertRule('@media (min-width: ' + bp / 16 + 'em) {' + str + '}', sheet.cssRules.length);
                    }
                }
            }
        }

        function initTools() {
            // == slides ==
            updateSlideStatus();

            // == live region ==
            outerWrapper.insertAdjacentHTML('afterbegin', '<div class="tns-liveregion tns-visually-hidden" aria-live="polite" aria-atomic="true">slide <span class="current">' + getLiveRegionStr() + '</span>  of ' + slideCount + '</div>');
            liveregionCurrent = outerWrapper.querySelector('.tns-liveregion .current');

            // == autoplayInit ==
            if (hasAutoplay) {
                let txt = autoplay ? 'stop' : 'start';
                if (autoplayButton) {
                    setAttrs(autoplayButton, { 'data-action': txt });
                } else if (options.autoplayButtonOutput) {
                    outerWrapper.insertAdjacentHTML(getInsertPosition(options.autoplayPosition), '<button type="button" data-action="' + txt + '">' + autoplayHtmlStrings[0] + txt + autoplayHtmlStrings[1] + autoplayText[0] + '</button>');
                    autoplayButton = outerWrapper.querySelector('[data-action]');
                }

                // add event
                if (autoplayButton) {
                    addEvents(autoplayButton, { 'click': toggleAutoplay });
                }

                if (autoplay) {
                    startAutoplay();
                    if (autoplayHoverPause) { addEvents(container, hoverEvents); }
                    if (autoplayResetOnVisibility) { addEvents(container, visibilityEvent); }
                }
            }

            // == navInit ==
            if (hasNav) {
                let initIndex = !carousel ? 0 : cloneCount;
                // customized nav
                // will not hide the navs in case they're thumbnails
                if (navContainer) {
                    setAttrs(navContainer, { 'aria-label': 'Carousel Pagination' });
                    navItems = navContainer.children;
                    forEach(navItems, function(item, i) {
                        setAttrs(item, {
                            'data-nav': i,
                            'tabindex': '-1',
                            'aria-label': navStr + (i + 1),
                            'aria-controls': slideId,
                        });
                    });

                    // generated nav
                } else {
                    let navHtml = '',
                        hiddenStr = navAsThumbnails ? '' : 'style="display:none"';
                    for (let i = 0; i < slideCount; i++) {
                        // hide nav items by default
                        navHtml += '<button type="button" data-nav="' + i + '" tabindex="-1" aria-controls="' + slideId + '" ' + hiddenStr + ' aria-label="' + navStr + (i + 1) + '"></button>';
                    }
                    navHtml = '<div class="tns-nav" aria-label="Carousel Pagination">' + navHtml + '</div>';
                    outerWrapper.insertAdjacentHTML(getInsertPosition(options.navPosition), navHtml);

                    navContainer = outerWrapper.querySelector('.tns-nav');
                    navItems = navContainer.children;
                }

                updateNavVisibility();

                // add transition
                if (TRANSITIONDURATION) {
                    let prefix = TRANSITIONDURATION.substring(0, TRANSITIONDURATION.length - 18).toLowerCase(),
                        str = 'transition: all ' + speed / 1000 + 's';

                    if (prefix) {
                        str = '-' + prefix + '-' + str;
                    }

                    addCSSRule(sheet, '[aria-controls^=' + slideId + '-item]', str, getCssRulesLength(sheet));
                }

                setAttrs(navItems[navCurrentIndex], { 'aria-label': navStr + (navCurrentIndex + 1) + navStrCurrent });
                removeAttrs(navItems[navCurrentIndex], 'tabindex');
                addClass(navItems[navCurrentIndex], navActiveClass);

                // add events
                addEvents(navContainer, navEvents);
            }



            // == controlsInit ==
            if (hasControls) {
                if (!controlsContainer && (!prevButton || !nextButton)) {
                    outerWrapper.insertAdjacentHTML(getInsertPosition(options.controlsPosition), '<div class="tns-controls" aria-label="Carousel Navigation" tabindex="0"><button type="button" data-controls="prev" tabindex="-1" aria-controls="' + slideId + '">' + controlsText[0] + '</button><button type="button" data-controls="next" tabindex="-1" aria-controls="' + slideId + '">' + controlsText[1] + '</button></div>');

                    controlsContainer = outerWrapper.querySelector('.tns-controls');
                }

                if (!prevButton || !nextButton) {
                    prevButton = controlsContainer.children[0];
                    nextButton = controlsContainer.children[1];
                }

                if (options.controlsContainer) {
                    setAttrs(controlsContainer, {
                        'aria-label': 'Carousel Navigation',
                        'tabindex': '0'
                    });
                }

                if (options.controlsContainer || (options.prevButton && options.nextButton)) {
                    setAttrs([prevButton, nextButton], {
                        'aria-controls': slideId,
                        'tabindex': '-1',
                    });
                }

                if (options.controlsContainer || (options.prevButton && options.nextButton)) {
                    setAttrs(prevButton, { 'data-controls': 'prev' });
                    setAttrs(nextButton, { 'data-controls': 'next' });
                }

                prevIsButton = isButton(prevButton);
                nextIsButton = isButton(nextButton);

                updateControlsStatus();

                // add events
                if (controlsContainer) {
                    addEvents(controlsContainer, controlsEvents);
                } else {
                    addEvents(prevButton, controlsEvents);
                    addEvents(nextButton, controlsEvents);
                }
            }

            // hide tools if needed
            disableUI();
        }

        function initEvents() {
            // add events
            if (carousel && TRANSITIONEND) {
                let eve = {};
                eve[TRANSITIONEND] = onTransitionEnd;
                addEvents(container, eve);
            }

            if (touch) { addEvents(container, touchEvents, options.preventScrollOnTouch); }
            if (mouseDrag) { addEvents(container, dragEvents); }
            if (arrowKeys) { addEvents(doc, docmentKeydownEvent); }

            if (nested === 'inner') {
                events.on('outerResized', function() {
                    resizeTasks();
                    events.emit('innerLoaded', info());
                });
            } else if (responsive || fixedWidth || autoWidth || autoHeight || !horizontal) {
                addEvents(win, { 'resize': onResize });
            }

            if (autoHeight) {
                if (nested === 'outer') {
                    events.on('innerLoaded', doAutoHeight);
                } else if (!disable) { doAutoHeight(); }
            }

            doLazyLoad();
            if (disable) { disableSlider(); } else if (freeze) { freezeSlider(); }

            events.on('indexChanged', additionalUpdates);
            if (nested === 'inner') { events.emit('innerLoaded', info()); }
            if (typeof onInit === 'function') { onInit(info()); }
            isOn = true;
        }

        function destroy() {
            // sheet
            sheet.disabled = true;
            if (sheet.ownerNode) { sheet.ownerNode.remove(); }

            // remove win event listeners
            removeEvents(win, { 'resize': onResize });

            // arrowKeys, controls, nav
            if (arrowKeys) { removeEvents(doc, docmentKeydownEvent); }
            if (controlsContainer) { removeEvents(controlsContainer, controlsEvents); }
            if (navContainer) { removeEvents(navContainer, navEvents); }

            // autoplay
            removeEvents(container, hoverEvents);
            removeEvents(container, visibilityEvent);
            if (autoplayButton) { removeEvents(autoplayButton, { 'click': toggleAutoplay }); }
            if (autoplay) { clearInterval(autoplayTimer); }

            // container
            if (carousel && TRANSITIONEND) {
                let eve = {};
                eve[TRANSITIONEND] = onTransitionEnd;
                removeEvents(container, eve);
            }
            if (touch) { removeEvents(container, touchEvents); }
            if (mouseDrag) { removeEvents(container, dragEvents); }

            // cache Object values in options && reset HTML
            let htmlList = [containerHTML, controlsContainerHTML, prevButtonHTML, nextButtonHTML, navContainerHTML, autoplayButtonHTML];

            tnsList.forEach(function(item, i) {
                var el = item === 'container' ? outerWrapper : options[item];

                if (typeof el === 'object' && el) {
                    var prevEl = el.previousElementSibling ? el.previousElementSibling : false,
                        parentEl = el.parentNode;
                    el.outerHTML = htmlList[i];
                    options[item] = prevEl ? prevEl.nextElementSibling : parentEl.firstElementChild;
                }
            });


            // reset variables
            tnsList = animateIn = animateOut = animateDelay = animateNormal = horizontal = outerWrapper = innerWrapper = container = containerParent = containerHTML = slideItems = slideCount = breakpointZone = windowWidth = autoWidth = fixedWidth = edgePadding = gutter = viewport = items = slideBy = viewportMax = arrowKeys = speed = rewind = loop = autoHeight = sheet = lazyload = slidePositions = slideItemsOut = cloneCount = slideCountNew = hasRightDeadZone = rightBoundary = updateIndexBeforeTransform = transformAttr = transformPrefix = transformPostfix = getIndexMax = index = indexCached = indexMin = indexMax = resizeTimer = swipeAngle = moveDirectionExpected = running = onInit = events = newContainerClasses = slideId = disable = disabled = freezable = freeze = frozen = controlsEvents = navEvents = hoverEvents = visibilityEvent = docmentKeydownEvent = touchEvents = dragEvents = hasControls = hasNav = navAsThumbnails = hasAutoplay = hasTouch = hasMouseDrag = slideActiveClass = imgCompleteClass = imgEvents = imgsComplete = controls = controlsText = controlsContainer = controlsContainerHTML = prevButton = nextButton = prevIsButton = nextIsButton = nav = navContainer = navContainerHTML = navItems = pages = pagesCached = navClicked = navCurrentIndex = navCurrentIndexCached = navActiveClass = navStr = navStrCurrent = autoplay = autoplayTimeout = autoplayDirection = autoplayText = autoplayHoverPause = autoplayButton = autoplayButtonHTML = autoplayResetOnVisibility = autoplayHtmlStrings = autoplayTimer = animating = autoplayHoverPaused = autoplayUserPaused = autoplayVisibilityPaused = initPosition = lastPosition = translateInit = disX = disY = panStart = rafIndex = getDist = touch = mouseDrag = null;
            // check variables
            // [animateIn, animateOut, animateDelay, animateNormal, horizontal, outerWrapper, innerWrapper, container, containerParent, containerHTML, slideItems, slideCount, breakpointZone, windowWidth, autoWidth, fixedWidth, edgePadding, gutter, viewport, items, slideBy, viewportMax, arrowKeys, speed, rewind, loop, autoHeight, sheet, lazyload, slidePositions, slideItemsOut, cloneCount, slideCountNew, hasRightDeadZone, rightBoundary, updateIndexBeforeTransform, transformAttr, transformPrefix, transformPostfix, getIndexMax, index, indexCached, indexMin, indexMax, resizeTimer, swipeAngle, moveDirectionExpected, running, onInit, events, newContainerClasses, slideId, disable, disabled, freezable, freeze, frozen, controlsEvents, navEvents, hoverEvents, visibilityEvent, docmentKeydownEvent, touchEvents, dragEvents, hasControls, hasNav, navAsThumbnails, hasAutoplay, hasTouch, hasMouseDrag, slideActiveClass, imgCompleteClass, imgEvents, imgsComplete, controls, controlsText, controlsContainer, controlsContainerHTML, prevButton, nextButton, prevIsButton, nextIsButton, nav, navContainer, navContainerHTML, navItems, pages, pagesCached, navClicked, navCurrentIndex, navCurrentIndexCached, navActiveClass, navStr, navStrCurrent, autoplay, autoplayTimeout, autoplayDirection, autoplayText, autoplayHoverPause, autoplayButton, autoplayButtonHTML, autoplayResetOnVisibility, autoplayHtmlStrings, autoplayTimer, animating, autoplayHoverPaused, autoplayUserPaused, autoplayVisibilityPaused, initPosition, lastPosition, translateInit, disX, disY, panStart, rafIndex, getDist, touch, mouseDrag ].forEach(function(item) { if (item !== null) { console.log(item); } });

            for (var a in this) {
                if (a !== 'rebuild') { this[a] = null; }
            }
            isOn = false;
        }

        // === ON RESIZE ===
        // responsive || fixedWidth || autoWidth || !horizontal
        function onResize(e) {
            raf(function() { resizeTasks(getEvent(e)); });
        }

        function resizeTasks(e) {
            if (!isOn) { return; }
            if (nested === 'outer') { events.emit('outerResized', info(e)); }
            windowWidth = getWindowWidth();
            var bpChanged,
                breakpointZoneTem = breakpointZone,
                needContainerTransform = false;

            if (responsive) {
                setBreakpointZone();
                bpChanged = breakpointZoneTem !== breakpointZone;
                // if (hasRightDeadZone) { needContainerTransform = true; } // *?
                if (bpChanged) { events.emit('newBreakpointStart', info(e)); }
            }

            var indChanged,
                itemsChanged,
                itemsTem = items,
                disableTem = disable,
                freezeTem = freeze,
                arrowKeysTem = arrowKeys,
                controlsTem = controls,
                navTem = nav,
                touchTem = touch,
                mouseDragTem = mouseDrag,
                autoplayTem = autoplay,
                autoplayHoverPauseTem = autoplayHoverPause,
                autoplayResetOnVisibilityTem = autoplayResetOnVisibility,
                indexTem = index;

            if (bpChanged) {
                var fixedWidthTem = fixedWidth,
                    autoHeightTem = autoHeight,
                    controlsTextTem = controlsText,
                    centerTem = center,
                    autoplayTextTem = autoplayText;

                if (!CSSMQ) {
                    var gutterTem = gutter,
                        edgePaddingTem = edgePadding;
                }
            }

            // get option:
            // fixed width: viewport, fixedWidth, gutter => items
            // others: window width => all variables
            // all: items => slideBy
            arrowKeys = getOption('arrowKeys');
            controls = getOption('controls');
            nav = getOption('nav');
            touch = getOption('touch');
            center = getOption('center');
            mouseDrag = getOption('mouseDrag');
            autoplay = getOption('autoplay');
            autoplayHoverPause = getOption('autoplayHoverPause');
            autoplayResetOnVisibility = getOption('autoplayResetOnVisibility');

            if (bpChanged) {
                disable = getOption('disable');
                fixedWidth = getOption('fixedWidth');
                speed = getOption('speed');
                autoHeight = getOption('autoHeight');
                controlsText = getOption('controlsText');
                autoplayText = getOption('autoplayText');
                autoplayTimeout = getOption('autoplayTimeout');

                if (!CSSMQ) {
                    edgePadding = getOption('edgePadding');
                    gutter = getOption('gutter');
                }
            }
            // update options
            resetVariblesWhenDisable(disable);

            viewport = getViewportWidth(); // <= edgePadding, gutter
            if ((!horizontal || autoWidth) && !disable) {
                setSlidePositions();
                if (!horizontal) {
                    updateContentWrapperHeight(); // <= setSlidePositions
                    needContainerTransform = true;
                }
            }
            if (fixedWidth || autoWidth) {
                rightBoundary = getRightBoundary(); // autoWidth: <= viewport, slidePositions, gutter
                // fixedWidth: <= viewport, fixedWidth, gutter
                indexMax = getIndexMax(); // autoWidth: <= rightBoundary, slidePositions
                // fixedWidth: <= rightBoundary, fixedWidth, gutter
            }

            if (bpChanged || fixedWidth) {
                items = getOption('items');
                slideBy = getOption('slideBy');
                itemsChanged = items !== itemsTem;

                if (itemsChanged) {
                    if (!fixedWidth && !autoWidth) { indexMax = getIndexMax(); } // <= items
                    // check index before transform in case
                    // slider reach the right edge then items become bigger
                    updateIndex();
                }
            }

            if (bpChanged) {
                if (disable !== disableTem) {
                    if (disable) {
                        disableSlider();
                    } else {
                        enableSlider(); // <= slidePositions, rightBoundary, indexMax
                    }
                }
            }

            if (freezable && (bpChanged || fixedWidth || autoWidth)) {
                freeze = getFreeze(); // <= autoWidth: slidePositions, gutter, viewport, rightBoundary
                // <= fixedWidth: fixedWidth, gutter, rightBoundary
                // <= others: items

                if (freeze !== freezeTem) {
                    if (freeze) {
                        doContainerTransform(getContainerTransformValue(getStartIndex(0)));
                        freezeSlider();
                    } else {
                        unfreezeSlider();
                        needContainerTransform = true;
                    }
                }
            }

            resetVariblesWhenDisable(disable || freeze); // controls, nav, touch, mouseDrag, arrowKeys, autoplay, autoplayHoverPause, autoplayResetOnVisibility
            if (!autoplay) { autoplayHoverPause = autoplayResetOnVisibility = false; }

            if (arrowKeys !== arrowKeysTem) {
                arrowKeys ?
                    addEvents(doc, docmentKeydownEvent) :
                    removeEvents(doc, docmentKeydownEvent);
            }
            if (controls !== controlsTem) {
                if (controls) {
                    if (controlsContainer) {
                        showElement(controlsContainer);
                    } else {
                        if (prevButton) { showElement(prevButton); }
                        if (nextButton) { showElement(nextButton); }
                    }
                } else {
                    if (controlsContainer) {
                        hideElement(controlsContainer);
                    } else {
                        if (prevButton) { hideElement(prevButton); }
                        if (nextButton) { hideElement(nextButton); }
                    }
                }
            }
            if (nav !== navTem) {
                if (nav) {
                    showElement(navContainer);
                    updateNavVisibility();
                } else {
                    hideElement(navContainer);
                }
            }
            if (touch !== touchTem) {
                touch ?
                    addEvents(container, touchEvents, options.preventScrollOnTouch) :
                    removeEvents(container, touchEvents);
            }
            if (mouseDrag !== mouseDragTem) {
                mouseDrag ?
                    addEvents(container, dragEvents) :
                    removeEvents(container, dragEvents);
            }
            if (autoplay !== autoplayTem) {
                if (autoplay) {
                    if (autoplayButton) { showElement(autoplayButton); }
                    if (!animating && !autoplayUserPaused) { startAutoplay(); }
                } else {
                    if (autoplayButton) { hideElement(autoplayButton); }
                    if (animating) { stopAutoplay(); }
                }
            }
            if (autoplayHoverPause !== autoplayHoverPauseTem) {
                autoplayHoverPause ?
                    addEvents(container, hoverEvents) :
                    removeEvents(container, hoverEvents);
            }
            if (autoplayResetOnVisibility !== autoplayResetOnVisibilityTem) {
                autoplayResetOnVisibility ?
                    addEvents(doc, visibilityEvent) :
                    removeEvents(doc, visibilityEvent);
            }

            if (bpChanged) {
                if (fixedWidth !== fixedWidthTem || center !== centerTem) { needContainerTransform = true; }

                if (autoHeight !== autoHeightTem) {
                    if (!autoHeight) { innerWrapper.style.height = ''; }
                }

                if (controls && controlsText !== controlsTextTem) {
                    prevButton.innerHTML = controlsText[0];
                    nextButton.innerHTML = controlsText[1];
                }

                if (autoplayButton && autoplayText !== autoplayTextTem) {
                    var i = autoplay ? 1 : 0,
                        html = autoplayButton.innerHTML,
                        len = html.length - autoplayTextTem[i].length;
                    if (html.substring(len) === autoplayTextTem[i]) {
                        autoplayButton.innerHTML = html.substring(0, len) + autoplayText[i];
                    }
                }
            } else {
                if (center && (fixedWidth || autoWidth)) { needContainerTransform = true; }
            }

            if (itemsChanged || fixedWidth && !autoWidth) {
                pages = getPages();
                updateNavVisibility();
            }

            indChanged = index !== indexTem;
            if (indChanged) {
                events.emit('indexChanged', info());
                needContainerTransform = true;
            } else if (itemsChanged) {
                if (!indChanged) { additionalUpdates(); }
            } else if (fixedWidth || autoWidth) {
                doLazyLoad();
                updateSlideStatus();
                updateLiveRegion();
            }

            if (itemsChanged && !carousel) { updateGallerySlidePositions(); }

            if (!disable && !freeze) {
                // non-mediaqueries: IE8
                if (bpChanged && !CSSMQ) {
                    // middle wrapper styles

                    // inner wrapper styles
                    if (edgePadding !== edgePaddingTem || gutter !== gutterTem) {
                        innerWrapper.style.cssText = getInnerWrapperStyles(edgePadding, gutter, fixedWidth, speed, autoHeight);
                    }

                    if (horizontal) {
                        // container styles
                        if (carousel) {
                            container.style.width = getContainerWidth(fixedWidth, gutter, items);
                        }

                        // slide styles
                        var str = getSlideWidthStyle(fixedWidth, gutter, items) +
                            getSlideGutterStyle(gutter);

                        // remove the last line and
                        // add new styles
                        removeCSSRule(sheet, getCssRulesLength(sheet) - 1);
                        addCSSRule(sheet, '#' + slideId + ' > .tns-item', str, getCssRulesLength(sheet));
                    }
                }

                // auto height
                if (autoHeight) { doAutoHeight(); }

                if (needContainerTransform) {
                    doContainerTransformSilent();
                    indexCached = index;
                }
            }

            if (bpChanged) { events.emit('newBreakpointEnd', info(e)); }
        }





        // === INITIALIZATION FUNCTIONS === //
        function getFreeze() {
            if (!fixedWidth && !autoWidth) {
                var a = center ? items - (items - 1) / 2 : items;
                return slideCount <= a;
            }

            var width = fixedWidth ? (fixedWidth + gutter) * slideCount : slidePositions[slideCount],
                vp = edgePadding ? viewport + edgePadding * 2 : viewport + gutter;

            if (center) {
                vp -= fixedWidth ? (viewport - fixedWidth) / 2 : (viewport - (slidePositions[index + 1] - slidePositions[index] - gutter)) / 2;
            }

            return width <= vp;
        }

        function setBreakpointZone() {
            breakpointZone = 0;
            for (var bp in responsive) {
                bp = parseInt(bp); // convert string to number
                if (windowWidth >= bp) { breakpointZone = bp; }
            }
        }

        // (slideBy, indexMin, indexMax) => index
        var updateIndex = (function() {
            return loop ?
                carousel ?
                // loop + carousel
                function() {
                    var leftEdge = indexMin,
                        rightEdge = indexMax;

                    leftEdge += slideBy;
                    rightEdge -= slideBy;

                    // adjust edges when has edge paddings
                    // or fixed-width slider with extra space on the right side
                    if (edgePadding) {
                        leftEdge += 1;
                        rightEdge -= 1;
                    } else if (fixedWidth) {
                        if ((viewport + gutter) % (fixedWidth + gutter)) { rightEdge -= 1; }
                    }

                    if (cloneCount) {
                        if (index > rightEdge) {
                            index -= slideCount;
                        } else if (index < leftEdge) {
                            index += slideCount;
                        }
                    }
                } :
                // loop + gallery
                function() {
                    if (index > indexMax) {
                        while (index >= indexMin + slideCount) { index -= slideCount; }
                    } else if (index < indexMin) {
                        while (index <= indexMax - slideCount) { index += slideCount; }
                    }
                } :
                // non-loop
                function() {
                    index = Math.max(indexMin, Math.min(indexMax, index));
                };
        })();

        function disableUI() {
            if (!autoplay && autoplayButton) { hideElement(autoplayButton); }
            if (!nav && navContainer) { hideElement(navContainer); }
            if (!controls) {
                if (controlsContainer) {
                    hideElement(controlsContainer);
                } else {
                    if (prevButton) { hideElement(prevButton); }
                    if (nextButton) { hideElement(nextButton); }
                }
            }
        }

        function enableUI() {
            if (autoplay && autoplayButton) { showElement(autoplayButton); }
            if (nav && navContainer) { showElement(navContainer); }
            if (controls) {
                if (controlsContainer) {
                    showElement(controlsContainer);
                } else {
                    if (prevButton) { showElement(prevButton); }
                    if (nextButton) { showElement(nextButton); }
                }
            }
        }

        function freezeSlider() {
            if (frozen) { return; }

            // remove edge padding from inner wrapper
            if (edgePadding) { innerWrapper.style.margin = '0px'; }

            // add class tns-transparent to cloned slides
            if (cloneCount) {
                var str = 'tns-transparent';
                for (var i = cloneCount; i--;) {
                    if (carousel) { addClass(slideItems[i], str); }
                    addClass(slideItems[slideCountNew - i - 1], str);
                }
            }

            // update tools
            disableUI();

            frozen = true;
        }

        function unfreezeSlider() {
            if (!frozen) { return; }

            // restore edge padding for inner wrapper
            // for mordern browsers
            if (edgePadding && CSSMQ) { innerWrapper.style.margin = ''; }

            // remove class tns-transparent to cloned slides
            if (cloneCount) {
                var str = 'tns-transparent';
                for (var i = cloneCount; i--;) {
                    if (carousel) { removeClass(slideItems[i], str); }
                    removeClass(slideItems[slideCountNew - i - 1], str);
                }
            }

            // update tools
            enableUI();

            frozen = false;
        }

        function disableSlider() {
            if (disabled) { return; }

            sheet.disabled = true;
            container.className = container.className.replace(newContainerClasses.substring(1), '');
            removeAttrs(container, ['style']);
            if (loop) {
                for (var j = cloneCount; j--;) {
                    if (carousel) { hideElement(slideItems[j]); }
                    hideElement(slideItems[slideCountNew - j - 1]);
                }
            }

            // vertical slider
            if (!horizontal || !carousel) { removeAttrs(innerWrapper, ['style']); }

            // gallery
            if (!carousel) {
                for (var i = index, l = index + slideCount; i < l; i++) {
                    var item = slideItems[i];
                    removeAttrs(item, ['style']);
                    removeClass(item, animateIn);
                    removeClass(item, animateNormal);
                }
            }

            // update tools
            disableUI();

            disabled = true;
        }

        function enableSlider() {
            if (!disabled) { return; }

            sheet.disabled = false;
            container.className += newContainerClasses;
            doContainerTransformSilent();

            if (loop) {
                for (var j = cloneCount; j--;) {
                    if (carousel) { showElement(slideItems[j]); }
                    showElement(slideItems[slideCountNew - j - 1]);
                }
            }

            // gallery
            if (!carousel) {
                for (var i = index, l = index + slideCount; i < l; i++) {
                    var item = slideItems[i],
                        classN = i < index + items ? animateIn : animateNormal;
                    item.style.left = (i - index) * 100 / items + '%';
                    addClass(item, classN);
                }
            }

            // update tools
            enableUI();

            disabled = false;
        }

        function updateLiveRegion() {
            var str = getLiveRegionStr();
            if (liveregionCurrent.innerHTML !== str) { liveregionCurrent.innerHTML = str; }
        }

        function getLiveRegionStr() {
            var arr = getVisibleSlideRange(),
                start = arr[0] + 1,
                end = arr[1] + 1;
            return start === end ? start + '' : start + ' to ' + end;
        }

        function getVisibleSlideRange(val) {
            if (val == null) { val = getContainerTransformValue(); }
            var start = index,
                end, rangestart, rangeend;

            // get range start, range end for autoWidth and fixedWidth
            if (center || edgePadding) {
                if (autoWidth || fixedWidth) {
                    rangestart = -(parseFloat(val) + edgePadding);
                    rangeend = rangestart + viewport + edgePadding * 2;
                }
            } else {
                if (autoWidth) {
                    rangestart = slidePositions[index];
                    rangeend = rangestart + viewport;
                }
            }

            // get start, end
            // - check auto width
            if (autoWidth) {
                slidePositions.forEach(function(point, i) {
                    if (i < slideCountNew) {
                        if ((center || edgePadding) && point <= rangestart + 0.5) { start = i; }
                        if (rangeend - point >= 0.5) { end = i; }
                    }
                });

                // - check percentage width, fixed width
            } else {

                if (fixedWidth) {
                    var cell = fixedWidth + gutter;
                    if (center || edgePadding) {
                        start = Math.floor(rangestart / cell);
                        end = Math.ceil(rangeend / cell - 1);
                    } else {
                        end = start + Math.ceil(viewport / cell) - 1;
                    }

                } else {
                    if (center || edgePadding) {
                        var a = items - 1;
                        if (center) {
                            start -= a / 2;
                            end = index + a / 2;
                        } else {
                            end = index + a;
                        }

                        if (edgePadding) {
                            var b = edgePadding * items / viewport;
                            start -= b;
                            end += b;
                        }

                        start = Math.floor(start);
                        end = Math.ceil(end);
                    } else {
                        end = start + items - 1;
                    }
                }

                start = Math.max(start, 0);
                end = Math.min(end, slideCountNew - 1);
            }

            return [start, end];
        }

        function doLazyLoad() {
            if (lazyload && !disable) {
                var arg = getVisibleSlideRange();
                arg.push(lazyloadSelector);

                getImageArray.apply(null, arg).forEach(function(img) {
                    if (!hasClass(img, imgCompleteClass)) {
                        // stop propagation transitionend event to container
                        var eve = {};
                        eve[TRANSITIONEND] = function(e) { e.stopPropagation(); };
                        addEvents(img, eve);

                        addEvents(img, imgEvents);

                        // update src
                        img.src = getAttr(img, 'data-src');

                        // update srcset
                        var srcset = getAttr(img, 'data-srcset');
                        if (srcset) { img.srcset = srcset; }

                        addClass(img, 'loading');
                    }
                });
            }
        }

        function onImgLoaded(e) {
            imgLoaded(getTarget(e));
        }

        function onImgFailed(e) {
            imgFailed(getTarget(e));
        }

        function imgLoaded(img) {
            addClass(img, 'loaded');
            imgCompleted(img);
        }

        function imgFailed(img) {
            addClass(img, 'failed');
            imgCompleted(img);
        }

        function imgCompleted(img) {
            addClass(img, imgCompleteClass);
            removeClass(img, 'loading');
            removeEvents(img, imgEvents);
        }

        function getImageArray(start, end, imgSelector) {
            var imgs = [];
            if (!imgSelector) { imgSelector = 'img'; }

            while (start <= end) {
                forEach(slideItems[start].querySelectorAll(imgSelector), function(img) { imgs.push(img); });
                start++;
            }

            return imgs;
        }

        // check if all visible images are loaded
        // and update container height if it's done
        function doAutoHeight() {
            var imgs = getImageArray.apply(null, getVisibleSlideRange());
            raf(function() { imgsLoadedCheck(imgs, updateInnerWrapperHeight); });
        }

        function imgsLoadedCheck(imgs, cb) {
            // execute callback function if all images are complete
            if (imgsComplete) { return cb(); }

            // check image classes
            imgs.forEach(function(img, index) {
                if (!lazyload && img.complete) { imgCompleted(img); } // Check image.complete
                if (hasClass(img, imgCompleteClass)) { imgs.splice(index, 1); }
            });

            // execute callback function if selected images are all complete
            if (!imgs.length) { return cb(); }

            // otherwise execute this functiona again
            raf(function() { imgsLoadedCheck(imgs, cb); });
        }

        function additionalUpdates() {
            doLazyLoad();
            updateSlideStatus();
            updateLiveRegion();
            updateControlsStatus();
            updateNavStatus();
        }


        function update_carousel_transition_duration() {
            if (carousel && autoHeight) {
                middleWrapper.style[TRANSITIONDURATION] = speed / 1000 + 's';
            }
        }

        function getMaxSlideHeight(slideStart, slideRange) {
            var heights = [];
            for (var i = slideStart, l = Math.min(slideStart + slideRange, slideCountNew); i < l; i++) {
                heights.push(slideItems[i].offsetHeight);
            }

            return Math.max.apply(null, heights);
        }

        // update inner wrapper height
        // 1. get the max-height of the visible slides
        // 2. set transitionDuration to speed
        // 3. update inner wrapper height to max-height
        // 4. set transitionDuration to 0s after transition done
        function updateInnerWrapperHeight() {
            var maxHeight = autoHeight ? getMaxSlideHeight(index, items) : getMaxSlideHeight(cloneCount, slideCount),
                wp = middleWrapper ? middleWrapper : innerWrapper;

            if (wp.style.height !== maxHeight) { wp.style.height = maxHeight + 'px'; }
        }

        // get the distance from the top edge of the first slide to each slide
        // (init) => slidePositions
        function setSlidePositions() {
            slidePositions = [0];
            var attr = horizontal ? 'left' : 'top',
                attr2 = horizontal ? 'right' : 'bottom',
                base = slideItems[0].getBoundingClientRect()[attr];

            forEach(slideItems, function(item, i) {
                // skip the first slide
                if (i) { slidePositions.push(item.getBoundingClientRect()[attr] - base); }
                // add the end edge
                if (i === slideCountNew - 1) { slidePositions.push(item.getBoundingClientRect()[attr2] - base); }
            });
        }

        // update slide
        function updateSlideStatus() {
            var range = getVisibleSlideRange(),
                start = range[0],
                end = range[1];

            forEach(slideItems, function(item, i) {
                // show slides
                if (i >= start && i <= end) {
                    if (hasAttr(item, 'aria-hidden')) {
                        removeAttrs(item, ['aria-hidden', 'tabindex']);
                        addClass(item, slideActiveClass);
                    }
                    // hide slides
                } else {
                    if (!hasAttr(item, 'aria-hidden')) {
                        setAttrs(item, {
                            'aria-hidden': 'true',
                            'tabindex': '-1'
                        });
                        removeClass(item, slideActiveClass);
                    }
                }
            });
        }

        // gallery: update slide position
        function updateGallerySlidePositions() {
            var l = index + Math.min(slideCount, items);
            for (var i = slideCountNew; i--;) {
                var item = slideItems[i];

                if (i >= index && i < l) {
                    // add transitions to visible slides when adjusting their positions
                    addClass(item, 'tns-moving');

                    item.style.left = (i - index) * 100 / items + '%';
                    addClass(item, animateIn);
                    removeClass(item, animateNormal);
                } else if (item.style.left) {
                    item.style.left = '';
                    addClass(item, animateNormal);
                    removeClass(item, animateIn);
                }

                // remove outlet animation
                removeClass(item, animateOut);
            }

            // removing '.tns-moving'
            setTimeout(function() {
                forEach(slideItems, function(el) {
                    removeClass(el, 'tns-moving');
                });
            }, 300);
        }

        // set tabindex on Nav
        function updateNavStatus() {
            // get current nav
            if (nav) {
                navCurrentIndex = navClicked >= 0 ? navClicked : getCurrentNavIndex();
                navClicked = -1;

                if (navCurrentIndex !== navCurrentIndexCached) {
                    var navPrev = navItems[navCurrentIndexCached],
                        navCurrent = navItems[navCurrentIndex];

                    setAttrs(navPrev, {
                        'tabindex': '-1',
                        'aria-label': navStr + (navCurrentIndexCached + 1)
                    });
                    removeClass(navPrev, navActiveClass);

                    setAttrs(navCurrent, { 'aria-label': navStr + (navCurrentIndex + 1) + navStrCurrent });
                    removeAttrs(navCurrent, 'tabindex');
                    addClass(navCurrent, navActiveClass);

                    navCurrentIndexCached = navCurrentIndex;
                }
            }
        }

        function getLowerCaseNodeName(el) {
            return el.nodeName.toLowerCase();
        }

        function isButton(el) {
            return getLowerCaseNodeName(el) === 'button';
        }

        function isAriaDisabled(el) {
            return el.getAttribute('aria-disabled') === 'true';
        }

        function disEnableElement(isButton, el, val) {
            if (isButton) {
                el.disabled = val;
            } else {
                el.setAttribute('aria-disabled', val.toString());
            }
        }

        // set 'disabled' to true on controls when reach the edges
        function updateControlsStatus() {
            if (!controls || rewind || loop) { return; }

            var prevDisabled = (prevIsButton) ? prevButton.disabled : isAriaDisabled(prevButton),
                nextDisabled = (nextIsButton) ? nextButton.disabled : isAriaDisabled(nextButton),
                disablePrev = (index <= indexMin) ? true : false,
                disableNext = (!rewind && index >= indexMax) ? true : false;

            if (disablePrev && !prevDisabled) {
                disEnableElement(prevIsButton, prevButton, true);
            }
            if (!disablePrev && prevDisabled) {
                disEnableElement(prevIsButton, prevButton, false);
            }
            if (disableNext && !nextDisabled) {
                disEnableElement(nextIsButton, nextButton, true);
            }
            if (!disableNext && nextDisabled) {
                disEnableElement(nextIsButton, nextButton, false);
            }
        }

        // set duration
        function resetDuration(el, str) {
            if (TRANSITIONDURATION) { el.style[TRANSITIONDURATION] = str; }
        }

        function getSliderWidth() {
            return fixedWidth ? (fixedWidth + gutter) * slideCountNew : slidePositions[slideCountNew];
        }

        function getCenterGap(num) {
            if (num == null) { num = index; }

            var gap = edgePadding ? gutter : 0;
            return autoWidth ? ((viewport - gap) - (slidePositions[num + 1] - slidePositions[num] - gutter)) / 2 :
                fixedWidth ? (viewport - fixedWidth) / 2 :
                (items - 1) / 2;
        }

        function getRightBoundary() {
            var gap = edgePadding ? gutter : 0,
                result = (viewport + gap) - getSliderWidth();

            if (center && !loop) {
                result = fixedWidth ? -(fixedWidth + gutter) * (slideCountNew - 1) - getCenterGap() :
                    getCenterGap(slideCountNew - 1) - slidePositions[slideCountNew - 1];
            }
            if (result > 0) { result = 0; }

            return result;
        }

        function getContainerTransformValue(num) {
            if (num == null) { num = index; }

            var val;
            if (horizontal && !autoWidth) {
                if (fixedWidth) {
                    val = -(fixedWidth + gutter) * num;
                    if (center) { val += getCenterGap(); }
                } else {
                    var denominator = TRANSFORM ? slideCountNew : items;
                    if (center) { num -= getCenterGap(); }
                    val = -num * 100 / denominator;
                }
            } else {
                val = -slidePositions[num];
                if (center && autoWidth) {
                    val += getCenterGap();
                }
            }

            if (hasRightDeadZone) { val = Math.max(val, rightBoundary); }

            val += (horizontal && !autoWidth && !fixedWidth) ? '%' : 'px';

            return val;
        }

        function doContainerTransformSilent(val) {
            resetDuration(container, '0s');
            doContainerTransform(val);
        }

        function doContainerTransform(val) {
            if (val == null) { val = getContainerTransformValue(); }
            container.style[transformAttr] = transformPrefix + val + transformPostfix;
        }

        function animateSlide(number, classOut, classIn, isOut) {
            var l = number + items;
            if (!loop) { l = Math.min(l, slideCountNew); }

            for (var i = number; i < l; i++) {
                var item = slideItems[i];

                // set item positions
                if (!isOut) { item.style.left = (i - index) * 100 / items + '%'; }

                if (animateDelay && TRANSITIONDELAY) {
                    item.style[TRANSITIONDELAY] = item.style[ANIMATIONDELAY] = animateDelay * (i - number) / 1000 + 's';
                }
                removeClass(item, classOut);
                addClass(item, classIn);

                if (isOut) { slideItemsOut.push(item); }
            }
        }

        // make transfer after click/drag:
        // 1. change 'transform' property for mordern browsers
        // 2. change 'left' property for legacy browsers
        var transformCore = (function() {
            return carousel ?
                function() {
                    resetDuration(container, '');
                    if (TRANSITIONDURATION || !speed) {
                        // for morden browsers with non-zero duration or
                        // zero duration for all browsers
                        doContainerTransform();
                        // run fallback function manually
                        // when duration is 0 / container is hidden
                        if (!speed || !isVisible(container)) { onTransitionEnd(); }

                    } else {
                        // for old browser with non-zero duration
                        jsTransform(container, transformAttr, transformPrefix, transformPostfix, getContainerTransformValue(), speed, onTransitionEnd);
                    }

                    if (!horizontal) { updateContentWrapperHeight(); }
                } :
                function() {
                    slideItemsOut = [];

                    var eve = {};
                    eve[TRANSITIONEND] = eve[ANIMATIONEND] = onTransitionEnd;
                    removeEvents(slideItems[indexCached], eve);
                    addEvents(slideItems[index], eve);

                    animateSlide(indexCached, animateIn, animateOut, true);
                    animateSlide(index, animateNormal, animateIn);

                    // run fallback function manually
                    // when transition or animation not supported / duration is 0
                    if (!TRANSITIONEND || !ANIMATIONEND || !speed || !isVisible(container)) { onTransitionEnd(); }
                };
        })();

        function render(e, sliderMoved) {
            if (updateIndexBeforeTransform) { updateIndex(); }

            // render when slider was moved (touch or drag) even though index may not change
            if (index !== indexCached || sliderMoved) {
                // events
                events.emit('indexChanged', info());
                events.emit('transitionStart', info());
                if (autoHeight) { doAutoHeight(); }

                // pause autoplay when click or keydown from user
                if (animating && e && ['click', 'keydown'].indexOf(e.type) >= 0) { stopAutoplay(); }

                running = true;
                transformCore();
            }
        }

        /*
         * Transfer prefixed properties to the same format
         * CSS: -Webkit-Transform => webkittransform
         * JS: WebkitTransform => webkittransform
         * @param {string} str - property
         *
         */
        function strTrans(str) {
            return str.toLowerCase().replace(/-/g, '');
        }

        // AFTER TRANSFORM
        // Things need to be done after a transfer:
        // 1. check index
        // 2. add classes to visible slide
        // 3. disable controls buttons when reach the first/last slide in non-loop slider
        // 4. update nav status
        // 5. lazyload images
        // 6. update container height
        function onTransitionEnd(event) {
            // check running on gallery mode
            // make sure trantionend/animationend events run only once
            if (carousel || running) {
                events.emit('transitionEnd', info(event));

                if (!carousel && slideItemsOut.length > 0) {
                    for (var i = 0; i < slideItemsOut.length; i++) {
                        var item = slideItemsOut[i];
                        // set item positions
                        item.style.left = '';

                        if (ANIMATIONDELAY && TRANSITIONDELAY) {
                            item.style[ANIMATIONDELAY] = '';
                            item.style[TRANSITIONDELAY] = '';
                        }
                        removeClass(item, animateOut);
                        addClass(item, animateNormal);
                    }
                }

                /* update slides, nav, controls after checking ...
                 * => legacy browsers who don't support 'event'
                 *    have to check event first, otherwise event.target will cause an error
                 * => or 'gallery' mode:
                 *   + event target is slide item
                 * => or 'carousel' mode:
                 *   + event target is container,
                 *   + event.property is the same with transform attribute
                 */
                if (!event ||
                    !carousel && event.target.parentNode === container ||
                    event.target === container && strTrans(event.propertyName) === strTrans(transformAttr)) {

                    if (!updateIndexBeforeTransform) {
                        let indexTem = index;
                        updateIndex();
                        if (index !== indexTem) {
                            events.emit('indexChanged', info());

                            doContainerTransformSilent();
                        }
                    }

                    if (nested === 'inner') { events.emit('innerLoaded', info()); }
                    running = false;
                    indexCached = index;
                }
            }

        }

        // # ACTIONS
        function goTo(targetIndex, e) {
            if (freeze) { return; }

            // prev slideBy
            if (targetIndex === 'prev') {
                onControlsClick(e, -1);

                // next slideBy
            } else if (targetIndex === 'next') {
                onControlsClick(e, 1);

                // go to exact slide
            } else {
                if (running) {
                    if (preventActionWhenRunning) { return; } else { onTransitionEnd(); }
                }

                let absIndex = getAbsIndex(),
                    indexGap = 0;

                if (targetIndex === 'first') {
                    indexGap = -absIndex;
                } else if (targetIndex === 'last') {
                    indexGap = carousel ? slideCount - items - absIndex : slideCount - 1 - absIndex;
                } else {
                    if (typeof targetIndex !== 'number') { targetIndex = parseInt(targetIndex); }

                    if (!isNaN(targetIndex)) {
                        // from directly called goTo function
                        if (!e) { targetIndex = Math.max(0, Math.min(slideCount - 1, targetIndex)); }

                        indexGap = targetIndex - absIndex;
                    }
                }

                // gallery: make sure new page won't overlap with current page
                if (!carousel && indexGap && Math.abs(indexGap) < items) {
                    let factor = indexGap > 0 ? 1 : -1;
                    indexGap += (index + indexGap - slideCount) >= indexMin ? slideCount * factor : slideCount * 2 * factor * -1;
                }

                index += indexGap;

                // make sure index is in range
                if (carousel && loop) {
                    if (index < indexMin) { index += slideCount; }
                    if (index > indexMax) { index -= slideCount; }
                }

                // if index is changed, start rendering
                if (getAbsIndex(index) !== getAbsIndex(indexCached)) {
                    render(e);
                }

            }
        }

        // on controls click
        function onControlsClick(e, dir) {
            if (running) {
                if (preventActionWhenRunning) { return; } else { onTransitionEnd(); }
            }
            var passEventObject;

            if (!dir) {
                e = getEvent(e);
                let target = getTarget(e);

                while (target !== controlsContainer && [prevButton, nextButton].indexOf(target) < 0) { target = target.parentNode; }

                var targetIn = [prevButton, nextButton].indexOf(target);
                if (targetIn >= 0) {
                    passEventObject = true;
                    dir = targetIn === 0 ? -1 : 1;
                }
            }

            if (rewind) {
                if (index === indexMin && dir === -1) {
                    goTo('last', e);
                    return;
                } else if (index === indexMax && dir === 1) {
                    goTo('first', e);
                    return;
                }
            }

            if (dir) {
                index += slideBy * dir;
                if (autoWidth) { index = Math.floor(index); }
                // pass e when click control buttons or keydown
                render((passEventObject || (e && e.type === 'keydown')) ? e : null);
            }
        }

        // on nav click
        function onNavClick(e) {
            if (running) {
                if (preventActionWhenRunning) { return; } else { onTransitionEnd(); }
            }

            e = getEvent(e);
            let target = getTarget(e),
                navIndex;

            // find the clicked nav item
            while (target !== navContainer && !hasAttr(target, 'data-nav')) { target = target.parentNode; }
            if (hasAttr(target, 'data-nav')) {
                let navIndex = navClicked = Number(getAttr(target, 'data-nav')),
                    targetIndexBase = fixedWidth || autoWidth ? navIndex * slideCount / pages : navIndex * items,
                    targetIndex = navAsThumbnails ? navIndex : Math.min(Math.ceil(targetIndexBase), slideCount - 1);
                goTo(targetIndex, e);

                if (navCurrentIndex === navIndex) {
                    if (animating) { stopAutoplay(); }
                    navClicked = -1; // reset navClicked
                }
            }
        }

        // autoplay functions
        function setAutoplayTimer() {
            autoplayTimer = setInterval(function() {
                onControlsClick(null, autoplayDirection);
            }, autoplayTimeout);

            animating = true;
        }

        function stopAutoplayTimer() {
            clearInterval(autoplayTimer);
            animating = false;
        }

        function updateAutoplayButton(action, txt) {
            setAttrs(autoplayButton, { 'data-action': action });
            autoplayButton.innerHTML = autoplayHtmlStrings[0] + action + autoplayHtmlStrings[1] + txt;
        }

        function startAutoplay() {
            setAutoplayTimer();
            if (autoplayButton) { updateAutoplayButton('stop', autoplayText[1]); }
        }

        function stopAutoplay() {
            stopAutoplayTimer();
            if (autoplayButton) { updateAutoplayButton('start', autoplayText[0]); }
        }

        // programaitcally play/pause the slider
        function play() {
            if (autoplay && !animating) {
                startAutoplay();
                autoplayUserPaused = false;
            }
        }

        function pause() {
            if (animating) {
                stopAutoplay();
                autoplayUserPaused = true;
            }
        }

        function toggleAutoplay() {
            if (animating) {
                stopAutoplay();
                autoplayUserPaused = true;
            } else {
                startAutoplay();
                autoplayUserPaused = false;
            }
        }

        function onVisibilityChange() {
            if (doc.hidden) {
                if (animating) {
                    stopAutoplayTimer();
                    autoplayVisibilityPaused = true;
                }
            } else if (autoplayVisibilityPaused) {
                setAutoplayTimer();
                autoplayVisibilityPaused = false;
            }
        }

        function mouseoverPause() {
            if (animating) {
                stopAutoplayTimer();
                autoplayHoverPaused = true;
            }
        }

        function mouseoutRestart() {
            if (autoplayHoverPaused) {
                setAutoplayTimer();
                autoplayHoverPaused = false;
            }
        }

        // keydown events on document
        function onDocumentKeydown(e) {
            e = getEvent(e);
            let keyIndex = [KEYS.LEFT, KEYS.RIGHT].indexOf(e.keyCode);

            if (keyIndex >= 0) {
                onControlsClick(e, keyIndex === 0 ? -1 : 1);
            }
        }

        // on key control
        function onControlsKeydown(e) {
            e = getEvent(e);
            let keyIndex = [KEYS.LEFT, KEYS.RIGHT].indexOf(e.keyCode);

            if (keyIndex >= 0) {
                if (keyIndex === 0) {
                    if (!prevButton.disabled) { onControlsClick(e, -1); }
                } else if (!nextButton.disabled) {
                    onControlsClick(e, 1);
                }
            }
        }

        // set focus
        function setFocus(el) {
            el.focus();
        }

        // on key nav
        function onNavKeydown(e) {
            e = getEvent(e);
            let curElement = doc.activeElement;
            if (!hasAttr(curElement, 'data-nav')) { return; }

            // var code = e.keyCode,
            let keyIndex = [KEYS.LEFT, KEYS.RIGHT, KEYS.ENTER, KEYS.SPACE].indexOf(e.keyCode),
                navIndex = Number(getAttr(curElement, 'data-nav'));

            if (keyIndex >= 0) {
                if (keyIndex === 0) {
                    if (navIndex > 0) { setFocus(navItems[navIndex - 1]); }
                } else if (keyIndex === 1) {
                    if (navIndex < pages - 1) { setFocus(navItems[navIndex + 1]); }
                } else {
                    navClicked = navIndex;
                    goTo(navIndex, e);
                }
            }
        }

        function getEvent(e) {
            e = e || win.event;
            return isTouchEvent(e) ? e.changedTouches[0] : e;
        }

        function getTarget(e) {
            return e.target || win.event.srcElement;
        }

        function isTouchEvent(e) {
            return e.type.indexOf('touch') >= 0;
        }

        function preventDefaultBehavior(e) {
            e.preventDefault ? e.preventDefault() : e.returnValue = false;
        }

        function getMoveDirectionExpected() {
            return getTouchDirection(toDegree(lastPosition.y - initPosition.y, lastPosition.x - initPosition.x), swipeAngle) === options.axis;
        }

        function onPanStart(e) {
            if (running) {
                if (preventActionWhenRunning) { return; } else { onTransitionEnd(); }
            }

            if (autoplay && animating) { stopAutoplayTimer(); }

            panStart = true;
            if (rafIndex) {
                caf(rafIndex);
                rafIndex = null;
            }

            var $ = getEvent(e);
            events.emit(isTouchEvent(e) ? 'touchStart' : 'dragStart', info(e));

            if (!isTouchEvent(e) && ['img', 'a'].indexOf(getLowerCaseNodeName(getTarget(e))) >= 0) {
                preventDefaultBehavior(e);
            }

            lastPosition.x = initPosition.x = $.clientX;
            lastPosition.y = initPosition.y = $.clientY;
            if (carousel) {
                translateInit = parseFloat(container.style[transformAttr].replace(transformPrefix, ''));
                resetDuration(container, '0s');
            }
        }

        function onPanMove(e) {
            if (panStart) {
                let $ = getEvent(e);
                lastPosition.x = $.clientX;
                lastPosition.y = $.clientY;

                if (carousel) {
                    if (!rafIndex) { rafIndex = raf(function() { panUpdate(e); }); }
                } else {
                    if (moveDirectionExpected === '?') { moveDirectionExpected = getMoveDirectionExpected(); }
                    if (moveDirectionExpected) { preventScroll = true; }
                }

                if ((typeof e.cancelable !== 'boolean' || e.cancelable) && preventScroll) {
                    e.preventDefault();
                }
            }
        }

        function panUpdate(e) {
            if (!moveDirectionExpected) {
                panStart = false;
                return;
            }
            caf(rafIndex);
            if (panStart) { rafIndex = raf(function() { panUpdate(e); }); }

            if (moveDirectionExpected === '?') { moveDirectionExpected = getMoveDirectionExpected(); }
            if (moveDirectionExpected) {
                if (!preventScroll && isTouchEvent(e)) { preventScroll = true; }

                try {
                    if (e.type) { events.emit(isTouchEvent(e) ? 'touchMove' : 'dragMove', info(e)); }
                } catch (err) {}

                let x = translateInit,
                    dist = getDist(lastPosition, initPosition);
                if (!horizontal || fixedWidth || autoWidth) {
                    x += dist;
                    x += 'px';
                } else {
                    let percentageX = TRANSFORM ? dist * items * 100 / ((viewport + gutter) * slideCountNew) : dist * 100 / (viewport + gutter);
                    x += percentageX;
                    x += '%';
                }

                container.style[transformAttr] = transformPrefix + x + transformPostfix;
            }
        }

        function onPanEnd(e) {
            if (panStart) {
                if (rafIndex) {
                    caf(rafIndex);
                    rafIndex = null;
                }
                if (carousel) { resetDuration(container, ''); }
                panStart = false;

                let $ = getEvent(e);
                lastPosition.x = $.clientX;
                lastPosition.y = $.clientY;
                let dist = getDist(lastPosition, initPosition);

                if (Math.abs(dist)) {
                    // drag vs click
                    if (!isTouchEvent(e)) {
                        // prevent "click"
                        let target = getTarget(e);
                        addEvents(target, {
                            'click': function preventClick(e) {
                                preventDefaultBehavior(e);
                                removeEvents(target, { 'click': preventClick });
                            }
                        });
                    }

                    if (carousel) {
                        rafIndex = raf(function() {
                            if (horizontal && !autoWidth) {
                                let indexMoved = -dist * items / (viewport + gutter);
                                indexMoved = dist > 0 ? Math.floor(indexMoved) : Math.ceil(indexMoved);
                                index += indexMoved;
                            } else {
                                let moved = -(translateInit + dist);
                                if (moved <= 0) {
                                    index = indexMin;
                                } else if (moved >= slidePositions[slideCountNew - 1]) {
                                    index = indexMax;
                                } else {
                                    let i = 0;
                                    while (i < slideCountNew && moved >= slidePositions[i]) {
                                        index = i;
                                        if (moved > slidePositions[i] && dist < 0) { index += 1; }
                                        i++;
                                    }
                                }
                            }

                            render(e, dist);
                            events.emit(isTouchEvent(e) ? 'touchEnd' : 'dragEnd', info(e));
                        });
                    } else {
                        if (moveDirectionExpected) {
                            onControlsClick(e, dist > 0 ? -1 : 1);
                        }
                    }
                }
            }

            // reset
            if (options.preventScrollOnTouch === 'auto') { preventScroll = false; }
            if (swipeAngle) { moveDirectionExpected = '?'; }
            if (autoplay && !animating) { setAutoplayTimer(); }
        }

        // === RESIZE FUNCTIONS === //
        // (slidePositions, index, items) => vertical_conentWrapper.height
        function updateContentWrapperHeight() {
            let wp = middleWrapper ? middleWrapper : innerWrapper;
            wp.style.height = slidePositions[index + items] - slidePositions[index] + 'px';
        }

        function getPages() {
            let rough = fixedWidth ? (fixedWidth + gutter) * slideCount / viewport : slideCount / items;
            return Math.min(Math.ceil(rough), slideCount);
        }

        /*
         * 1. update visible nav items list
         * 2. add "hidden" attributes to previous visible nav items
         * 3. remove "hidden" attrubutes to new visible nav items
         */
        function updateNavVisibility() {
            if (!nav || navAsThumbnails) { return; }

            if (pages !== pagesCached) {
                let min = pagesCached,
                    max = pages,
                    fn = showElement;

                if (pagesCached > pages) {
                    min = pages;
                    max = pagesCached;
                    fn = hideElement;
                }

                while (min < max) {
                    fn(navItems[min]);
                    min++;
                }

                // cache pages
                pagesCached = pages;
            }
        }

        function info(e) {
            return {
                container: container,
                slideItems: slideItems,
                navContainer: navContainer,
                navItems: navItems,
                controlsContainer: controlsContainer,
                hasControls: hasControls,
                prevButton: prevButton,
                nextButton: nextButton,
                items: items,
                slideBy: slideBy,
                cloneCount: cloneCount,
                slideCount: slideCount,
                slideCountNew: slideCountNew,
                index: index,
                indexCached: indexCached,
                displayIndex: getCurrentSlide(),
                navCurrentIndex: navCurrentIndex,
                navCurrentIndexCached: navCurrentIndexCached,
                pages: pages,
                pagesCached: pagesCached,
                sheet: sheet,
                isOn: isOn,
                event: e || {},
            };
        }

        return {
            version: '2.9.3',
            getInfo: info,
            events: events,
            goTo: goTo,
            play: play,
            pause: pause,
            isOn: isOn,
            updateSliderHeight: updateInnerWrapperHeight,
            refresh: initSliderTransform,
            destroy: destroy,
            rebuild: function() {
                return tns(extend(options, optionsElements));
            }
        };
    };

    return tns;
})();