<commit_before>(function(core) {

    if (typeof define == "function" && define.amd) { // AMD
        define("uikit", function(){

            var uikit = core(window, window.jQuery, window.document);

            uikit.load = function(res, req, onload, config) {

                var resources = res.split(','), load = [], i, base = (config.config && config.config.uikit && config.config.uikit.base ? config.config.uikit.base : "").replace(/\/+$/g, "");

                if (!base) {
                    throw new Error( "Please define base path to UIkit in the requirejs config." );
                }

                for (i = 0; i < resources.length; i += 1) {
                    var resource = resources[i].replace(/\./g, '/');
                    load.push(base+'/'+resource+'/'+resource);
                }

                req(load, function() {
                    onload(uikit);
                });
            };

            return uikit;
        });
    }

    if (!window.jQuery) {
        throw new Error( "UIkit requires jQuery" );
    }

    if (window && window.jQuery) {
        core(window, window.jQuery, window.document);
    }

})(function(global, $, doc) {

    "use strict";

    var UI = $.UIkit || {}, $html = $("html"), $win = $(window), $doc = $(document);

    if (UI.fn) {
        return UI;
    }

    UI.version = '2.11.0';
    UI.$doc    = $doc;
    UI.$win    = $win;

    UI.fn = function(command, options) {

        var args = arguments, cmd = command.match(/^([a-z\-]+)(?:\.([a-z]+))?/i), component = cmd[1], method = cmd[2];

        if (!UI[component]) {
            $.error("UIkit component [" + component + "] does not exist.");
            return this;
        }

        return this.each(function() {
            var $this = $(this), data = $this.data(component);
            if (!data) $this.data(component, (data = UI[component](this, method ? undefined : options)));
            if (method) data[method].apply(data, Array.prototype.slice.call(args, 1));
        });
    };

    UI.support = {};
    UI.support.transition = (function() {

        var transitionEnd = (function() {

            var element = doc.body || doc.documentElement,
                transEndEventNames = {
                    WebkitTransition: 'webkitTransitionEnd',
                    MozTransition: 'transitionend',
                    OTransition: 'oTransitionEnd otransitionend',
                    transition: 'transitionend'
                }, name;

            for (name in transEndEventNames) {
                if (element.style[name] !== undefined) return transEndEventNames[name];
            }
        }());

        return transitionEnd && { end: transitionEnd };
    })();

    UI.support.animation = (function() {

        var animationEnd = (function() {

            var element = doc.body || doc.documentElement,
                animEndEventNames = {
                    WebkitAnimation: 'webkitAnimationEnd',
                    MozAnimation: 'animationend',
                    OAnimation: 'oAnimationEnd oanimationend',
                    animation: 'animationend'
                }, name;

            for (name in animEndEventNames) {
                if (element.style[name] !== undefined) return animEndEventNames[name];
            }
        }());

        return animationEnd && { end: animationEnd };
    })();

    UI.support.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function(callback){ setTimeout(callback, 1000/60); };
    UI.support.touch                 = (
        ('ontouchstart' in window && navigator.userAgent.toLowerCase().match(/mobile|tablet/)) ||
        (global.DocumentTouch && document instanceof global.DocumentTouch)  ||
        (global.navigator['msPointerEnabled'] && global.navigator['msMaxTouchPoints'] > 0) || //IE 10
        (global.navigator['pointerEnabled'] && global.navigator['maxTouchPoints'] > 0) || //IE >=11
        false
    );
    UI.support.mutationobserver = (global.MutationObserver || global.WebKitMutationObserver || null);

    UI.Utils = {};

    UI.Utils.debounce = function(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };

    UI.Utils.removeCssRules = function(selectorRegEx) {
        var idx, idxs, stylesheet, _i, _j, _k, _len, _len1, _len2, _ref;

        if(!selectorRegEx) return;

        setTimeout(function(){
            try {
              _ref = document.styleSheets;
              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                stylesheet = _ref[_i];
                idxs = [];
                stylesheet.cssRules = stylesheet.cssRules;
                for (idx = _j = 0, _len1 = stylesheet.cssRules.length; _j < _len1; idx = ++_j) {
                  if (stylesheet.cssRules[idx].type === CSSRule.STYLE_RULE && selectorRegEx.test(stylesheet.cssRules[idx].selectorText)) {
                    idxs.unshift(idx);
                  }
                }
                for (_k = 0, _len2 = idxs.length; _k < _len2; _k++) {
                  stylesheet.deleteRule(idxs[_k]);
                }
              }
            } catch (_error) {}
        }, 0);
    };

    UI.Utils.isInView = function(element, options) {

        var $element = $(element);

        if (!$element.is(':visible')) {
            return false;
        }

        var window_left = $win.scrollLeft(), window_top = $win.scrollTop(), offset = $element.offset(), left = offset.left, top = offset.top;

        options = $.extend({topoffset:0, leftoffset:0}, options);

        if (top + $element.height() >= window_top && top - options.topoffset <= window_top + $win.height() &&
            left + $element.width() >= window_left && left - options.leftoffset <= window_left + $win.width()) {
          return true;
        } else {
          return false;
        }
    };

    UI.Utils.checkDisplay = function(context) {
        $('[data-uk-margin], [data-uk-grid-match], [data-uk-grid-margin], [data-uk-check-display]', context || document).trigger('uk.check.display');
    };

    UI.Utils.options = function(string) {

        if ($.isPlainObject(string)) return string;

        var start = (string ? string.indexOf("{") : -1), options = {};

        if (start != -1) {
            try {
                options = (new Function("", "var json = " + string.substr(start) + "; return JSON.parse(JSON.stringify(json));"))();
            } catch (e) {}
        }

        return options;
    };

    UI.Utils.template = function(str, data) {

        var tokens = str.replace(/\n/g, '\\n').replace(/\{\{\{\s*(.+?)\s*\}\}\}/g, "{{!$1}}").split(/(\{\{\s*(.+?)\s*\}\})/g),
            i=0, toc, cmd, prop, val, fn, output = [], openblocks = 0;

        while(i < tokens.length) {

            toc = tokens[i];

            if(toc.match(/\{\{\s*(.+?)\s*\}\}/)) {
                i = i + 1;
                toc  = tokens[i];
                cmd  = toc[0];
                prop = toc.substring(toc.match(/^(\^|\#|\!|\~|\:)/) ? 1:0);

                switch(cmd) {
                    case '~':
                        output.push("for(var $i=0;$i<"+prop+".length;$i++) { var $item = "+prop+"[$i];");
                        openblocks++;
                        break;
                    case ':':
                        output.push("for(var $key in "+prop+") { var $val = "+prop+"[$key];");
                        openblocks++;
                        break;
                    case '#':
                        output.push("if("+prop+") {");
                        openblocks++;
                        break;
                    case '^':
                        output.push("if(!"+prop+") {");
                        openblocks++;
                        break;
                    case '/':
                        output.push("}");
                        openblocks--;
                        break;
                    case '!':
                        output.push("__ret.push("+prop+");");
                        break;
                    default:
                        output.push("__ret.push(escape("+prop+"));");
                        break;
                }
            } else {
                output.push("__ret.push('"+toc.replace(/\'/g, "\\'")+"');");
            }
            i = i + 1;
        }

        fn  = [
            'var __ret = [];',
            'try {',
            'with($data){', (!openblocks ? output.join('') : '__ret = ["Not all blocks are closed correctly."]'), '};',
            '}catch(e){__ret = [e.message];}',
            'return __ret.join("").replace(/\\n\\n/g, "\\n");',
            "function escape(html) { return String(html).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');}"
        ].join("\n");

        var func = new Function('$data', fn);
        return data ? func(data) : func;
    };

    UI.Utils.events       = {};
    UI.Utils.events.click = UI.support.touch ? 'tap' : 'click';

    $.UIkit = UI;
    $.fn.uk = UI.fn;

    $.UIkit.langdirection = $html.attr("dir") == "rtl" ? "right" : "left";

    // DOM mutation save ready helper function

    UI.domObservers = [];

    UI.domObserve = function(selector, fn) {

        if(!UI.support.mutationobserver) return;

        $(selector).each(function() {

            var element = this;

            try {

                var observer = new UI.support.mutationobserver(UI.Utils.debounce(function(mutations) {
                    fn.apply(element, []);
                    $(element).trigger('uk.dom.changed');
                }, 50));

                // pass in the target node, as well as the observer options
                observer.observe(element, { childList: true, subtree: true });

            } catch(e) {}
        });
    };

    UI.ready = function(fn) {
        $(function() { fn(document); });
        UI.domObservers.push(fn);
    };

    UI.components = {};

    UI.component = function(name, def) {

        var fn = function(element, options) {

            var $this = this;

            this.element = element ? $(element) : null;
            this.options = $.extend(true, {}, this.defaults, options);
            this.plugins = {};

            if (this.element) {
                this.element.data(name, this);
            }

            this.init();

            (this.options.plugins.length ? this.options.plugins : Object.keys(fn.plugins)).forEach(function(plugin) {

                if (fn.plugins[plugin].init) {
                    fn.plugins[plugin].init($this);
                    $this.plugins[plugin] = true;
                }

            });

            this.trigger('init', [this]);
        };

        fn.plugins = {};

        $.extend(true, fn.prototype, {

            defaults : {plugins: []},

            init: function(){},

            on: function(){
                return $(this.element || this).on.apply(this.element || this, arguments);
            },

            one: function(){
                return $(this.element || this).one.apply(this.element || this, arguments);
            },

            off: function(evt){
                return $(this.element || this).off(evt);
            },

            trigger: function(evt, params) {
                return $(this.element || this).trigger(evt, params);
            },

            find: function(selector) {
                return this.element ? this.element.find(selector) : $([]);
            },

            proxy: function(obj, methods) {

                var $this = this;

                methods.split(' ').forEach(function(method) {
                    if (!$this[method]) $this[method] = function() { return obj[method].apply(obj, arguments); };
                });
            },

            mixin: function(obj, methods) {

                var $this = this;

                methods.split(' ').forEach(function(method) {
                    if (!$this[method]) $this[method] = obj[method].bind($this);
                });
            },

        }, def);

        this.components[name] = fn;

        this[name] = function() {

            var element, options;

            if(arguments.length) {
                switch(arguments.length) {
                    case 1:

                        if (typeof arguments[0] === "string" || arguments[0].nodeType || arguments[0] instanceof jQuery) {
                            element = $(arguments[0]);
                        } else {
                            options = arguments[0];
                        }

                        break;
                    case 2:

                        element = $(arguments[0]);
                        options = arguments[1];
                        break;
                }
            }

            if (element && element.data(name)) {
                return element.data(name);
            }

            return (new UI.components[name](element, options));
        };

        return fn;
    };

    UI.plugin = function(component, name, def) {
        this.components[component].plugins[name] = def;
    };

    $doc.on('uk.domready', function(){
        UI.domObservers.forEach(function(fn){
            fn(document);
        });
        $doc.trigger('uk.dom.changed');
    });

    $(function(){

        // custom scroll observer
        setInterval((function(){

            var memory = {x: window.pageXOffset, y:window.pageYOffset};

            var fn = function(){

                if (memory.x != window.pageXOffset || memory.y != window.pageYOffset) {
                    memory = {x: window.pageXOffset, y:window.pageYOffset};
                    $doc.trigger('uk-scroll', [memory]);
                }
            };

            if ($.UIkit.support.touch) {
                $doc.on('touchmove touchend MSPointerMove MSPointerUp', fn);
            }

            if(memory.x || memory.y) fn();

            return fn;

        })(), 15);

        // Check for dom modifications
        UI.domObserve('[data-uk-observe]', function() {

            var ele = this;

            UI.domObservers.forEach(function(fn){
                fn(ele);
            });
        });

        if (UI.support.touch) {

            // remove css hover rules for touch devices
            // UI.Utils.removeCssRules(/\.uk-(?!navbar).*:hover/);

            // viewport unit fix for uk-height-viewport - should be fixed in iOS 8
            if (navigator.userAgent.match(/(iPad|iPhone|iPod)/g)) {

                UI.$win.on('load orientationchange resize', UI.Utils.debounce((function(){

                    var fn = function() {
                        $('.uk-height-viewport').css('height', window.innerHeight);
                        return fn;
                    };

                    return fn();

                })(), 100));
            }
        }
    });

    // add touch identifier class
    $html.addClass(UI.support.touch ? "uk-touch" : "uk-notouch");

    // add uk-hover class on tap to support overlays on touch devices
    if (UI.support.touch) {

        var hoverset = false, selector = '.uk-overlay, .uk-overlay-toggle, .uk-has-hover', exclude;

        $doc.on('touchstart MSPointerDown', selector, function() {

            if(hoverset) $('.uk-hover').removeClass('uk-hover');

            hoverset = $(this).addClass('uk-hover');

        }).on('touchend MSPointerUp', function(e) {

            exclude = $(e.target).parents(selector);

            if (hoverset) hoverset.not(exclude).removeClass('uk-hover');
        });
    }

    return UI;
});<commit_msg>amd loading fix<commit_after>(function(core) {

    if (typeof define == "function" && define.amd) { // AMD
        define("uikit", function(){

            var uikit = core(window, window.jQuery, window.document);

            uikit.load = function(res, req, onload, config) {

                var resources = res.split(','), load = [], i, base = (config.config && config.config.uikit && config.config.uikit.base ? config.config.uikit.base : "").replace(/\/+$/g, "");

                if (!base) {
                    throw new Error( "Please define base path to UIkit in the requirejs config." );
                }

                for (i = 0; i < resources.length; i += 1) {
                    var resource = resources[i].replace(/\./g, '/');
                    load.push(base+'/components/'+resource);
                }

                req(load, function() {
                    onload(uikit);
                });
            };

            return uikit;
        });
    }

    if (!window.jQuery) {
        throw new Error( "UIkit requires jQuery" );
    }

    if (window && window.jQuery) {
        core(window, window.jQuery, window.document);
    }

})(function(global, $, doc) {

    "use strict";

    var UI = $.UIkit || {}, $html = $("html"), $win = $(window), $doc = $(document);

    if (UI.fn) {
        return UI;
    }

    UI.version = '2.11.0';
    UI.$doc    = $doc;
    UI.$win    = $win;

    UI.fn = function(command, options) {

        var args = arguments, cmd = command.match(/^([a-z\-]+)(?:\.([a-z]+))?/i), component = cmd[1], method = cmd[2];

        if (!UI[component]) {
            $.error("UIkit component [" + component + "] does not exist.");
            return this;
        }

        return this.each(function() {
            var $this = $(this), data = $this.data(component);
            if (!data) $this.data(component, (data = UI[component](this, method ? undefined : options)));
            if (method) data[method].apply(data, Array.prototype.slice.call(args, 1));
        });
    };

    UI.support = {};
    UI.support.transition = (function() {

        var transitionEnd = (function() {

            var element = doc.body || doc.documentElement,
                transEndEventNames = {
                    WebkitTransition: 'webkitTransitionEnd',
                    MozTransition: 'transitionend',
                    OTransition: 'oTransitionEnd otransitionend',
                    transition: 'transitionend'
                }, name;

            for (name in transEndEventNames) {
                if (element.style[name] !== undefined) return transEndEventNames[name];
            }
        }());

        return transitionEnd && { end: transitionEnd };
    })();

    UI.support.animation = (function() {

        var animationEnd = (function() {

            var element = doc.body || doc.documentElement,
                animEndEventNames = {
                    WebkitAnimation: 'webkitAnimationEnd',
                    MozAnimation: 'animationend',
                    OAnimation: 'oAnimationEnd oanimationend',
                    animation: 'animationend'
                }, name;

            for (name in animEndEventNames) {
                if (element.style[name] !== undefined) return animEndEventNames[name];
            }
        }());

        return animationEnd && { end: animationEnd };
    })();

    UI.support.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function(callback){ setTimeout(callback, 1000/60); };
    UI.support.touch                 = (
        ('ontouchstart' in window && navigator.userAgent.toLowerCase().match(/mobile|tablet/)) ||
        (global.DocumentTouch && document instanceof global.DocumentTouch)  ||
        (global.navigator['msPointerEnabled'] && global.navigator['msMaxTouchPoints'] > 0) || //IE 10
        (global.navigator['pointerEnabled'] && global.navigator['maxTouchPoints'] > 0) || //IE >=11
        false
    );
    UI.support.mutationobserver = (global.MutationObserver || global.WebKitMutationObserver || null);

    UI.Utils = {};

    UI.Utils.debounce = function(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };

    UI.Utils.removeCssRules = function(selectorRegEx) {
        var idx, idxs, stylesheet, _i, _j, _k, _len, _len1, _len2, _ref;

        if(!selectorRegEx) return;

        setTimeout(function(){
            try {
              _ref = document.styleSheets;
              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                stylesheet = _ref[_i];
                idxs = [];
                stylesheet.cssRules = stylesheet.cssRules;
                for (idx = _j = 0, _len1 = stylesheet.cssRules.length; _j < _len1; idx = ++_j) {
                  if (stylesheet.cssRules[idx].type === CSSRule.STYLE_RULE && selectorRegEx.test(stylesheet.cssRules[idx].selectorText)) {
                    idxs.unshift(idx);
                  }
                }
                for (_k = 0, _len2 = idxs.length; _k < _len2; _k++) {
                  stylesheet.deleteRule(idxs[_k]);
                }
              }
            } catch (_error) {}
        }, 0);
    };

    UI.Utils.isInView = function(element, options) {

        var $element = $(element);

        if (!$element.is(':visible')) {
            return false;
        }

        var window_left = $win.scrollLeft(), window_top = $win.scrollTop(), offset = $element.offset(), left = offset.left, top = offset.top;

        options = $.extend({topoffset:0, leftoffset:0}, options);

        if (top + $element.height() >= window_top && top - options.topoffset <= window_top + $win.height() &&
            left + $element.width() >= window_left && left - options.leftoffset <= window_left + $win.width()) {
          return true;
        } else {
          return false;
        }
    };

    UI.Utils.checkDisplay = function(context) {
        $('[data-uk-margin], [data-uk-grid-match], [data-uk-grid-margin], [data-uk-check-display]', context || document).trigger('uk.check.display');
    };

    UI.Utils.options = function(string) {

        if ($.isPlainObject(string)) return string;

        var start = (string ? string.indexOf("{") : -1), options = {};

        if (start != -1) {
            try {
                options = (new Function("", "var json = " + string.substr(start) + "; return JSON.parse(JSON.stringify(json));"))();
            } catch (e) {}
        }

        return options;
    };

    UI.Utils.template = function(str, data) {

        var tokens = str.replace(/\n/g, '\\n').replace(/\{\{\{\s*(.+?)\s*\}\}\}/g, "{{!$1}}").split(/(\{\{\s*(.+?)\s*\}\})/g),
            i=0, toc, cmd, prop, val, fn, output = [], openblocks = 0;

        while(i < tokens.length) {

            toc = tokens[i];

            if(toc.match(/\{\{\s*(.+?)\s*\}\}/)) {
                i = i + 1;
                toc  = tokens[i];
                cmd  = toc[0];
                prop = toc.substring(toc.match(/^(\^|\#|\!|\~|\:)/) ? 1:0);

                switch(cmd) {
                    case '~':
                        output.push("for(var $i=0;$i<"+prop+".length;$i++) { var $item = "+prop+"[$i];");
                        openblocks++;
                        break;
                    case ':':
                        output.push("for(var $key in "+prop+") { var $val = "+prop+"[$key];");
                        openblocks++;
                        break;
                    case '#':
                        output.push("if("+prop+") {");
                        openblocks++;
                        break;
                    case '^':
                        output.push("if(!"+prop+") {");
                        openblocks++;
                        break;
                    case '/':
                        output.push("}");
                        openblocks--;
                        break;
                    case '!':
                        output.push("__ret.push("+prop+");");
                        break;
                    default:
                        output.push("__ret.push(escape("+prop+"));");
                        break;
                }
            } else {
                output.push("__ret.push('"+toc.replace(/\'/g, "\\'")+"');");
            }
            i = i + 1;
        }

        fn  = [
            'var __ret = [];',
            'try {',
            'with($data){', (!openblocks ? output.join('') : '__ret = ["Not all blocks are closed correctly."]'), '};',
            '}catch(e){__ret = [e.message];}',
            'return __ret.join("").replace(/\\n\\n/g, "\\n");',
            "function escape(html) { return String(html).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');}"
        ].join("\n");

        var func = new Function('$data', fn);
        return data ? func(data) : func;
    };

    UI.Utils.events       = {};
    UI.Utils.events.click = UI.support.touch ? 'tap' : 'click';

    $.UIkit = UI;
    $.fn.uk = UI.fn;

    $.UIkit.langdirection = $html.attr("dir") == "rtl" ? "right" : "left";

    // DOM mutation save ready helper function

    UI.domObservers = [];

    UI.domObserve = function(selector, fn) {

        if(!UI.support.mutationobserver) return;

        $(selector).each(function() {

            var element = this;

            try {

                var observer = new UI.support.mutationobserver(UI.Utils.debounce(function(mutations) {
                    fn.apply(element, []);
                    $(element).trigger('uk.dom.changed');
                }, 50));

                // pass in the target node, as well as the observer options
                observer.observe(element, { childList: true, subtree: true });

            } catch(e) {}
        });
    };

    UI.ready = function(fn) {
        $(function() { fn(document); });
        UI.domObservers.push(fn);
    };

    UI.components = {};

    UI.component = function(name, def) {

        var fn = function(element, options) {

            var $this = this;

            this.element = element ? $(element) : null;
            this.options = $.extend(true, {}, this.defaults, options);
            this.plugins = {};

            if (this.element) {
                this.element.data(name, this);
            }

            this.init();

            (this.options.plugins.length ? this.options.plugins : Object.keys(fn.plugins)).forEach(function(plugin) {

                if (fn.plugins[plugin].init) {
                    fn.plugins[plugin].init($this);
                    $this.plugins[plugin] = true;
                }

            });

            this.trigger('init', [this]);
        };

        fn.plugins = {};

        $.extend(true, fn.prototype, {

            defaults : {plugins: []},

            init: function(){},

            on: function(){
                return $(this.element || this).on.apply(this.element || this, arguments);
            },

            one: function(){
                return $(this.element || this).one.apply(this.element || this, arguments);
            },

            off: function(evt){
                return $(this.element || this).off(evt);
            },

            trigger: function(evt, params) {
                return $(this.element || this).trigger(evt, params);
            },

            find: function(selector) {
                return this.element ? this.element.find(selector) : $([]);
            },

            proxy: function(obj, methods) {

                var $this = this;

                methods.split(' ').forEach(function(method) {
                    if (!$this[method]) $this[method] = function() { return obj[method].apply(obj, arguments); };
                });
            },

            mixin: function(obj, methods) {

                var $this = this;

                methods.split(' ').forEach(function(method) {
                    if (!$this[method]) $this[method] = obj[method].bind($this);
                });
            },

        }, def);

        this.components[name] = fn;

        this[name] = function() {

            var element, options;

            if(arguments.length) {
                switch(arguments.length) {
                    case 1:

                        if (typeof arguments[0] === "string" || arguments[0].nodeType || arguments[0] instanceof jQuery) {
                            element = $(arguments[0]);
                        } else {
                            options = arguments[0];
                        }

                        break;
                    case 2:

                        element = $(arguments[0]);
                        options = arguments[1];
                        break;
                }
            }

            if (element && element.data(name)) {
                return element.data(name);
            }

            return (new UI.components[name](element, options));
        };

        return fn;
    };

    UI.plugin = function(component, name, def) {
        this.components[component].plugins[name] = def;
    };

    $doc.on('uk.domready', function(){
        UI.domObservers.forEach(function(fn){
            fn(document);
        });
        $doc.trigger('uk.dom.changed');
    });

    $(function(){

        // custom scroll observer
        setInterval((function(){

            var memory = {x: window.pageXOffset, y:window.pageYOffset};

            var fn = function(){

                if (memory.x != window.pageXOffset || memory.y != window.pageYOffset) {
                    memory = {x: window.pageXOffset, y:window.pageYOffset};
                    $doc.trigger('uk-scroll', [memory]);
                }
            };

            if ($.UIkit.support.touch) {
                $doc.on('touchmove touchend MSPointerMove MSPointerUp', fn);
            }

            if(memory.x || memory.y) fn();

            return fn;

        })(), 15);

        // Check for dom modifications
        UI.domObserve('[data-uk-observe]', function() {

            var ele = this;

            UI.domObservers.forEach(function(fn){
                fn(ele);
            });
        });

        if (UI.support.touch) {

            // remove css hover rules for touch devices
            // UI.Utils.removeCssRules(/\.uk-(?!navbar).*:hover/);

            // viewport unit fix for uk-height-viewport - should be fixed in iOS 8
            if (navigator.userAgent.match(/(iPad|iPhone|iPod)/g)) {

                UI.$win.on('load orientationchange resize', UI.Utils.debounce((function(){

                    var fn = function() {
                        $('.uk-height-viewport').css('height', window.innerHeight);
                        return fn;
                    };

                    return fn();

                })(), 100));
            }
        }
    });

    // add touch identifier class
    $html.addClass(UI.support.touch ? "uk-touch" : "uk-notouch");

    // add uk-hover class on tap to support overlays on touch devices
    if (UI.support.touch) {

        var hoverset = false, selector = '.uk-overlay, .uk-overlay-toggle, .uk-has-hover', exclude;

        $doc.on('touchstart MSPointerDown', selector, function() {

            if(hoverset) $('.uk-hover').removeClass('uk-hover');

            hoverset = $(this).addClass('uk-hover');

        }).on('touchend MSPointerUp', function(e) {

            exclude = $(e.target).parents(selector);

            if (hoverset) hoverset.not(exclude).removeClass('uk-hover');
        });
    }

    return UI;
});
<|endoftext|>


<commit_before>process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = function(config) {
  config.set({
    frameworks: ['mocha', 'karma-typescript'],
    files: [
      {pattern: 'src/**/*.ts'},
      {pattern: 'test/**/*.ts'},
      {pattern: 'test/**/*.html'},
      {pattern: 'dist/**/*.min.js'}
    ],
    preprocessors: {
      '**/*.ts': ['karma-typescript'],  // *.tsx for React Jsx
    },
    karmaTypescriptConfig: {
      tsconfig: 'tsconfig.json',
      // we include this here to keep our test files separate,
      // but not include tests in normal builds
      include: [ 'test/**/*.ts'],
      reports: {} // Do not produce coverage html.
    },
    //logLevel: config.LOG_DEBUG,
    reporters: ['progress', 'karma-typescript'],
    browsers: ['ChromeHeadless'], // ['Chrome'/*, 'Firefox'*/],
    reportSlowerThan: 500,
    browserNoActivityTimeout: 30000
  });
};
<commit_msg>Karma config fix<commit_after>process.env.CHROME_BIN = require('puppeteer').executablePath();

module.exports = function(config) {
  config.set({
    frameworks: ['mocha', 'karma-typescript'],
    files: [
      {pattern: 'src/**/*.ts'},
      {pattern: 'test/**/*.ts'},
      {pattern: 'test/**/*.html'},
      {pattern: 'dist/**/*.min.js'}
    ],
    preprocessors: {
      '**/*.ts': ['karma-typescript'],  // *.tsx for React Jsx
    },
    karmaTypescriptConfig: {
      tsconfig: 'tsconfig.json',
      // we include this here to keep our test files separate,
      // but not include tests in normal builds
      include: [ 'test/**/*.ts'],
      reports: {} // Do not produce coverage html.
    },
    //logLevel: config.LOG_DEBUG,
    reporters: ['progress', 'karma-typescript'],
    browsers: ['Chrome_no_sandbox'],
    customLaunchers: {
      Chrome_no_sandbox: {
        base: 'Chrome',
        flags: [
          '--no-sandbox',
          '--disable-setuid-sandbox',
          '--headless',
          '--disable-gpu',
          '--remote-debugging-port=9222',
        ],
      },
    },
    reportSlowerThan: 500,
    browserNoActivityTimeout: 500000,
    browserDisconnectTimeout: 300000,
    pingTimeout: 1000000
  });
};
<|endoftext|>


<commit_before>const socket = io();
var player, nobat, name , clicked = false;
$('form').submit(function () {
    name = $("#nameInp").val();
    var mode = $("#modeSel").val();
    socket.emit('game', {
        name: name,
        mode: mode
    });
    return false;
});

socket.on('waitG', () => {
    player = 1;
    $("#sendBtn").html("please wait for second player ...");
    $("#sendBtn").css('color', 'red');
    $("#sendBtn").prop('disabled', true);
});

socket.on('startG', (data) => {
    if (player) {
        player = data.p1;
    } else {
        player = data.p2;
    }
    $("#sendBtn").html("after 7 seconds, game willbe starting");
    $("#sendBtn").css('color', 'black');
    $("#sendBtn").prop('disabled', true);
    setTimeout(() => {
        $("#body").empty();
        $("#head").empty();
        $("head").append(`
            <meta charset="utf-8">
            <meta name="viewport" content="initial-scale=1.0">
            <title>game</title>
            <link href="http://fonts.googleapis.com/css?family=Raleway:300,400,400,100" rel="stylesheet" type="text/css">
            <link rel="stylesheet" href="css/standardize.css">
            <link rel="stylesheet" href="css/game-grid.css">
            <link rel="stylesheet" href="css/game.css">`);
        $("#body").append(`

                <button id="0" class="_button _button-1 js-btn-g-v"></button>
                <button id="1" class="_button _button-2 js-btn-g-v"></button>
                <button id="2" class="_button _button-3 js-btn-g-v"></button>
                <button id="3" class="_button _button-4 js-btn-g-v"></button>
                <button id="4" class="_button _button-5 js-btn-g-v"></button>
                <button id="5" class="_button _button-6 js-btn-g-v"></button>
                <button id="6" class="_button _button-7 js-btn-g-v"></button>
                <button id="7" class="_button _button-8 js-btn-g-v"></button>
                <button id="8" class="_button _button-9 js-btn-g-v"></button>

you are : ${player==0?'x':'o'}

${player==0?'you\'r':'his/m'} turn.

                <button class="_button _button-10" id="btnCancel">cancel</button>

`);
        $("#body").attr('class', 'container container-1 clearfix');
    }, 7 * 1000);
});

$("body").on('click', '#btns', function (e) {
    if (!clicked) {
        var number = parseInt($(e.target).attr('id'));
        if (isNaN(number)) return;
        socket.emit('btn', {
            name: name,
            i: parseInt(number / 3),
            j: number % 3
        });
        clicked = true;
    } else {}
});

$("body").on('click', '#btnCancel', () => {
    if (confirm('are you sure?')) {
        socket.emit('disconnecting', 'by user');
        window.open('', '_self').close();
        window.close();
        close();
        $("#body").empty();
        $("#head").empty();
        $("head").append(`<link rel="stylesheet" href="css/index.css">`);
        $("#body").append(`<p class="text">Good Bye</p>`);
    }
});

socket.on('btnR', (r) => {
    if (r.r == true) {
        $("#" + r.num).html(r.p == 0 ? 'x' : 'o');
        $("#turn").html((r.p == player ? 'his/m' : 'you\'r') + " turn.");
    } else {
        alert("its his/m turn or select disable button.");
    }
    clicked = false;
})

socket.on('win', (data) => {
    $("#body").empty();
    $("#head").empty();
    $("head").append(`<link rel="stylesheet" href="css/index.css">`);
    if (player == data.winner) {
        $("#body").append(`<p class="text">YOU WON</p><p class="text">you'r rating is ${data.rating}</p>`);
    } else {
        $("#body").append(`<p class="text">GAME OVER</p>`);
    }
})

socket.on('equ', (data) => {
    $("#body").empty();
    $("#head").empty();
    $("head").append(`<link rel="stylesheet" href="css/index.css">`);
    $("#body").append(`<p class="text">YOU EQUATED</p><p class="text">you'r rating is ${data.players[player]} </p>`);
})

socket.on('dis', () => {
    $("#body").empty();
    $("#head").empty();
    $("head").append(`<link rel="stylesheet" href="css/index.css">`);
    $("#body").append(`<p class="text">DISCONNECTION</br>YOU WON</br>(but not saved in rating.)</p>`);
})
<commit_msg>Update indexj.js<commit_after>const socket = io('https://noters.ir/socket.io');
var player, nobat, name , clicked = false;
$('form').submit(function () {
    name = $("#nameInp").val();
    var mode = $("#modeSel").val();
    socket.emit('game', {
        name: name,
        mode: mode
    });
    return false;
});

socket.on('waitG', () => {
    player = 1;
    $("#sendBtn").html("please wait for second player ...");
    $("#sendBtn").css('color', 'red');
    $("#sendBtn").prop('disabled', true);
});

socket.on('startG', (data) => {
    if (player) {
        player = data.p1;
    } else {
        player = data.p2;
    }
    $("#sendBtn").html("after 7 seconds, game willbe starting");
    $("#sendBtn").css('color', 'black');
    $("#sendBtn").prop('disabled', true);
    setTimeout(() => {
        $("#body").empty();
        $("#head").empty();
        $("head").append(`
            <meta charset="utf-8">
            <meta name="viewport" content="initial-scale=1.0">
            <title>game</title>
            <link href="http://fonts.googleapis.com/css?family=Raleway:300,400,400,100" rel="stylesheet" type="text/css">
            <link rel="stylesheet" href="css/standardize.css">
            <link rel="stylesheet" href="css/game-grid.css">
            <link rel="stylesheet" href="css/game.css">`);
        $("#body").append(`

                <button id="0" class="_button _button-1 js-btn-g-v"></button>
                <button id="1" class="_button _button-2 js-btn-g-v"></button>
                <button id="2" class="_button _button-3 js-btn-g-v"></button>
                <button id="3" class="_button _button-4 js-btn-g-v"></button>
                <button id="4" class="_button _button-5 js-btn-g-v"></button>
                <button id="5" class="_button _button-6 js-btn-g-v"></button>
                <button id="6" class="_button _button-7 js-btn-g-v"></button>
                <button id="7" class="_button _button-8 js-btn-g-v"></button>
                <button id="8" class="_button _button-9 js-btn-g-v"></button>

you are : ${player==0?'x':'o'}

${player==0?'you\'r':'his/m'} turn.

                <button class="_button _button-10" id="btnCancel">cancel</button>

`);
        $("#body").attr('class', 'container container-1 clearfix');
    }, 7 * 1000);
});

$("body").on('click', '#btns', function (e) {
    if (!clicked) {
        var number = parseInt($(e.target).attr('id'));
        if (isNaN(number)) return;
        socket.emit('btn', {
            name: name,
            i: parseInt(number / 3),
            j: number % 3
        });
        clicked = true;
    } else {}
});

$("body").on('click', '#btnCancel', () => {
    if (confirm('are you sure?')) {
        socket.emit('disconnecting', 'by user');
        window.open('', '_self').close();
        window.close();
        close();
        $("#body").empty();
        $("#head").empty();
        $("head").append(`<link rel="stylesheet" href="css/index.css">`);
        $("#body").append(`<p class="text">Good Bye</p>`);
    }
});

socket.on('btnR', (r) => {
    if (r.r == true) {
        $("#" + r.num).html(r.p == 0 ? 'x' : 'o');
        $("#turn").html((r.p == player ? 'his/m' : 'you\'r') + " turn.");
    } else {
        alert("its his/m turn or select disable button.");
    }
    clicked = false;
})

socket.on('win', (data) => {
    $("#body").empty();
    $("#head").empty();
    $("head").append(`<link rel="stylesheet" href="css/index.css">`);
    if (player == data.winner) {
        $("#body").append(`<p class="text">YOU WON</p><p class="text">you'r rating is ${data.rating}</p>`);
    } else {
        $("#body").append(`<p class="text">GAME OVER</p>`);
    }
})

socket.on('equ', (data) => {
    $("#body").empty();
    $("#head").empty();
    $("head").append(`<link rel="stylesheet" href="css/index.css">`);
    $("#body").append(`<p class="text">YOU EQUATED</p><p class="text">you'r rating is ${data.players[player]} </p>`);
})

socket.on('dis', () => {
    $("#body").empty();
    $("#head").empty();
    $("head").append(`<link rel="stylesheet" href="css/index.css">`);
    $("#body").append(`<p class="text">DISCONNECTION</br>YOU WON</br>(but not saved in rating.)</p>`);
})
<|endoftext|>


<commit_before>import Container from '../mixin/container';
import Lazyload from '../mixin/lazyload';
import Position from '../mixin/position';
import Togglable from '../mixin/togglable';
import {
    addClass,
    append,
    apply,
    attr,
    css,
    hasClass,
    includes,
    isTouch,
    matches,
    MouseTracker,
    observeResize,
    offset,
    offsetViewport,
    on,
    once,
    parent,
    pointerCancel,
    pointerDown,
    pointerEnter,
    pointerLeave,
    pointerUp,
    query,
    removeClass,
    scrollParents,
    within,
} from 'uikit-util';
import { preventBackgroundScroll, preventOverscroll } from '../mixin/modal';

export let active;

export default {
    mixins: [Container, Lazyload, Position, Togglable],

    args: 'pos',

    props: {
        mode: 'list',
        toggle: Boolean,
        boundary: Boolean,
        target: Boolean,
        targetX: Boolean,
        targetY: Boolean,
        stretch: Boolean,
        delayShow: Number,
        delayHide: Number,
        display: String,
        clsDrop: String,
        animateOut: Boolean,
        bgScroll: Boolean,
    },

    data: {
        mode: ['click', 'hover'],
        toggle: '- *',
        boundary: false,
        target: false,
        targetX: false,
        targetY: false,
        stretch: false,
        delayShow: 0,
        delayHide: 800,
        display: null,
        clsDrop: false,
        animateOut: false,
        bgScroll: true,
        animation: ['uk-animation-fade'],
        cls: 'uk-open',
        container: false,
    },

    computed: {
        target({ target, targetX, targetY }, $el) {
            targetX = targetX || target || this.targetEl;
            targetY = targetY || target || this.targetEl;

            return [
                targetX === true ? window : query(targetX, $el),
                targetY === true ? window : query(targetY, $el),
            ];
        },
    },

    created() {
        this.tracker = new MouseTracker();
    },

    beforeConnect() {
        this.clsDrop = this.$props.clsDrop || `uk-${this.$options.name}`;
    },

    connected() {
        addClass(this.$el, this.clsDrop);

        if (this.toggle && !this.targetEl) {
            this.targetEl = this.$create('toggle', query(this.toggle, this.$el), {
                target: this.$el,
                mode: this.mode,
            }).$el;
            attr(this.targetEl, 'aria-haspopup', true);
            this.lazyload(this.targetEl);
        }
    },

    disconnected() {
        if (this.isActive()) {
            active = null;
        }
        this.targetEl = null;
    },

    events: [
        {
            name: 'click',

            delegate() {
                return `.${this.clsDrop}-close`;
            },

            handler(e) {
                e.preventDefault();
                this.hide(false);
            },
        },

        {
            name: 'click',

            delegate() {
                return 'a[href^="#"]';
            },

            handler({ defaultPrevented, current: { hash } }) {
                if (!defaultPrevented && hash && !within(hash, this.$el)) {
                    this.hide(false);
                }
            },
        },

        {
            name: 'beforescroll',

            handler() {
                this.hide(false);
            },
        },

        {
            name: 'toggle',

            self: true,

            handler(e, toggle) {
                e.preventDefault();

                if (this.isToggled()) {
                    this.hide(false);
                } else {
                    this.show(toggle?.$el, false);
                }
            },
        },

        {
            name: 'toggleshow',

            self: true,

            handler(e, toggle) {
                e.preventDefault();
                this.show(toggle?.$el);
            },
        },

        {
            name: 'togglehide',

            self: true,

            handler(e) {
                e.preventDefault();
                if (!matches(this.$el, ':focus,:hover')) {
                    this.hide();
                }
            },
        },

        {
            name: `${pointerEnter} focusin`,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (!isTouch(e)) {
                    this.clearTimers();
                }
            },
        },

        {
            name: `${pointerLeave} focusout`,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (!isTouch(e) && e.relatedTarget) {
                    this.hide();
                }
            },
        },

        {
            name: 'toggled',

            self: true,

            handler(e, toggled) {
                if (!toggled) {
                    return;
                }

                this.clearTimers();
                this.position();
            },
        },

        {
            name: 'show',

            self: true,

            handler() {
                active = this;

                this.tracker.init();

                for (const handler of [
                    on(
                        document,
                        pointerDown,
                        ({ target }) =>
                            !within(target, this.$el) &&
                            once(
                                document,
                                `${pointerUp} ${pointerCancel} scroll`,
                                ({ defaultPrevented, type, target: newTarget }) => {
                                    if (
                                        !defaultPrevented &&
                                        type === pointerUp &&
                                        target === newTarget &&
                                        !(this.targetEl && within(target, this.targetEl))
                                    ) {
                                        this.hide(false);
                                    }
                                },
                                true
                            )
                    ),

                    on(document, 'keydown', (e) => {
                        if (e.keyCode === 27) {
                            this.hide(false);
                        }
                    }),

                    ...(this.bgScroll
                        ? []
                        : [preventOverscroll(this.$el), preventBackgroundScroll()]),

                    ...(this.display === 'static'
                        ? []
                        : (() => {
                              const handler = () => this.$emit();
                              return [
                                  on(window, 'resize', handler),
                                  on(document, 'scroll', handler, true),
                                  (() => {
                                      const observer = observeResize(
                                          scrollParents(this.$el),
                                          handler
                                      );
                                      return () => observer.disconnect();
                                  })(),
                              ];
                          })()),
                ]) {
                    once(this.$el, 'hide', handler, { self: true });
                }
            },
        },

        {
            name: 'beforehide',

            self: true,

            handler() {
                this.clearTimers();
            },
        },

        {
            name: 'hide',

            handler({ target }) {
                if (this.$el !== target) {
                    active =
                        active === null && within(target, this.$el) && this.isToggled()
                            ? this
                            : active;
                    return;
                }

                active = this.isActive() ? null : active;
                this.tracker.cancel();
            },
        },
    ],

    update: {
        write() {
            if (this.isToggled() && !hasClass(this.$el, this.clsEnter)) {
                this.position();
            }
        },
    },

    methods: {
        show(target = this.targetEl, delay = true) {
            if (this.isToggled() && target && this.targetEl && target !== this.targetEl) {
                this.hide(false, false);
            }

            this.targetEl = target;

            this.clearTimers();

            if (this.isActive()) {
                return;
            }

            if (active) {
                if (delay && active.isDelaying) {
                    this.showTimer = setTimeout(() => matches(target, ':hover') && this.show(), 10);
                    return;
                }

                let prev;
                while (active && prev !== active && !within(this.$el, active.$el)) {
                    prev = active;
                    active.hide(false, false);
                }
            }

            if (this.container && parent(this.$el) !== this.container) {
                append(this.container, this.$el);
            }

            this.showTimer = setTimeout(
                () => this.toggleElement(this.$el, true),
                (delay && this.delayShow) || 0
            );
        },

        hide(delay = true, animate = true) {
            const hide = () => this.toggleElement(this.$el, false, this.animateOut && animate);

            this.clearTimers();

            this.isDelaying = getPositionedElements(this.$el).some((el) =>
                this.tracker.movesTo(el)
            );

            if (delay && this.isDelaying) {
                this.hideTimer = setTimeout(this.hide, 50);
            } else if (delay && this.delayHide) {
                this.hideTimer = setTimeout(hide, this.delayHide);
            } else {
                hide();
            }
        },

        clearTimers() {
            clearTimeout(this.showTimer);
            clearTimeout(this.hideTimer);
            this.showTimer = null;
            this.hideTimer = null;
            this.isDelaying = false;
        },

        isActive() {
            return active === this;
        },

        position() {
            removeClass(this.$el, `${this.clsDrop}-stack`);
            css(this.$el, { width: '', height: '', maxWidth: '', top: '', left: '' });

            // Ensure none positioned element does not generate scrollbars
            this.$el.hidden = true;

            const boundary = query(this.boundary, this.$el);
            const boundaryOffset = offsetViewport(boundary || window);
            const viewports = this.target.map((target) => offsetViewport(scrollParents(target)[0]));
            const viewportOffset = this.getViewportOffset(this.$el);

            const dirs = [
                [0, ['x', 'width', 'left', 'right']],
                [1, ['y', 'height', 'top', 'bottom']],
            ];

            for (const [i, [axis, prop]] of dirs) {
                if (this.axis !== axis && includes([axis, true], this.stretch)) {
                    css(
                        this.$el,
                        prop,
                        Math.min(boundaryOffset[prop], viewports[i][prop] - 2 * viewportOffset)
                    );
                }
            }

            const maxWidth = viewports[0].width - 2 * viewportOffset;

            if (this.$el.offsetWidth > maxWidth) {
                addClass(this.$el, `${this.clsDrop}-stack`);
            }

            css(this.$el, 'maxWidth', maxWidth);

            this.$el.hidden = false;

            this.positionAt(this.$el, this.target, boundary);

            for (const [i, [axis, prop, start, end]] of dirs) {
                if (this.axis === axis && includes([axis, true], this.stretch)) {
                    const positionOffset = Math.abs(this.getPositionOffset(this.$el));
                    const targetOffset = offset(this.target[i]);
                    const elOffset = offset(this.$el);

                    css(
                        this.$el,
                        prop,
                        (targetOffset[start] > elOffset[start]
                            ? targetOffset[start] -
                              Math.max(boundaryOffset[start], viewports[i][start] + viewportOffset)
                            : Math.min(boundaryOffset[end], viewports[i][end] - viewportOffset) -
                              targetOffset[end]) - positionOffset
                    );

                    this.positionAt(this.$el, this.target, boundary);
                }
            }
        },
    },
};

function getPositionedElements(el) {
    const result = [];
    apply(el, (el) => css(el, 'position') !== 'static' && result.push(el));
    return result;
}
<commit_msg>fix: listen for scroll events on scrollable ancestor elements only<commit_after>import Container from '../mixin/container';
import Lazyload from '../mixin/lazyload';
import Position from '../mixin/position';
import Togglable from '../mixin/togglable';
import {
    addClass,
    append,
    apply,
    attr,
    css,
    hasClass,
    includes,
    isTouch,
    matches,
    MouseTracker,
    observeResize,
    offset,
    offsetViewport,
    on,
    once,
    parent,
    pointerCancel,
    pointerDown,
    pointerEnter,
    pointerLeave,
    pointerUp,
    query,
    removeClass,
    scrollParents,
    within,
} from 'uikit-util';
import { preventBackgroundScroll, preventOverscroll } from '../mixin/modal';

export let active;

export default {
    mixins: [Container, Lazyload, Position, Togglable],

    args: 'pos',

    props: {
        mode: 'list',
        toggle: Boolean,
        boundary: Boolean,
        target: Boolean,
        targetX: Boolean,
        targetY: Boolean,
        stretch: Boolean,
        delayShow: Number,
        delayHide: Number,
        display: String,
        clsDrop: String,
        animateOut: Boolean,
        bgScroll: Boolean,
    },

    data: {
        mode: ['click', 'hover'],
        toggle: '- *',
        boundary: false,
        target: false,
        targetX: false,
        targetY: false,
        stretch: false,
        delayShow: 0,
        delayHide: 800,
        display: null,
        clsDrop: false,
        animateOut: false,
        bgScroll: true,
        animation: ['uk-animation-fade'],
        cls: 'uk-open',
        container: false,
    },

    computed: {
        target({ target, targetX, targetY }, $el) {
            targetX = targetX || target || this.targetEl;
            targetY = targetY || target || this.targetEl;

            return [
                targetX === true ? window : query(targetX, $el),
                targetY === true ? window : query(targetY, $el),
            ];
        },
    },

    created() {
        this.tracker = new MouseTracker();
    },

    beforeConnect() {
        this.clsDrop = this.$props.clsDrop || `uk-${this.$options.name}`;
    },

    connected() {
        addClass(this.$el, this.clsDrop);

        if (this.toggle && !this.targetEl) {
            this.targetEl = this.$create('toggle', query(this.toggle, this.$el), {
                target: this.$el,
                mode: this.mode,
            }).$el;
            attr(this.targetEl, 'aria-haspopup', true);
            this.lazyload(this.targetEl);
        }
    },

    disconnected() {
        if (this.isActive()) {
            active = null;
        }
        this.targetEl = null;
    },

    events: [
        {
            name: 'click',

            delegate() {
                return `.${this.clsDrop}-close`;
            },

            handler(e) {
                e.preventDefault();
                this.hide(false);
            },
        },

        {
            name: 'click',

            delegate() {
                return 'a[href^="#"]';
            },

            handler({ defaultPrevented, current: { hash } }) {
                if (!defaultPrevented && hash && !within(hash, this.$el)) {
                    this.hide(false);
                }
            },
        },

        {
            name: 'beforescroll',

            handler() {
                this.hide(false);
            },
        },

        {
            name: 'toggle',

            self: true,

            handler(e, toggle) {
                e.preventDefault();

                if (this.isToggled()) {
                    this.hide(false);
                } else {
                    this.show(toggle?.$el, false);
                }
            },
        },

        {
            name: 'toggleshow',

            self: true,

            handler(e, toggle) {
                e.preventDefault();
                this.show(toggle?.$el);
            },
        },

        {
            name: 'togglehide',

            self: true,

            handler(e) {
                e.preventDefault();
                if (!matches(this.$el, ':focus,:hover')) {
                    this.hide();
                }
            },
        },

        {
            name: `${pointerEnter} focusin`,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (!isTouch(e)) {
                    this.clearTimers();
                }
            },
        },

        {
            name: `${pointerLeave} focusout`,

            filter() {
                return includes(this.mode, 'hover');
            },

            handler(e) {
                if (!isTouch(e) && e.relatedTarget) {
                    this.hide();
                }
            },
        },

        {
            name: 'toggled',

            self: true,

            handler(e, toggled) {
                if (!toggled) {
                    return;
                }

                this.clearTimers();
                this.position();
            },
        },

        {
            name: 'show',

            self: true,

            handler() {
                active = this;

                this.tracker.init();

                for (const handler of [
                    on(
                        document,
                        pointerDown,
                        ({ target }) =>
                            !within(target, this.$el) &&
                            once(
                                document,
                                `${pointerUp} ${pointerCancel} scroll`,
                                ({ defaultPrevented, type, target: newTarget }) => {
                                    if (
                                        !defaultPrevented &&
                                        type === pointerUp &&
                                        target === newTarget &&
                                        !(this.targetEl && within(target, this.targetEl))
                                    ) {
                                        this.hide(false);
                                    }
                                },
                                true
                            )
                    ),

                    on(document, 'keydown', (e) => {
                        if (e.keyCode === 27) {
                            this.hide(false);
                        }
                    }),

                    ...(this.bgScroll
                        ? []
                        : [preventOverscroll(this.$el), preventBackgroundScroll()]),

                    ...(this.display === 'static'
                        ? []
                        : (() => {
                              const handler = () => this.$emit();
                              return [
                                  on(window, 'resize', handler),
                                  on([document, scrollParents(this.$el)], 'scroll', handler),
                                  (() => {
                                      const observer = observeResize(
                                          scrollParents(this.$el),
                                          handler
                                      );
                                      return () => observer.disconnect();
                                  })(),
                              ];
                          })()),
                ]) {
                    once(this.$el, 'hide', handler, { self: true });
                }
            },
        },

        {
            name: 'beforehide',

            self: true,

            handler() {
                this.clearTimers();
            },
        },

        {
            name: 'hide',

            handler({ target }) {
                if (this.$el !== target) {
                    active =
                        active === null && within(target, this.$el) && this.isToggled()
                            ? this
                            : active;
                    return;
                }

                active = this.isActive() ? null : active;
                this.tracker.cancel();
            },
        },
    ],

    update: {
        write() {
            if (this.isToggled() && !hasClass(this.$el, this.clsEnter)) {
                this.position();
            }
        },
    },

    methods: {
        show(target = this.targetEl, delay = true) {
            if (this.isToggled() && target && this.targetEl && target !== this.targetEl) {
                this.hide(false, false);
            }

            this.targetEl = target;

            this.clearTimers();

            if (this.isActive()) {
                return;
            }

            if (active) {
                if (delay && active.isDelaying) {
                    this.showTimer = setTimeout(() => matches(target, ':hover') && this.show(), 10);
                    return;
                }

                let prev;
                while (active && prev !== active && !within(this.$el, active.$el)) {
                    prev = active;
                    active.hide(false, false);
                }
            }

            if (this.container && parent(this.$el) !== this.container) {
                append(this.container, this.$el);
            }

            this.showTimer = setTimeout(
                () => this.toggleElement(this.$el, true),
                (delay && this.delayShow) || 0
            );
        },

        hide(delay = true, animate = true) {
            const hide = () => this.toggleElement(this.$el, false, this.animateOut && animate);

            this.clearTimers();

            this.isDelaying = getPositionedElements(this.$el).some((el) =>
                this.tracker.movesTo(el)
            );

            if (delay && this.isDelaying) {
                this.hideTimer = setTimeout(this.hide, 50);
            } else if (delay && this.delayHide) {
                this.hideTimer = setTimeout(hide, this.delayHide);
            } else {
                hide();
            }
        },

        clearTimers() {
            clearTimeout(this.showTimer);
            clearTimeout(this.hideTimer);
            this.showTimer = null;
            this.hideTimer = null;
            this.isDelaying = false;
        },

        isActive() {
            return active === this;
        },

        position() {
            removeClass(this.$el, `${this.clsDrop}-stack`);
            css(this.$el, { width: '', height: '', maxWidth: '', top: '', left: '' });

            // Ensure none positioned element does not generate scrollbars
            this.$el.hidden = true;

            const boundary = query(this.boundary, this.$el);
            const boundaryOffset = offsetViewport(boundary || window);
            const viewports = this.target.map((target) => offsetViewport(scrollParents(target)[0]));
            const viewportOffset = this.getViewportOffset(this.$el);

            const dirs = [
                [0, ['x', 'width', 'left', 'right']],
                [1, ['y', 'height', 'top', 'bottom']],
            ];

            for (const [i, [axis, prop]] of dirs) {
                if (this.axis !== axis && includes([axis, true], this.stretch)) {
                    css(
                        this.$el,
                        prop,
                        Math.min(boundaryOffset[prop], viewports[i][prop] - 2 * viewportOffset)
                    );
                }
            }

            const maxWidth = viewports[0].width - 2 * viewportOffset;

            if (this.$el.offsetWidth > maxWidth) {
                addClass(this.$el, `${this.clsDrop}-stack`);
            }

            css(this.$el, 'maxWidth', maxWidth);

            this.$el.hidden = false;

            this.positionAt(this.$el, this.target, boundary);

            for (const [i, [axis, prop, start, end]] of dirs) {
                if (this.axis === axis && includes([axis, true], this.stretch)) {
                    const positionOffset = Math.abs(this.getPositionOffset(this.$el));
                    const targetOffset = offset(this.target[i]);
                    const elOffset = offset(this.$el);

                    css(
                        this.$el,
                        prop,
                        (targetOffset[start] > elOffset[start]
                            ? targetOffset[start] -
                              Math.max(boundaryOffset[start], viewports[i][start] + viewportOffset)
                            : Math.min(boundaryOffset[end], viewports[i][end] - viewportOffset) -
                              targetOffset[end]) - positionOffset
                    );

                    this.positionAt(this.$el, this.target, boundary);
                }
            }
        },
    },
};

function getPositionedElements(el) {
    const result = [];
    apply(el, (el) => css(el, 'position') !== 'static' && result.push(el));
    return result;
}
<|endoftext|>


<commit_before>/* global TrelloPowerUp */

var WHITE_ICON = './images/icon-white.svg';
var GRAY_ICON = './images/icon-gray.svg';
var APP_ID = '380ed9c5-4e06-4b73-b2e0-115ab3810cb8';
var DEFAULT = "XXX";

var KEY_ACCESS_TOKEN = "accessToken";
var KEY_ENDPOINT_HINT= "endpointHint";
var KEY_REFRESH_TOKEN = "refreshToken";
var KEY_EXPIRES_IN = "expiresIn";

var parkMap = {
    acad: 'Acadia National Park',
    arch: 'Arches National Park',
    badl: 'Badlands National Park',
    brca: 'Bryce Canyon National Park',
    crla: 'Crater Lake National Park',
    dena: 'Denali National Park',
    glac: 'Glacier National Park',
    grca: 'Grand Canyon National Park',
    grte: 'Grand Teton National Park',
    olym: 'Olympic National Park',
    yell: 'Yellowstone National Park',
    yose: 'Yosemite National Park',
    zion: 'Zion National Park'
};

var getBadges = function (t) {
    return t.card('name')
        .get('name')
        .then(function (cardName) {
            var badgeColor;
            var icon = GRAY_ICON;
            var lowercaseName = cardName.toLowerCase();
            if (lowercaseName.indexOf('green') > -1) {
                badgeColor = 'green';
                icon = WHITE_ICON;
            } else if (lowercaseName.indexOf('yellow') > -1) {
                badgeColor = 'yellow';
                icon = WHITE_ICON;
            } else if (lowercaseName.indexOf('red') > -1) {
                badgeColor = 'red';
                icon = WHITE_ICON;
            }

            if (lowercaseName.indexOf('dynamic') > -1) {
                // dynamic badges can have their function rerun after a set number
                // of seconds defined by refresh. Minimum of 10 seconds.
                return [{
                    dynamic: function () {
                        return {
                            title: 'Detail Badge', // for detail badges only
                            text: 'Dynamic ' + (Math.random() * 100).toFixed(0).toString(),
                            icon: icon, // for card front badges only
                            color: badgeColor,
                            refresh: 10
                        }
                    }
                }]
            }

            if (lowercaseName.indexOf('static') > -1) {
                // return an array of badge objects
                return [{
                    title: 'Detail Badge', // for detail badges only
                    text: 'Static',
                    icon: icon, // for card front badges only
                    color: badgeColor
                }];
            } else {
                return [];
            }
        })
};

var formatNPSUrl = function (t, url) {
    if (!/^https?:\/\/www\.nps\.gov\/[a-z]{4}\//.test(url)) {
        return null;
    }
    var parkShort = /^https?:\/\/www\.nps\.gov\/([a-z]{4})\//.exec(url)[1];
    if (parkShort && parkMap[parkShort]) {
        return parkMap[parkShort];
    } else {
        return null;
    }
};

var boardButtonCallback = function (t) {
    /*return t.popup({
        title: 'Popup List Example',
        items: [
            {
                text: 'Open Overlay',
                callback: function (t) {
                    return t.overlay({
                        url: './overlay.html',
                        args: {rand: (Math.random() * 100).toFixed(0)}
                    })
                        .then(function () {
                            return t.closePopup();
                        });
                }
            },
            {
                text: 'Open Board Bar',
                callback: function (t) {
                    return t.boardBar({
                        url: './board-bar.html',
                        height: 200
                    })
                        .then(function () {
                            return t.closePopup();
                        });
                }
            }
        ]
    });*/

    return t.overlay({
        url: './javascript/editor/grapheditor/www/index.html',
        args: {rand: (Math.random() * 100).toFixed(0)}
    })
        .then(function () {
            return t.closePopup();
        });

};

var cardButtonCallback = function (t) {
    /*var items = Object.keys(parkMap).map(function (parkCode) {
        var urlForCode = 'http://www.nps.gov/' + parkCode + '/';
        return {
            text: parkMap[parkCode],
            url: urlForCode,
            callback: function (t) {
                return t.attach({url: urlForCode, name: parkMap[parkCode]})
                    .then(function () {
                        return t.closePopup();
                    })
            }
        };
    });

    return t.popup({
        title: 'Popup Search Example',
        items: items,
        search: {
            count: 5,
            placeholder: 'Search National Parks',
            empty: 'No parks found'
        }
    });*/

    return t.overlay({
        url: '../index.html',
        args: {rand: (Math.random() * 100).toFixed(0)}
    })
        .then(function () {
            return t.closePopup();
        });

};

TrelloPowerUp.initialize({

    'authorization-status': function(t) {

        /*return t.get('board', 'private', KEY_ACCESS_TOKEN, DEFAULT)
            .then(function (token) {
                console.log(token);

                if(token===DEFAULT) {

                    // if false
                    // `show-authorization` is displayed
                    return new TrelloPowerUp.Promise((resolve) =>
                        resolve({ authorized: false })
                    )
                }
                else {

                    // if true
                    // t.set has been used
                    // `remove settings` is displayed

                    return new TrelloPowerUp.Promise((resolve) =>
                        resolve({ authorized: true })
                    )

                }

            });*/

        return new TrelloPowerUp.Promise((resolve) =>
            resolve({ authorized: true })
        )

    },

    'show-authorization': function(t) {
        return t.popup({
            title: 'Authorize',
            items: [
                {
                    text: "Connect Microsoft or Office 365 Account",
                    callback: function () {
                        console.log("TODO settings auth");
                    }
                }
            ]
        });
    },

    'show-settings': function (t, options) {
        return t.popup({
            title: 'Settings',
            url: './settings.html',
            height: 184
        });
    },

    // Allows you to add one or more buttons on the right side of the back of cards
    // Each button should have { icon, text, callback -> method }
    'card-buttons': function (t, options) {
        return [{
            icon: GRAY_ICON,
            text: 'New Drawing',
            callback: cardButtonCallback
        }];
    },

    'attachment-sections': function (t, options) {

        //console.log(JSON.stringify(options));

        // options.entries is a list of the attachments for this card
        // you can look through them and 'claim' any that you want to
        // include in your section.

        // we will just claim urls for Yellowstone
        var claimed = options.entries.filter(drawFilter);

        // you can have more than one attachment section on a card
        // you can group items together into one section, have a section
        // per attachment, or anything in between.
        if (claimed && claimed.length > 0) {
            // if the title for your section requires a network call or other
            // potentially length operation you can provide a function for the title
            // that returns the section title. If you do so, provide a unique id for
            // your section

            //console.log(JSON.stringify(claimed));

            return [{
                id: BASE_URL, // optional if you aren't using a function for the title
                claimed: claimed,
                icon: GRAY_ICON,
                title: 'Attached Drawings',
                content: {
                    type: 'iframe',
                    url: t.signUrl('./section.html', {arg: claimed}),
                    height: 230
                }
            }];
        } else {
            return [];
        }
    },
    'attachment-thumbnail': function (t, attachment) {
        var name = formatDrawUrl(t, attachment.url);
        if (name) {
            // return an object with some or all of these properties:
            // url, title, image, openText, modified (Date), created (Date), createdBy, modifiedBy
            return {
                title: name,
                url: attachment.url,
                openText: "Open Externally",
                image: {
                    url: './images/icon-gray.svg',
                    logo: true // false if this is a thumbnail
                },
                initialize: {
                    type: 'iframe',
                    url: TrelloPowerUp.util.relativeUrl('./authorize.html')
                }
            };
        } else {
            throw t.NotHandled();
        }
    },

    // Allows you to add one or more buttons to a Board at the top right of the screen
    'board-buttons': function (t, options) {
        return [{
            icon: WHITE_ICON,
            text: 'Office 365',
            callback: boardButtonCallback
        }];
    },

    'card-badges': function (t, card) {
        console.log('card-badges');
        console.log(JSON.stringify(t));
        console.log(JSON.stringify(card));
        return getBadges(t);
    },

    'card-detail-badges': function (t, options) {
        return getBadges(t);
    },
    'card-from-url': function (t, options) {
        var parkName = formatNPSUrl(t, options.url);
        if (parkName) {
            return {
                name: parkName,
                desc: 'An awesome park: ' + options.url
            };
        } else {
            throw t.NotHandled();
        }
    },
    'format-url': function (t, options) {
        var name = formatDrawUrl(t, options.url);

        console.log("format-url> "+JSON.stringify(name));

        if (name) {
            return {
                icon: GRAY_ICON,
                text: name
            };
        } else {
            throw t.NotHandled();
        }
    }
});
<commit_msg>card badge<commit_after>/* global TrelloPowerUp */

var WHITE_ICON = './images/icon-white.svg';
var GRAY_ICON = './images/icon-gray.svg';
var APP_ID = '380ed9c5-4e06-4b73-b2e0-115ab3810cb8';
var DEFAULT = "XXX";

var KEY_ACCESS_TOKEN = "accessToken";
var KEY_ENDPOINT_HINT= "endpointHint";
var KEY_REFRESH_TOKEN = "refreshToken";
var KEY_EXPIRES_IN = "expiresIn";

var parkMap = {
    acad: 'Acadia National Park',
    arch: 'Arches National Park',
    badl: 'Badlands National Park',
    brca: 'Bryce Canyon National Park',
    crla: 'Crater Lake National Park',
    dena: 'Denali National Park',
    glac: 'Glacier National Park',
    grca: 'Grand Canyon National Park',
    grte: 'Grand Teton National Park',
    olym: 'Olympic National Park',
    yell: 'Yellowstone National Park',
    yose: 'Yosemite National Park',
    zion: 'Zion National Park'
};

var getBadges = function (t) {
    return t.card('name')
        .get('name')
        .then(function (cardName) {
            var badgeColor;
            var icon = GRAY_ICON;
            var lowercaseName = cardName.toLowerCase();
            if (lowercaseName.indexOf('green') > -1) {
                badgeColor = 'green';
                icon = WHITE_ICON;
            } else if (lowercaseName.indexOf('yellow') > -1) {
                badgeColor = 'yellow';
                icon = WHITE_ICON;
            } else if (lowercaseName.indexOf('red') > -1) {
                badgeColor = 'red';
                icon = WHITE_ICON;
            }

            if (lowercaseName.indexOf('dynamic') > -1) {
                // dynamic badges can have their function rerun after a set number
                // of seconds defined by refresh. Minimum of 10 seconds.
                return [{
                    dynamic: function () {
                        return {
                            title: 'Detail Badge', // for detail badges only
                            text: 'Dynamic ' + (Math.random() * 100).toFixed(0).toString(),
                            icon: icon, // for card front badges only
                            color: badgeColor,
                            refresh: 10
                        }
                    }
                }]
            }

            if (lowercaseName.indexOf('static') > -1) {
                // return an array of badge objects
                return [{
                    title: 'Detail Badge', // for detail badges only
                    text: 'Static',
                    icon: icon, // for card front badges only
                    color: badgeColor
                }];
            } else {
                return [];
            }
        })
};

var formatNPSUrl = function (t, url) {
    if (!/^https?:\/\/www\.nps\.gov\/[a-z]{4}\//.test(url)) {
        return null;
    }
    var parkShort = /^https?:\/\/www\.nps\.gov\/([a-z]{4})\//.exec(url)[1];
    if (parkShort && parkMap[parkShort]) {
        return parkMap[parkShort];
    } else {
        return null;
    }
};

var boardButtonCallback = function (t) {
    /*return t.popup({
        title: 'Popup List Example',
        items: [
            {
                text: 'Open Overlay',
                callback: function (t) {
                    return t.overlay({
                        url: './overlay.html',
                        args: {rand: (Math.random() * 100).toFixed(0)}
                    })
                        .then(function () {
                            return t.closePopup();
                        });
                }
            },
            {
                text: 'Open Board Bar',
                callback: function (t) {
                    return t.boardBar({
                        url: './board-bar.html',
                        height: 200
                    })
                        .then(function () {
                            return t.closePopup();
                        });
                }
            }
        ]
    });*/

    return t.overlay({
        url: './javascript/editor/grapheditor/www/index.html',
        args: {rand: (Math.random() * 100).toFixed(0)}
    })
        .then(function () {
            return t.closePopup();
        });

};

var cardButtonCallback = function (t) {
    /*var items = Object.keys(parkMap).map(function (parkCode) {
        var urlForCode = 'http://www.nps.gov/' + parkCode + '/';
        return {
            text: parkMap[parkCode],
            url: urlForCode,
            callback: function (t) {
                return t.attach({url: urlForCode, name: parkMap[parkCode]})
                    .then(function () {
                        return t.closePopup();
                    })
            }
        };
    });

    return t.popup({
        title: 'Popup Search Example',
        items: items,
        search: {
            count: 5,
            placeholder: 'Search National Parks',
            empty: 'No parks found'
        }
    });*/

    return t.overlay({
        url: '../index.html',
        args: {rand: (Math.random() * 100).toFixed(0)}
    })
        .then(function () {
            return t.closePopup();
        });

};

TrelloPowerUp.initialize({

    'authorization-status': function(t) {

        /*return t.get('board', 'private', KEY_ACCESS_TOKEN, DEFAULT)
            .then(function (token) {
                console.log(token);

                if(token===DEFAULT) {

                    // if false
                    // `show-authorization` is displayed
                    return new TrelloPowerUp.Promise((resolve) =>
                        resolve({ authorized: false })
                    )
                }
                else {

                    // if true
                    // t.set has been used
                    // `remove settings` is displayed

                    return new TrelloPowerUp.Promise((resolve) =>
                        resolve({ authorized: true })
                    )

                }

            });*/

        return new TrelloPowerUp.Promise((resolve) =>
            resolve({ authorized: true })
        )

    },

    'show-authorization': function(t) {
        return t.popup({
            title: 'Authorize',
            items: [
                {
                    text: "Connect Microsoft or Office 365 Account",
                    callback: function () {
                        console.log("TODO settings auth");
                    }
                }
            ]
        });
    },

    'show-settings': function (t, options) {
        return t.popup({
            title: 'Settings',
            url: './settings.html',
            height: 184
        });
    },

    // Allows you to add one or more buttons on the right side of the back of cards
    // Each button should have { icon, text, callback -> method }
    'card-buttons': function (t, options) {
        return [{
            icon: GRAY_ICON,
            text: 'New Drawing',
            callback: cardButtonCallback
        }];
    },

    'attachment-sections': function (t, options) {

        //console.log(JSON.stringify(options));

        // options.entries is a list of the attachments for this card
        // you can look through them and 'claim' any that you want to
        // include in your section.

        // we will just claim urls for Yellowstone
        var claimed = options.entries.filter(drawFilter);

        // you can have more than one attachment section on a card
        // you can group items together into one section, have a section
        // per attachment, or anything in between.
        if (claimed && claimed.length > 0) {
            // if the title for your section requires a network call or other
            // potentially length operation you can provide a function for the title
            // that returns the section title. If you do so, provide a unique id for
            // your section

            //console.log(JSON.stringify(claimed));

            return [{
                id: BASE_URL, // optional if you aren't using a function for the title
                claimed: claimed,
                icon: GRAY_ICON,
                title: 'Attached Drawings',
                content: {
                    type: 'iframe',
                    url: t.signUrl('./section.html', {arg: claimed}),
                    height: 230
                }
            }];
        } else {
            return [];
        }
    },
    'attachment-thumbnail': function (t, attachment) {
        var name = formatDrawUrl(t, attachment.url);
        if (name) {
            // return an object with some or all of these properties:
            // url, title, image, openText, modified (Date), created (Date), createdBy, modifiedBy
            return {
                title: name,
                url: attachment.url,
                openText: "Open Externally",
                image: {
                    url: './images/icon-gray.svg',
                    logo: true // false if this is a thumbnail
                },
                initialize: {
                    type: 'iframe',
                    url: TrelloPowerUp.util.relativeUrl('./authorize.html')
                }
            };
        } else {
            throw t.NotHandled();
        }
    },

    // Allows you to add one or more buttons to a Board at the top right of the screen
    'board-buttons': function (t, options) {
        return [{
            icon: WHITE_ICON,
            text: 'Office 365',
            callback: boardButtonCallback
        }];
    },

    'card-badges': function (t, card) {
        console.log('card-badges');
        //console.log(JSON.stringify(t));
        console.log(JSON.stringify(card));
        return getBadges(t);
    },

    'card-detail-badges': function (t, options) {
        return getBadges(t);
    },
    'card-from-url': function (t, options) {
        var parkName = formatNPSUrl(t, options.url);
        if (parkName) {
            return {
                name: parkName,
                desc: 'An awesome park: ' + options.url
            };
        } else {
            throw t.NotHandled();
        }
    },
    'format-url': function (t, options) {
        var name = formatDrawUrl(t, options.url);

        console.log("format-url> "+JSON.stringify(name));

        if (name) {
            return {
                icon: GRAY_ICON,
                text: name
            };
        } else {
            throw t.NotHandled();
        }
    }
});
<|endoftext|>


<commit_before><commit_msg>upd Russian translation<commit_after><|endoftext|>


<commit_before>import THREE from 'three.js';
import { randomPointOnSphere } from './math';

const size = 128;
const scale = () => THREE.Math.randFloat( 0.1, 0.5 );

const canvas = document.createElement( 'canvas' );
const ctx = canvas.getContext( '2d' );

canvas.width = size;
canvas.height = size;

/*
      ratio
      |----|
    +--------+
    | +----+ |
    | |    | |
    | +----+ |
    +--------+
 */
function drawShadowRect( ctx, color, ratio, blur ) {
  ctx.shadowColor = color;
  ctx.shadowBlur = blur * size;

  ctx.fillStyle = color;
  ctx.fillRect(
    size * ( 1 - ratio ) / 2,
    size * ( 1 - ratio ) / 2,
    size * ratio,
    size * ratio
  );
}

drawShadowRect( ctx, '#f85', 0.8, 0.2 );
drawShadowRect( ctx, '#fff', 0.4, 0.2 );

const texture = new THREE.Texture( canvas );
texture.needsUpdate = true;

const material = new THREE.SpriteMaterial({
  blending: THREE.AdditiveBlending,
  map: texture,
  rotation: Math.PI / 4,
  transparent: true,
  opacity: 0.9
});

export class ExplosionSprite extends THREE.Sprite {
  constructor() {
    super( material );
    this.reset();
  }

  reset()  {
    this.scale.setLength( scale() );
  }
}

const speed = 2;
const decay = 6;

export default class Explosion extends THREE.Group {
  constructor( radius = 0.2, count = 24 ) {
    super();

    this.radius = radius;
    this.velocities = [];

    let i = count;
    while ( i-- ) {
      this.add( new ExplosionSprite() );
      this.velocities.push( new THREE.Vector3() );
    }

    this.reset();
  }

  update( dt ) {
    const drag = Math.exp( -decay * dt );

    this.children.forEach(( sprite, index ) => {
      const velocity = this.velocities[ index ];
      sprite.position.addScaledVector( velocity, dt );
      velocity.multiplyScalar( drag );
      sprite.scale.multiplyScalar( drag );
    });
  }

  reset() {
    this.children.forEach( sprite => {
      randomPointOnSphere( sprite.position )
        .multiplyScalar( THREE.Math.randFloatSpread( this.radius ) );

      sprite.reset();
    });

    this.velocities.forEach( velocity => {
      randomPointOnSphere( velocity ).setLength( speed );
    });
  }
}
<commit_msg>Add ExplosionPool.<commit_after>import THREE from 'three.js';
import { randomPointOnSphere } from './math';

const size = 128;
const scale = () => THREE.Math.randFloat( 0.1, 0.5 );

const canvas = document.createElement( 'canvas' );
const ctx = canvas.getContext( '2d' );

canvas.width = size;
canvas.height = size;

/*
      ratio
      |----|
    +--------+
    | +----+ |
    | |    | |
    | +----+ |
    +--------+
 */
function drawShadowRect( ctx, color, ratio, blur ) {
  ctx.shadowColor = color;
  ctx.shadowBlur = blur * size;

  ctx.fillStyle = color;
  ctx.fillRect(
    size * ( 1 - ratio ) / 2,
    size * ( 1 - ratio ) / 2,
    size * ratio,
    size * ratio
  );
}

drawShadowRect( ctx, '#f85', 0.8, 0.2 );
drawShadowRect( ctx, '#fff', 0.4, 0.2 );

const texture = new THREE.Texture( canvas );
texture.needsUpdate = true;

const material = new THREE.SpriteMaterial({
  blending: THREE.AdditiveBlending,
  map: texture,
  rotation: Math.PI / 4,
  transparent: true,
  opacity: 0.9
});

export class ExplosionSprite extends THREE.Sprite {
  constructor() {
    super( material );
    this.reset();
  }

  reset()  {
    this.scale.setLength( scale() );
  }
}

const speed = 2;
const decay = 6;

export default class Explosion extends THREE.Group {
  constructor( radius = 0.2, count = 24 ) {
    super();

    this.radius = radius;
    this.velocities = [];

    let i = count;
    while ( i-- ) {
      this.add( new ExplosionSprite() );
      this.velocities.push( new THREE.Vector3() );
    }

    this.reset();
  }

  update( dt ) {
    const drag = Math.exp( -decay * dt );

    this.children.forEach(( sprite, index ) => {
      const velocity = this.velocities[ index ];
      sprite.position.addScaledVector( velocity, dt );
      velocity.multiplyScalar( drag );
      sprite.scale.multiplyScalar( drag );
    });
  }

  reset() {
    this.children.forEach( sprite => {
      randomPointOnSphere( sprite.position )
        .multiplyScalar( THREE.Math.randFloatSpread( this.radius ) );

      sprite.reset();
    });

    this.velocities.forEach( velocity => {
      randomPointOnSphere( velocity ).setLength( speed );
    });
  }

  magnitude() {
    return this.children.reduce(( magnitude, sprite ) => {
      return magnitude + sprite.scale.length();
    }, 0 );
  }
}

export class ExplosionPool extends THREE.Group {
  constructor( ...args ) {
    super( ...args );

    this.pool = [];
    this.limit = 1e-2;
  }

  get() {
    let explosion;

    if ( this.pool.length ) {
      explosion = this.pool.pop();
      explosion.reset();
    } else {
      explosion = new Explosion();
    }

    this.add( explosion );
    return explosion;
  }

  update() {
    const inactive = [];

    this.children.forEach( explosion => {
      if ( explosion.magnitude() < this.limit ) {
        inactive.push( explosion );
      }
    });

    inactive.forEach( explosion => explosion.parent.remove( explosion ) );
    this.pool.push( ...inactive );
  }
}
<|endoftext|>