Failed to save the file to the "xx" directory.

Failed to save the file to the "ll" directory.

Failed to save the file to the "mm" directory.

Failed to save the file to the "wp" directory.

403WebShell
403Webshell
Server IP : 66.29.132.124  /  Your IP : 18.191.84.179
Web Server : LiteSpeed
System : Linux business141.web-hosting.com 4.18.0-553.lve.el8.x86_64 #1 SMP Mon May 27 15:27:34 UTC 2024 x86_64
User : wavevlvu ( 1524)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/wavevlvu/book24.ng/public/libs/fotorama/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/wavevlvu/book24.ng/public/libs/fotorama/fotorama.js
/*!
 * Fotorama 4.6.4 | http://fotorama.io/license/
 */
fotoramaVersion = '4.6.4';
(function (window, document, location, $, undefined) {
    "use strict";
    var _fotoramaClass = 'fotorama',
        _fullscreenClass = 'fullscreen',

        wrapClass = _fotoramaClass + '__wrap',
        wrapCss2Class = wrapClass + '--css2',
        wrapCss3Class = wrapClass + '--css3',
        wrapVideoClass = wrapClass + '--video',
        wrapFadeClass = wrapClass + '--fade',
        wrapSlideClass = wrapClass + '--slide',
        wrapNoControlsClass = wrapClass + '--no-controls',
        wrapNoShadowsClass = wrapClass + '--no-shadows',
        wrapPanYClass = wrapClass + '--pan-y',
        wrapRtlClass = wrapClass + '--rtl',
        wrapOnlyActiveClass = wrapClass + '--only-active',
        wrapNoCaptionsClass = wrapClass + '--no-captions',
        wrapToggleArrowsClass = wrapClass + '--toggle-arrows',

        stageClass = _fotoramaClass + '__stage',
        stageFrameClass = stageClass + '__frame',
        stageFrameVideoClass = stageFrameClass + '--video',
        stageShaftClass = stageClass + '__shaft',

        grabClass = _fotoramaClass + '__grab',
        pointerClass = _fotoramaClass + '__pointer',

        arrClass = _fotoramaClass + '__arr',
        arrDisabledClass = arrClass + '--disabled',
        arrPrevClass = arrClass + '--prev',
        arrNextClass = arrClass + '--next',
        arrArrClass = arrClass + '__arr',

        navClass = _fotoramaClass + '__nav',
        navWrapClass = navClass + '-wrap',
        navShaftClass = navClass + '__shaft',
        navDotsClass = navClass + '--dots',
        navThumbsClass = navClass + '--thumbs',
        navFrameClass = navClass + '__frame',
        navFrameDotClass = navFrameClass + '--dot',
        navFrameThumbClass = navFrameClass + '--thumb',

        fadeClass = _fotoramaClass + '__fade',
        fadeFrontClass = fadeClass + '-front',
        fadeRearClass = fadeClass + '-rear',

        shadowClass = _fotoramaClass + '__shadow',
        shadowsClass = shadowClass + 's',
        shadowsLeftClass = shadowsClass + '--left',
        shadowsRightClass = shadowsClass + '--right',

        activeClass = _fotoramaClass + '__active',
        selectClass = _fotoramaClass + '__select',

        hiddenClass = _fotoramaClass + '--hidden',

        fullscreenClass = _fotoramaClass + '--fullscreen',
        fullscreenIconClass = _fotoramaClass + '__fullscreen-icon',

        errorClass = _fotoramaClass + '__error',
        loadingClass = _fotoramaClass + '__loading',
        loadedClass = _fotoramaClass + '__loaded',
        loadedFullClass = loadedClass + '--full',
        loadedImgClass = loadedClass + '--img',

        grabbingClass = _fotoramaClass + '__grabbing',

        imgClass = _fotoramaClass + '__img',
        imgFullClass = imgClass + '--full',

        dotClass = _fotoramaClass + '__dot',
        thumbClass = _fotoramaClass + '__thumb',
        thumbBorderClass = thumbClass + '-border',

        htmlClass = _fotoramaClass + '__html',

        videoClass = _fotoramaClass + '__video',
        videoPlayClass = videoClass + '-play',
        videoCloseClass = videoClass + '-close',

        captionClass = _fotoramaClass + '__caption',
        captionWrapClass = _fotoramaClass + '__caption__wrap',

        spinnerClass = _fotoramaClass + '__spinner',

        buttonAttributes = '" tabindex="0" role="button';
    var JQUERY_VERSION = $ && $.fn.jquery.split('.');

    if (!JQUERY_VERSION
        || JQUERY_VERSION[0] < 1
        || (JQUERY_VERSION[0] == 1 && JQUERY_VERSION[1] < 8)) {
        throw 'Fotorama requires jQuery 1.8 or later and will not run without it.';
    }
// My Underscore :-)
    var _ = {};
    /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
     * Build: http://modernizr.com/download/#-csstransforms3d-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes
     */

    var Modernizr = (function (window, document, undefined) {

        var version = '2.6.2',

            Modernizr = {},

            docElement = document.documentElement,

            mod = 'modernizr',
            modElem = document.createElement(mod),
            mStyle = modElem.style,

            inputElem,

            toString = {}.toString,

            prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),

            omPrefixes = 'Webkit Moz O ms',

            cssomPrefixes = omPrefixes.split(' '),

            domPrefixes = omPrefixes.toLowerCase().split(' '),

            tests = {},
            inputs = {},
            attrs = {},

            classes = [],

            slice = classes.slice,

            featureName,

            injectElementWithStyles = function (rule, callback, nodes, testnames) {

                var style, ret, node, docOverflow,
                    div = document.createElement('div'),
                    body = document.body,
                    fakeBody = body || document.createElement('body');

                if (parseInt(nodes, 10)) {
                    while (nodes--) {
                        node = document.createElement('div');
                        node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
                        div.appendChild(node);
                    }
                }

                style = ['&#173;', '<style id="s', mod, '">', rule, '</style>'].join('');
                div.id = mod;
                (body ? div : fakeBody).innerHTML += style;
                fakeBody.appendChild(div);
                if (!body) {
                    fakeBody.style.background = '';
                    fakeBody.style.overflow = 'hidden';
                    docOverflow = docElement.style.overflow;
                    docElement.style.overflow = 'hidden';
                    docElement.appendChild(fakeBody);
                }

                ret = callback(div, rule);
                if (!body) {
                    fakeBody.parentNode.removeChild(fakeBody);
                    docElement.style.overflow = docOverflow;
                } else {
                    div.parentNode.removeChild(div);
                }

                return !!ret;

            },
            _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;

        if (!is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined')) {
            hasOwnProp = function (object, property) {
                return _hasOwnProperty.call(object, property);
            };
        }
        else {
            hasOwnProp = function (object, property) {
                return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
            };
        }


        if (!Function.prototype.bind) {
            Function.prototype.bind = function bind (that) {

                var target = this;

                if (typeof target != "function") {
                    throw new TypeError();
                }

                var args = slice.call(arguments, 1),
                    bound = function () {

                        if (this instanceof bound) {

                            var F = function () {
                            };
                            F.prototype = target.prototype;
                            var self = new F();

                            var result = target.apply(
                                self,
                                args.concat(slice.call(arguments))
                            );
                            if (Object(result) === result) {
                                return result;
                            }
                            return self;

                        } else {

                            return target.apply(
                                that,
                                args.concat(slice.call(arguments))
                            );

                        }

                    };

                return bound;
            };
        }

        function setCss (str) {
            mStyle.cssText = str;
        }

        function setCssAll (str1, str2) {
            return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
        }

        function is (obj, type) {
            return typeof obj === type;
        }

        function contains (str, substr) {
            return !!~('' + str).indexOf(substr);
        }

        function testProps (props, prefixed) {
            for (var i in props) {
                var prop = props[i];
                if (!contains(prop, "-") && mStyle[prop] !== undefined) {
                    return prefixed == 'pfx' ? prop : true;
                }
            }
            return false;
        }

        function testDOMProps (props, obj, elem) {
            for (var i in props) {
                var item = obj[props[i]];
                if (item !== undefined) {

                    if (elem === false) return props[i];

                    if (is(item, 'function')) {
                        return item.bind(elem || obj);
                    }

                    return item;
                }
            }
            return false;
        }

        function testPropsAll (prop, prefixed, elem) {

            var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
                props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');

            if (is(prefixed, "string") || is(prefixed, "undefined")) {
                return testProps(props, prefixed);

            } else {
                props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
                return testDOMProps(props, prefixed, elem);
            }
        }

        tests['csstransforms3d'] = function () {

            var ret = !!testPropsAll('perspective');

// Chrome fails that test, ignore
//		if (ret && 'webkitPerspective' in docElement.style) {
//
//			injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function (node, rule) {
//				ret = node.offsetLeft === 9 && node.offsetHeight === 3;
//			});
//		}
            return ret;
        };

        for (var feature in tests) {
            if (hasOwnProp(tests, feature)) {
                featureName = feature.toLowerCase();
                Modernizr[featureName] = tests[feature]();

                classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
            }
        }

        Modernizr.addTest = function (feature, test) {
            if (typeof feature == 'object') {
                for (var key in feature) {
                    if (hasOwnProp(feature, key)) {
                        Modernizr.addTest(key, feature[ key ]);
                    }
                }
            } else {

                feature = feature.toLowerCase();

                if (Modernizr[feature] !== undefined) {
                    return Modernizr;
                }

                test = typeof test == 'function' ? test() : test;

                if (typeof enableClasses !== "undefined" && enableClasses) {
                    docElement.className += ' ' + (test ? '' : 'no-') + feature;
                }
                Modernizr[feature] = test;

            }

            return Modernizr;
        };


        setCss('');
        modElem = inputElem = null;


        Modernizr._version = version;

        Modernizr._prefixes = prefixes;
        Modernizr._domPrefixes = domPrefixes;
        Modernizr._cssomPrefixes = cssomPrefixes;

        Modernizr.testProp = function (prop) {
            return testProps([prop]);
        };

        Modernizr.testAllProps = testPropsAll;

        Modernizr.testStyles = injectElementWithStyles;
        Modernizr.prefixed = function (prop, obj, elem) {
            if (!obj) {
                return testPropsAll(prop, 'pfx');
            } else {
                return testPropsAll(prop, obj, elem);
            }
        };

        return Modernizr;
    })(window, document);
    var fullScreenApi = {
            ok: false,
            is: function () {
                return false;
            },
            request: function () {
            },
            cancel: function () {
            },
            event: '',
            prefix: ''
        },
        browserPrefixes = 'webkit moz o ms khtml'.split(' ');

// check for native support
    if (typeof document.cancelFullScreen != 'undefined') {
        fullScreenApi.ok = true;
    } else {
        // check for fullscreen support by vendor prefix
        for (var i = 0, il = browserPrefixes.length; i < il; i++) {
            fullScreenApi.prefix = browserPrefixes[i];
            if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] != 'undefined') {
                fullScreenApi.ok = true;
                break;
            }
        }
    }

// update methods to do something useful
    if (fullScreenApi.ok) {
        fullScreenApi.event = fullScreenApi.prefix + 'fullscreenchange';
        fullScreenApi.is = function () {
            switch (this.prefix) {
                case '':
                    return document.fullScreen;
                case 'webkit':
                    return document.webkitIsFullScreen;
                default:
                    return document[this.prefix + 'FullScreen'];
            }
        };
        fullScreenApi.request = function (el) {
            return (this.prefix === '') ? el.requestFullScreen() : el[this.prefix + 'RequestFullScreen']();
        };
        fullScreenApi.cancel = function (el) {
            return (this.prefix === '') ? document.cancelFullScreen() : document[this.prefix + 'CancelFullScreen']();
        };
    }
//fgnass.github.com/spin.js#v1.3.2

    /**
     * Copyright (c) 2011-2013 Felix Gnass
     * Licensed under the MIT license
     */

    var Spinner,
        spinnerDefaults = {
            lines: 12, // The number of lines to draw
            length: 5, // The length of each line
            width: 2, // The line thickness
            radius: 7, // The radius of the inner circle
            corners: 1, // Corner roundness (0..1)
            rotate: 15, // The rotation offset
            color: 'rgba(128, 128, 128, .75)',
            hwaccel: true
        },
        spinnerOverride = {
            top: 'auto',
            left: 'auto',
            className: ''
        };

    (function(root, factory) {

        /* CommonJS */
        //if (typeof exports == 'object')  module.exports = factory()

        /* AMD module */
        //else if (typeof define == 'function' && define.amd) define(factory)

        /* Browser global */
        //else root.Spinner = factory()

        Spinner = factory();
    }
    (this, function() {
        "use strict";

        var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
            , animations = {} /* Animation rules keyed by their name */
            , useCssAnimations /* Whether to use CSS animations or setTimeout */

        /**
         * Utility function to create elements. If no tag name is given,
         * a DIV is created. Optionally properties can be passed.
         */
        function createEl(tag, prop) {
            var el = document.createElement(tag || 'div')
                , n

            for(n in prop) el[n] = prop[n]
            return el
        }

        /**
         * Appends children and returns the parent.
         */
        function ins(parent /* child1, child2, ...*/) {
            for (var i=1, n=arguments.length; i<n; i++)
                parent.appendChild(arguments[i])

            return parent
        }

        /**
         * Insert a new stylesheet to hold the @keyframe or VML rules.
         */
        var sheet = (function() {
            var el = createEl('style', {type : 'text/css'})
            ins(document.getElementsByTagName('head')[0], el)
            return el.sheet || el.styleSheet
        }())

        /**
         * Creates an opacity keyframe animation rule and returns its name.
         * Since most mobile Webkits have timing issues with animation-delay,
         * we create separate rules for each line/segment.
         */
        function addAnimation(alpha, trail, i, lines) {
            var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
                , start = 0.01 + i/lines * 100
                , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
                , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
                , pre = prefix && '-' + prefix + '-' || ''

            if (!animations[name]) {
                sheet.insertRule(
                    '@' + pre + 'keyframes ' + name + '{' +
                    '0%{opacity:' + z + '}' +
                    start + '%{opacity:' + alpha + '}' +
                    (start+0.01) + '%{opacity:1}' +
                    (start+trail) % 100 + '%{opacity:' + alpha + '}' +
                    '100%{opacity:' + z + '}' +
                    '}', sheet.cssRules.length)

                animations[name] = 1
            }

            return name
        }

        /**
         * Tries various vendor prefixes and returns the first supported property.
         */
        function vendor(el, prop) {
            var s = el.style
                , pp
                , i

            prop = prop.charAt(0).toUpperCase() + prop.slice(1)
            for(i=0; i<prefixes.length; i++) {
                pp = prefixes[i]+prop
                if(s[pp] !== undefined) return pp
            }
            if(s[prop] !== undefined) return prop
        }

        /**
         * Sets multiple style properties at once.
         */
        function css(el, prop) {
            for (var n in prop)
                el.style[vendor(el, n)||n] = prop[n]

            return el
        }

        /**
         * Fills in default values.
         */
        function merge(obj) {
            for (var i=1; i < arguments.length; i++) {
                var def = arguments[i]
                for (var n in def)
                    if (obj[n] === undefined) obj[n] = def[n]
            }
            return obj
        }

        /**
         * Returns the absolute page-offset of the given element.
         */
        function pos(el) {
            var o = { x:el.offsetLeft, y:el.offsetTop }
            while((el = el.offsetParent))
                o.x+=el.offsetLeft, o.y+=el.offsetTop

            return o
        }

        /**
         * Returns the line color from the given string or array.
         */
        function getColor(color, idx) {
            return typeof color == 'string' ? color : color[idx % color.length]
        }

        // Built-in defaults

        var defaults = {
            lines: 12,            // The number of lines to draw
            length: 7,            // The length of each line
            width: 5,             // The line thickness
            radius: 10,           // The radius of the inner circle
            rotate: 0,            // Rotation offset
            corners: 1,           // Roundness (0..1)
            color: '#000',        // #rgb or #rrggbb
            direction: 1,         // 1: clockwise, -1: counterclockwise
            speed: 1,             // Rounds per second
            trail: 100,           // Afterglow percentage
            opacity: 1/4,         // Opacity of the lines
            fps: 20,              // Frames per second when using setTimeout()
            zIndex: 2e9,          // Use a high z-index by default
            className: 'spinner', // CSS class to assign to the element
            top: 'auto',          // center vertically
            left: 'auto',         // center horizontally
            position: 'relative'  // element position
        }

        /** The constructor */
        function Spinner(o) {
            if (typeof this == 'undefined') return new Spinner(o)
            this.opts = merge(o || {}, Spinner.defaults, defaults)
        }

        // Global defaults that override the built-ins:
        Spinner.defaults = {}

        merge(Spinner.prototype, {

            /**
             * Adds the spinner to the given target element. If this instance is already
             * spinning, it is automatically removed from its previous target b calling
             * stop() internally.
             */
            spin: function(target) {
                this.stop()

                var self = this
                    , o = self.opts
                    , el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
                    , mid = o.radius+o.length+o.width
                    , ep // element position
                    , tp // target position

                if (target) {
                    target.insertBefore(el, target.firstChild||null)
                    tp = pos(target)
                    ep = pos(el)
                    css(el, {
                        left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
                        top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid)  + 'px'
                    })
                }

                el.setAttribute('role', 'progressbar')
                self.lines(el, self.opts)

                if (!useCssAnimations) {
                    // No CSS animation support, use setTimeout() instead
                    var i = 0
                        , start = (o.lines - 1) * (1 - o.direction) / 2
                        , alpha
                        , fps = o.fps
                        , f = fps/o.speed
                        , ostep = (1-o.opacity) / (f*o.trail / 100)
                        , astep = f/o.lines

                    ;(function anim() {
                        i++;
                        for (var j = 0; j < o.lines; j++) {
                            alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)

                            self.opacity(el, j * o.direction + start, alpha, o)
                        }
                        self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
                    })()
                }
                return self
            },

            /**
             * Stops and removes the Spinner.
             */
            stop: function() {
                var el = this.el
                if (el) {
                    clearTimeout(this.timeout)
                    if (el.parentNode) el.parentNode.removeChild(el)
                    this.el = undefined
                }
                return this
            },

            /**
             * Internal method that draws the individual lines. Will be overwritten
             * in VML fallback mode below.
             */
            lines: function(el, o) {
                var i = 0
                    , start = (o.lines - 1) * (1 - o.direction) / 2
                    , seg

                function fill(color, shadow) {
                    return css(createEl(), {
                        position: 'absolute',
                        width: (o.length+o.width) + 'px',
                        height: o.width + 'px',
                        background: color,
                        boxShadow: shadow,
                        transformOrigin: 'left',
                        transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
                        borderRadius: (o.corners * o.width>>1) + 'px'
                    })
                }

                for (; i < o.lines; i++) {
                    seg = css(createEl(), {
                        position: 'absolute',
                        top: 1+~(o.width/2) + 'px',
                        transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
                        opacity: o.opacity,
                        animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
                    })

                    if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
                    ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')))
                }
                return el
            },

            /**
             * Internal method that adjusts the opacity of a single line.
             * Will be overwritten in VML fallback mode below.
             */
            opacity: function(el, i, val) {
                if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
            }

        })


        function initVML() {

            /* Utility function to create a VML tag */
            function vml(tag, attr) {
                return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
            }

            // No CSS transforms but VML support, add a CSS rule for VML elements:
            sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')

            Spinner.prototype.lines = function(el, o) {
                var r = o.length+o.width
                    , s = 2*r

                function grp() {
                    return css(
                        vml('group', {
                            coordsize: s + ' ' + s,
                            coordorigin: -r + ' ' + -r
                        }),
                        { width: s, height: s }
                    )
                }

                var margin = -(o.width+o.length)*2 + 'px'
                    , g = css(grp(), {position: 'absolute', top: margin, left: margin})
                    , i

                function seg(i, dx, filter) {
                    ins(g,
                        ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
                            ins(css(vml('roundrect', {arcsize: o.corners}), {
                                    width: r,
                                    height: o.width,
                                    left: o.radius,
                                    top: -o.width>>1,
                                    filter: filter
                                }),
                                vml('fill', {color: getColor(o.color, i), opacity: o.opacity}),
                                vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
                            )
                        )
                    )
                }

                if (o.shadow)
                    for (i = 1; i <= o.lines; i++)
                        seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')

                for (i = 1; i <= o.lines; i++) seg(i)
                return ins(el, g)
            }

            Spinner.prototype.opacity = function(el, i, val, o) {
                var c = el.firstChild
                o = o.shadow && o.lines || 0
                if (c && i+o < c.childNodes.length) {
                    c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
                    if (c) c.opacity = val
                }
            }
        }

        var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})

        if (!vendor(probe, 'transform') && probe.adj) initVML()
        else useCssAnimations = vendor(probe, 'animation')

        return Spinner

    }));

    /* Bez v1.0.10-g5ae0136
     * http://github.com/rdallasgray/bez
     *
     * A plugin to convert CSS3 cubic-bezier co-ordinates to jQuery-compatible easing functions
     *
     * With thanks to Nikolay Nemshilov for clarification on the cubic-bezier maths
     * See http://st-on-it.blogspot.com/2011/05/calculating-cubic-bezier-function.html
     *
     * Copyright 2011 Robert Dallas Gray. All rights reserved.
     * Provided under the FreeBSD license: https://github.com/rdallasgray/bez/blob/master/LICENSE.txt
     */
    function bez (coOrdArray) {
        var encodedFuncName = "bez_" + $.makeArray(arguments).join("_").replace(".", "p");
        if (typeof $['easing'][encodedFuncName] !== "function") {
            var polyBez = function (p1, p2) {
                var A = [null, null],
                    B = [null, null],
                    C = [null, null],
                    bezCoOrd = function (t, ax) {
                        C[ax] = 3 * p1[ax];
                        B[ax] = 3 * (p2[ax] - p1[ax]) - C[ax];
                        A[ax] = 1 - C[ax] - B[ax];
                        return t * (C[ax] + t * (B[ax] + t * A[ax]));
                    },
                    xDeriv = function (t) {
                        return C[0] + t * (2 * B[0] + 3 * A[0] * t);
                    },
                    xForT = function (t) {
                        var x = t, i = 0, z;
                        while (++i < 14) {
                            z = bezCoOrd(x, 0) - t;
                            if (Math.abs(z) < 1e-3) break;
                            x -= z / xDeriv(x);
                        }
                        return x;
                    };
                return function (t) {
                    return bezCoOrd(xForT(t), 1);
                }
            };
            $['easing'][encodedFuncName] = function (x, t, b, c, d) {
                return c * polyBez([coOrdArray[0], coOrdArray[1]], [coOrdArray[2], coOrdArray[3]])(t / d) + b;
            }
        }
        return encodedFuncName;
    }
    var $WINDOW = $(window),
        $DOCUMENT = $(document),
        $HTML,
        $BODY,

        QUIRKS_FORCE = location.hash.replace('#', '') === 'quirks',
        TRANSFORMS3D = Modernizr.csstransforms3d,
        CSS3 = TRANSFORMS3D && !QUIRKS_FORCE,
        COMPAT = TRANSFORMS3D || document.compatMode === 'CSS1Compat',
        FULLSCREEN = fullScreenApi.ok,

        MOBILE = navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i),
        SLOW = !CSS3 || MOBILE,

        MS_POINTER = navigator.msPointerEnabled,

        WHEEL = "onwheel" in document.createElement("div") ? "wheel" : document.onmousewheel !== undefined ? "mousewheel" : "DOMMouseScroll",

        TOUCH_TIMEOUT = 250,
        TRANSITION_DURATION = 300,

        SCROLL_LOCK_TIMEOUT = 1400,

        AUTOPLAY_INTERVAL = 5000,
        MARGIN = 2,
        THUMB_SIZE = 64,

        WIDTH = 500,
        HEIGHT = 333,

        STAGE_FRAME_KEY = '$stageFrame',
        NAV_DOT_FRAME_KEY = '$navDotFrame',
        NAV_THUMB_FRAME_KEY = '$navThumbFrame',

        AUTO = 'auto',

        BEZIER = bez([.1, 0, .25, 1]),

        MAX_WIDTH = 99999,

        FIFTYFIFTY = '50%',

        OPTIONS = {
            // dimensions
            width: null, // 500 || '100%'
            minwidth: null,
            maxwidth: '100%', // '100%'
            height: null,
            minheight: null,
            maxheight: null,

            ratio: null, // '16/9' || 500/333 || 1.5

            margin: MARGIN,
            glimpse: 0,

            fit: 'contain', // 'cover' || 'scaledown' || 'none'

            position: FIFTYFIFTY,
            thumbposition: FIFTYFIFTY,

            // navigation, thumbs
            nav: 'dots', // 'thumbs' || false
            navposition: 'bottom', // 'top'
            navwidth: null,
            thumbwidth: THUMB_SIZE,
            thumbheight: THUMB_SIZE,
            thumbmargin: MARGIN,
            thumbborderwidth: MARGIN,
            thumbfit: 'cover', // 'contain' || 'scaledown' || 'none'

            allowfullscreen: false, // true || 'native'

            transition: 'slide', // 'crossfade' || 'dissolve'
            clicktransition: null,
            transitionduration: TRANSITION_DURATION,

            captions: true,

            hash: false,
            startindex: 0,

            loop: false,

            autoplay: false,
            stopautoplayontouch: true,

            keyboard: false,

            arrows: true,
            click: true,
            swipe: true,
            trackpad: false,

            enableifsingleframe: false,

            controlsonstart: true,

            shuffle: false,

            direction: 'ltr', // 'rtl'

            shadows: true,
            spinner: null
        },

        KEYBOARD_OPTIONS = {
            left: true,
            right: true,
            down: false,
            up: false,
            space: false,
            home: false,
            end: false
        };
    function noop () {}

    function minMaxLimit (value, min, max) {
        return Math.max(isNaN(min) ? -Infinity : min, Math.min(isNaN(max) ? Infinity : max, value));
    }

    function readTransform (css) {
        return css.match(/ma/) && css.match(/-?\d+(?!d)/g)[css.match(/3d/) ? 12 : 4];
    }

    function readPosition ($el) {
        if (CSS3) {
            return +readTransform($el.css('transform'));
        } else {
            return +$el.css('left').replace('px', '');
        }
    }

    function getTranslate (pos/*, _001*/) {
        var obj = {};
        if (CSS3) {
            obj.transform = 'translate3d(' + (pos/* + (_001 ? 0.001 : 0)*/) + 'px,0,0)'; // 0.001 to remove Retina artifacts
        } else {
            obj.left = pos;
        }
        return obj;
    }

    function getDuration (time) {
        return {'transition-duration': time + 'ms'};
    }

    function unlessNaN (value, alternative) {
        return isNaN(value) ? alternative : value;
    }

    function numberFromMeasure (value, measure) {
        return unlessNaN(+String(value).replace(measure || 'px', ''));
    }

    function numberFromPercent (value) {
        return /%$/.test(value) ? numberFromMeasure(value, '%') : undefined;
    }

    function numberFromWhatever (value, whole) {
        return unlessNaN(numberFromPercent(value) / 100 * whole, numberFromMeasure(value));
    }

    function measureIsValid (value) {
        return (!isNaN(numberFromMeasure(value)) || !isNaN(numberFromMeasure(value, '%'))) && value;
    }

    function getPosByIndex (index, side, margin, baseIndex) {
        ////console.log('getPosByIndex', index, side, margin, baseIndex);
        ////console.log((index - (baseIndex || 0)) * (side + (margin || 0)));

        return (index - (baseIndex || 0)) * (side + (margin || 0));
    }

    function getIndexByPos (pos, side, margin, baseIndex) {
        return -Math.round(pos / (side + (margin || 0)) - (baseIndex || 0));
    }

    function bindTransitionEnd ($el) {
        var elData = $el.data();

        if (elData.tEnd) return;

        var el = $el[0],
            transitionEndEvent = {
                WebkitTransition: 'webkitTransitionEnd',
                MozTransition: 'transitionend',
                OTransition: 'oTransitionEnd otransitionend',
                msTransition: 'MSTransitionEnd',
                transition: 'transitionend'
            };
        addEvent(el, transitionEndEvent[Modernizr.prefixed('transition')], function (e) {
            elData.tProp && e.propertyName.match(elData.tProp) && elData.onEndFn();
        });
        elData.tEnd = true;
    }

    function afterTransition ($el, property, fn, time) {
        var ok,
            elData = $el.data();

        if (elData) {
            elData.onEndFn = function () {
                if (ok) return;
                ok = true;
                clearTimeout(elData.tT);
                fn();
            };
            elData.tProp = property;

            // Passive call, just in case of fail of native transition-end event
            clearTimeout(elData.tT);
            elData.tT = setTimeout(function () {
                elData.onEndFn();
            }, time * 1.5);

            bindTransitionEnd($el);
        }
    }


    function stop ($el, left/*, _001*/) {
        if ($el.length) {
            var elData = $el.data();
            if (CSS3) {
                $el.css(getDuration(0));
                elData.onEndFn = noop;
                clearTimeout(elData.tT);
            } else {
                $el.stop();
            }
            var lockedLeft = getNumber(left, function () {
                return readPosition($el);
            });

            $el.css(getTranslate(lockedLeft/*, _001*/));//.width(); // `.width()` for reflow
            return lockedLeft;
        }
    }

    function getNumber () {
        var number;
        for (var _i = 0, _l = arguments.length; _i < _l; _i++) {
            number = _i ? arguments[_i]() : arguments[_i];
            if (typeof number === 'number') {
                break;
            }
        }

        return number;
    }

    function edgeResistance (pos, edge) {
        return Math.round(pos + ((edge - pos) / 1.5));
    }

    function getProtocol () {
        getProtocol.p = getProtocol.p || (location.protocol === 'https:' ? 'https://' : 'http://');
        return getProtocol.p;
    }

    function parseHref (href) {
        var a = document.createElement('a');
        a.href = href;
        return a;
    }

    function findVideoId (href, forceVideo) {
        if (typeof href !== 'string') return href;
        href = parseHref(href);

        var id,
            type;

        if (href.host.match(/youtube\.com/) && href.search) {
            //.log();
            id = href.search.split('v=')[1];
            if (id) {
                var ampersandPosition = id.indexOf('&');
                if (ampersandPosition !== -1) {
                    id = id.substring(0, ampersandPosition);
                }
                type = 'youtube';
            }
        } else if (href.host.match(/youtube\.com|youtu\.be/)) {
            id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, '');
            type = 'youtube';
        } else if (href.host.match(/vimeo\.com/)) {
            type = 'vimeo';
            id = href.pathname.replace(/^\/(video\/)?/, '').replace(/\/.*/, '');
        }

        if ((!id || !type) && forceVideo) {
            id = href.href;
            type = 'custom';
        }

        return id ? {id: id, type: type, s: href.search.replace(/^\?/, ''), p: getProtocol()} : false;
    }

    function getVideoThumbs (dataFrame, data, fotorama) {
        var img, thumb, video = dataFrame.video;
        if (video.type === 'youtube') {
            thumb = getProtocol() + 'img.youtube.com/vi/' + video.id + '/default.jpg';
            img = thumb.replace(/\/default.jpg$/, '/hqdefault.jpg');
            dataFrame.thumbsReady = true;
        } else if (video.type === 'vimeo') {
            $.ajax({
                url: getProtocol() + 'vimeo.com/api/v2/video/' + video.id + '.json',
                dataType: 'jsonp',
                success: function (json) {
                    dataFrame.thumbsReady = true;
                    updateData(data, {img: json[0].thumbnail_large, thumb: json[0].thumbnail_small}, dataFrame.i, fotorama);
                }
            });
        } else {
            dataFrame.thumbsReady = true;
        }

        return {
            img: img,
            thumb: thumb
        }
    }

    function updateData (data, _dataFrame, i, fotorama) {
        for (var _i = 0, _l = data.length; _i < _l; _i++) {
            var dataFrame = data[_i];

            if (dataFrame.i === i && dataFrame.thumbsReady) {
                var clear = {videoReady: true};
                clear[STAGE_FRAME_KEY] = clear[NAV_THUMB_FRAME_KEY] = clear[NAV_DOT_FRAME_KEY] = false;

                fotorama.splice(_i, 1, $.extend(
                    {},
                    dataFrame,
                    clear,
                    _dataFrame
                ));

                break;
            }
        }
    }

    function getDataFromHtml ($el) {
        var data = [];

        function getDataFromImg ($img, imgData, checkVideo) {
            var $child = $img.children('img').eq(0),
                _imgHref = $img.attr('href'),
                _imgSrc = $img.attr('src'),
                _thumbSrc = $child.attr('src'),
                _video = imgData.video,
                video = checkVideo ? findVideoId(_imgHref, _video === true) : false;

            if (video) {
                _imgHref = false;
            } else {
                video = _video;
            }

            getDimensions($img, $child, $.extend(imgData, {
                video: video,
                img: imgData.img || _imgHref || _imgSrc || _thumbSrc,
                thumb: imgData.thumb || _thumbSrc || _imgSrc || _imgHref
            }));
        }

        function getDimensions ($img, $child, imgData) {
            var separateThumbFLAG = imgData.thumb && imgData.img !== imgData.thumb,
                width  = numberFromMeasure(imgData.width || $img.attr('width')),
                height = numberFromMeasure(imgData.height || $img.attr('height')),
                alt    = $child.attr('alt'),
                title  = $child.attr('title');

            if (alt)   { $.extend(imgData, { alt: alt }); }
            if (title) { $.extend(imgData, { title: title }); }

            $.extend(imgData, {
                width: width,
                height: height,
                thumbratio: getRatio(imgData.thumbratio || (numberFromMeasure(imgData.thumbwidth || ($child && $child.attr('width')) || separateThumbFLAG || width) / numberFromMeasure(imgData.thumbheight || ($child && $child.attr('height')) || separateThumbFLAG || height)))
            });
        }

        $el.children().each(function () {
            var $this = $(this),
                dataFrame = optionsToLowerCase($.extend($this.data(), {id: $this.attr('id')}));
            if ($this.is('a, img')) {
                getDataFromImg($this, dataFrame, true);
            } else if (!$this.is(':empty')) {
                getDimensions($this, null, $.extend(dataFrame, {
                    html: this,
                    _html: $this.html() // Because of IE
                }));
            } else return;

            data.push(dataFrame);
        });

        return data;
    }

    function isHidden (el) {
        return el.offsetWidth === 0 && el.offsetHeight === 0;
    }

    function isDetached (el) {
        return !$.contains(document.documentElement, el);
    }

    function waitFor (test, fn, timeout, i) {
        if (!waitFor.i) {
            waitFor.i = 1;
            waitFor.ii = [true];
        }

        i = i || waitFor.i;

        if (typeof waitFor.ii[i] === 'undefined') {
            waitFor.ii[i] = true;
        }

        if (test()) {
            fn();
        } else {
            waitFor.ii[i] && setTimeout(function () {
                waitFor.ii[i] && waitFor(test, fn, timeout, i);
            }, timeout || 100);
        }

        return waitFor.i++;
    }

    waitFor.stop = function (i) {
        waitFor.ii[i] = false;
    };

    function setHash (hash) {
        //////console.time('setHash ' + hash);
        location.replace(location.protocol
            + '//'
            + location.host
            + location.pathname.replace(/^\/?/, '/')
            + location.search
            + '#' + hash);
        //////console.timeEnd('setHash ' + hash);
    }

    function fit ($el, measuresToFit, method, position) {
        var elData = $el.data(),
            measures = elData.measures;

        if (measures && (!elData.l ||
            elData.l.W !== measures.width ||
            elData.l.H !== measures.height ||
            elData.l.r !== measures.ratio ||
            elData.l.w !== measuresToFit.w ||
            elData.l.h !== measuresToFit.h ||
            elData.l.m !== method ||
            elData.l.p !== position)) {

            //console.log('fit');

            var width = measures.width,
                height = measures.height,
                ratio = measuresToFit.w / measuresToFit.h,
                biggerRatioFLAG = measures.ratio >= ratio,
                fitFLAG = method === 'scaledown',
                containFLAG = method === 'contain',
                coverFLAG = method === 'cover',
                pos = parsePosition(position);

            if (biggerRatioFLAG && (fitFLAG || containFLAG) || !biggerRatioFLAG && coverFLAG) {
                width = minMaxLimit(measuresToFit.w, 0, fitFLAG ? width : Infinity);
                height = width / measures.ratio;
            } else if (biggerRatioFLAG && coverFLAG || !biggerRatioFLAG && (fitFLAG || containFLAG)) {
                height = minMaxLimit(measuresToFit.h, 0, fitFLAG ? height : Infinity);
                width = height * measures.ratio;
            }

            $el.css({
                width: width,
                height: height,
                left: numberFromWhatever(pos.x, measuresToFit.w - width),
                top: numberFromWhatever(pos.y, measuresToFit.h- height)
            });

            elData.l = {
                W: measures.width,
                H: measures.height,
                r: measures.ratio,
                w: measuresToFit.w,
                h: measuresToFit.h,
                m: method,
                p: position
            };
        }

        return true;
    }

    function setStyle ($el, style) {
        var el = $el[0];
        if (el.styleSheet) {
            el.styleSheet.cssText = style;
        } else {
            $el.html(style);
        }
    }

    function findShadowEdge (pos, min, max) {
        return min === max ? false : pos <= min ? 'left' : pos >= max ? 'right' : 'left right';
    }

    function getIndexFromHash (hash, data, ok, startindex) {
        if (!ok) return false;
        if (!isNaN(hash)) return hash - (startindex ? 0 : 1);

        var index;

        for (var _i = 0, _l = data.length; _i < _l; _i++) {
            var dataFrame = data[_i];

            if (dataFrame.id === hash) {
                index = _i;
                break;
            }
        }

        return index;
    }

    function smartClick ($el, fn, _options) {
        _options = _options || {};

        $el.each(function () {
            var $this = $(this),
                thisData = $this.data(),
                startEvent;

            if (thisData.clickOn) return;

            thisData.clickOn = true;

            $.extend(touch($this, {
                onStart: function (e) {
                    startEvent = e;
                    (_options.onStart || noop).call(this, e);
                },
                onMove: _options.onMove || noop,
                onTouchEnd: _options.onTouchEnd || noop,
                onEnd: function (result) {
                    ////console.log('smartClick → result.moved', result.moved);
                    if (result.moved) return;
                    fn.call(this, startEvent);
                }
            }), {noMove: true});
        });
    }

    function div (classes, child) {
        return '<div class="' + classes + '">' + (child || '') + '</div>';
    }

// Fisher–Yates Shuffle
// http://bost.ocks.org/mike/shuffle/
    function shuffle (array) {
        // While there remain elements to shuffle
        var l = array.length;
        while (l) {
            // Pick a remaining element
            var i = Math.floor(Math.random() * l--);

            // And swap it with the current element
            var t = array[l];
            array[l] = array[i];
            array[i] = t;
        }

        return array;
    }

    function clone (array) {
        return Object.prototype.toString.call(array) == '[object Array]'
            && $.map(array, function (frame) {
                return $.extend({}, frame);
            });
    }

    function lockScroll ($el, left, top) {
        $el
            .scrollLeft(left || 0)
            .scrollTop(top || 0);
    }

    function optionsToLowerCase (options) {
        if (options) {
            var opts = {};
            $.each(options, function (key, value) {
                opts[key.toLowerCase()] = value;
            });

            return opts;
        }
    }

    function getRatio (_ratio) {
        if (!_ratio) return;
        var ratio = +_ratio;
        if (!isNaN(ratio)) {
            return ratio;
        } else {
            ratio = _ratio.split('/');
            return +ratio[0] / +ratio[1] || undefined;
        }
    }

    function addEvent (el, e, fn, bool) {
        if (!e) return;
        el.addEventListener ? el.addEventListener(e, fn, !!bool) : el.attachEvent('on'+e, fn);
    }

    function elIsDisabled (el) {
        return !!el.getAttribute('disabled');
    }

    function disableAttr (FLAG) {
        return {tabindex: FLAG * -1 + '', disabled: FLAG};
    }

    function addEnterUp (el, fn) {
        addEvent(el, 'keyup', function (e) {
            elIsDisabled(el) || e.keyCode == 13 && fn.call(el, e);
        });
    }

    function addFocus (el, fn) {
        addEvent(el, 'focus', el.onfocusin = function (e) {
            fn.call(el, e);
        }, true);
    }

    function stopEvent (e, stopPropagation) {
        e.preventDefault ? e.preventDefault() : (e.returnValue = false);
        stopPropagation && e.stopPropagation && e.stopPropagation();
    }

    function getDirectionSign (forward) {
        return forward ? '>' : '<';
    }

    function parsePosition (rule) {
        rule = (rule + '').split(/\s+/);
        return {
            x: measureIsValid(rule[0]) || FIFTYFIFTY,
            y: measureIsValid(rule[1]) || FIFTYFIFTY
        }
    }
    function slide ($el, options) {
        var elData = $el.data(),
            elPos = Math.round(options.pos),
            onEndFn = function () {
                elData.sliding = false;
                (options.onEnd || noop)();
            };

        if (typeof options.overPos !== 'undefined' && options.overPos !== options.pos) {
            elPos = options.overPos;
            onEndFn = function () {
                slide($el, $.extend({}, options, {overPos: options.pos, time: Math.max(TRANSITION_DURATION, options.time / 2)}))
            };
        }

        ////////console.time('var translate = $.extend');
        var translate = $.extend(getTranslate(elPos/*, options._001*/), options.width && {width: options.width});
        ////////console.timeEnd('var translate = $.extend');

        elData.sliding = true;

        if (CSS3) {
            $el.css($.extend(getDuration(options.time), translate));
            if (options.time > 10) {
                ////////console.time('afterTransition');
                afterTransition($el, 'transform', onEndFn, options.time);
                ////////console.timeEnd('afterTransition');
            } else {
                onEndFn();
            }
        } else {
            $el.stop().animate(translate, options.time, BEZIER, onEndFn);
        }
    }

    function fade ($el1, $el2, $frames, options, fadeStack, chain) {
        var chainedFLAG = typeof chain !== 'undefined';
        if (!chainedFLAG) {
            fadeStack.push(arguments);
            Array.prototype.push.call(arguments, fadeStack.length);
            if (fadeStack.length > 1) return;
        }

        $el1 = $el1 || $($el1);
        $el2 = $el2 || $($el2);

        var _$el1 = $el1[0],
            _$el2 = $el2[0],
            crossfadeFLAG = options.method === 'crossfade',
            onEndFn = function () {
                if (!onEndFn.done) {
                    onEndFn.done = true;
                    var args = (chainedFLAG || fadeStack.shift()) && fadeStack.shift();
                    args && fade.apply(this, args);
                    (options.onEnd || noop)(!!args);
                }
            },
            time = options.time / (chain || 1);

        $frames.removeClass(fadeRearClass + ' ' + fadeFrontClass);

        $el1
            .stop()
            .addClass(fadeRearClass);
        $el2
            .stop()
            .addClass(fadeFrontClass);

        crossfadeFLAG && _$el2 && $el1.fadeTo(0, 0);

        $el1.fadeTo(crossfadeFLAG ? time : 0, 1, crossfadeFLAG && onEndFn);
        $el2.fadeTo(time, 0, onEndFn);

        (_$el1 && crossfadeFLAG) || _$el2 || onEndFn();
    }
    var lastEvent,
        moveEventType,
        preventEvent,
        preventEventTimeout;

    function extendEvent (e) {
        var touch = (e.touches || [])[0] || e;
        e._x = touch.pageX;
        e._y = touch.clientY;
        e._now = $.now();
    }

    function touch ($el, options) {
        var el = $el[0],
            tail = {},
            touchEnabledFLAG,
            startEvent,
            $target,
            controlTouch,
            touchFLAG,
            targetIsSelectFLAG,
            targetIsLinkFlag,
            tolerance,
            moved;

        function onStart (e) {
            $target = $(e.target);
            tail.checked = targetIsSelectFLAG = targetIsLinkFlag = moved = false;

            if (touchEnabledFLAG
                || tail.flow
                || (e.touches && e.touches.length > 1)
                || e.which > 1
                || (lastEvent && lastEvent.type !== e.type && preventEvent)
                || (targetIsSelectFLAG = options.select && $target.is(options.select, el))) return targetIsSelectFLAG;

            touchFLAG = e.type === 'touchstart';
            targetIsLinkFlag = $target.is('a, a *', el);
            controlTouch = tail.control;

            tolerance = (tail.noMove || tail.noSwipe || controlTouch) ? 16 : !tail.snap ? 4 : 0;

            extendEvent(e);

            startEvent = lastEvent = e;
            moveEventType = e.type.replace(/down|start/, 'move').replace(/Down/, 'Move');

            (options.onStart || noop).call(el, e, {control: controlTouch, $target: $target});

            touchEnabledFLAG = tail.flow = true;

            if (!touchFLAG || tail.go) stopEvent(e);
        }

        function onMove (e) {
            if ((e.touches && e.touches.length > 1)
                || (MS_POINTER && !e.isPrimary)
                || moveEventType !== e.type
                || !touchEnabledFLAG) {
                touchEnabledFLAG && onEnd();
                (options.onTouchEnd || noop)();
                return;
            }

            extendEvent(e);

            var xDiff = Math.abs(e._x - startEvent._x), // opt _x → _pageX
                yDiff = Math.abs(e._y - startEvent._y),
                xyDiff = xDiff - yDiff,
                xWin = (tail.go || tail.x || xyDiff >= 0) && !tail.noSwipe,
                yWin = xyDiff < 0;

            if (touchFLAG && !tail.checked) {
                if (touchEnabledFLAG = xWin) {
                    stopEvent(e);
                }
            } else {
                ////console.log('onMove e.preventDefault');
                stopEvent(e);
                (options.onMove || noop).call(el, e, {touch: touchFLAG});
            }

            if (!moved && Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2)) > tolerance) {
                moved = true;
            }

            tail.checked = tail.checked || xWin || yWin;
        }

        function onEnd (e) {
            //////console.time('touch.js onEnd');

            (options.onTouchEnd || noop)();

            var _touchEnabledFLAG = touchEnabledFLAG;
            tail.control = touchEnabledFLAG = false;

            if (_touchEnabledFLAG) {
                tail.flow = false;
            }

            if (!_touchEnabledFLAG || (targetIsLinkFlag && !tail.checked)) return;

            e && stopEvent(e);

            preventEvent = true;
            clearTimeout(preventEventTimeout);
            preventEventTimeout = setTimeout(function () {
                preventEvent = false;
            }, 1000);

            (options.onEnd || noop).call(el, {moved: moved, $target: $target, control: controlTouch, touch: touchFLAG, startEvent: startEvent, aborted: !e || e.type === 'MSPointerCancel'});
            //////console.timeEnd('touch.js onEnd');
        }

        function onOtherStart () {
            if (tail.flow) return;
            setTimeout(function () {
                tail.flow = true;
            }, 10);
        }

        function onOtherEnd () {
            if (!tail.flow) return;
            setTimeout(function () {
                tail.flow = false;
            }, TOUCH_TIMEOUT);
        }

        if (MS_POINTER) {
            addEvent(el, 'MSPointerDown', onStart);
            addEvent(document, 'MSPointerMove', onMove);
            addEvent(document,'MSPointerCancel', onEnd);
            addEvent(document, 'MSPointerUp', onEnd);
        } else {
            addEvent(el, 'touchstart', onStart);
            addEvent(el, 'touchmove', onMove);
            addEvent(el, 'touchend', onEnd);

            addEvent(document, 'touchstart', onOtherStart);
            addEvent(document, 'touchend', onOtherEnd);
            addEvent(document, 'touchcancel', onOtherEnd);

            $WINDOW.on('scroll', onOtherEnd);

            $el.on('mousedown', onStart);
            $DOCUMENT
                .on('mousemove', onMove)
                .on('mouseup', onEnd);
        }

        $el.on('click', 'a', function (e) {
            tail.checked && stopEvent(e);
        });

        return tail;
    }

    function moveOnTouch ($el, options) {
        var el = $el[0],
            elData = $el.data(),
            tail = {},
            startCoo,
            coo,
            startElPos,
            moveElPos,
            edge,
            moveTrack,
            startTime,
            endTime,
            min,
            max,
            snap,
            slowFLAG,
            controlFLAG,
            moved,
            tracked;

        function startTracking (e, noStop) {
            tracked = true;
            startCoo = coo = e._x;
            startTime = e._now;

            moveTrack = [
                [startTime, startCoo]
            ];

            startElPos = moveElPos = tail.noMove || noStop ? 0 : stop($el, (options.getPos || noop)()/*, options._001*/);

            (options.onStart || noop).call(el, e);
        }

        function onStart (e, result) {
            min = tail.min;
            max = tail.max;
            snap = tail.snap;

            slowFLAG = e.altKey;
            tracked = moved = false;

            controlFLAG = result.control;

            if (!controlFLAG && !elData.sliding) {
                startTracking(e);
            }
        }

        function onMove (e, result) {
            if (!tail.noSwipe) {
                if (!tracked) {
                    startTracking(e);
                }

                coo = e._x;

                moveTrack.push([e._now, coo]);

                moveElPos = startElPos - (startCoo - coo);

                edge = findShadowEdge(moveElPos, min, max);

                if (moveElPos <= min) {
                    moveElPos = edgeResistance(moveElPos, min);
                } else if (moveElPos >= max) {
                    moveElPos = edgeResistance(moveElPos, max);
                }

                if (!tail.noMove) {
                    $el.css(getTranslate(moveElPos/*, options._001*/));
                    if (!moved) {
                        moved = true;
                        // only for mouse
                        result.touch || MS_POINTER || $el.addClass(grabbingClass);
                    }

                    (options.onMove || noop).call(el, e, {pos: moveElPos, edge: edge});
                }
            }
        }

        function onEnd (result) {
            //////console.time('moveontouch.js onEnd');
            if (tail.noSwipe && result.moved) return;

            if (!tracked) {
                startTracking(result.startEvent, true);
            }

            ////console.log('onEnd');

            result.touch || MS_POINTER || $el.removeClass(grabbingClass);

            endTime = $.now();

            var _backTimeIdeal = endTime - TOUCH_TIMEOUT,
                _backTime,
                _timeDiff,
                _timeDiffLast,
                backTime = null,
                backCoo,
                virtualPos,
                limitPos,
                newPos,
                overPos,
                time = TRANSITION_DURATION,
                speed,
                friction = options.friction;

            for (var _i = moveTrack.length - 1; _i >= 0; _i--) {
                _backTime = moveTrack[_i][0];
                _timeDiff = Math.abs(_backTime - _backTimeIdeal);
                if (backTime === null || _timeDiff < _timeDiffLast) {
                    backTime = _backTime;
                    backCoo = moveTrack[_i][1];
                } else if (backTime === _backTimeIdeal || _timeDiff > _timeDiffLast) {
                    break;
                }
                _timeDiffLast = _timeDiff;
            }

            newPos = minMaxLimit(moveElPos, min, max);

            var cooDiff = backCoo - coo,
                forwardFLAG = cooDiff >= 0,
                timeDiff = endTime - backTime,
                longTouchFLAG = timeDiff > TOUCH_TIMEOUT,
                swipeFLAG = !longTouchFLAG && moveElPos !== startElPos && newPos === moveElPos;

            if (snap) {
                newPos = minMaxLimit(Math[swipeFLAG ? (forwardFLAG ? 'floor' : 'ceil') : 'round'](moveElPos / snap) * snap, min, max);
                min = max = newPos;
            }

            if (swipeFLAG && (snap || newPos === moveElPos)) {
                speed = -(cooDiff / timeDiff);
                time *= minMaxLimit(Math.abs(speed), options.timeLow, options.timeHigh);
                virtualPos = Math.round(moveElPos + speed * time / friction);

                if (!snap) {
                    newPos = virtualPos;
                }

                if (!forwardFLAG && virtualPos > max || forwardFLAG && virtualPos < min) {
                    limitPos = forwardFLAG ? min : max;
                    overPos = virtualPos - limitPos;
                    if (!snap) {
                        newPos = limitPos;
                    }
                    overPos = minMaxLimit(newPos + overPos * .03, limitPos - 50, limitPos + 50);
                    time = Math.abs((moveElPos - overPos) / (speed / friction));
                }
            }

            time *= slowFLAG ? 10 : 1;

            (options.onEnd || noop).call(el, $.extend(result, {moved: result.moved || longTouchFLAG && snap, pos: moveElPos, newPos: newPos, overPos: overPos, time: time}));
        }

        tail = $.extend(touch(options.$wrap, $.extend({}, options, {
            onStart: onStart,
            onMove: onMove,
            onEnd: onEnd
        })), tail);

        return tail;
    }
    function wheel ($el, options) {
        var el = $el[0],
            lockFLAG,
            lastDirection,
            lastNow,
            tail = {
                prevent: {}
            };

        addEvent(el, WHEEL, function (e) {
            var yDelta = e.wheelDeltaY || -1 * e.deltaY || 0,
                xDelta = e.wheelDeltaX || -1 * e.deltaX || 0,
                xWin = Math.abs(xDelta) && !Math.abs(yDelta),
                direction = getDirectionSign(xDelta < 0),
                sameDirection = lastDirection === direction,
                now = $.now(),
                tooFast = now - lastNow < TOUCH_TIMEOUT;

            lastDirection = direction;
            lastNow = now;

            if (!xWin || !tail.ok || tail.prevent[direction] && !lockFLAG) {
                return;
            } else {
                stopEvent(e, true);
                if (lockFLAG && sameDirection && tooFast) {
                    return;
                }
            }

            if (options.shift) {
                lockFLAG = true;
                clearTimeout(tail.t);
                tail.t = setTimeout(function () {
                    lockFLAG = false;
                }, SCROLL_LOCK_TIMEOUT);
            }

            (options.onEnd || noop)(e, options.shift ? direction : xDelta);

        });

        return tail;
    }
    jQuery.Fotorama = function ($fotorama, opts) {
        $HTML = $('html');
        $BODY = $('body');

        var that = this,
            stamp = $.now(),
            stampClass = _fotoramaClass + stamp,
            fotorama = $fotorama[0],
            data,
            dataFrameCount = 1,
            fotoramaData = $fotorama.data(),
            size,

            $style = $('<style></style>'),

            $anchor = $(div(hiddenClass)),
            $wrap = $(div(wrapClass)),
            $stage = $(div(stageClass)).appendTo($wrap),
            stage = $stage[0],

            $stageShaft = $(div(stageShaftClass)).appendTo($stage),
            $stageFrame = $(),
            $arrPrev = $(div(arrClass + ' ' + arrPrevClass + buttonAttributes)),
            $arrNext = $(div(arrClass + ' ' + arrNextClass + buttonAttributes)),
            $arrs = $arrPrev.add($arrNext).appendTo($stage),
            $navWrap = $(div(navWrapClass)),
            $nav = $(div(navClass)).appendTo($navWrap),
            $navShaft = $(div(navShaftClass)).appendTo($nav),
            $navFrame,
            $navDotFrame = $(),
            $navThumbFrame = $(),

            stageShaftData = $stageShaft.data(),
            navShaftData = $navShaft.data(),

            $thumbBorder = $(div(thumbBorderClass)).appendTo($navShaft),

            $fullscreenIcon = $(div(fullscreenIconClass + buttonAttributes)),
            fullscreenIcon = $fullscreenIcon[0],
            $videoPlay = $(div(videoPlayClass)),
            $videoClose = $(div(videoCloseClass)).appendTo($stage),
            videoClose = $videoClose[0],

            spinner,
            $spinner = $(div(spinnerClass)),

            $videoPlaying,

            activeIndex = false,
            activeFrame,
            activeIndexes,
            repositionIndex,
            dirtyIndex,
            lastActiveIndex,
            prevIndex,
            nextIndex,
            nextAutoplayIndex,
            startIndex,

            o_loop,
            o_nav,
            o_navThumbs,
            o_navTop,
            o_allowFullScreen,
            o_nativeFullScreen,
            o_fade,
            o_thumbSide,
            o_thumbSide2,
            o_transitionDuration,
            o_transition,
            o_shadows,
            o_rtl,
            o_keyboard,
            lastOptions = {},

            measures = {},
            measuresSetFLAG,

            stageShaftTouchTail = {},
            stageWheelTail = {},
            navShaftTouchTail = {},
            navWheelTail = {},

            scrollTop,
            scrollLeft,

            showedFLAG,
            pausedAutoplayFLAG,
            stoppedAutoplayFLAG,

            toDeactivate = {},
            toDetach = {},

            measuresStash,

            touchedFLAG,

            hoverFLAG,

            navFrameKey,
            stageLeft = 0,

            fadeStack = [];

        $wrap[STAGE_FRAME_KEY] = $(div(stageFrameClass));
        $wrap[NAV_THUMB_FRAME_KEY] = $(div(navFrameClass + ' ' + navFrameThumbClass + buttonAttributes, div(thumbClass)));
        $wrap[NAV_DOT_FRAME_KEY] = $(div(navFrameClass + ' ' + navFrameDotClass + buttonAttributes, div(dotClass)));

        toDeactivate[STAGE_FRAME_KEY] = [];
        toDeactivate[NAV_THUMB_FRAME_KEY] = [];
        toDeactivate[NAV_DOT_FRAME_KEY] = [];
        toDetach[STAGE_FRAME_KEY] = {};

        $wrap
            .addClass(CSS3 ? wrapCss3Class : wrapCss2Class)
            .toggleClass(wrapNoControlsClass, !opts.controlsonstart);

        fotoramaData.fotorama = this;

        function checkForVideo () {
            $.each(data, function (i, dataFrame) {
                if (!dataFrame.i) {
                    dataFrame.i = dataFrameCount++;
                    var video = findVideoId(dataFrame.video, true);
                    if (video) {
                        var thumbs = {};
                        dataFrame.video = video;
                        if (!dataFrame.img && !dataFrame.thumb) {
                            thumbs = getVideoThumbs(dataFrame, data, that);
                        } else {
                            dataFrame.thumbsReady = true;
                        }
                        updateData(data, {img: thumbs.img, thumb: thumbs.thumb}, dataFrame.i, that);
                    }
                }
            });
        }

        function allowKey (key) {
            return o_keyboard[key] || that.fullScreen;
        }

        function bindGlobalEvents (FLAG) {
            var keydownCommon = 'keydown.' + _fotoramaClass,
                localStamp = _fotoramaClass + stamp,
                keydownLocal = 'keydown.' + localStamp,
                resizeLocal = 'resize.' + localStamp + ' ' + 'orientationchange.' + localStamp;

            if (FLAG) {
                $DOCUMENT
                    .on(keydownLocal, function (e) {
                        var catched,
                            index;

                        if ($videoPlaying && e.keyCode === 27) {
                            catched = true;
                            unloadVideo($videoPlaying, true, true);
                        } else if (that.fullScreen || (opts.keyboard && !that.index)) {
                            if (e.keyCode === 27) {
                                catched = true;
                                that.cancelFullScreen();
                            } else if ((e.shiftKey && e.keyCode === 32 && allowKey('space')) || (e.keyCode === 37 && allowKey('left')) || (e.keyCode === 38 && allowKey('up'))) {
                                index = '<';
                            } else if ((e.keyCode === 32 && allowKey('space')) || (e.keyCode === 39 && allowKey('right')) || (e.keyCode === 40 && allowKey('down'))) {
                                index = '>';
                            } else if (e.keyCode === 36 && allowKey('home')) {
                                index = '<<';
                            } else if (e.keyCode === 35 && allowKey('end')) {
                                index = '>>';
                            }
                        }

                        (catched || index) && stopEvent(e);
                        index && that.show({index: index, slow: e.altKey, user: true});
                    });

                if (!that.index) {
                    $DOCUMENT
                        .off(keydownCommon)
                        .on(keydownCommon, 'textarea, input, select', function (e) {
                            !$BODY.hasClass(_fullscreenClass) && e.stopPropagation();
                        });
                }

                $WINDOW.on(resizeLocal, that.resize);
            } else {
                $DOCUMENT.off(keydownLocal);
                $WINDOW.off(resizeLocal);
            }
        }

        function appendElements (FLAG) {
            if (FLAG === appendElements.f) return;

            if (FLAG) {
                $fotorama
                    .html('')
                    .addClass(_fotoramaClass + ' ' + stampClass)
                    .append($wrap)
                    .before($style)
                    .before($anchor);

                addInstance(that);
            } else {
                $wrap.detach();
                $style.detach();
                $anchor.detach();
                $fotorama
                    .html(fotoramaData.urtext)
                    .removeClass(stampClass);

                hideInstance(that);
            }

            bindGlobalEvents(FLAG);
            appendElements.f = FLAG;
        }

        function setData () {
            data = that.data = data || clone(opts.data) || getDataFromHtml($fotorama);
            size = that.size = data.length;

            !ready.ok && opts.shuffle && shuffle(data);

            checkForVideo();

            activeIndex = limitIndex(activeIndex);

            size && appendElements(true);
        }

        function stageNoMove () {
            var _noMove = (size < 2 && !opts.enableifsingleframe) || $videoPlaying;
            stageShaftTouchTail.noMove = _noMove || o_fade;
            stageShaftTouchTail.noSwipe = _noMove || !opts.swipe;

            !o_transition && $stageShaft.toggleClass(grabClass, !opts.click && !stageShaftTouchTail.noMove && !stageShaftTouchTail.noSwipe);
            MS_POINTER && $wrap.toggleClass(wrapPanYClass, !stageShaftTouchTail.noSwipe);
        }

        function setAutoplayInterval (interval) {
            if (interval === true) interval = '';
            opts.autoplay = Math.max(+interval || AUTOPLAY_INTERVAL, o_transitionDuration * 1.5);
        }

        /**
         * Options on the fly
         * */
        function setOptions () {
            that.options = opts = optionsToLowerCase(opts);

            o_fade = (opts.transition === 'crossfade' || opts.transition === 'dissolve');

            o_loop = opts.loop && (size > 2 || (o_fade && (!o_transition || o_transition !== 'slide')));

            o_transitionDuration = +opts.transitionduration || TRANSITION_DURATION;

            o_rtl = opts.direction === 'rtl';

            o_keyboard = $.extend({}, opts.keyboard && KEYBOARD_OPTIONS, opts.keyboard);

            var classes = {add: [], remove: []};

            function addOrRemoveClass (FLAG, value) {
                classes[FLAG ? 'add' : 'remove'].push(value);
            }

            if (size > 1 || opts.enableifsingleframe) {
                o_nav = opts.nav;
                o_navTop = opts.navposition === 'top';
                classes.remove.push(selectClass);

                $arrs.toggle(!!opts.arrows);
            } else {
                o_nav = false;
                $arrs.hide();
            }

            spinnerStop();
            spinner = new Spinner($.extend(spinnerDefaults, opts.spinner, spinnerOverride, {direction: o_rtl ? -1 : 1}));

            arrsUpdate();
            stageWheelUpdate();

            if (opts.autoplay) setAutoplayInterval(opts.autoplay);

            o_thumbSide = numberFromMeasure(opts.thumbwidth) || THUMB_SIZE;
            o_thumbSide2 = numberFromMeasure(opts.thumbheight) || THUMB_SIZE;

            stageWheelTail.ok = navWheelTail.ok = opts.trackpad && !SLOW;

            stageNoMove();

            extendMeasures(opts, [measures]);

            o_navThumbs = o_nav === 'thumbs';

            if (o_navThumbs) {
                frameDraw(size, 'navThumb');

                $navFrame = $navThumbFrame;
                navFrameKey = NAV_THUMB_FRAME_KEY;

                setStyle($style, $.Fotorama.jst.style({w: o_thumbSide, h: o_thumbSide2, b: opts.thumbborderwidth, m: opts.thumbmargin, s: stamp, q: !COMPAT}));

                $nav
                    .addClass(navThumbsClass)
                    .removeClass(navDotsClass);
            } else if (o_nav === 'dots') {
                frameDraw(size, 'navDot');

                $navFrame = $navDotFrame;
                navFrameKey = NAV_DOT_FRAME_KEY;

                $nav
                    .addClass(navDotsClass)
                    .removeClass(navThumbsClass);
            } else {
                o_nav = false;
                $nav.removeClass(navThumbsClass + ' ' + navDotsClass);
            }

            if (o_nav) {
                if (o_navTop) {
                    $navWrap.insertBefore($stage);
                } else {
                    $navWrap.insertAfter($stage);
                }
                frameAppend.nav = false;
                frameAppend($navFrame, $navShaft, 'nav');
            }

            o_allowFullScreen = opts.allowfullscreen;

            if (o_allowFullScreen) {
                $fullscreenIcon.prependTo($stage);
                o_nativeFullScreen = FULLSCREEN && o_allowFullScreen === 'native';
            } else {
                $fullscreenIcon.detach();
                o_nativeFullScreen = false;
            }

            addOrRemoveClass(o_fade, wrapFadeClass);
            addOrRemoveClass(!o_fade, wrapSlideClass);
            addOrRemoveClass(!opts.captions, wrapNoCaptionsClass);
            addOrRemoveClass(o_rtl, wrapRtlClass);
            addOrRemoveClass(opts.arrows !== 'always', wrapToggleArrowsClass);

            o_shadows = opts.shadows && !SLOW;
            addOrRemoveClass(!o_shadows, wrapNoShadowsClass);

            $wrap
                .addClass(classes.add.join(' '))
                .removeClass(classes.remove.join(' '));

            lastOptions = $.extend({}, opts);
        }

        function normalizeIndex (index) {
            return index < 0 ? (size + (index % size)) % size : index >= size ? index % size : index;
        }

        function limitIndex (index) {
            return minMaxLimit(index, 0, size - 1);
        }

        function edgeIndex (index) {
            return o_loop ? normalizeIndex(index) : limitIndex(index);
        }

        function getPrevIndex (index) {
            return index > 0 || o_loop ? index - 1 : false;
        }

        function getNextIndex (index) {
            return index < size - 1 || o_loop ? index + 1 : false;
        }

        function setStageShaftMinmaxAndSnap () {
            stageShaftTouchTail.min = o_loop ? -Infinity : -getPosByIndex(size - 1, measures.w, opts.margin, repositionIndex);
            stageShaftTouchTail.max = o_loop ? Infinity : -getPosByIndex(0, measures.w, opts.margin, repositionIndex);
            stageShaftTouchTail.snap = measures.w + opts.margin;
        }

        function setNavShaftMinMax () {
            ////////console.log('setNavShaftMinMax', measures.nw);
            navShaftTouchTail.min = Math.min(0, measures.nw - $navShaft.width());
            navShaftTouchTail.max = 0;
            $navShaft.toggleClass(grabClass, !(navShaftTouchTail.noMove = navShaftTouchTail.min === navShaftTouchTail.max));
        }

        function eachIndex (indexes, type, fn) {
            if (typeof indexes === 'number') {
                indexes = new Array(indexes);
                var rangeFLAG = true;
            }
            return $.each(indexes, function (i, index) {
                if (rangeFLAG) index = i;
                if (typeof index === 'number') {
                    var dataFrame = data[normalizeIndex(index)];

                    if (dataFrame) {
                        var key = '$' + type + 'Frame',
                            $frame = dataFrame[key];

                        fn.call(this, i, index, dataFrame, $frame, key, $frame && $frame.data());
                    }
                }
            });
        }

        function setMeasures (width, height, ratio, index) {
            if (!measuresSetFLAG || (measuresSetFLAG === '*' && index === startIndex)) {

                //////console.log('setMeasures', index, opts.width, opts.height);

                width = measureIsValid(opts.width) || measureIsValid(width) || WIDTH;
                height = measureIsValid(opts.height) || measureIsValid(height) || HEIGHT;
                that.resize({
                    width: width,
                    ratio: opts.ratio || ratio || width / height
                }, 0, index !== startIndex && '*');
            }
        }

        function loadImg (indexes, type, specialMeasures, method, position, again) {
            eachIndex(indexes, type, function (i, index, dataFrame, $frame, key, frameData) {

                if (!$frame) return;

                var fullFLAG = that.fullScreen && dataFrame.full && dataFrame.full !== dataFrame.img && !frameData.$full && type === 'stage';

                if (frameData.$img && !again && !fullFLAG) return;

                var img = new Image(),
                    $img = $(img),
                    imgData = $img.data();

                frameData[fullFLAG ? '$full' : '$img'] = $img;

                var srcKey = type === 'stage' ? (fullFLAG ? 'full' : 'img') : 'thumb',
                    src = dataFrame[srcKey],
                    dummy = fullFLAG ? null : dataFrame[type === 'stage' ? 'thumb' : 'img'];

                if (type === 'navThumb') $frame = frameData.$wrap;

                function triggerTriggerEvent (event) {
                    var _index = normalizeIndex(index);
                    triggerEvent(event, {
                        index: _index,
                        src: src,
                        frame: data[_index]
                    });
                }

                function error () {
                    $img.remove();

                    $.Fotorama.cache[src] = 'error';

                    if ((!dataFrame.html || type !== 'stage') && dummy && dummy !== src) {
                        dataFrame[srcKey] = src = dummy;
                        loadImg([index], type, specialMeasures, method, position, true);
                    } else {
                        if (src && !dataFrame.html && !fullFLAG) {
                            $frame
                                .trigger('f:error')
                                .removeClass(loadingClass)
                                .addClass(errorClass);

                            triggerTriggerEvent('error');
                        } else if (type === 'stage') {
                            $frame
                                .trigger('f:load')
                                .removeClass(loadingClass + ' ' + errorClass)
                                .addClass(loadedClass);

                            triggerTriggerEvent('load');
                            setMeasures();
                        }

                        frameData.state = 'error';

                        if (size > 1 && data[index] === dataFrame && !dataFrame.html && !dataFrame.deleted && !dataFrame.video && !fullFLAG) {
                            dataFrame.deleted = true;
                            that.splice(index, 1);
                        }
                    }
                }

                function loaded () {
                    //////console.log('loaded: ' + src);

                    ////console.log('$.Fotorama.measures[src]', $.Fotorama.measures[src]);

                    $.Fotorama.measures[src] = imgData.measures = $.Fotorama.measures[src] || {
                        width: img.width,
                        height: img.height,
                        ratio: img.width / img.height
                    };

                    setMeasures(imgData.measures.width, imgData.measures.height, imgData.measures.ratio, index);

                    $img
                        .off('load error')
                        .addClass(imgClass + (fullFLAG ? ' ' + imgFullClass : ''))
                        .prependTo($frame);

                    fit($img, ($.isFunction(specialMeasures) ? specialMeasures() : specialMeasures) || measures, method || dataFrame.fit || opts.fit, position || dataFrame.position || opts.position);

                    $.Fotorama.cache[src] = frameData.state = 'loaded';

                    setTimeout(function () {
                        $frame
                            .trigger('f:load')
                            .removeClass(loadingClass + ' ' + errorClass)
                            .addClass(loadedClass + ' ' + (fullFLAG ? loadedFullClass : loadedImgClass));

                        if (type === 'stage') {
                            triggerTriggerEvent('load');
                        } else if (dataFrame.thumbratio === AUTO || !dataFrame.thumbratio && opts.thumbratio === AUTO) {
                            // danger! reflow for all thumbnails
                            dataFrame.thumbratio = imgData.measures.ratio;
                            reset();
                        }
                    }, 0);
                }

                if (!src) {
                    error();
                    return;
                }

                function waitAndLoad () {
                    var _i = 10;
                    waitFor(function () {
                        return !touchedFLAG || !_i-- && !SLOW;
                    }, function () {
                        loaded();
                    });
                }

                if (!$.Fotorama.cache[src]) {
                    $.Fotorama.cache[src] = '*';

                    $img
                        .on('load', waitAndLoad)
                        .on('error', error);
                } else {
                    (function justWait () {
                        if ($.Fotorama.cache[src] === 'error') {
                            error();
                        } else if ($.Fotorama.cache[src] === 'loaded') {
                            ////console.log('take from cache: ' + src);
                            setTimeout(waitAndLoad, 0);
                        } else {
                            setTimeout(justWait, 100);
                        }
                    })();
                }

                frameData.state = '';

                if ( frameData.data.hasOwnProperty('alt')   ) { img.alt   = frameData.data.alt; }
                if ( frameData.data.hasOwnProperty('title') ) { img.title = frameData.data.title; }

                img.src = src;
            });
        }

        function spinnerSpin ($el) {
            $spinner.append(spinner.spin().el).appendTo($el);
        }

        function spinnerStop () {
            $spinner.detach();
            spinner && spinner.stop();
        }

        function updateFotoramaState () {
            var $frame = activeFrame[STAGE_FRAME_KEY];

            if ($frame && !$frame.data().state) {
                spinnerSpin($frame);
                $frame.on('f:load f:error', function () {
                    $frame.off('f:load f:error');
                    spinnerStop();
                });
            }
        }

        function addNavFrameEvents (frame) {
            addEnterUp(frame, onNavFrameClick);
            addFocus(frame, function () {

                setTimeout(function () {
                    lockScroll($nav);
                }, 0);
                slideNavShaft({time: o_transitionDuration, guessIndex: $(this).data().eq, minMax: navShaftTouchTail});
            });
        }

        function frameDraw (indexes, type) {
            eachIndex(indexes, type, function (i, index, dataFrame, $frame, key, frameData) {
                if ($frame) return;

                $frame = dataFrame[key] = $wrap[key].clone();
                frameData = $frame.data();
                frameData.data = dataFrame;
                var frame = $frame[0];

                if (type === 'stage') {

                    if (dataFrame.html) {
                        $('<div class="' + htmlClass + '"></div>')
                            .append(
                                dataFrame._html ? $(dataFrame.html)
                                        .removeAttr('id')
                                        .html(dataFrame._html) // Because of IE
                                    : dataFrame.html
                            )
                            .appendTo($frame);
                    }

                    dataFrame.caption && $(div(captionClass, div(captionWrapClass, dataFrame.caption))).appendTo($frame);

                    dataFrame.video && $frame
                        .addClass(stageFrameVideoClass)
                        .append($videoPlay.clone());

                    // This solves tabbing problems
                    addFocus(frame, function () {
                        setTimeout(function () {
                            lockScroll($stage);
                        }, 0);
                        clickToShow({index: frameData.eq, user: true});
                    });

                    $stageFrame = $stageFrame.add($frame);
                } else if (type === 'navDot') {
                    addNavFrameEvents(frame);
                    $navDotFrame = $navDotFrame.add($frame);
                } else if (type === 'navThumb') {
                    addNavFrameEvents(frame);
                    frameData.$wrap = $frame.children(':first');
                    $navThumbFrame = $navThumbFrame.add($frame);
                    if (dataFrame.video) {
                        frameData.$wrap.append($videoPlay.clone());
                    }
                }
            });
        }

        function callFit ($img, measuresToFit, method, position) {
            return $img && $img.length && fit($img, measuresToFit, method, position);
        }

        function stageFramePosition (indexes) {
            eachIndex(indexes, 'stage', function (i, index, dataFrame, $frame, key, frameData) {
                if (!$frame) return;

                var normalizedIndex = normalizeIndex(index),
                    method = dataFrame.fit || opts.fit,
                    position = dataFrame.position || opts.position;
                frameData.eq = normalizedIndex;

                toDetach[STAGE_FRAME_KEY][normalizedIndex] = $frame.css($.extend({left: o_fade ? 0 : getPosByIndex(index, measures.w, opts.margin, repositionIndex)}, o_fade && getDuration(0)));

                if (isDetached($frame[0])) {
                    $frame.appendTo($stageShaft);
                    unloadVideo(dataFrame.$video);
                }

                callFit(frameData.$img, measures, method, position);
                callFit(frameData.$full, measures, method, position);
            });
        }

        function thumbsDraw (pos, loadFLAG) {
            if (o_nav !== 'thumbs' || isNaN(pos)) return;

            var leftLimit = -pos,
                rightLimit = -pos + measures.nw;

            $navThumbFrame.each(function () {
                var $this = $(this),
                    thisData = $this.data(),
                    eq = thisData.eq,
                    getSpecialMeasures = function () {
                        return {
                            h: o_thumbSide2,
                            w: thisData.w
                        }
                    },
                    specialMeasures = getSpecialMeasures(),
                    dataFrame = data[eq] || {},
                    method = dataFrame.thumbfit || opts.thumbfit,
                    position = dataFrame.thumbposition || opts.thumbposition;

                specialMeasures.w = thisData.w;

                if (thisData.l + thisData.w < leftLimit
                    || thisData.l > rightLimit
                    || callFit(thisData.$img, specialMeasures, method, position)) return;

                loadFLAG && loadImg([eq], 'navThumb', getSpecialMeasures, method, position);
            });
        }

        function frameAppend ($frames, $shaft, type) {
            if (!frameAppend[type]) {

                var thumbsFLAG = type === 'nav' && o_navThumbs,
                    left = 0;

                $shaft.append(
                    $frames
                        .filter(function () {
                            var actual,
                                $this = $(this),
                                frameData = $this.data();
                            for (var _i = 0, _l = data.length; _i < _l; _i++) {
                                if (frameData.data === data[_i]) {
                                    actual = true;
                                    frameData.eq = _i;
                                    break;
                                }
                            }
                            return actual || $this.remove() && false;
                        })
                        .sort(function (a, b) {
                            return $(a).data().eq - $(b).data().eq;
                        })
                        .each(function () {

                            if (!thumbsFLAG) return;

                            var $this = $(this),
                                frameData = $this.data(),
                                thumbwidth = Math.round(o_thumbSide2 * frameData.data.thumbratio) || o_thumbSide;

                            frameData.l = left;
                            frameData.w = thumbwidth;

                            $this.css({width: thumbwidth});

                            left += thumbwidth + opts.thumbmargin;
                        })
                );

                frameAppend[type] = true;
            }
        }

        function getDirection (x) {
            return x - stageLeft > measures.w / 3;
        }

        function disableDirrection (i) {
            return !o_loop && (!(activeIndex + i) || !(activeIndex - size + i)) && !$videoPlaying;
        }

        function arrsUpdate () {
            var disablePrev = disableDirrection(0),
                disableNext = disableDirrection(1);
            $arrPrev
                .toggleClass(arrDisabledClass, disablePrev)
                .attr(disableAttr(disablePrev));
            $arrNext
                .toggleClass(arrDisabledClass, disableNext)
                .attr(disableAttr(disableNext));
        }

        function stageWheelUpdate () {
            if (stageWheelTail.ok) {
                stageWheelTail.prevent = {'<': disableDirrection(0), '>': disableDirrection(1)};
            }
        }

        function getNavFrameBounds ($navFrame) {
            var navFrameData = $navFrame.data(),
                left,
                width;

            if (o_navThumbs) {
                left = navFrameData.l;
                width = navFrameData.w;
            } else {
                left = $navFrame.position().left;
                width = $navFrame.width();
            }

            return {
                c: left + width / 2,
                min: -left + opts.thumbmargin * 10,
                max: -left + measures.w - width - opts.thumbmargin * 10
            };
        }

        function slideThumbBorder (time) {
            var navFrameData = activeFrame[navFrameKey].data();
            slide($thumbBorder, {
                time: time * 1.2,
                pos: navFrameData.l,
                width: navFrameData.w - opts.thumbborderwidth * 2
            });
        }

        function slideNavShaft (options) {
            ////console.log('slideNavShaft', options.guessIndex, options.keep, slideNavShaft.l);
            var $guessNavFrame = data[options.guessIndex][navFrameKey];
            if ($guessNavFrame) {
                var overflowFLAG = navShaftTouchTail.min !== navShaftTouchTail.max,
                    minMax = options.minMax || overflowFLAG && getNavFrameBounds(activeFrame[navFrameKey]),
                    l = overflowFLAG && (options.keep && slideNavShaft.l ? slideNavShaft.l : minMaxLimit((options.coo || measures.nw / 2) - getNavFrameBounds($guessNavFrame).c, minMax.min, minMax.max)),
                    pos = overflowFLAG && minMaxLimit(l, navShaftTouchTail.min, navShaftTouchTail.max),
                    time = options.time * 1.1;

                slide($navShaft, {
                    time: time,
                    pos: pos || 0,
                    onEnd: function () {
                        thumbsDraw(pos, true);
                    }
                });

                //if (time) thumbsDraw(pos);

                setShadow($nav, findShadowEdge(pos, navShaftTouchTail.min, navShaftTouchTail.max));
                slideNavShaft.l = l;
            }
        }

        function navUpdate () {
            deactivateFrames(navFrameKey);
            toDeactivate[navFrameKey].push(activeFrame[navFrameKey].addClass(activeClass));
        }

        function deactivateFrames (key) {
            var _toDeactivate = toDeactivate[key];

            while (_toDeactivate.length) {
                _toDeactivate.shift().removeClass(activeClass);
            }
        }

        function detachFrames (key) {
            var _toDetach = toDetach[key];

            //////console.log('_toDetach', _toDetach);
            //////console.log('activeIndexes', activeIndexes);

            $.each(activeIndexes, function (i, index) {
                delete _toDetach[normalizeIndex(index)];
            });

            $.each(_toDetach, function (index, $frame) {
                delete _toDetach[index];
                //////console.log('Detach', index);
                $frame.detach();
            });
        }

        function stageShaftReposition (skipOnEnd) {

            repositionIndex = dirtyIndex = activeIndex;

            var $frame = activeFrame[STAGE_FRAME_KEY];

            if ($frame) {
                deactivateFrames(STAGE_FRAME_KEY);
                toDeactivate[STAGE_FRAME_KEY].push($frame.addClass(activeClass));

                skipOnEnd || that.show.onEnd(true);
                stop($stageShaft, 0, true);

                detachFrames(STAGE_FRAME_KEY);
                stageFramePosition(activeIndexes);
                setStageShaftMinmaxAndSnap();
                setNavShaftMinMax();
            }
        }

        function extendMeasures (options, measuresArray) {
            if (!options) return;

            $.each(measuresArray, function (i, measures) {
                if (!measures) return;

                $.extend(measures, {
                    width: options.width || measures.width,
                    height: options.height,
                    minwidth: options.minwidth,
                    maxwidth: options.maxwidth,
                    minheight: options.minheight,
                    maxheight: options.maxheight,
                    ratio: getRatio(options.ratio)
                })
            });
        }

        function triggerEvent (event, extra) {
            $fotorama.trigger(_fotoramaClass + ':' + event, [that, extra]);
        }

        function onTouchStart () {
            clearTimeout(onTouchEnd.t);
            touchedFLAG = 1;

            if (opts.stopautoplayontouch) {
                that.stopAutoplay();
            } else {
                pausedAutoplayFLAG = true;
            }
        }

        function onTouchEnd () {
            if (!touchedFLAG) return;
            if (!opts.stopautoplayontouch) {
                releaseAutoplay();
                changeAutoplay();
            }

            onTouchEnd.t = setTimeout(function () {
                touchedFLAG = 0;
            }, TRANSITION_DURATION + TOUCH_TIMEOUT);
            //////console.timeEnd('onTouchEnd');
        }

        function releaseAutoplay () {
            ////console.log('releaseAutoplay');
            pausedAutoplayFLAG = !!($videoPlaying || stoppedAutoplayFLAG);
        }

        function changeAutoplay () {
            ////console.log('changeAutoplay');

            clearTimeout(changeAutoplay.t);
            waitFor.stop(changeAutoplay.w);

            if (!opts.autoplay || pausedAutoplayFLAG) {
                if (that.autoplay) {
                    that.autoplay = false;
                    triggerEvent('stopautoplay');
                }

                return;
            }

            ////console.log('changeAutoplay continue');

            if (!that.autoplay) {
                that.autoplay = true;
                triggerEvent('startautoplay');
            }

            var _activeIndex = activeIndex;


            var frameData = activeFrame[STAGE_FRAME_KEY].data();
            changeAutoplay.w = waitFor(function () {
                ////console.log('wait for the state of the current frame');
                return frameData.state || _activeIndex !== activeIndex;
            }, function () {
                ////console.log('the current frame is ready');
                changeAutoplay.t = setTimeout(function () {
                    ////console.log('changeAutoplay.t setTimeout', pausedAutoplayFLAG, _activeIndex !== activeIndex);
                    if (pausedAutoplayFLAG || _activeIndex !== activeIndex) return;

                    var _nextAutoplayIndex = nextAutoplayIndex,
                        nextFrameData = data[_nextAutoplayIndex][STAGE_FRAME_KEY].data();

                    changeAutoplay.w = waitFor(function () {
                        ////console.log('wait for the state of the next frame');
                        return nextFrameData.state || _nextAutoplayIndex !== nextAutoplayIndex;
                    }, function () {
                        if (pausedAutoplayFLAG || _nextAutoplayIndex !== nextAutoplayIndex) return;
                        that.show(o_loop ? getDirectionSign(!o_rtl) : nextAutoplayIndex);
                    });
                }, opts.autoplay);
            });

        }

        that.startAutoplay = function (interval) {
            if (that.autoplay) return this;
            pausedAutoplayFLAG = stoppedAutoplayFLAG = false;
            setAutoplayInterval(interval || opts.autoplay);
            changeAutoplay();

            return this;
        };

        that.stopAutoplay = function () {
            if (that.autoplay) {
                pausedAutoplayFLAG = stoppedAutoplayFLAG = true;
                changeAutoplay();
            }
            return this;
        };

        that.show = function (options) {
            ////console.log('that.show');
            //////console.time('that.show prepare');
            var index;

            if (typeof options !== 'object') {
                index = options;
                options = {};
            } else {
                index = options.index;
            }

            index = index === '>' ? dirtyIndex + 1 : index === '<' ? dirtyIndex - 1 : index === '<<' ? 0 : index === '>>' ? size - 1 : index;
            index = isNaN(index) ? getIndexFromHash(index, data, true) : index;
            index = typeof index === 'undefined' ? activeIndex || 0 : index;

            that.activeIndex = activeIndex = edgeIndex(index);
            prevIndex = getPrevIndex(activeIndex);
            nextIndex = getNextIndex(activeIndex);
            nextAutoplayIndex = normalizeIndex(activeIndex + (o_rtl ? -1 : 1));
            activeIndexes = [activeIndex, prevIndex, nextIndex];

            dirtyIndex = o_loop ? index : activeIndex;

            var diffIndex = Math.abs(lastActiveIndex - dirtyIndex),
                time = getNumber(options.time, function () {
                    return Math.min(o_transitionDuration * (1 + (diffIndex - 1) / 12), o_transitionDuration * 2);
                }),
                overPos = options.overPos;

            if (options.slow) time *= 10;

            var _activeFrame = activeFrame;
            that.activeFrame = activeFrame = data[activeIndex];
            //////console.timeEnd('that.show prepare');

            var silent = _activeFrame === activeFrame && !options.user;

            //setTimeout(function () {
            //////console.time('unloadVideo');
            unloadVideo($videoPlaying, activeFrame.i !== data[normalizeIndex(repositionIndex)].i);
            //////console.timeEnd('unloadVideo');
            //////console.time('frameDraw');
            frameDraw(activeIndexes, 'stage');
            //////console.timeEnd('frameDraw');
            //////console.time('stageFramePosition');
            stageFramePosition(SLOW ? [dirtyIndex] : [dirtyIndex, getPrevIndex(dirtyIndex), getNextIndex(dirtyIndex)]);
            //////console.timeEnd('stageFramePosition');
            //////console.time('updateTouchTails');
            updateTouchTails('go', true);
            //////console.timeEnd('updateTouchTails');
            //////console.time('triggerEvent');

            silent || triggerEvent('show', {
                user: options.user,
                time: time
            });
            //////console.timeEnd('triggerEvent');
            //}, 0);

            //////console.time('bind onEnd');

            pausedAutoplayFLAG = true;

            var onEnd = that.show.onEnd = function (skipReposition) {
                if (onEnd.ok) return;
                onEnd.ok = true;

                skipReposition || stageShaftReposition(true);

                if (!silent) {
                    triggerEvent('showend', {
                        user: options.user
                    });
                }

                ////console.log('o_transition', o_transition);

                if (!skipReposition && o_transition && o_transition !== opts.transition) {
                    ////console.log('set transition back to: ' + o_transition);
                    that.setOptions({transition: o_transition});
                    o_transition = false;
                    return;
                }

                updateFotoramaState();
                loadImg(activeIndexes, 'stage');

                updateTouchTails('go', false);
                stageWheelUpdate();

                stageCursor();
                releaseAutoplay();
                changeAutoplay();
            };
            //////console.timeEnd('bind onEnd');

            if (!o_fade) {
                //////console.time('slide');
                slide($stageShaft, {
                    pos: -getPosByIndex(dirtyIndex, measures.w, opts.margin, repositionIndex),
                    overPos: overPos,
                    time: time,
                    onEnd: onEnd/*,
        _001: true*/
                });
                //////console.timeEnd('slide');
            } else {
                var $activeFrame = activeFrame[STAGE_FRAME_KEY],
                    $prevActiveFrame = activeIndex !== lastActiveIndex ? data[lastActiveIndex][STAGE_FRAME_KEY] : null;

                fade($activeFrame, $prevActiveFrame, $stageFrame, {
                    time: time,
                    method: opts.transition,
                    onEnd: onEnd
                }, fadeStack);
            }

            //////console.time('arrsUpdate');
            arrsUpdate();
            //////console.timeEnd('arrsUpdate');

            if (o_nav) {
                //////console.time('navUpdate');
                navUpdate();
                //////console.timeEnd('navUpdate');

                //////console.time('slideNavShaft');
                var guessIndex = limitIndex(activeIndex + minMaxLimit(dirtyIndex - lastActiveIndex, -1, 1));
                slideNavShaft({time: time, coo: guessIndex !== activeIndex && options.coo, guessIndex: typeof options.coo !== 'undefined' ? guessIndex : activeIndex, keep: silent});
                //////console.timeEnd('slideNavShaft');

                //////console.time('slideThumbBorder');
                if (o_navThumbs) slideThumbBorder(time);
                //////console.timeEnd('slideThumbBorder');
            }

            //////console.time('that.show end');
            showedFLAG = typeof lastActiveIndex !== 'undefined' && lastActiveIndex !== activeIndex;
            lastActiveIndex = activeIndex;
            opts.hash && showedFLAG && !that.eq && setHash(activeFrame.id || activeIndex + 1);
            //////console.timeEnd('that.show end');

            //////console.timeEnd('that.show');

            return this;
        };

        that.requestFullScreen = function () {
            if (o_allowFullScreen && !that.fullScreen) {
                scrollTop = $WINDOW.scrollTop();
                scrollLeft = $WINDOW.scrollLeft();

                lockScroll($WINDOW);

                updateTouchTails('x', true);

                measuresStash = $.extend({}, measures);

                $fotorama
                    .addClass(fullscreenClass)
                    .appendTo($BODY.addClass(_fullscreenClass));

                $HTML.addClass(_fullscreenClass);

                unloadVideo($videoPlaying, true, true);

                that.fullScreen = true;

                if (o_nativeFullScreen) {
                    fullScreenApi.request(fotorama);
                }

                that.resize();
                loadImg(activeIndexes, 'stage');
                updateFotoramaState();

                triggerEvent('fullscreenenter');
            }

            return this;
        };

        function cancelFullScreen () {
            if (that.fullScreen) {
                that.fullScreen = false;

                if (FULLSCREEN) {
                    fullScreenApi.cancel(fotorama);
                }

                $BODY.removeClass(_fullscreenClass);
                $HTML.removeClass(_fullscreenClass);

                $fotorama
                    .removeClass(fullscreenClass)
                    .insertAfter($anchor);

                measures = $.extend({}, measuresStash);

                unloadVideo($videoPlaying, true, true);

                updateTouchTails('x', false);

                that.resize();
                loadImg(activeIndexes, 'stage');

                lockScroll($WINDOW, scrollLeft, scrollTop);

                triggerEvent('fullscreenexit');
            }
        }

        that.cancelFullScreen = function () {
            if (o_nativeFullScreen && fullScreenApi.is()) {
                fullScreenApi.cancel(document);
            } else {
                cancelFullScreen();
            }

            return this;
        };

        that.toggleFullScreen = function () {
            return that[(that.fullScreen ? 'cancel' : 'request') + 'FullScreen']();
        };

        addEvent(document, fullScreenApi.event, function () {
            if (data && !fullScreenApi.is() && !$videoPlaying) {
                cancelFullScreen();
            }
        });

        that.resize = function (options) {
            if (!data) return this;

            var time = arguments[1] || 0,
                setFLAG = arguments[2];

            extendMeasures(!that.fullScreen ? optionsToLowerCase(options) : {width: '100%', maxwidth: null, minwidth: null, height: '100%', maxheight: null, minheight: null}, [measures, setFLAG || that.fullScreen || opts]);

            var width = measures.width,
                height = measures.height,
                ratio = measures.ratio,
                windowHeight = $WINDOW.height() - (o_nav ? $nav.height() : 0);

            if (measureIsValid(width)) {
                $wrap
                    .addClass(wrapOnlyActiveClass)
                    .css({width: width, minWidth: measures.minwidth || 0, maxWidth: measures.maxwidth || MAX_WIDTH});

                width = measures.W = measures.w = $wrap.width();
                measures.nw = o_nav && numberFromWhatever(opts.navwidth, width) || width;

                if (opts.glimpse) {
                    // Glimpse
                    measures.w -= Math.round((numberFromWhatever(opts.glimpse, width) || 0) * 2);
                }

                $stageShaft.css({width: measures.w, marginLeft: (measures.W - measures.w) / 2});

                //////console.log('measures.W', measures.W);
                //////console.log('measures.w', measures.w);

                height = numberFromWhatever(height, windowHeight);

                height = height || (ratio && width / ratio);

                if (height) {
                    width = Math.round(width);
                    height = measures.h = Math.round(minMaxLimit(height, numberFromWhatever(measures.minheight, windowHeight), numberFromWhatever(measures.maxheight, windowHeight)));

                    $stage
                        .stop()
                        .animate({width: width, height: height}, time, function () {
                            $wrap.removeClass(wrapOnlyActiveClass);
                        });

                    stageShaftReposition();

                    if (o_nav) {
                        $nav
                            .stop()
                            .animate({width: measures.nw}, time);

                        slideNavShaft({guessIndex: activeIndex, time: time, keep: true});
                        if (o_navThumbs && frameAppend.nav) slideThumbBorder(time);
                    }

                    measuresSetFLAG = setFLAG || true;

                    ready();
                }
            }

            stageLeft = $stage.offset().left;

            return this;
        };

        that.setOptions = function (options) {
            $.extend(opts, options);
            reset();
            return this;
        };

        that.shuffle = function () {
            data && shuffle(data) && reset();
            return this;
        };

        function setShadow ($el, edge) {
            //////console.time('setShadow');
            if (o_shadows) {
                $el.removeClass(shadowsLeftClass + ' ' + shadowsRightClass);
                edge && !$videoPlaying && $el.addClass(edge.replace(/^|\s/g, ' ' + shadowsClass + '--'));
            }
            //////console.timeEnd('setShadow');
        }

        that.destroy = function () {
            that.cancelFullScreen();
            that.stopAutoplay();

            data = that.data = null;

            appendElements();

            activeIndexes = [];
            detachFrames(STAGE_FRAME_KEY);

            reset.ok = false;

            return this;
        };

        that.playVideo = function () {
            var dataFrame = activeFrame,
                video = dataFrame.video,
                _activeIndex = activeIndex;

            if (typeof video === 'object' && dataFrame.videoReady) {
                o_nativeFullScreen && that.fullScreen && that.cancelFullScreen();

                waitFor(function () {
                    return !fullScreenApi.is() || _activeIndex !== activeIndex;
                }, function () {
                    if (_activeIndex === activeIndex) {
                        dataFrame.$video = dataFrame.$video || $($.Fotorama.jst.video(video));
                        dataFrame.$video.appendTo(dataFrame[STAGE_FRAME_KEY]);

                        $wrap.addClass(wrapVideoClass);
                        $videoPlaying = dataFrame.$video;

                        stageNoMove();

                        $arrs.blur();
                        $fullscreenIcon.blur();

                        triggerEvent('loadvideo');
                    }
                });
            }

            return this;
        };

        that.stopVideo = function () {
            unloadVideo($videoPlaying, true, true);
            return this;
        };

        function unloadVideo ($video, unloadActiveFLAG, releaseAutoplayFLAG) {
            if (unloadActiveFLAG) {
                $wrap.removeClass(wrapVideoClass);
                $videoPlaying = false;

                stageNoMove();
            }

            if ($video && $video !== $videoPlaying) {
                $video.remove();
                triggerEvent('unloadvideo');
            }

            if (releaseAutoplayFLAG) {
                releaseAutoplay();
                changeAutoplay();
            }
        }

        function toggleControlsClass (FLAG) {
            $wrap.toggleClass(wrapNoControlsClass, FLAG);
        }

        function stageCursor (e) {
            if (stageShaftTouchTail.flow) return;

            var x = e ? e.pageX : stageCursor.x,
                pointerFLAG = x && !disableDirrection(getDirection(x)) && opts.click;

            if (stageCursor.p !== pointerFLAG
                && $stage.toggleClass(pointerClass, pointerFLAG)) {
                stageCursor.p = pointerFLAG;
                stageCursor.x = x;
            }
        }

        $stage.on('mousemove', stageCursor);

        function clickToShow (showOptions) {
            clearTimeout(clickToShow.t);

            if (opts.clicktransition && opts.clicktransition !== opts.transition) {
                ////console.log('change transition to: ' + opts.clicktransition);

                // this timeout is for yield events flow
                setTimeout(function () {
                    // save original transition for later
                    var _o_transition = opts.transition;

                    that.setOptions({transition: opts.clicktransition});

                    // now safe to pass base transition to o_transition, so that.show will restor it
                    o_transition = _o_transition;
                    // this timeout is here to prevent jerking in some browsers
                    clickToShow.t = setTimeout(function () {
                        that.show(showOptions);
                    }, 10);
                }, 0);
            } else {
                that.show(showOptions);
            }
        }

        function onStageTap (e, toggleControlsFLAG) {
            //////console.time('onStageTap');
            var target = e.target,
                $target = $(target);

            if ($target.hasClass(videoPlayClass)) {
                that.playVideo();
            } else if (target === fullscreenIcon) {
                that.toggleFullScreen();
            } else if ($videoPlaying) {
                target === videoClose && unloadVideo($videoPlaying, true, true);
            } else {
                if (toggleControlsFLAG) {
                    toggleControlsClass();
                } else if (opts.click) {

                    clickToShow({index: e.shiftKey || getDirectionSign(getDirection(e._x)), slow: e.altKey, user: true});
                }
            }
            //////console.timeEnd('onStageTap');
        }

        function updateTouchTails (key, value) {
            stageShaftTouchTail[key] = navShaftTouchTail[key] = value;
        }

        stageShaftTouchTail = moveOnTouch($stageShaft, {
            onStart: onTouchStart,
            onMove: function (e, result) {
                setShadow($stage, result.edge);
            },
            onTouchEnd: onTouchEnd,
            onEnd: function (result) {
                //////console.time('stageShaftTouchTail.onEnd');
                setShadow($stage);

                //////console.log('result', result);

                var toggleControlsFLAG = (MS_POINTER && !hoverFLAG || result.touch) && opts.arrows && opts.arrows !== 'always';

                if (result.moved || (toggleControlsFLAG && result.pos !== result.newPos && !result.control)) {
                    var index = getIndexByPos(result.newPos, measures.w, opts.margin, repositionIndex);
                    that.show({
                        index: index,
                        time: o_fade ? o_transitionDuration : result.time,
                        overPos: result.overPos,
                        user: true
                    });
                } else if (!result.aborted && !result.control) {
                    onStageTap(result.startEvent, toggleControlsFLAG);
                }
                //////console.timeEnd('stageShaftTouchTail.onEnd');
            },
//    getPos: function () {
//      return -getPosByIndex(dirtyIndex, measures.w, opts.margin, repositionIndex);
//    },
            //_001: true,
            timeLow: 1,
            timeHigh: 1,
            friction: 2,
            select: '.' + selectClass + ', .' + selectClass + ' *',
            $wrap: $stage
        });

        navShaftTouchTail = moveOnTouch($navShaft, {
            onStart: onTouchStart,
            onMove: function (e, result) {
                setShadow($nav, result.edge);
            },
            onTouchEnd: onTouchEnd,
            onEnd: function (result) {

                function onEnd () {
                    slideNavShaft.l = result.newPos;
                    releaseAutoplay();
                    changeAutoplay();
                    thumbsDraw(result.newPos, true);
                }

                if (!result.moved) {
                    var target = result.$target.closest('.' + navFrameClass, $navShaft)[0];
                    target && onNavFrameClick.call(target, result.startEvent);
                } else if (result.pos !== result.newPos) {
                    pausedAutoplayFLAG = true;
                    slide($navShaft, {
                        time: result.time,
                        pos: result.newPos,
                        overPos: result.overPos,
                        onEnd: onEnd
                    });
                    thumbsDraw(result.newPos);
                    o_shadows && setShadow($nav, findShadowEdge(result.newPos, navShaftTouchTail.min, navShaftTouchTail.max));
                } else {
                    onEnd();
                }
            },
            timeLow: .5,
            timeHigh: 2,
            friction: 5,
            $wrap: $nav
        });

        stageWheelTail = wheel($stage, {
            shift: true,
            onEnd: function (e, direction) {
                //////console.log('wheel $stage onEnd', direction);
                onTouchStart();
                onTouchEnd();
                that.show({index: direction, slow: e.altKey})
            }
        });

        navWheelTail = wheel($nav, {
            onEnd: function (e, direction) {
                //////console.log('wheel $nav onEnd', direction);
                onTouchStart();
                onTouchEnd();
                var newPos = stop($navShaft) + direction * .25;
                $navShaft.css(getTranslate(minMaxLimit(newPos, navShaftTouchTail.min, navShaftTouchTail.max)));
                o_shadows && setShadow($nav, findShadowEdge(newPos, navShaftTouchTail.min, navShaftTouchTail.max));
                navWheelTail.prevent = {'<': newPos >= navShaftTouchTail.max, '>': newPos <= navShaftTouchTail.min};
                clearTimeout(navWheelTail.t);
                navWheelTail.t = setTimeout(function () {
                    slideNavShaft.l = newPos;
                    thumbsDraw(newPos, true)
                }, TOUCH_TIMEOUT);
                thumbsDraw(newPos);
            }
        });

        $wrap.hover(
            function () {
                setTimeout(function () {
                    if (touchedFLAG) return;
                    toggleControlsClass(!(hoverFLAG = true));
                }, 0);
            }, function () {
                if (!hoverFLAG) return;
                toggleControlsClass(!(hoverFLAG = false));
            }
        );

        function onNavFrameClick (e) {
            var index = $(this).data().eq;
            clickToShow({index: index, slow: e.altKey, user: true, coo: e._x - $nav.offset().left});
        }

        function onArrClick (e) {
            clickToShow({index: $arrs.index(this) ? '>' : '<', slow: e.altKey, user: true});
        }

        smartClick($arrs, function (e) {
            stopEvent(e);
            onArrClick.call(this, e);
        }, {
            onStart: function () {
                onTouchStart();
                stageShaftTouchTail.control = true;
            },
            onTouchEnd: onTouchEnd
        });

        function addFocusOnControls (el) {
            addFocus(el, function () {
                setTimeout(function () {
                    lockScroll($stage);
                }, 0);
                toggleControlsClass(false);
            });
        }

        $arrs.each(function () {
            addEnterUp(this, function (e) {
                onArrClick.call(this, e);
            });
            addFocusOnControls(this);
        });

        addEnterUp(fullscreenIcon, that.toggleFullScreen);
        addFocusOnControls(fullscreenIcon);

        function reset () {
            setData();
            setOptions();

            if (!reset.i) {
                reset.i = true;
                // Only once
                var _startindex = opts.startindex;
                if (_startindex || opts.hash && location.hash) {
                    startIndex = getIndexFromHash(_startindex || location.hash.replace(/^#/, ''), data, that.index === 0 || _startindex, _startindex);
                }
                activeIndex = repositionIndex = dirtyIndex = lastActiveIndex = startIndex = edgeIndex(startIndex) || 0;/*(o_rtl ? size - 1 : 0)*///;
            }

            if (size) {
                if (changeToRtl()) return;

                if ($videoPlaying) {
                    unloadVideo($videoPlaying, true);
                }

                activeIndexes = [];
                detachFrames(STAGE_FRAME_KEY);

                reset.ok = true;

                that.show({index: activeIndex, time: 0});
                that.resize();
            } else {
                that.destroy();
            }
        }

        function changeToRtl () {
            //////console.log('changeToRtl');
            if (!changeToRtl.f === o_rtl) {
                changeToRtl.f = o_rtl;
                activeIndex = size - 1 - activeIndex;
                //////console.log('changeToRtl execute, activeIndex is', activeIndex);
                that.reverse();

                return true;
            }
        }

        $.each('load push pop shift unshift reverse sort splice'.split(' '), function (i, method) {
            that[method] = function () {
                data = data || [];
                if (method !== 'load') {
                    Array.prototype[method].apply(data, arguments);
                } else if (arguments[0] && typeof arguments[0] === 'object' && arguments[0].length) {
                    data = clone(arguments[0]);
                }
                reset();
                return that;
            }
        });

        function ready () {
            if (!ready.ok) {
                ready.ok = true;
                triggerEvent('ready');
            }
        }

        reset();
    };


    $.fn.fotorama = function (opts) {
        return this.each(function () {
            var that = this,
                $fotorama = $(this),
                fotoramaData = $fotorama.data(),
                fotorama = fotoramaData.fotorama;

            if (!fotorama) {
                waitFor(function () {
                    return !isHidden(that);
                }, function () {
                    fotoramaData.urtext = $fotorama.html();
                    new $.Fotorama($fotorama,
                        /* Priority for options:
                         * 1. <div data-loop="true"></div>
                         * 2. $('div').fotorama({loop: false})
                         * 3. Defaults */
                        $.extend(
                            {},
                            OPTIONS,
                            window.fotoramaDefaults,
                            opts,
                            fotoramaData
                        )
                    );
                });
            } else {
                fotorama.setOptions(opts, true);
            }
        });
    };
    $.Fotorama.instances = [];

    function calculateIndexes () {
        $.each($.Fotorama.instances, function (index, instance) {
            instance.index = index;
        });
    }

    function addInstance (instance) {
        $.Fotorama.instances.push(instance);
        calculateIndexes();
    }

    function hideInstance (instance) {
        $.Fotorama.instances.splice(instance.index, 1);
        calculateIndexes();
    }
    $.Fotorama.cache = {};
    $.Fotorama.measures = {};
    $ = $ || {};
    $.Fotorama = $.Fotorama || {};
    $.Fotorama.jst = $.Fotorama.jst || {};

    $.Fotorama.jst.style = function(v) {
        var __t, __p = '', __e = _.escape;
        __p += '.fotorama' +
            ((__t = ( v.s )) == null ? '' : __t) +
            ' .fotorama__nav--thumbs .fotorama__nav__frame{\npadding:' +
            ((__t = ( v.m )) == null ? '' : __t) +
            'px;\nheight:' +
            ((__t = ( v.h )) == null ? '' : __t) +
            'px}\n.fotorama' +
            ((__t = ( v.s )) == null ? '' : __t) +
            ' .fotorama__thumb-border{\nheight:' +
            ((__t = ( v.h - v.b * (v.q ? 0 : 2) )) == null ? '' : __t) +
            'px;\nborder-width:' +
            ((__t = ( v.b )) == null ? '' : __t) +
            'px;\nmargin-top:' +
            ((__t = ( v.m )) == null ? '' : __t) +
            'px}';
        return __p
    };

    $.Fotorama.jst.video = function(v) {
        var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
        function print() { __p += __j.call(arguments, '') }
        __p += '<div class="fotorama__video"><iframe src="';
        print((v.type == 'youtube' ? v.p + 'youtube.com/embed/' + v.id +'?autoplay=1' : v.type == 'vimeo' ? v.p + 'player.vimeo.com/video/' + v.id + '?autoplay=1&badge=0' : v.id)  + (v.s && v.type != 'custom' ? '&' + v.s : '')) ;
        __p += '" frameborder="0" allowfullscreen></iframe></div>\n';
        return __p
    };
    $(function () {
        $('.' + _fotoramaClass + ':not([data-auto="false"])').fotorama();
    });
})(window, document, location, typeof jQuery !== 'undefined' && jQuery);

Youez - 2016 - github.com/yon3zu
LinuXploit