// a fix for the rotation issue in v1.1

document.addEventListener("blur", function () {
    document.body.style.transform = "rotate(90deg)";
    setTimeout(function () {
        document.body.style.transform = "rotate(0deg)";
    }, 1000)
}, false);

document.addEventListener("focus", function () {
    document.body.style.transform = "rotate(0deg)";
}, false);

window.addEventListener("contextmenu", function (e) {
    e.preventDefault();
    e.stopPropagation();
    return false;
}, true);

var GLOBAL_ZOOM = matchMedia("(min-width: 960px)").matches ? 2 : 1;

(function () {
define('utils/Class',[], function () {

    /* Simple JavaScript Inheritance
     * By John Resig http://ejohn.org/
     * MIT Licensed.
     */
    // Inspired by base2 and Prototype

    var Class;

    (function () {
        var initializing = false;

        var fnTest = /var xyz/.test(function () {
            //noinspection JSUnusedLocalSymbols
            var xyz;
        }) ? /\b_super\b/ : /.*/;

        // The base Class implementation (does nothing)
        Class = function () {
        };

        // Create a new Class that inherits from this class
        Class.extend = function (prop) {
            var _super = this.prototype;

            // Instantiate a base class (but only create the instance,
            // don't run the init constructor)
            initializing = true;
            var prototype = new this();
            initializing = false;

            // Copy the properties over onto the new prototype
            for (var name in prop) {
                // Check if we're overwriting an existing function
                //noinspection JSUnfilteredForInLoop
                prototype[name] = typeof prop[name] == "function" &&
                    typeof _super[name] == "function" && fnTest.test(prop[name]) ?
                    (function (name, fn) {
                        return function () {
                            var tmp = this._super;

                            // Add a new ._super() method that is the same method
                            // but on the super-class
                            //noinspection JSUnfilteredForInLoop
                            this._super = _super[name];

                            // The method only need to be bound temporarily, so we
                            // remove it when we're done executing
                            var ret = fn.apply(this, arguments);
                            this._super = tmp;

                            return ret;
                        };
                    })(name, prop[name]) :
                    prop[name];
            }

            // The dummy class constructor
            /**
             * @constructor
             */
            function Class() {
                // All construction is actually done in the init method
                if (!initializing && this.init)
                    this.init.apply(this, arguments);
            }

            // Populate our constructed prototype object
            Class.prototype = prototype;

            // Enforce the constructor to be what we expect
            Class.prototype.constructor = Class;

            // And make this class extendable
            Class.extend = arguments.callee;

            return Class;
        };
    })();

    return Class;
});
define('core/RGBAColor',
    [],
    function () {
        /**
         * RGBAColor constructor
         * @constructor
         * @param r {number} red
         * @param g {number} green
         * @param b {number} blue
         * @param a {number} alpha
         */
        function RGBAColor(r, g, b, a) {
            /** type {number} */
            this.r = r;
            /** type {number} */
            this.g = g;
            /** type {number} */
            this.b = b;
            /** type {number} */
            this.a = a;
        }

        RGBAColor.prototype.rgbaStyle = function () {
            return 'rgba(' +
                ((this.r * 255) >> 0) + ',' +
                ((this.g * 255) >> 0) + ',' +
                ((this.b * 255) >> 0) + ',' +
                this.a.toFixed(2) + ')';
        };

        /**
         * @param other {RGBAColor}
         * @return {boolean}
         */
        RGBAColor.prototype.equals = function (other) {
            return this.r === other.r &&
                this.g === other.g &&
                this.b === other.b &&
                this.a === other.a;
        };

        RGBAColor.prototype.copy = function () {
            return new RGBAColor(this.r, this.g, this.b, this.a);
        };

        RGBAColor.prototype.copyFrom = function (source) {
            this.r = source.r;
            this.g = source.g;
            this.b = source.b;
            this.a = source.a;
        };

        RGBAColor.prototype.add = function (other) {
            this.r += other.r;
            this.g += other.g;
            this.b += other.b;
            this.a += other.a;
        };

        RGBAColor.prototype.multiply = function (s) {
            this.r *= s;
            this.g *= s;
            this.b *= s;
            this.a *= s;
        };

        /**
         * @type {RGBAColor}
         * @const
         */
        RGBAColor.transparent = new RGBAColor(0, 0, 0, 0);

        /**
         * @type {RGBAColor}
         * @const
         */
        RGBAColor.solidOpaque = new RGBAColor(1, 1, 1, 1);

        /**
         * @type {RGBAColor}
         * @const
         */
        RGBAColor.red = new RGBAColor(1, 0, 0, 1);

        /**
         * @type {RGBAColor}
         * @const
         */
        RGBAColor.blue = new RGBAColor(0, 0, 1, 1);

        /**
         * @type {RGBAColor}
         * @const
         */
        RGBAColor.green = new RGBAColor(0, 1, 0, 1);

        /**
         * @type {RGBAColor}
         * @const
         */
        RGBAColor.black = new RGBAColor(0, 0, 0, 1);

        /**
         * @type {RGBAColor}
         * @const
         */
        RGBAColor.white = RGBAColor.solidOpaque;

        /**
         * @enum {string}
         */
        RGBAColor.styles = {
            SOLID_OPAQUE: "rgba(255,255,255,1)",
            TRANSPARENT: "rgba(0,0,0,0)"
        };

        return RGBAColor;
    }
);
define('core/Alignment',
    [],
    function () {
        var Alignment = {
            /** @const
             *  @type {number}
             */
            UNDEFINED: 0,
            /** @const
             *  @type {number}
             */
            LEFT: 1,
            /** @const
             *  @type {number}
             */
            HCENTER: 2,
            /** @const
             *  @type {number}
             */
            RIGHT: 4,
            /** @const
             *  @type {number}
             */
            TOP: 8,
            /** @const
             *  @type {number}
             */
            VCENTER: 16,
            /** @const
             *  @type {number}
             */
            BOTTOM: 32,
            /** @const
             *  @type {number}
             */
            CENTER: 2 | 16,
            /**
             * @param s {string} input string
             * @return {number}
             */
            parse: function (s) {

                var a = this.UNDEFINED;
                if (s.indexOf("LEFT") > 0)
                    a = this.LEFT;
                else if (s.indexOf("HCENTER") > 0 || s === "CENTER")
                    a = this.HCENTER;
                else if (s.indexOf("RIGHT") > 0)
                    a = this.RIGHT;

                if (s.indexOf("TOP") > 0)
                    a |= this.TOP;
                else if (s.indexOf("VCENTER") > 0 || s === "CENTER")
                    a |= this.VCENTER;
                else if (s.indexOf("BOTTOM") > 0)
                    a |= this.BOTTOM;

                return a;
            }
        };

        return Alignment;
    }
);

define('core/Vector',
    [],
    function () {

        /**
         * Vector constructor
         * @constructor
         * @param x {number}
         * @param y {number}
         */
        function Vector(x, y) {
            this.x = x;
            this.y = y;
        }

        Vector.prototype.add = function (v2) {
            this.x += v2.x;
            this.y += v2.y;
        };

        Vector.prototype.subtract = function (v2) {
            this.x -= v2.x;
            this.y -= v2.y;
        };

        Vector.prototype.multiply = function (s) {
            this.x *= s;
            this.y *= s;
        };

        Vector.prototype.divide = function (s) {
            this.x /= s;
            this.y /= s;
        };

        Vector.prototype.distance = function (v2) {
            var tx = this.x - v2.x,
                ty = this.y - v2.y,
                dot = tx * tx + ty * ty;
            return Math.sqrt(dot);
        };

        Vector.prototype.getLength = function () {
            var dot = this.x * this.x + this.y * this.y;
            return Math.sqrt(dot);
        };

        /**
         * @param v2 {Vector}
         * @return {number}
         */
        Vector.prototype.getDot = function (v2) {
            return this.x * v2.x + this.y * v2.y;
        };

        /**
         * @return {boolean}
         */
        Vector.prototype.isZero = function () {
            return this.x === 0 && this.y === 0;
        };

        /**
         * @param v2 {Vector}
         * @return {boolean}
         */
        Vector.prototype.equals = function (v2) {
            return this.x === v2.x && this.y === v2.y;
        };

        Vector.prototype.setToZero = function () {
            this.x = 0;
            this.y = 0;
        };

        Vector.prototype.normalize = function () {
            this.multiply(1 / this.getLength());
        };

        /** @return {number} */
        Vector.prototype.angle = function () {
            return Math.atan(this.y / this.x);
        };

        /** @return {number} */
        Vector.prototype.normalizedAngle = function () {
            // Note: y goes first in Math.atan2()
            return Math.atan2(this.y, this.x);
        };

        /** @return {Vector} */
        Vector.prototype.copy = function () {
            return new Vector(this.x, this.y);
        };

        /**
         * Copies the values from another vector
         * @param v {Vector} source vector
         */
        Vector.prototype.copyFrom = function (v) {
            this.x = v.x;
            this.y = v.y;
        };

        Vector.prototype.round = function () {
            this.x = Math.round(this.x);
            //noinspection JSSuspiciousNameCombination
            this.y = Math.round(this.y);
        };

        Vector.prototype.rotate = function (rad) {
            //noinspection UnnecessaryLocalVariableJS
            var cosA = Math.cos(rad),
                sinA = Math.sin(rad),
                nx = this.x * cosA - this.y * sinA,
                ny = this.x * sinA + this.y * cosA;

            this.x = nx;
            this.y = ny;
        };

        Vector.prototype.rotateAround = function (rad, cx, cy) {
            // shift to the rotation point
            this.x -= cx;
            this.y -= cy;

            this.rotate(rad);

            // shift back to original location
            this.x += cx;
            this.y += cy;
        };

        /** @return {string} */
        Vector.prototype.toString = function () {
            return '[' + this.x + ', ' + this.y + ']';
        };

        /**
         *  Convenience method to create a new zero-based vector
         *  @return {Vector}
         */
        Vector.newZero = function () {
            return new Vector(0, 0);
        };
        Vector.zero = new Vector(0, 0);

        Vector.newUndefined = function () {
            return new Vector(0x7FFFFFFF, 0x7FFFFFFF);
        };
        Vector.undefined = Vector.newUndefined();

        // NOTE: we want to avoid creating new objects when possible so
        // we have member methods that modify vector instances. We also
        // have static methods below which return a new vector without
        // modifying any source vectors.
        /**
         * @param v1 {Vector}
         * @param v2 {Vector}
         * @return {Vector}
         */
        Vector.add = function (v1, v2) {
            return new Vector(v1.x + v2.x, v1.y + v2.y);
        };
        /**
         * @param v1 {Vector}
         * @param v2 {Vector}
         * @return {Vector}
         */
        Vector.subtract = function (v1, v2) {
            return new Vector(v1.x - v2.x, v1.y - v2.y);
        };
        /**
         * @param v {Vector}
         * @param s {number} scalar multiplier
         */
        Vector.multiply = function (v, s) {
            return new Vector(v.x * s, v.y * s);
        };
        /**
         * @param v {Vector} source vector
         * @param s {number} scalar divisor
         * @return {Vector}
         */
        Vector.divide = function (v, s) {
            return new Vector(v.x / s, v.y / s);
        };
        Vector.distance = function (x1, y1, x2, y2) {
            var tx = x1 - x2,
                ty = y1 - y2,
                dot = tx * tx + ty * ty;
            return Math.sqrt(dot);
        };
        /**
         * @param v {Vector}
         * @return {Vector}
         */
        Vector.perpendicular = function (v) {
            //noinspection JSSuspiciousNameCombination
            return new Vector(-v.y, v.x);
        };
        /**
         * @param v {Vector}
         * @return {Vector}
         */
        Vector.rPerpendicular = function (v) {
            //noinspection JSSuspiciousNameCombination
            return new Vector(v.y, -v.x);
        };
        /**
         * @param v {Vector}
         * @return {Vector}
         */
        Vector.normalize = function (v) {
            return this.multiply(v, 1 / v.getLength());
        };
        /**
         * @param v {Vector}
         * @return {Vector}
         */
        Vector.negate = function (v) {
            return new Vector(-v.x, -v.y);
        };


        // initialize temp arrays used in bezier calcs to avoid allocations
        Vector._tmpBezierX = new Array(64);
        Vector._tmpBezierY = new Array(64);

        Vector.calcPathBezier = function (points, delta) {
            var result = new Vector(0, 0);
            Vector.setCalcPathBezier(points, delta, result);
            return result;
        };

        /**
         * Calculates the bezier path vector
         * @param points {Array.<Vector>}
         * @param delta {number}
         * @param result {Vector}
         */
        Vector.setCalcPathBezier = function (points, delta, result) {
            var count = points.length;
            if (count <= 1) {
                result.x = result.y = 0;
                return;
            }

            var xs = Vector._tmpBezierX,
                ys = Vector._tmpBezierY,
                d1 = 1 - delta;

            for (var j = 0; j < count; j++) {
                var point = points[j];
                xs[j] = point.x;
                ys[j] = point.y;
            }

            var countMinusOne = count - 1;
            for (; countMinusOne > 0; count--, countMinusOne--) {
                var i = 0, iPlusOne = 1;
                for (; i < countMinusOne; i++, iPlusOne++) {
                    xs[i] = xs[i] * d1 + xs[iPlusOne] * delta;
                    ys[i] = ys[i] * d1 + ys[iPlusOne] * delta;
                }
            }
            result.x = xs[0];
            result.y = ys[0];
        };

        /**
         * @param angle {number}
         * @return {Vector}
         */
        Vector.forAngle = function (angle) {
            return new Vector(Math.cos(angle), Math.sin(angle));
        };

        return Vector;
    }
);
define('utils/Constants',
    ['core/Vector'],
    function (Vector) {

        /**
         * convertion rate between screen pixels and si system meters
         * @const
         * @type {number}
         */
        var PIXEL_TO_SI_METERS_K = 80;

        var Constants = {

            /**
             * @const
             * @type {number}
             */
            MAX_TOUCHES: 2,

            /**
             * @const
             * @type {number}
             */
            DIM_TIMEOUT: 0.15,

            /**
             * @const
             * @type {number}
             */
            UNDEFINED: -1,

            /**
             * Used in timelines to decide when we should stop caring (occurs
             * when very small amounts of time are left in the animation).
             * @const
             * @type {number}
             */
            FLOAT_PRECISION: 0.000001,

            /**
             * how many times the world moves slower
             * @const
             * @type {number}
             */
            TIME_SCALE: 1,

            /**
             * convertion rate between screen pixels and si system meters
             * @const
             * @type {number}
             */
            PIXEL_TO_SI_METERS_K: PIXEL_TO_SI_METERS_K,

            /**
             * @const
             * @type {number}
             */
            AIR_RESISTANCE: 0.15,

            /**
             * @const
             * @type {number}
             */
            MAX_FORCES: 10,

            /**
             * @const
             * @type {number}
             */
            CANDY2_FLAG: -2,


            /**
             * Max integer. Javascript actually supports larger ints (up to 2^53)
             * because all numbers are 64-bit floats. However, we want to use the
             * max 32 bit int because sometimes Javascript utilizes integer math
             * when possible under the covers, which is much faster.
             * @const
             * @type {number}
             */
            INT_MAX: 0x7FFFFFFF
        };

        return Constants;
    }
);



define('utils/Canvas',
    [ 'core/RGBAColor' ],
    function (RGBAColor) {

        var Canvas = {
            domReady: function (elementId) {
                this.setTarget(document.getElementById(elementId));
            },
            setTarget: function (element) {
                this.id = element;
                this.element = element;
                this.context = this.element.getContext('2d');
                this.setStyleColor(RGBAColor.white);
            },
            /**
             * Sets the fill and stroke styles
             * @param color {RGBAColor}
             */
            setStyleColor: function (color) {
                var rgba = color.rgbaStyle();
                this.context.fillStyle = rgba;
                this.context.strokeStyle = rgba;
                //console.log('Color changed to: ' + rgba);
            },
            setStyles: function (style) {
                this.context.fillStyle = style;
                this.context.strokeStyle = style;
            },
            /**
             * Fills a shape specified using a triangle strip array, ex:
             *   0 -- 2 -- 4 -- 6
             *   |    |    |    |
             *   1 -- 3 -- 5 -- 7
             * @param points
             * @param style
             */
            fillTriangleStrip: function (points, style) {
                var ctx = this.context,
                    point = points[0];
                ctx.fillStyle = style;
                ctx.beginPath();
                ctx.moveTo(point.x, point.y);

                // draw the bottom portion of the shape
                for (var i = 1, len = points.length; i < len; i += 2) {
                    point = points[i];
                    ctx.lineTo(point.x, point.y);
                }

                // draw the top portion
                for (i = points.length - 2; i >= 0; i -= 2) {
                    point = points[i];
                    ctx.lineTo(point.x, point.y);
                }

                ctx.fill();  // auto-closes path
            }

        };

        return Canvas;
    }
);
define('visual/ActionType',[], function () {
    /**
     * @enum {string}
     */
    var ActionType = {
        SET_VISIBLE: "ACTION_SET_VISIBLE",
        SET_TOUCHABLE: "ACTION_SET_TOUCHABLE",
        SET_UPDATEABLE: "ACTION_SET_UPDATEABLE",
        SET_DRAWQUAD: "ACTION_SET_DRAWQUAD",

        PLAY_TIMELINE: "ACTION_PLAY_TIMELINE",
        PAUSE_TIMELINE: "ACTION_PAUSE_TIMELINE",
        STOP_TIMELINE: "ACTION_STOP_TIMELINE",
        JUMP_TO_TIMELINE_FRAME: "ACTION_JUMP_TO_TIMELINE_FRAME"
    };

    return ActionType;
});
define('visual/Action',[], function () {

    function ActionData(name, param, subParam) {
        this.actionName = name;
        this.actionParam = param;
        this.actionSubParam = subParam;
    }

    function Action(target, data) {
        this.actionTarget = target;
        this.data = data;
    }

    Action.create = function (target, actionName, param, subParam) {
        var data = new ActionData(actionName, param, subParam);
        return new Action(target, data);
    };

    return Action;

});


define('visual/TrackType',[], function () {
    /**
     * @enum {number}
     */
    var TrackType = {
        POSITION: 0,
        SCALE: 1,
        ROTATION: 2,
        COLOR: 3,
        ACTION: 4,
        COUNT: 5
    };

    return TrackType;
});

define('visual/KeyFrame',
    [
        'core/Vector',
        'core/RGBAColor',
        'visual/Action',
        'visual/TrackType'
    ],
    function (Vector, RGBAColor, Action, TrackType) {

        function KeyFrameValue() {
            this.pos = Vector.newZero();
            this.scale = Vector.newZero();
            this.rotationAngle = 0;
            this.color = RGBAColor.solidOpaque.copy();
            this.actionSet = [];
        }

        KeyFrameValue.prototype.copy = function () {
            var clone = new KeyFrameValue();
            clone.pos = this.pos.copy();
            clone.scale = this.scale.copy();
            clone.rotationAngle = this.rotationAngle;
            clone.color = this.color.copy();

            // NOTE: this assumes actions are values (not object refs)
            clone.actionSet = this.actionSet.slice(0);
            return clone;
        };

        /**
         * KeyFrame constructor
         * @param time {number}
         * @param trackType {TrackType}
         * @param transitionType {KeyFrame.TransitionType}
         * @param value {KeyFrameValue}
         */
        function KeyFrame(time, trackType, transitionType, value) {
            this.timeOffset = time;
            this.trackType = trackType;
            this.transitionType = transitionType;
            this.value = value;
        }


        KeyFrame.prototype.copy = function () {
            return new KeyFrame(
                this.timeOffset,
                this.trackType,
                this.transitionType,
                this.value.copy());
        };

        /**
         * @enum {number}
         */
        KeyFrame.TransitionType = {
            LINEAR: 0,
            IMMEDIATE: 1,
            EASE_IN: 2,
            EASE_OUT: 3
        };

        /**
         * Creates an empty keyframe
         * @return {KeyFrame}
         */
        KeyFrame.newEmpty = function () {
            return new KeyFrame(
                0, // time
                TrackType.POSITION, // default track type
                KeyFrame.TransitionType.LINEAR, // default transition type
                new KeyFrameValue());
        };

        KeyFrame.makePos = function (x, y, transition, time) {
            var v = new KeyFrameValue();
            v.pos.x = x;
            v.pos.y = y;
            return new KeyFrame(time, TrackType.POSITION, transition, v);
        };

        KeyFrame.makeScale = function (x, y, transition, time) {
            var v = new KeyFrameValue();
            v.scale.x = x;
            v.scale.y = y;
            return new KeyFrame(time, TrackType.SCALE, transition, v);
        };

        KeyFrame.makeRotation = function (r, transition, time) {
            var v = new KeyFrameValue();
            v.rotationAngle = r;
            return new KeyFrame(time, TrackType.ROTATION, transition, v);
        };

        KeyFrame.makeColor = function (color, transition, time) {
            var v = new KeyFrameValue();
            v.color = color;
            return new KeyFrame(time, TrackType.COLOR, transition, v);
        };

        KeyFrame.makeAction = function (actions, time) {
            var v = new KeyFrameValue();
            v.actionSet = actions;
            return new KeyFrame(time, TrackType.ACTION, KeyFrame.TransitionType.LINEAR, v);
        };

        KeyFrame.makeSingleAction = function (target, actionName, actionParam, actionSubParam, time) {
            var v = new KeyFrameValue(),
                action = Action.create(target, actionName, actionParam, actionSubParam);
            v.actionSet = [ action ];
            return new KeyFrame(time, TrackType.ACTION, KeyFrame.TransitionType.LINEAR, v);
        };

        return KeyFrame;
    }
);

define('visual/TimelineTrack',
    [
        'utils/Class',
        'visual/KeyFrame',
        'visual/TrackType',
        'utils/Constants'
    ],
    function (Class, KeyFrame, TrackType, Constants) {
        /**
         * @enum {number}
         */
        var TrackState = {
            NOT_ACTIVE: 0,
            ACTIVE: 1
        };

        var TimelineTrack = Class.extend({
            init: function (timeline, trackType) {
                this.type = trackType;
                this.state = TrackState.NOT_ACTIVE;
                this.relative = false;

                this.startTime = 0;
                this.endTime = 0;

                this.keyFrames = [];

                this.t = timeline;

                this.nextKeyFrame = Constants.UNDEFINED;
                this.currentStepPerSecond = KeyFrame.newEmpty();
                this.currentStepAcceleration = KeyFrame.newEmpty();
                this.elementPrevState = KeyFrame.newEmpty();
                this.keyFrameTimeLeft = 0;
                this.overrun = 0;

                if (trackType === TrackType.ACTION) {
                    this.actionSets = [];
                }
            },
            deactivate: function () {
                this.state = TrackState.NOT_ACTIVE;
            },
            addKeyFrame: function (keyFrame) {
                this.setKeyFrame(keyFrame, this.keyFrames.length);
            },
            setKeyFrame: function (keyFrame, index) {
                this.keyFrames[index] = keyFrame;

                if (this.type === TrackType.ACTION) {
                    this.actionSets.push(keyFrame.value.actionSet);
                }
            },
            getFrameTime: function (frameIndex) {
                var total = 0;
                for (var i = 0; i <= frameIndex; i++) {
                    total += this.keyFrames[i].timeOffset;
                }
                return total;
            },
            updateRange: function () {
                this.startTime = this.getFrameTime(0);
                this.endTime = this.getFrameTime(this.keyFrames.length - 1);
            },
            updateActionTrack: function (delta) {
                if (this.state === TrackState.NOT_ACTIVE) {
                    if (!this.t.timelineDirReverse) {
                        if (!(this.t.time - delta > this.endTime || this.t.time < this.startTime)) {
                            if (this.keyFrames.length > 1) {
                                this.state = TrackState.ACTIVE;
                                this.nextKeyFrame = 0;
                                this.overrun = this.t.time - this.startTime;

                                this.nextKeyFrame++;
                                this.initActionKeyFrame(
                                    this.keyFrames[this.nextKeyFrame - 1],
                                    this.keyFrames[this.nextKeyFrame].timeOffset);
                            }
                            else {
                                this.initActionKeyFrame(this.keyFrames[0], 0);
                            }
                        }
                    }
                    else {
                        if (!(this.t.time + delta < this.startTime || this.t.time > this.endTime)) {
                            if (this.keyFrames.length > 1) {
                                this.state = TrackState.ACTIVE;
                                this.nextKeyFrame = this.keyFrames.length - 1;
                                this.overrun = this.endTime - this.t.time;
                                this.nextKeyFrame--;
                                this.initActionKeyFrame(
                                    this.keyFrames[this.nextKeyFrame + 1],
                                    this.keyFrames[this.nextKeyFrame].timeOffset);
                            }
                            else {
                                this.initActionKeyFrame(this.keyFrames[0], 0);
                            }
                        }
                    }
                    return;
                }

                this.keyFrameTimeLeft -= delta;

                // FLOAT_PRECISION is used to fix the situation when timeline
                // time >= timeline length but keyFrameTimeLeft is not <= 0
                if (this.keyFrameTimeLeft <= Constants.FLOAT_PRECISION) {
                    if (this.t.onKeyFrame) {
                        this.t.onKeyFrame(this.t, this.keyFrames[this.nextKeyFrame], this.nextKeyFrame);
                    }

                    this.overrun = -this.keyFrameTimeLeft;

                    if (this.nextKeyFrame === this.keyFrames.length - 1) {
                        this.setElementFromKeyFrame(this.keyFrames[this.nextKeyFrame]);
                        this.state = TrackState.NOT_ACTIVE;
                    }
                    else if (this.nextKeyFrame === 0) {
                        this.setElementFromKeyFrame(this.keyFrames[this.nextKeyFrame]);
                        this.state = TrackState.NOT_ACTIVE
                    }
                    else {
                        if (!this.t.timelineDirReverse) {
                            this.nextKeyFrame++;
                            this.initActionKeyFrame(
                                this.keyFrames[this.nextKeyFrame - 1],
                                this.keyFrames[this.nextKeyFrame].timeOffset);
                        }
                        else {
                            this.nextKeyFrame--;
                            var kf = this.keyFrames[this.nextKeyFrame + 1];
                            this.initActionKeyFrame(kf, kf.timeOffset);
                        }
                    }
                }
            },
            updateNonActionTrack: function (delta) {
                var t = this.t, kf;
                if (this.state === TrackState.NOT_ACTIVE) {
                    if (t.time >= this.startTime && t.time <= this.endTime) {
                        this.state = TrackState.ACTIVE;
                        if (!t.timelineDirReverse) {
                            this.nextKeyFrame = 0;
                            this.overrun = t.time - this.startTime;
                            this.nextKeyFrame++;
                            kf = this.keyFrames[this.nextKeyFrame];
                            this.initKeyFrameStepFrom(
                                this.keyFrames[this.nextKeyFrame - 1],
                                kf,
                                kf.timeOffset);
                        }
                        else {
                            this.nextKeyFrame = this.keyFrames.length - 1;
                            this.overrun = this.endTime - t.time;
                            this.nextKeyFrame--;
                            kf = this.keyFrames[this.nextKeyFrame + 1];
                            this.initKeyFrameStepFrom(
                                kf,
                                this.keyFrames[this.nextKeyFrame],
                                kf.timeOffset);
                        }
                    }
                    return;
                }

                this.keyFrameTimeLeft -= delta;
                kf = this.keyFrames[this.nextKeyFrame];
                if (kf.transitionType === KeyFrame.TransitionType.EASE_IN ||
                    kf.transitionType === KeyFrame.TransitionType.EASE_OUT) {
                    switch (this.type) {
                        case TrackType.POSITION:
                            var saPos = this.currentStepAcceleration.value.pos,
                                xPosDelta = saPos.x * delta,
                                yPosDelta = saPos.y * delta,
                                spsPos = this.currentStepPerSecond.value.pos,
                                oldPosX = spsPos.x,
                                oldPosY = spsPos.y;
                            spsPos.x += xPosDelta;
                            spsPos.y += yPosDelta;
                            t.element.x += (oldPosX + xPosDelta / 2) * delta;
                            t.element.y += (oldPosY + yPosDelta / 2) * delta;
                            break;
                        case TrackType.SCALE:
                            var saScale = this.currentStepAcceleration.value.scale,
                                xScaleDelta = saScale.x * delta,
                                yScaleDelta = saScale.y * delta,
                                spsScale = this.currentStepPerSecond.value.scale,
                                oldScaleX = spsScale.x,
                                oldScaleY = spsScale.y;
                            spsScale.x += xScaleDelta;
                            spsScale.y += yScaleDelta;
                            t.element.scaleX += (oldScaleX + xScaleDelta / 2) * delta;
                            t.element.scaleY += (oldScaleY + yScaleDelta / 2) * delta;
                            break;
                        case TrackType.ROTATION:
                            var rDelta = this.currentStepAcceleration.value.rotationAngle * delta,
                                oldRotationAngle = this.currentStepPerSecond.value.rotationAngle;
                            this.currentStepPerSecond.value.rotationAngle += rDelta;
                            t.element.rotation += (oldRotationAngle + rDelta / 2) * delta;
                            break;
                        case TrackType.COLOR:
                            var spsColor = this.currentStepPerSecond.value.color,
                                oldColorR = spsColor.r,
                                oldColorG = spsColor.g,
                                oldColorB = spsColor.b,
                                oldColorA = spsColor.a,
                                saColor = this.currentStepAcceleration.value.color,
                                deltaR = saColor.r * delta,
                                deltaG = saColor.g * delta,
                                deltaB = saColor.b * delta,
                                deltaA = saColor.a * delta;

                            // NOTE: it looks like there may be a bug in iOS? For now, we'll follow
                            // it by adding the delta twice
                            spsColor.r += (deltaR * 2);
                            spsColor.g += (deltaG * 2);
                            spsColor.b += (deltaB * 2);
                            spsColor.a += (deltaA * 2);

                            var elemColor = t.element.color;
                            elemColor.r += (oldColorR + deltaR / 2) * delta;
                            elemColor.g += (oldColorG + deltaG / 2) * delta;
                            elemColor.b += (oldColorB + deltaB / 2) * delta;
                            elemColor.a += (oldColorA + deltaA / 2) * delta;
                            break;
                        case TrackType.ACTION:
                            break;
                    }
                }
                else if (kf.transitionType === KeyFrame.TransitionType.LINEAR) {
                    var elem = t.element,
                        spsValue = this.currentStepPerSecond.value;
                    switch (this.type) {
                        case TrackType.POSITION:
                            elem.x += spsValue.pos.x * delta;
                            elem.y += spsValue.pos.y * delta;
                            break;
                        case TrackType.SCALE:
                            elem.scaleX += spsValue.scale.x * delta;
                            elem.scaleY += spsValue.scale.y * delta;
                            break;
                        case TrackType.ROTATION:
                            elem.rotationAngle += spsValue.rotationAngle * delta;
                            break;
                        case TrackType.COLOR:
                            elem.color.r += spsValue.color.r * delta;
                            elem.color.g += spsValue.color.g * delta;
                            elem.color.b += spsValue.color.b * delta;
                            elem.color.a += spsValue.color.a * delta;
                            break;
                        case TrackType.ACTION:
                            break;
                    }
                }

                if (this.keyFrameTimeLeft <= Constants.FLOAT_PRECISION) {
                    if (t.onKeyFrame) {
                        t.onKeyFrame(t, this.keyFrames[this.nextKeyFrame], this.nextKeyFrame);
                    }

                    this.overrun = -this.keyFrameTimeLeft;

                    if (this.nextKeyFrame === this.keyFrames.length - 1) {
                        this.setElementFromKeyFrame(this.keyFrames[this.nextKeyFrame]);
                        this.state = TrackState.NOT_ACTIVE;
                    }
                    else if (this.nextKeyFrame === 0) {
                        this.setElementFromKeyFrame(this.keyFrames[this.nextKeyFrame]);
                        this.state = TrackState.NOT_ACTIVE;
                    }
                    else {
                        if (!t.timelineDirReverse) {
                            this.nextKeyFrame++;
                            kf = this.keyFrames[this.nextKeyFrame];
                            this.initKeyFrameStepFrom(
                                this.keyFrames[this.nextKeyFrame - 1],
                                kf,
                                kf.timeOffset);
                        }
                        else {
                            this.nextKeyFrame--;
                            kf = this.keyFrames[this.nextKeyFrame + 1];
                            this.initKeyFrameStepFrom(
                                kf,
                                this.keyFrames[this.nextKeyFrame],
                                kf.timeOffset);
                        }
                    }
                }
            },
            initActionKeyFrame: function (kf, time) {
                this.keyFrameTimeLeft = time;
                this.setElementFromKeyFrame(kf);

                if (this.overrun > 0) {
                    this.updateActionTrack(this.overrun);
                    this.overrun = 0;
                }
            },
            /**
             * @param kf {KeyFrame}
             */
            setElementFromKeyFrame: function (kf) {
                switch (this.type) {
                    case TrackType.POSITION:
                        var elem = this.t.element,
                            kfPos = kf.value.pos;
                        if (!this.relative) {
                            elem.x = kfPos.x;
                            elem.y = kfPos.y;
                        }
                        else {
                            var prevPos = this.elementPrevState.value.pos;
                            elem.x = prevPos.x + kfPos.x;
                            elem.y = prevPos.y + kfPos.y;
                        }
                        break;
                    case TrackType.SCALE:
                        var kfScale = kf.value.scale;
                        elem = this.t.element;
                        if (!this.relative) {
                            elem.scaleX = kfScale.x;
                            elem.scaleY = kfScale.y;
                        }
                        else {
                            var prevScale = this.elementPrevState.value.scale;
                            elem.scaleX = prevScale.x + kfScale.x;
                            elem.scaleY = prevScale.y + kfScale.y;
                        }
                        break;
                    case TrackType.ROTATION:
                        if (!this.relative) {
                            this.t.element.rotation = kf.value.rotationAngle;
                        }
                        else {
                            this.t.element.rotation = this.elementPrevState.value.rotationAngle +
                                kf.value.rotationAngle;
                        }
                        break;
                    case TrackType.COLOR:
                        var elemColor = this.t.element.color,
                            kfColor = kf.value.color;
                        if (!this.relative) {
                            elemColor.copyFrom(kfColor);
                        }
                        else {
                            var prevColor = this.elementPrevState.value.color;
                            elemColor.r = prevColor.r + kfColor.r;
                            elemColor.g = prevColor.g + kfColor.g;
                            elemColor.b = prevColor.b + kfColor.b;
                            elemColor.a = prevColor.a + kfColor.a;
                        }
                        break;
                    case TrackType.ACTION:
                        var actionSet = kf.value.actionSet;
                        for (var i = 0, len = actionSet.length; i < len; i++) {
                            var action = actionSet[i];
                            action.actionTarget.handleAction(action.data);
                        }
                        break;
                }
            },
            setKeyFrameFromElement: function (kf) {
                var kfValue = kf.value,
                    elem = this.t.element;
                switch (this.type) {
                    case TrackType.POSITION:
                        kfValue.pos.x = elem.x;
                        kfValue.pos.y = elem.y;
                        break;
                    case TrackType.SCALE:
                        kfValue.scale.x = elem.scaleX;
                        kfValue.scale.y = elem.scaleY;
                        break;
                    case TrackType.ROTATION:
                        kfValue.rotationAngle = elem.rotation;
                        break;
                    case TrackType.COLOR:
                        kfValue.color.copyFrom(elem.color);
                        break;
                    case TrackType.ACTION:
                        break;
                }
            },
            initKeyFrameStepFrom: function (src, dst, time) {
                this.keyFrameTimeLeft = time;

                this.setKeyFrameFromElement(this.elementPrevState);
                this.setElementFromKeyFrame(src);

                var spsValue = this.currentStepPerSecond.value,
                    saValue = this.currentStepAcceleration.value;
                switch (this.type) {
                    case TrackType.POSITION:
                        var spsPos = spsValue.pos,
                            dstPos = dst.value.pos,
                            srcPos = src.value.pos;
                        spsPos.x = (dstPos.x - srcPos.x) / this.keyFrameTimeLeft;
                        spsPos.y = (dstPos.y - srcPos.y) / this.keyFrameTimeLeft;
                        break;
                    case TrackType.SCALE:
                        var spsScale = spsValue.scale,
                            dstScale = dst.value.scale,
                            srcScale = src.value.scale;
                        spsScale.x = (dstScale.x - srcScale.x) / this.keyFrameTimeLeft;
                        spsScale.y = (dstScale.y - srcScale.y) / this.keyFrameTimeLeft;
                        break;
                    case TrackType.ROTATION:
                        spsValue.rotationAngle = (dst.value.rotationAngle - src.value.rotationAngle) / this.keyFrameTimeLeft;
                        break;
                    case TrackType.COLOR:
                        var spsColor = spsValue.color,
                            dstColor = dst.value.color,
                            srcColor = src.value.color;
                        spsColor.r = (dstColor.r - srcColor.r) / this.keyFrameTimeLeft;
                        spsColor.g = (dstColor.g - srcColor.g) / this.keyFrameTimeLeft;
                        spsColor.b = (dstColor.b - srcColor.b) / this.keyFrameTimeLeft;
                        spsColor.a = (dstColor.a - srcColor.a) / this.keyFrameTimeLeft;
                        break;
                    case TrackType.ACTION:
                        break;
                }

                var isEaseIn = (dst.transitionType === KeyFrame.TransitionType.EASE_IN),
                    isEaseOut = (dst.transitionType == KeyFrame.TransitionType.EASE_OUT);
                if (isEaseIn || isEaseOut) {
                    switch (this.type) {
                        case TrackType.POSITION:
                            spsPos = spsValue.pos;
                            var saPos = saValue.pos;
                            spsPos.multiply(2);
                            saPos.x = spsPos.x / this.keyFrameTimeLeft;
                            saPos.y = spsPos.y / this.keyFrameTimeLeft;
                            if (isEaseIn) {
                                spsPos.x = 0;
                                spsPos.y = 0;
                            }
                            else {
                                saPos.multiply(-1);
                            }
                            break;
                        case TrackType.SCALE:
                            spsScale = spsValue.scale;
                            var saScale = saValue.scale;
                            spsScale.multiply(2);
                            saScale.x = spsScale.x / this.keyFrameTimeLeft;
                            saScale.y = spsScale.y / this.keyFrameTimeLeft;
                            if (isEaseIn) {
                                spsScale.x = 0;
                                spsScale.y = 0;
                            }
                            else {
                                saScale.multiply(-1);
                            }
                            break;
                        case TrackType.ROTATION:
                            spsValue.rotationAngle *= 2;
                            saValue.rotationAngle = spsValue.rotationAngle / this.keyFrameTimeLeft;
                            if (isEaseIn) {
                                spsValue.rotationAngle = 0;
                            }
                            else {
                                saValue.rotationAngle *= -1;
                            }
                            break;
                        case TrackType.COLOR:
                            spsColor = spsValue.color;
                            var saColor = saValue.color;
                            spsColor.multiply(2);
                            saColor.r = spsColor.r / this.keyFrameTimeLeft;
                            saColor.g = spsColor.g / this.keyFrameTimeLeft;
                            saColor.b = spsColor.b / this.keyFrameTimeLeft;
                            saColor.a = spsColor.a / this.keyFrameTimeLeft;
                            if (isEaseIn) {
                                spsColor.multiply(0);
                            }
                            else {
                                saColor.multiply(-1);
                            }

                            break;
                        case TrackType.ACTION:
                            break;
                    }
                }

                if (this.overrun > 0) {
                    this.updateNonActionTrack(this.overrun);
                    this.overrun = 0;
                }
            }
        });

        return TimelineTrack;
    }
);

define('visual/Timeline',
    [
        'utils/Class',
        'visual/TimelineTrack',
        'visual/TrackType',
        'utils/Constants'
    ],
    function (Class, TimelineTrack, TrackType, Constants) {

        var Timeline = Class.extend({
            init: function () {
                this.time = 0;
                this.length = 0;
                this.loopsLimit = Constants.UNDEFINED;
                this.state = Timeline.StateType.STOPPED;
                this.loopType = Timeline.LoopType.NO_LOOP;
                this.tracks = [];

                // callback fired when the timeline finishes playing
                this.onFinished = null;

                // callback fired when the timeline reaches a key frame
                this.onKeyFrame = null;

                this.timelineDirReverse = false;

                this.element = null;
            },
            addKeyFrame: function (keyFrame) {
                var track = this.tracks[keyFrame.trackType],
                    index = (track == null) ? 0 : track.keyFrames.length;
                this.setKeyFrame(keyFrame, index);
            },
            setKeyFrame: function (keyFrame, index) {
                var track = this.tracks[keyFrame.trackType];
                if (!track) {
                    this.tracks[keyFrame.trackType] = track =
                        new TimelineTrack(this, keyFrame.trackType);
                }
                track.setKeyFrame(keyFrame, index);
            },
            getTrack: function (index) {
                return this.tracks[index];
            },
            play: function () {
                if (this.state !== Timeline.StateType.PAUSED) {
                    this.time = 0;
                    this.timelineDirReverse = false;
                    this.length = 0;

                    for (var i = 0, len = this.tracks.length; i < len; i++) {
                        var track = this.tracks[i];
                        if (track) {
                            track.updateRange();
                            if (track.endTime > this.length) {
                                this.length = track.endTime;
                            }
                        }
                    }
                    this.update(0);
                }
                this.state = Timeline.StateType.PLAYING;
                
            },
            pause: function () {
                this.state = Timeline.StateType.PAUSED;
            },
            jumpToTrack: function (trackIndex, keyFrame) {
                if (this.state === Timeline.StateType.STOPPED) {
                    this.state = Timeline.StateType.PAUSED;
                }
                var delta = this.tracks[trackIndex].getFrameTime(keyFrame) - this.time;
                this.update(delta);
            },
            stop: function () {
                this.state = Timeline.StateType.STOPPED;
                this.deactivateTracks();
            },
            deactivateTracks: function () {
                for (var i = 0, len = this.tracks.length; i < len; i++) {
                    var track = this.tracks[i];
                    if (track) {
                        track.deactivate();
                    }
                }
            },
            update: function (delta) {
                if (this.state !== Timeline.StateType.PLAYING)
                    return;

                if (!this.timelineDirReverse)
                    this.time += delta;
                else
                    this.time -= delta;

                for (var i = 0, len = this.tracks.length; i < len; i++) {
                    var track = this.tracks[i];
                    if (track != null) {
                        if (track.type === TrackType.ACTION)
                            track.updateActionTrack(delta);
                        else
                            track.updateNonActionTrack(delta);
                    }
                }

                if (this.loopType === Timeline.LoopType.PING_PONG) {
                    var reachedEnd = this.timelineDirReverse === false &&
                        this.time >= this.length - Constants.FLOAT_PRECISION;
                    if (reachedEnd) {
                        this.time = Math.max(0, this.length - (this.time - this.length));
                        this.timelineDirReverse = true;
                    }
                    else {
                        var reachedStart = this.timelineDirReverse && this.time <= Constants.FLOAT_PRECISION;
                        if (reachedStart) {
                            if (this.loopsLimit > 0) {
                                this.loopsLimit--;
                                if (this.loopsLimit === 0) {
                                    this.stop();
                                    if (this.onFinished) {
                                        this.onFinished(this);
                                    }
                                }
                            }

                            this.time = Math.min(-this.time, this.length);
                            this.timelineDirReverse = false;
                        }
                    }
                }
                else if (this.loopType === Timeline.LoopType.REPLAY) {
                    if (this.time >= this.length - Constants.FLOAT_PRECISION) {
                        if (this.loopsLimit > 0) {
                            this.loopsLimit--;
                            if (this.loopsLimit === 0) {
                                this.stop();
                                if (this.onFinished) {
                                    this.onFinished(this);
                                }
                            }
                        }

                        this.time = Math.min(this.time - this.length, this.length);
                    }
                }
                else if (this.loopType === Timeline.LoopType.NO_LOOP) {
                    if (this.time >= this.length - Constants.FLOAT_PRECISION) {
                        this.stop();
                        if (this.onFinished) {
                            this.onFinished(this);
                        }
                    }
                }
            }
        });


        /**
         * @enum {number}
         */
        Timeline.LoopType = {
            NO_LOOP: 0,
            REPLAY: 1,
            PING_PONG: 2
        };

        /**
         * @enum {number}
         */
        Timeline.StateType = {
            STOPPED: 0,
            PLAYING: 1,
            PAUSED: 2
        };

        return Timeline;
    }
);
define('utils/Radians',
    [],
    function () {
        /**
         * Helper class for dealing with radians
         */
        var Radians = {
            /**
             * @const
             * @type {number}
             */
            degrees360: 6.283185307179586, // Math.PI * 2
            /**
             * Converts degrees to radians
             * @param degrees {number}
             * @return {number}
             */
            fromDegrees: function (degrees) {
                return degrees * 0.017453292519943295; // degrees * (Math.PI / 180)
            },
            /**
             * Converts radians to degrees
             * @param radians {number}
             * @return {number}
             */
            toDegrees: function (radians) {
                return radians * 57.29577951308232; // radians * 180 / Math.PI
            }
        };

        return Radians;
    }
);

define('visual/BaseElement',
    [
        'utils/Class',
        'core/RGBAColor',
        'core/Alignment',
        'utils/Constants',
        'utils/Canvas',
        'visual/ActionType',
        'visual/Timeline',
        'utils/Radians'
    ],
    function (Class, RGBAColor, Alignment, Constants, Canvas, ActionType, Timeline, Radians) {

        var BaseElement = Class.extend({
            init: function () {
                /** @type {BaseElement} */
                this.parent = null;

                /** @type {boolean} */
                this.visible = true;
                /** @type {boolean} */
                this.touchable = true;
                /** @type {boolean} */
                this.updateable = true;

                /** @type {string} */
                this.name = null;

                /** @type {number} */
                this.x = 0;
                /** @type {number} */
                this.y = 0;

                // absolute coords of top left corner
                /** @type {number} */
                this.drawX = 0;
                /** @type {number} */
                this.drawY = 0;

                /** @type {number} */
                this.width = 0;
                /** @type {number} */
                this.height = 0;

                /** @type {number} */
                this.rotation = 0;

                // rotation center offset from the element center
                /** @type {number} */
                this.rotationCenterX = 0;
                /** @type {number} */
                this.rotationCenterY = 0;

                // use scaleX = -1 for horizontal flip, scaleY = -1 for vertical
                /** @type {number} */
                this.scaleX = 1;
                /** @type {number} */
                this.scaleY = 1;

                /** type {RGBAColor} */
                this.color = RGBAColor.solidOpaque.copy();

                /** type {number} */
                this.translateX = 0;
                this.translateY = 0;

                /**
                 * Sets the anchor on the element
                 *  type {number}
                 */
                this.anchor = Alignment.TOP | Alignment.LEFT;
                /** type {number} */
                this.parentAnchor = Alignment.UNDEFINED;

                /** type {bool} children will inherit transformations of the parent */
                this.passTransformationsToChilds = true;

                /** type {boolean} children will inherit color of the parent */
                this.passColorToChilds = true;

                /** type {boolean} touch events can be handled by multiple children */
                this.passTouchEventsToAllChilds = false;

                /**
                 * @protected
                 */
                this.children = [];

                /**
                 * @protected
                 */
                this.timelines = [];

                /**
                 * @private
                 * @type {number}
                 */
                this.currentTimelineIndex = Constants.UNDEFINED;

                /**
                 * @private
                 * @type {Timeline}
                 */
                this.currentTimeline = null;
            },
            /**
             * @private
             */
            calculateTopLeft: function () {
                var parentAnchor = this.parentAnchor,
                    parent = this.parent,
                    anchor = this.anchor;

                // align to parent
                if (parentAnchor !== 0 /*Alignment.UNDEFINED*/) {
                    // calculate the x offset first
                    if (parentAnchor & 1 /*Alignment.LEFT*/)
                        this.drawX = parent.drawX + this.x;
                    else if (parentAnchor & 2 /*Alignment.HCENTER*/)
                        this.drawX = parent.drawX + this.x + (parent.width / 2);
                    else if (parentAnchor & 4 /*Alignment.RIGHT*/)
                        this.drawX = parent.drawX + this.x + parent.width;

                    // now calculate y offset
                    if (parentAnchor & 8 /*Alignment.TOP*/)
                        this.drawY = parent.drawY + this.y;
                    else if (parentAnchor & 16 /*Alignment.VCENTER*/)
                        this.drawY = parent.drawY + this.y + (parent.height / 2);
                    else if (parentAnchor & 32 /*Alignment.BOTTOM*/)
                        this.drawY = parent.drawY + this.y + parent.height;
                }
                else {
                    this.drawX = this.x;
                    this.drawY = this.y;
                }

                // align self anchor
                if (!(anchor & 8 /*Alignment.TOP*/)) {
                    if (anchor & 16 /*Alignment.VCENTER*/)
                        this.drawY -= (this.height / 2);
                    else if (anchor & 32 /*Alignment.BOTTOM*/)
                        this.drawY -= this.height;
                }

                if (!(anchor & 1 /*Alignment.LEFT*/)) {
                    if (anchor & 2 /*Alignment.HCENTER*/)
                        this.drawX -= (this.width / 2);
                    else if (anchor & 4 /*Alignment.RIGHT*/)
                        this.drawX -= this.width;
                }
            },
            preDraw: function () {
                this.calculateTopLeft();

                var changeScale = (this.scaleX !== 0) && (this.scaleY !== 0) &&
                        ((this.scaleX !== 1) || (this.scaleY !== 1)),
                    changeRotation = (this.rotation !== 0),
                    changeTranslate = ((this.translateX !== 0) || (this.translateY !== 0)),
                    ctx = Canvas.context;

                // save existing canvas state first and then reset
                ctx.save();

                // apply transformations
                if (changeScale || changeRotation) {
                    var rotationOffsetX = ~~(this.drawX + (this.width / 2) + this.rotationCenterX),
                        rotationOffsetY = ~~(this.drawY + (this.height / 2) + this.rotationCenterY),
                        translatedRotation = (rotationOffsetX !== 0) || (rotationOffsetY !== 0);

                    // move to the right position in the canvas before changes
                    if (translatedRotation) {
                        ctx.translate(rotationOffsetX, rotationOffsetY);
                    }

                    if (changeRotation) {
                        ctx.rotate(Radians.fromDegrees(this.rotation));
                    }
                    if (changeScale) {
                        ctx.scale(this.scaleX, this.scaleY);
                    }

                    // move back to previous position
                    if (translatedRotation) {
                        ctx.translate(-rotationOffsetX, -rotationOffsetY);
                    }
                }

                if (changeTranslate) {
                    ctx.translate(this.translateX, this.translateY);
                }

                // change the alpha
                this.previousAlpha = ctx.globalAlpha;
                if (this.color.a !== 1 && this.color.a !== this.previousAlpha) {
                    ctx.globalAlpha = this.color.a;
                }
            },
            draw: function () {
                this.preDraw();
                this.postDraw();
            },
            drawBB: function () {
                var ctx = Canvas.context;
                ctx.strokeStyle = 'red';
                ctx.strokeRect(this.drawX, this.drawY, this.width, this.height);
            },
            postDraw: function () {
                var ctx = Canvas.context,
                    alphaChanged = (this.color.a !== 1 && this.color.a !== this.previousAlpha);

                // for debugging, draw vector from the origin towards 0 degrees
                if (this.drawZeroDegreesLine) {
                    var originX = this.drawX + (this.width >> 1) + this.rotationCenterX,
                        originY = this.drawY + (this.height >> 1) + this.rotationCenterY;

                    ctx.save();
                    ctx.lineWidth = 5;
                    ctx.strokeStyle = "#ff0000"; // red line
                    ctx.beginPath();
                    ctx.moveTo(originX, originY);
                    ctx.lineTo(originX, originY - 100);
                    ctx.closePath();
                    ctx.stroke();
                    ctx.restore();
                }

                if (!this.passTransformationsToChilds) {

                    if (this.isDrawBB) {
                        this.drawBB();
                    }

                    ctx.restore();

                    if (this.passColorToChilds) {
                        // canvas state includes alpha so we have to set it again
                        if (alphaChanged) {
                            Canvas.context.globalAlpha = this.color.a;
                        }
                    }
                }
                else if (!this.passColorToChilds) {
                    if (alphaChanged) {
                        Canvas.context.globalAlpha = this.previousAlpha;
                    }
                }

                // draw children
                var children = this.children,
                    numChildren = children.length;
                for (var i = 0; i < numChildren; i++) {
                    var child = children[i];
                    if (child.visible)
                        child.draw();
                }

                if (this.passTransformationsToChilds) {

                    if (this.isDrawBB) {
                        this.drawBB();
                    }

                    ctx.restore();
                }
                else if (this.passColorToChilds) {
                    if (alphaChanged) {
                        Canvas.context.globalAlpha = this.previousAlpha;
                    }
                }
            },
            /**
             * Updates timelines with the elapsed time
             * @param delta {number}
             */
            update: function (delta) {
                var children = this.children,
                    numChildren = children.length;
                for (var i = 0; i < numChildren; i++) {
                    var child = children[i];
                    if (child.updateable)
                        child.update(delta);
                }

                if (this.currentTimeline) {
                    this.currentTimeline.update(delta);
                }
            },
            /**
             * @param name {string}
             * @return {BaseElement}
             */
            getChildWithName: function (name) {
                var children = this.children,
                    numChildren = children.length;
                for (var i = 0; i < numChildren; i++) {
                    var child = children[i];
                    if (child.name === name)
                        return child;

                    var descendant = child.getChildWithName(name);
                    if (descendant !== null)
                        return descendant;
                }

                return null;
            },
            setSizeToChildsBounds: function () {
                this.calculateTopLeft();

                var minX = this.drawX,
                    minY = this.drawY,
                    maxX = this.drawX + this.width,
                    maxY = this.drawY + this.height,
                    children = this.children,
                    numChildren = children.length;

                for (var i = 0; i < numChildren; i++) {
                    var child = children[i];
                    child.calculateTopLeft();

                    if (child.drawX < minX)
                        minX = child.drawX;
                    if (child.drawY < minY)
                        minY = child.drawY;

                    var childMaxX = child.drawX + child.width,
                        childMaxY = child.drawY + child.height;
                    if (childMaxX > maxX)
                        maxX = childMaxX;
                    if (childMaxY > maxY)
                        maxY = childMaxY;
                }

                this.width = maxX - minX;
                this.height = maxY - minY;
            },
            /**
             * @param a {ActionData} action data
             * @return {boolean} true if an action was handled
             */
            handleAction: function (a) {
                switch (a.actionName) {
                    case ActionType.SET_VISIBLE:
                        this.visible = (a.actionSubParam !== 0);
                        break;
                    case ActionType.SET_UPDATEABLE:
                        this.updateable = (a.actionSubParam !== 0);
                        break;
                    case ActionType.SET_TOUCHABLE:
                        this.touchable = (a.actionSubParam !== 0);
                        break;
                    case ActionType.PLAY_TIMELINE:
                        this.playTimeline(a.actionSubParam);
                        break;
                    case ActionType.PAUSE_TIMELINE:
                        this.pauseCurrentTimeline();
                        break;
                    case ActionType.STOP_TIMELINE:
                        this.stopCurrentTimeline();
                        break;
                    case ActionType.JUMP_TO_TIMELINE_FRAME:
                        var timeline = this.currentTimeline;
                        timeline.jumpToTrack(a.actionParam, a.actionSubParam);
                        break;
                    default:
                        return false;
                }

                return true;
            },
            /**
             * @param child {BaseElement} child to add
             * @return {number} index of added child
             */
            addChild: function (child) {
                this.children.push(child);
                child.parent = this;
                return this.children.length - 1;
            },
            addChildWithID: function (child, index) {
                this.children[index] = child;
                child.parent = this;
            },
            /**
             * @param i {number} index of the child to remove
             */
            removeChildWithID: function (i) {
                var child = this.children.splice(i, 1);
                child.parent = null;
            },
            removeAllChildren: function () {
                this.children.length = 0;
            },
            /**
             * @param c {BaseElement} child to remove
             */
            removeChild: function (c) {
                var children = this.children,
                    numChildren = children.length;
                for (var i = 0; i < numChildren; i++) {
                    var child = children[i];
                    if (c === child) {
                        this.removeChildWithID(i);
                        return;
                    }
                }
            },
            /**
             * @param i {number} index of child
             * @return {BaseElement}
             */
            getChild: function (i) {
                return this.children[i];
            },
            /**
             * @return {number} number of children
             */
            childCount: function () {
                return this.children.length;
            },
            /**
             * @return {Array.<BaseElement>} children
             */
            getChildren: function () {
                return this.children;
            },
            addTimeline: function (timeline) {
                var index = this.timelines.length;
                this.addTimelineWithID(timeline, index);
                return index;
            },
            addTimelineWithID: function (timeline, index) {
                timeline.element = this;
                this.timelines[index] = timeline;
            },
            removeTimeline: function (index) {
                if (this.currentTimelineIndex === index)
                    this.stopCurrentTimeline();

                if (index < this.timelines.length) {
                    this.timelines.splice(index, 1);
                }
            },
            playTimeline: function (index) {
                if (this.currentTimeline) {
                    if (this.currentTimeline.state !== Timeline.StateType.STOPPED) {
                        this.currentTimeline.stop();
                    }
                }
                this.currentTimelineIndex = index;
                this.currentTimeline = this.timelines[index];
                this.currentTimeline.play();
            },
            pauseCurrentTimeline: function () {
                this.currentTimeline.pause();
            },
            stopCurrentTimeline: function () {
                this.currentTimeline.stop();
                this.currentTimeline = null;
                this.currentTimelineIndex = Constants.UNDEFINED;
            },
            /**
             * @param index {number}
             * @return {Timeline}
             */
            getTimeline: function (index) {
                return this.timelines[index];
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            onTouchDown: function (x, y) {
                var ret = false,
                    count = this.children.length;
                for (var i = count - 1; i >= 0; i--) {
                    var child = this.children[i];
                    if (child && child.touchable) {
                        if (child.onTouchDown(x, y) && ret === false) {
                            ret = true;
                            if (!this.passTouchEventsToAllChilds) {
                                return ret;
                            }
                        }
                    }
                }
                return ret;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            onTouchUp: function (x, y) {
                var ret = false,
                    count = this.children.length;
                for (var i = count - 1; i >= 0; i--) {
                    var child = this.children[i];
                    if (child && child.touchable) {
                        if (child.onTouchUp(x, y) && ret === false) {
                            ret = true;
                            if (!this.passTouchEventsToAllChilds) {
                                return ret;
                            }
                        }
                    }
                }
                return ret;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            onTouchMove: function (x, y) {
                var ret = false,
                    count = this.children.length;
                for (var i = count - 1; i >= 0; i--) {
                    var child = this.children[i];
                    if (child && child.touchable) {
                        if (child.onTouchMove(x, y) && ret === false) {
                            ret = true;
                            if (!this.passTouchEventsToAllChilds) {
                                return ret;
                            }
                        }
                    }
                }
                return ret;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            onDoubleClick: function (x, y) {
                var ret = false,
                    count = this.children.length;
                for (var i = count - 1; i >= 0; i--) {
                    var child = this.children[i];
                    if (child && child.touchable) {
                        if (child.onDoubleClick(x, y) && ret === false) {
                            ret = true;
                            if (!this.passTouchEventsToAllChilds) {
                                return ret;
                            }
                        }
                    }
                }
                return ret;
            },
            /**
             * @param enabled {boolean}
             */
            setEnabled: function (enabled) {
                this.visible = enabled;
                this.touchable = enabled;
                this.updateable = enabled;
            },
            /**
             * @return {boolean}
             */
            isEnabled: function () {
                return (this.visible && this.touchable && this.updateable);
            },
            show: function () {
                var children = this.children,
                    numChildren = children.length;
                for (var i = 0; i < numChildren; i++) {
                    var child = children[i];
                    if (child.visible)
                        child.show();
                }
            },
            hide: function () {
                var children = this.children,
                    numChildren = children.length;
                for (var i = 0; i < numChildren; i++) {
                    var child = children[i];
                    if (child.visible)
                        child.hide();
                }
            }
        });

        return BaseElement;
    }
);

define('core/Rectangle',
    [
        'core/Vector'
    ],
    function (Vector) {

        /**
         * Rectangle constructor
         * @constructor
         * @param x {number}
         * @param y {number}
         * @param w {number} width
         * @param h {number} height
         */
        function Rectangle(x, y, w, h) {
            this.x = x;
            this.y = y;
            this.w = w;
            this.h = h;
        }

        Rectangle.copy = function (r) {
            return new Rectangle(r.x, r.y, r.w, r.h);
        };

        Rectangle.scaleCopy = function (r, scale) {
            return new Rectangle(r.x * scale, r.y * scale, r.w * scale, r.h * scale);
        };

        /**
         * Returns true if rectangles overlap (used in collision detection)
         * @param x1l {number}
         * @param y1t {number}
         * @param x1r {number}
         * @param y1b {number}
         * @param x2l {number}
         * @param y2t {number}
         * @param x2r {number}
         * @param y2b {number}
         * @return {boolean}
         */
        Rectangle.rectInRect = function (x1l, y1t, x1r, y1b, x2l, y2t, x2r, y2b) {
            return !(x1l > x2r || x1r < x2l || y1t > y2b || y1b < y2t);
        };

        /**
         * get intersection rectangle, it's 0,0 is in the r1 top left corner
         *
         * // first rectangle
         * @param r1x {number}
         * @param r1y {number}
         * @param r1w {number}
         * @param r1h {number}
         *
         * // second rectangle
         * @param r2x {number}
         * @param r2y {number}
         * @param r2w {number}
         * @param r2h {number}
         *
         * @return {Rectangle}
         */
        Rectangle.rectInRectIntersection = function (r1x, r1y, r1w, r1h, r2x, r2y, r2w, r2h) {
            var res = new Rectangle(r2x - r1x, r2y - r1y, r2w, r2h);

            if (res.x < 0) {
                res.w += res.x;
                res.x = 0;
            }
            if (res.x + res.w > r1w) {
                res.w = r1w - res.x;
            }
            if (res.y < 0) {
                res.h += res.y;
                res.y = 0;
            }
            if (res.y + res.h > r1h) {
                res.h = r1h - res.y;
            }

            return res;
        };

        Rectangle.pointInRect = function (x, y, checkX, checkY, checkWidth, checkHeight) {
            return (x >= checkX &&
                x < checkX + checkWidth &&
                y >= checkY &&
                y < checkY + checkHeight);
        };


        /**
         * @const
         * @type {number}
         */
        var COHEN_LEFT = 1;

        /**
         * @const
         * @type {number}
         */
        var COHEN_RIGHT = 2;

        /**
         * @const
         * @type {number}
         */
        var COHEN_BOT = 4;
        /**
         * @const
         * @type {number}
         */
        var COHEN_TOP = 8;

        /**
         * @param x_min {number}
         * @param y_min {number}
         * @param x_max {number}
         * @param y_max {number}
         * @param p {Vector}
         * @return {number}
         */
        function vcode(x_min, y_min, x_max, y_max, p) {
            return ((p.x < x_min) ? COHEN_LEFT : 0) +
                ((p.x > x_max) ? COHEN_RIGHT : 0) +
                ((p.y < y_min) ? COHEN_BOT : 0) +
                ((p.y > y_max) ? COHEN_TOP : 0);
        }

        /**
         * Cohen-Sutherland algorithm from russian wikipedia
         * @param x1 {number}
         * @param y1 {number}
         * @param x2 {number}
         * @param y2 {number}
         * @param rx {number}
         * @param ry {number}
         * @param w {number}
         * @param h {number}
         * @return {boolean}
         */
        Rectangle.lineInRect = function (x1, y1, x2, y2, rx, ry, w, h) {
            var code_a, code_b, code;
            var a = new Vector(x1, y1),
                b = new Vector(x2, y2),
                c;

            //noinspection UnnecessaryLocalVariableJS
            var x_min = rx,
                y_min = ry,
                x_max = rx + w,
                y_max = ry + h;

            code_a = vcode(x_min, y_min, x_max, y_max, a);
            code_b = vcode(x_min, y_min, x_max, y_max, b);

            while (code_a || code_b) {
                if (code_a & code_b) {
                    return false;
                }

                if (code_a) {
                    code = code_a;
                    c = a;
                }
                else {
                    code = code_b;
                    c = b;
                }

                if (code & COHEN_LEFT) {
                    c.y += (y1 - y2) * (x_min - c.x) / (x1 - x2);
                    c.x = x_min;
                } else if (code & COHEN_RIGHT) {
                    c.y += (y1 - y2) * (x_max - c.x) / (x1 - x2);
                    c.x = x_max;
                }

                if (code & COHEN_BOT) {
                    c.x += (x1 - x2) * (y_min - c.y) / (y1 - y2);
                    c.y = y_min;
                }
                else if (code & COHEN_TOP) {
                    c.x += (x1 - x2) * (y_max - c.y) / (y1 - y2);
                    c.y = y_max;
                }

                if (code == code_a) {
                    code_a = vcode(x_min, y_min, x_max, y_max, a);
                }
                else {
                    code_b = vcode(x_min, y_min, x_max, y_max, b);
                }
            }

            //release from pool
            
            

            return true;
        };

        return Rectangle;
    }
);

define('visual/ImageMultiDrawer',
    [
        'visual/BaseElement',
        'utils/Canvas',
        'utils/Constants',
        'core/Rectangle'
    ],
    function (BaseElement, Canvas, Constants, Rectangle) {
        /**
         * Holds the information necessary to draw multiple quads from a
         * shared source image texture
         */
        var ImageMultiDrawer = BaseElement.extend({
            init: function (texture) {
                this._super();

                this.texture = texture;
                this.numberOfQuadsToDraw = Constants.UNDEFINED;

                // holds the position in the texture that should be drawn
                this.texCoordinates = [];

                // holds the position on the canvas to render the texture quad
                this.vertices = [];

                // hold the alpha for each quad (if null then we assume alpha=1)
                this.alphas = [];

                // NOTE: in OpenGL its possible to draw multiple quads at once. In
                // canvas we'll just draw them sequentially (no need for indices buffer)
            },
            setTextureQuad: function (index, textureQuad, vertexQuad, alpha) {
                this.texCoordinates[index] = textureQuad;
                this.vertices[index] = vertexQuad;
                this.alphas[index] = (alpha != null) ? alpha : 1;
            },
            removeQuads: function (index) {
                this.texCoordinates.splice(index, 1);
                this.vertices.splice(index, 1);
                this.alphas.splice(index, 1);
            },
            mapTextureQuad: function (quadIndex, dx, dy, index) {
                this.texCoordinates[index] = Rectangle.copy(this.texture.rects[quadIndex]);

                var offset = this.texture.offsets[quadIndex],
                    rect = this.texture.rects[quadIndex];
                this.vertices[index] = new Rectangle(
                    dx + offset.x, dy + offset.y, rect.w, rect.h);
                this.alphas[index] = 1;
            },
            drawNumberOfQuads: function (n) {
                if (n > this.texCoordinates.length) {
                    n = this.texCoordinates.length;
                }

                //console.log("DRAW NO OF QUADS", n)
                var ctx = Canvas.context;
                for (var i = 0; i < n; i++) {
                    var source = this.texCoordinates[i],
                        dest = this.vertices[i],
                        alpha = this.alphas[i],
                        previousAlpha = ctx.globalAlpha,
                        sourceW = Math.ceil(source.w),
                        sourceH = Math.ceil(source.h);

                    // verify we need to draw the source
                    if (sourceW === 0 || sourceH === 0) {
                        continue;
                    }

                    // change the alpha if necessary
                    if (alpha == null) {
                        // if alpha was not specified, we assume full opacity
                        alpha = 1;
                    }
                    else if (alpha <= 0) {
                        // no need to draw invisible images
                        continue;
                    }
                    else if (alpha < 1) {
                        ctx.globalAlpha = alpha;
                    }

                    // rotate the image if requested
                    var checkRotation = (this.rotationAngles && this.rotationAngles.length > i);
                    if (checkRotation) {
                        var rotationAngle = this.rotationAngles[i],
                            rotationPosition = this.rotationPositions[i],
                            rotateIsTranslated = (rotationPosition.x !== 0 || rotationPosition.y !== 0);

                        if (rotationAngle !== 0) {
                            if (rotateIsTranslated) {
                                ctx.translate(rotationPosition.x, rotationPosition.y);
                            }
                            ctx.rotate(rotationAngle);
                            if (rotateIsTranslated) {
                                ctx.translate(-rotationPosition.x, -rotationPosition.y);
                            }
                        }
                    }

                    // see if we need sub-pixel alignment
                    var qx, qy, qw, qh;
                    // if (this.drawPosIncrement) {
                    //     qx = Math.round(dest.x / this.drawPosIncrement) * this.drawPosIncrement;
                    //     qy = Math.round(dest.y / this.drawPosIncrement) * this.drawPosIncrement;
                    //     qw = Math.round(dest.w / this.drawPosIncrement) * this.drawPosIncrement;
                    //     qh = Math.round(dest.h / this.drawPosIncrement) * this.drawPosIncrement;
                    // }
                    // else {
                        // otherwise by default we snap to pixel boundaries for perf
                        qx = ~~(dest.x);
                        qy = ~~(dest.y);

                        // use ceil so that we match the source when scale is equal
                        qw = 1 + ~~(dest.w);
                        qh = 1 + ~~(dest.h);
                    //}

                    
                    ctx.drawImage(
                        this.texture.image,
                        source.x, source.y, sourceW, sourceH, // source coordinates
                        qx, qy, qw, qh);        // destination coordinates

                    // undo the rotation
                    if (checkRotation && rotationAngle !== 0) {
                        if (rotateIsTranslated) {
                            ctx.translate(rotationPosition.x, rotationPosition.y);
                        }
                        ctx.rotate(-rotationAngle);
                        if (rotateIsTranslated) {
                            ctx.translate(-rotationPosition.x, -rotationPosition.y);
                        }
                    }

                    // undo alpha changes
                    if (alpha !== 1) {
                        ctx.globalAlpha = previousAlpha;
                    }
                }
            },
            draw: function () {
                this.preDraw();

                // only draw if the image is non-transparent
                if (this.color.a !== 0) {
                    var ctx = Canvas.context,
                        shouldTranslate = ((this.drawX !== 0) || (this.drawY !== 0));

                    if (shouldTranslate) {
                        ctx.translate(this.drawX, this.drawY);
                    }

                    var count = (this.numberOfQuadsToDraw === Constants.UNDEFINED)
                        ? this.texCoordinates.length
                        : this.numberOfQuadsToDraw;
                    this.drawNumberOfQuads(count);

                    if (shouldTranslate) {
                        ctx.translate(-this.drawX, -this.drawY);
                    }
                }

                this.postDraw();
            }
        });

        return ImageMultiDrawer;
    }
);
define('resources/ResourceId',
    [],
    function () {

        /**
         * The resource id corresponds to the index of the entry in the RES_DATA array
         * @enum {number}
         */
        var ResourceId = {
            IMG_DEFAULT: 0,
            IMG_LOADERBAR_FULL: 1,
            IMG_CHILLINGO: 2,
            IMG_MENU_BUTTON_DEFAULT: 3,
            FNT_BIG_FONT: 4,
            FNT_SMALL_FONT: 5,
            IMG_MENU_LOADING: 6,
            SND_TAP: 7,
            STR_MENU: 8,
            SND_BUTTON: 9,
            SND_BUBBLE_BREAK: 10,
            SND_BUBBLE: 11,
            SND_CANDY_BREAK: 12,
            SND_MONSTER_CHEWING: 13,
            SND_MONSTER_CLOSE: 14,
            SND_MONSTER_OPEN: 15,
            SND_MONSTER_SAD: 16,
            SND_RING: 17,
            SND_ROPE_BLEAK_1: 18,
            SND_ROPE_BLEAK_2: 19,
            SND_ROPE_BLEAK_3: 20,
            SND_ROPE_BLEAK_4: 21,
            SND_ROPE_GET: 22,
            SND_STAR_1: 23,
            SND_STAR_2: 24,
            SND_STAR_3: 25,
            SND_ELECTRIC: 26,
            SND_PUMP_1: 27,
            SND_PUMP_2: 28,
            SND_PUMP_3: 29,
            SND_PUMP_4: 30,
            SND_SPIDER_ACTIVATE: 31,
            SND_SPIDER_FALL: 32,
            SND_SPIDER_WIN: 33,
            SND_WHEEL: 34,
            SND_WIN: 35,
            SND_GRAVITY_OFF: 36,
            SND_GRAVITY_ON: 37,
            SND_CANDY_LINK: 38,
            SND_BOUNCER: 39,
            IMG_MENU_BGR: 40,
            IMG_MENU_BUTTON_CRYSTAL: 41,
            IMG_MENU_POPUP: 42,
            IMG_MENU_BUTTON_CRYSTAL_ICON: 43,
            IMG_MENU_LOGO: 44,
            IMG_MENU_LEVEL_SELECTION: 45,
            IMG_MENU_PACK_SELECTION: 46,
            IMG_MENU_EXTRA_BUTTONS: 47,
            IMG_MENU_EXTRA_BUTTONS_EN: 48,
            IMG_MENU_BUTTON_SHORT: 49,
            IMG_HUD_BUTTONS: 50,
            IMG_OBJ_CANDY_01: 51,
            IMG_OBJ_SPIDER: 52,
            IMG_CONFETTI_PARTICLES: 53,
            IMG_MENU_PAUSE: 54,
            IMG_MENU_RESULT: 55,
            FNT_FONT_NUMBERS_BIG: 56,
            IMG_HUD_BUTTONS_EN: 57,
            IMG_MENU_RESULT_EN: 58,
            IMG_OBJ_STAR_DISAPPEAR: 59,
            IMG_OBJ_BUBBLE_FLIGHT: 60,
            IMG_OBJ_BUBBLE_POP: 61,
            IMG_OBJ_HOOK_AUTO: 62,
            IMG_OBJ_SPIKES_04: 63,
            IMG_OBJ_BUBBLE_ATTACHED: 64,
            IMG_OBJ_HOOK_01: 65,
            IMG_OBJ_HOOK_02: 66,
            IMG_OBJ_STAR_IDLE: 67,
            IMG_HUD_STAR: 68,
            IMG_OBJ_SPIKES_03: 69,
            IMG_OBJ_SPIKES_02: 70,
            IMG_OBJ_SPIKES_01: 71,
            IMG_CHAR_ANIMATIONS: 72,
            IMG_OBJ_HOOK_REGULATED: 73,
            IMG_OBJ_ELECTRODES: 74,
            IMG_OBJ_HOOK_MOVABLE: 75,
            IMG_OBJ_PUMP: 76,
            IMG_TUTORIAL_SIGNS: 77,
            IMG_OBJ_SOCKS: 78,
            IMG_OBJ_BOUNCER_01: 79,
            IMG_OBJ_BOUNCER_02: 80,
            IMG_OBJ_VINIL: 81,
            SND_SCRATCH_IN: 82,
            SND_SCRATCH_OUT: 83,
            SND_BUZZ: 84,
            SND_TELEPORT: 85,

            SND_MENU_MUSIC: 105,
            SND_GAME_MUSIC: 106,
            SND_GAME_MUSIC2: 107,

            IMG_DRAWING_HIDDEN: 108,
            IMG_OBJ_ROTATABLE_SPIKES_01: 111,
            IMG_OBJ_ROTATABLE_SPIKES_02: 112,
            IMG_OBJ_ROTATABLE_SPIKES_03: 113,
            IMG_OBJ_ROTATABLE_SPIKES_04: 114,
            IMG_OBJ_ROTATABLE_SPIKES_BUTTON: 115,
            IMG_OBJ_BEE_HD: 116,
            IMG_OBJ_POLLEN_HD: 117,
            SND_SPIKE_ROTATE_IN: 118,
            SND_SPIKE_ROTATE_OUT: 119,
            IMG_CHAR_SUPPORTS: 120,

            IMG_BGR_01_P1: 121,
            IMG_BGR_01_P2: 122,
            IMG_BGR_02_P1: 123,
            IMG_BGR_02_P2: 124,
            IMG_BGR_03_P1: 125,
            IMG_BGR_03_P2: 126,
            IMG_BGR_04_P1: 127,
            IMG_BGR_04_P2: 128,
            IMG_BGR_05_P1: 129,
            IMG_BGR_05_P2: 130,
            IMG_BGR_06_P1: 131,
            IMG_BGR_06_P2: 132,
            IMG_BGR_07_P1: 133,
            IMG_BGR_07_P2: 134,
            IMG_BGR_08_P1: 135,
            IMG_BGR_08_P2: 136,
            IMG_BGR_09_P1: 137,
            IMG_BGR_09_P2: 138,
            IMG_BGR_10_P1: 139,
            IMG_BGR_10_P2: 140,
            IMG_BGR_11_P1: 141,
            IMG_BGR_11_P2: 142,

            IMG_BGR_IE: 143,

            IMG_TIME_BGR_1: 144,
            IMG_TIME_BGR_2: 145,
            IMG_TIME_BGR_3: 146,
            IMG_TIME_BGR_4: 147,
            IMG_TIME_BGR_5: 148,
            IMG_TIME_BGR_6: 149,

            // time edition char animations
            IMG_CAESAR_ANIMATIONS_1: 150,
            IMG_CAESAR_ANIMATIONS_2: 151,
            IMG_CAESAR_ANIMATIONS_3: 152,
            IMG_CAESAR_ANIMATIONS_4: 153,
            IMG_PAINTER_ANIMATIONS_1: 154,
            IMG_PAINTER_ANIMATIONS_2: 155,
            IMG_PAINTER_ANIMATIONS_3: 156,
            IMG_PAINTER_ANIMATIONS_4: 157,
            IMG_PHARAOH_ANIMATIONS_1: 158,
            IMG_PHARAOH_ANIMATIONS_2: 159,
            IMG_PHARAOH_ANIMATIONS_3: 160,
            IMG_PHARAOH_ANIMATIONS_4: 161,
            IMG_PIRATE_ANIMATIONS_1: 162,
            IMG_PIRATE_ANIMATIONS_2: 163,
            IMG_PIRATE_ANIMATIONS_3: 164,
            IMG_PIRATE_ANIMATIONS_4: 165,
            IMG_PREHISTORIC_ANIMATIONS_1: 166,
            IMG_PREHISTORIC_ANIMATIONS_2: 167,
            IMG_PREHISTORIC_ANIMATIONS_3: 168,
            IMG_PREHISTORIC_ANIMATIONS_4: 169,
            IMG_VIKING_ANIMATIONS_1: 170,
            IMG_VIKING_ANIMATIONS_2: 171,
            IMG_VIKING_ANIMATIONS_3: 172,
            IMG_VIKING_ANIMATIONS_4: 173,

            SND_CANDY_HIT: 174,
            SND_PREHISTORIC_MONSTER_CHEWING : 175,
            SND_PREHISTORIC_MONSTER_OPEN : 176,
            SND_PREHISTORIC_MONSTER_CLOSE : 177,
            SND_PREHISTORIC_MONSTER_SAD : 178,
            SND_TIME_MENU_MUSIC: 179,
            IMG_TIME_STANDS: 180,

            RESOURCES_COUNT: 180
        };

        return ResourceId;
    }
);

define('ResInfo',
    [
        'resources/ResourceId'
    ],
    function (ResourceId) {

        var RES_INFO_2560 = [
            {id: 0},
            {id: 1},
            {id: 2},
            {id: 4, charOffset: -1, lineOffset: -42, spaceWidth: 20, chars: "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u00a9\u00c0\u00e0\u00c2\u00e2\u00c6\u00e6\u00c7\u00e7\u00c8\u00e8\u00c9\u00e9\u00ca\u00ea\u00cb\u00eb\u00ce\u00ee\u00cf\u00ef\u00d4\u00f4\u0152\u0153\u00d9\u00f9\u00db\u00fb\u00dc\u00fc\u00ab\u00bb\u20ac\u00c4\u00e4\u00c9\u00e9\u00d6\u00f6\u00dc\u00fc\u00df\u201e\u201c\u201d\u00b0\u0410\u0411\u0412\u0413\u0414\u0415\u0401\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0451\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f",
                kerning: {}, rects: [4, 4, 33, 156, 41, 4, 38, 156, 83, 4, 61, 156, 148, 4, 41, 156, 193, 4, 73, 156, 270, 4, 38, 156, 312, 4, 19, 156, 335, 4, 44, 156, 383, 4, 41, 156, 428, 4, 54, 156, 486, 4, 43, 156, 533, 4, 22, 156, 559, 4, 46, 156, 609, 4, 23, 156, 636, 4, 51, 156, 691, 4, 54, 156, 749, 4, 23, 156, 776, 4, 53, 156, 833, 4, 44, 156, 881, 4, 48, 156, 933, 4, 51, 156, 4, 164, 49, 156, 57, 164, 52, 156, 113, 164, 55, 156, 172, 164, 41, 156, 217, 164, 24, 156, 245, 164, 24, 156, 273, 164, 59, 156, 336, 164, 36, 156, 376, 164, 51, 156, 431, 164, 44, 156, 479, 164, 62, 156, 545, 164, 51, 156, 600, 164, 51, 156, 655, 164, 58, 156, 717, 164,
                48, 156, 769, 164, 46, 156, 819, 164, 45, 156, 868, 164, 50, 156, 922, 164, 49, 156, 975, 164, 24, 156, 4, 324, 45, 156, 53, 324, 45, 156, 102, 324, 54, 156, 160, 324, 73, 156, 237, 324, 43, 156, 284, 324, 63, 156, 351, 324, 59, 156, 414, 324, 54, 156, 472, 324, 51, 156, 527, 324, 57, 156, 588, 324, 56, 156, 648, 324, 59, 156, 711, 324, 52, 156, 767, 324, 74, 156, 845, 324, 63, 156, 912, 324, 47, 156, 4, 484, 67, 156, 75, 484, 65, 156, 144, 484, 54, 156, 202, 484, 56, 156, 262, 484, 40, 156, 306, 484, 74, 156, 384, 484, 24, 156, 412, 484, 45, 156, 461, 484, 51, 156, 516, 484, 49, 156, 569, 484, 48, 156, 621, 484, 43, 156, 668, 484,
                47, 156, 719, 484, 42, 156, 765, 484, 43, 156, 812, 484, 25, 156, 841, 484, 39, 156, 884, 484, 50, 156, 938, 484, 22, 156, 4, 644, 69, 156, 77, 644, 40, 156, 121, 644, 37, 156, 162, 644, 45, 156, 211, 644, 63, 156, 278, 644, 43, 156, 325, 644, 44, 156, 373, 644, 52, 156, 429, 644, 46, 156, 479, 644, 54, 156, 537, 644, 73, 156, 614, 644, 59, 156, 677, 644, 54, 156, 735, 644, 58, 156, 797, 644, 57, 156, 858, 644, 25, 156, 887, 644, 42, 156, 933, 644, 49, 156, 4, 804, 75, 156, 83, 804, 51, 156, 138, 804, 44, 156, 186, 804, 51, 156, 241, 804, 47, 156, 292, 804, 83, 156, 379, 804, 62, 156, 445, 804, 54, 156, 503, 804, 43, 156, 550, 804,
                45, 156, 599, 804, 45, 156, 648, 804, 45, 156, 697, 804, 45, 156, 746, 804, 45, 156, 795, 804, 45, 156, 844, 804, 45, 156, 893, 804, 45, 156, 942, 804, 41, 156, 4, 964, 41, 156, 49, 964, 33, 156, 86, 964, 34, 156, 124, 964, 63, 156, 191, 964, 48, 156, 243, 964, 82, 156, 329, 964, 61, 156, 394, 964, 53, 156, 451, 964, 44, 156, 499, 964, 53, 156, 556, 964, 43, 156, 603, 964, 53, 156, 660, 964, 43, 156, 707, 964, 53, 156, 764, 964, 59, 156, 827, 964, 65, 156, 896, 964, 51, 156, 951, 964, 44, 156, 4, 1124, 45, 156, 53, 1124, 45, 156, 102, 1124, 63, 156, 169, 1124, 48, 156, 221, 1124, 53, 156, 278, 1124, 43, 156, 325, 1124, 53, 156,
                382, 1124, 43, 156, 429, 1124, 43, 156, 476, 1124, 43, 156, 523, 1124, 40, 156, 567, 1124, 65, 156, 636, 1124, 47, 156, 687, 1124, 50, 156, 741, 1124, 49, 156, 794, 1124, 64, 156, 862, 1124, 46, 156, 912, 1124, 46, 156, 4, 1284, 73, 156, 81, 1284, 46, 156, 131, 1284, 54, 156, 189, 1284, 54, 156, 247, 1284, 62, 156, 313, 1284, 62, 156, 379, 1284, 64, 156, 447, 1284, 52, 156, 503, 1284, 59, 156, 566, 1284, 53, 156, 623, 1284, 48, 156, 675, 1284, 49, 156, 728, 1284, 59, 156, 791, 1284, 55, 156, 850, 1284, 59, 156, 913, 1284, 55, 156, 4, 1444, 58, 156, 66, 1444, 49, 156, 119, 1444, 72, 156, 195, 1444, 83, 156, 282, 1444, 62,
                156, 348, 1444, 58, 156, 410, 1444, 50, 156, 464, 1444, 48, 156, 516, 1444, 67, 156, 587, 1444, 51, 156, 642, 1444, 44, 156, 690, 1444, 41, 156, 735, 1444, 39, 156, 778, 1444, 37, 156, 819, 1444, 41, 156, 864, 1444, 40, 156, 908, 1444, 40, 156, 952, 1444, 61, 156, 4, 1604, 38, 156, 46, 1604, 43, 156, 93, 1604, 43, 156, 140, 1604, 44, 156, 188, 1604, 45, 156, 237, 1604, 62, 156, 303, 1604, 42, 156, 349, 1604, 40, 156, 393, 1604, 49, 156, 446, 1604, 46, 156, 496, 1604, 38, 156, 538, 1604, 69, 156, 611, 1604, 42, 156, 657, 1604, 61, 156, 722, 1604, 43, 156, 769, 1604, 45, 156, 818, 1604, 40, 156, 862, 1604, 63, 156, 929, 1604,
                65, 156, 4, 1764, 51, 156, 59, 1764, 51, 156, 114, 1764, 40, 156, 158, 1764, 39, 156, 201, 1764, 60, 156, 265, 1764, 40, 156, 309, 1764, 112, 156]},
            {id: 5, charOffset: 8, lineOffset: -140, spaceWidth: 45, chars: "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u00a9\u00c0\u00e0\u00c2\u00e2\u00c6\u00e6\u00c7\u00e7\u00c8\u00e8\u00c9\u00e9\u00ca\u00ea\u00cb\u00eb\u00ce\u00ee\u00cf\u00ef\u00d4\u00f4\u0152\u0153\u00d9\u00f9\u00db\u00fb\u00dc\u00fc\u00ab\u00bb\u20ac\u00c4\u00e4\u00c9\u00e9\u00d6\u00f6\u00dc\u00fc\u00df\u201e\u201c\u201d\u00b0\u0410\u0411\u0412\u0413\u0414\u0415\u0401\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0451\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f",
                kerning: {}, rects: [4, 4, 16, 156, 24, 4, 19, 156, 47, 4, 35, 156, 86, 4, 21, 156, 111, 4, 43, 156, 158, 4, 19, 156, 181, 4, 7, 156, 192, 4, 24, 156, 220, 4, 23, 156, 247, 4, 30, 156, 281, 4, 24, 156, 309, 4, 9, 156, 322, 4, 25, 156, 351, 4, 10, 156, 365, 4, 28, 156, 397, 4, 31, 156, 432, 4, 10, 156, 446, 4, 29, 156, 479, 4, 24, 156, 507, 4, 26, 156, 537, 4, 29, 156, 570, 4, 28, 156, 602, 4, 29, 156, 635, 4, 31, 156, 670, 4, 21, 156, 695, 4, 10, 156, 709, 4, 10, 156, 723, 4, 33, 156, 760, 4, 19, 156, 783, 4, 28, 156, 815, 4, 24, 156, 843, 4, 36, 156, 883, 4, 28, 156, 915, 4, 28, 156, 947, 4, 32, 156, 983, 4, 27, 156, 4, 164, 25, 156, 33, 164, 24,
                156, 61, 164, 28, 156, 93, 164, 28, 156, 125, 164, 10, 156, 139, 164, 25, 156, 168, 164, 24, 156, 196, 164, 31, 156, 231, 164, 43, 156, 278, 164, 24, 156, 306, 164, 37, 156, 347, 164, 34, 156, 385, 164, 30, 156, 419, 164, 29, 156, 452, 164, 32, 156, 488, 164, 31, 156, 523, 164, 34, 156, 561, 164, 29, 156, 594, 164, 43, 156, 641, 164, 36, 156, 681, 164, 26, 156, 711, 164, 39, 156, 754, 164, 38, 156, 796, 164, 30, 156, 830, 164, 32, 156, 866, 164, 22, 156, 892, 164, 44, 156, 940, 164, 11, 156, 955, 164, 25, 156, 984, 164, 29, 156, 4, 324, 27, 156, 35, 324, 26, 156, 65, 324, 24, 156, 93, 324, 26, 156, 123, 324, 23, 156, 150, 324, 24,
                156, 178, 324, 11, 156, 193, 324, 20, 156, 217, 324, 28, 156, 249, 324, 9, 156, 262, 324, 41, 156, 307, 324, 21, 156, 332, 324, 20, 156, 356, 324, 25, 156, 385, 324, 36, 156, 425, 324, 22, 156, 451, 324, 24, 156, 479, 324, 29, 156, 512, 324, 26, 156, 542, 324, 31, 156, 577, 324, 43, 156, 624, 324, 34, 156, 662, 324, 30, 156, 696, 324, 33, 156, 733, 324, 32, 156, 769, 324, 12, 156, 785, 324, 22, 156, 811, 324, 27, 156, 842, 324, 45, 156, 891, 324, 29, 156, 924, 324, 24, 156, 952, 324, 29, 156, 985, 324, 26, 156, 4, 484, 49, 156, 57, 484, 36, 156, 97, 484, 30, 156, 131, 484, 23, 156, 158, 484, 24, 156, 186, 484, 24, 156, 214, 484,
                24, 156, 242, 484, 24, 156, 270, 484, 24, 156, 298, 484, 24, 156, 326, 484, 24, 156, 354, 484, 24, 156, 382, 484, 22, 156, 408, 484, 22, 156, 434, 484, 17, 156, 455, 484, 17, 156, 476, 484, 36, 156, 516, 484, 27, 156, 547, 484, 50, 156, 601, 484, 35, 156, 640, 484, 29, 156, 673, 484, 24, 156, 701, 484, 29, 156, 734, 484, 23, 156, 761, 484, 29, 156, 794, 484, 23, 156, 821, 484, 30, 156, 855, 484, 34, 156, 893, 484, 37, 156, 934, 484, 29, 156, 967, 484, 24, 156, 995, 484, 24, 156, 4, 644, 24, 156, 32, 644, 36, 156, 72, 644, 27, 156, 103, 644, 29, 156, 136, 644, 23, 156, 163, 644, 30, 156, 197, 644, 23, 156, 224, 644, 23, 156, 251,
                644, 23, 156, 278, 644, 22, 156, 304, 644, 38, 156, 346, 644, 26, 156, 376, 644, 28, 156, 408, 644, 28, 156, 440, 644, 38, 156, 482, 644, 25, 156, 511, 644, 25, 156, 540, 644, 44, 156, 588, 644, 26, 156, 618, 644, 30, 156, 652, 644, 30, 156, 686, 644, 37, 156, 727, 644, 36, 156, 767, 644, 37, 156, 808, 644, 29, 156, 841, 644, 34, 156, 879, 644, 30, 156, 913, 644, 28, 156, 945, 644, 27, 156, 976, 644, 34, 156, 4, 804, 31, 156, 39, 804, 34, 156, 77, 804, 31, 156, 112, 804, 33, 156, 149, 804, 28, 156, 181, 804, 43, 156, 228, 804, 51, 156, 283, 804, 36, 156, 323, 804, 33, 156, 360, 804, 28, 156, 392, 804, 27, 156, 423, 804, 40, 156,
                467, 804, 29, 156, 500, 804, 24, 156, 528, 804, 22, 156, 554, 804, 20, 156, 578, 804, 19, 156, 601, 804, 22, 156, 627, 804, 21, 156, 652, 804, 21, 156, 677, 804, 36, 156, 717, 804, 20, 156, 741, 804, 24, 156, 769, 804, 24, 156, 797, 804, 24, 156, 825, 804, 25, 156, 854, 804, 35, 156, 893, 804, 24, 156, 921, 804, 22, 156, 947, 804, 28, 156, 979, 804, 26, 156, 4, 964, 20, 156, 28, 964, 41, 156, 73, 964, 23, 156, 100, 964, 36, 156, 140, 964, 24, 156, 168, 964, 25, 156, 197, 964, 22, 156, 223, 964, 37, 156, 264, 964, 38, 156, 306, 964, 30, 156, 340, 964, 29, 156, 373, 964, 22, 156, 399, 964, 21, 156, 424, 964, 35, 156, 463, 964, 22,
                156, 489, 964, 112, 156]},
            {id: 51, preCutWidth: 393, preCutHeight: 418, rects: [2, 2, 218, 226, 224, 2, 151, 151, 2, 232, 157, 158, 224, 157, 98, 62, 326, 157, 48, 45, 379, 2, 49, 59, 432, 2, 55, 57, 379, 65, 53, 63, 163, 232, 146, 147, 2, 394, 153, 163, 2, 561, 153, 166, 2, 731, 156, 169, 2, 904, 163, 175, 169, 904, 159, 175, 159, 394, 159, 159, 313, 232, 150, 150, 322, 394, 144, 150, 159, 561, 138, 146, 2, 1083, 302, 303, 301, 561, 107, 158, 412, 561, 96, 157, 2, 1390, 252, 268, 2, 1662, 278, 305, 2, 1971, 371, 397, 2, 2372, 385, 396, 2, 2772, 377, 386], offsets: [103, 130, 122, 133, 119, 131, 145, 176, 168, 182, 171,
                177, 168, 182, 160, 176, 119, 128, 115, 115, 115, 112, 115, 112, 115, 112, 119, 115, 122, 134, 131, 143, 137, 143, 140, 147, 47, 56, 143, 133, 155, 133, 69, 83, 50, 37, 6, -2, 0, -5, 8, 2]},
            {id: 52, preCutWidth: 552, preCutHeight: 552, rects: [0, 0, 88, 85, 0, 85, 46, 152, 46, 85, 106, 149, 0, 237, 78, 162, 78, 237, 93, 155, 0, 399, 88, 158, 152, 85, 46, 152, 88, 399, 144, 145, 0, 557, 138, 141, 0, 698, 145, 145, 0, 843, 146, 141, 0, 984, 161, 140, 0, 1124, 130, 155], offsets: [235, 190, 256, 121, 226, 124, 240, 111, 233, 118, 235, 115, 256, 121, 213, 220, 219, 232, 212, 220, 211, 232, 203, 178, 211, 273]},
            {id: 53, preCutWidth: 290,
                preCutHeight: 228, rects: [0, 0, 42, 38, 0, 38, 43, 36, 0, 74, 46, 33, 0, 107, 46, 33, 0, 140, 44, 36, 0, 176, 42, 38, 0, 214, 40, 41, 0, 255, 43, 43, 0, 298, 40, 41, 0, 339, 45, 33, 0, 372, 48, 31, 0, 403, 49, 38, 0, 441, 50, 45, 0, 486, 52, 53, 0, 539, 50, 39, 0, 578, 49, 28, 0, 606, 47, 31, 0, 637, 45, 33, 0, 670, 53, 53, 0, 723, 43, 53, 0, 776, 32, 54, 32, 776, 23, 53, 52, 486, 12, 53, 43, 723, 12, 53, 0, 830, 23, 55, 23, 830, 32, 54, 0, 885, 43, 53], offsets: [123, 98, 122, 99, 121, 100, 121, 100, 122, 99, 123, 98, 124, 96, 122, 95, 124, 96, 121, 100, 120, 101, 119, 97, 119, 94, 118, 90, 119, 97, 119, 103, 120, 101, 121, 100, 118, 89, 123,
                89, 128, 89, 133, 89, 138, 89, 138, 89, 133, 88, 128, 89, 123, 89]},
            {id: 56, charOffset: 2, lineOffset: 2, spaceWidth: 10, chars: "0123456789", kerning: {}, rects: [5, 5, 49, 156, 59, 5, 16, 156, 80, 5, 48, 156, 133, 5, 38, 156, 176, 5, 41, 156, 222, 5, 45, 156, 272, 5, 43, 156, 320, 5, 47, 156, 372, 5, 49, 156, 426, 5, 35, 156]},
            {id: 57, preCutWidth: 2560, preCutHeight: 122, rects: [0, 0, 203, 99, 0, 99, 203, 99], offsets: [2347, 9, 2347, 9]},
            {id: 59, preCutWidth: 552, preCutHeight: 552, rects: [0, 0, 246, 268, 0, 268, 334, 370, 246, 0, 205, 231, 0, 638, 308, 353, 0, 991, 387, 461, 0, 1452, 324, 399, 0, 1851, 335,
                328, 0, 2179, 296, 266, 0, 2445, 240, 230, 296, 2179, 207, 217, 308, 638, 204, 205, 240, 2445, 200, 194, 308, 843, 195, 133], offsets: [145, 116, 76, 38, 166, 132, 113, 56, 75, -2, 113, 34, 87, 82, 102, 114, 128, 124, 148, 130, 149, 134, 151, 138, 153, 141]},
            {id: 60, preCutWidth: 250, preCutHeight: 250, rects: [0, 0, 139, 170, 139, 0, 142, 169, 281, 0, 148, 163, 429, 0, 155, 155, 584, 0, 163, 148, 747, 0, 169, 142, 584, 148, 171, 139, 584, 287, 169, 141, 755, 148, 165, 145, 755, 293, 159, 151, 429, 155, 153, 159, 281, 163, 147, 164, 139, 169, 141, 169, 0, 170, 139, 170], offsets: [55, 40, 54, 41, 51, 44, 47, 48, 43, 51,
                40, 54, 39, 56, 40, 55, 42, 53, 46, 50, 48, 46, 51, 44, 54, 41, 55, 40]},
            {id: 61, preCutWidth: 449, preCutHeight: 446, rects: [0, 0, 308, 285, 0, 285, 302, 282, 0, 567, 297, 281, 0, 848, 296, 277, 0, 1125, 293, 274, 0, 1399, 293, 273, 0, 1672, 295, 271, 0, 1943, 297, 269, 0, 2212, 239, 226, 239, 2212, 241, 223, 0, 2438, 244, 221, 244, 2438, 249, 219], offsets: [83, 82, 87, 84, 90, 86, 91, 90, 93, 94, 92, 99, 90, 104, 88, 111, 101, 119, 97, 128, 92, 138, 86, 148]},
            {id: 62, preCutWidth: 275, preCutHeight: 275, rects: [0, 0, 140, 144, 0, 144, 42, 37], offsets: [69, 62, 117, 119]},
            {id: 63, preCutWidth: 833, preCutHeight: 250,
                rects: [0, 0, 566, 93], offsets: [133, 76]},
            {id: 64, preCutWidth: 250, preCutHeight: 250, rects: [0, 0, 155, 154, 0, 154, 181, 210, 0, 364, 185, 180, 0, 544, 183, 178], offsets: [47, 50, 32, 36, 35, 40, 35, 40]},
            {id: 65, preCutWidth: 275, preCutHeight: 275, rects: [0, 0, 125, 126, 0, 126, 37, 35], offsets: [78, 76, 122, 121]},
            {id: 66, preCutWidth: 275, preCutHeight: 275, rects: [0, 0, 125, 126, 0, 126, 37, 35], offsets: [75, 76, 119, 121]},
            {id: 67, preCutWidth: 552, preCutHeight: 552, rects: [2, 2, 234, 221, 240, 2, 77, 76, 240, 82, 70, 76, 321, 2, 64, 76, 321, 82, 58, 76, 389, 2, 51, 76, 389, 82, 46, 76,
                444, 2, 40, 77, 444, 83, 34, 77, 488, 2, 28, 78, 488, 84, 28, 78, 520, 2, 35, 77, 559, 2, 42, 77, 605, 2, 48, 77, 657, 2, 56, 77, 717, 2, 63, 77, 784, 2, 69, 77, 857, 2, 76, 77, 857, 83, 83, 78, 2, 227, 175, 175, 181, 227, 175, 175, 360, 227, 175, 175, 539, 227, 175, 175, 718, 227, 175, 175, 2, 406, 175, 175, 181, 406, 175, 175, 360, 406, 175, 175, 539, 406, 175, 175, 718, 406, 175, 175, 2, 585, 175, 175, 181, 585, 175, 175, 360, 585, 175, 175, 539, 585, 175, 175, 718, 585, 175, 175, 2, 764, 175, 175, 181, 764, 175, 175, 360, 764, 175, 175, 539, 764, 175, 175, 718, 764, 175, 175, 2, 943, 175, 175, 181, 943, 175, 175, 360, 943, 175,
                175, 539, 943, 175, 175, 718, 943, 175, 175, 2, 1122, 175, 175, 2, 1301, 175, 175, 2, 1480, 175, 175, 2, 1659, 175, 175, 2, 1838, 175, 175, 181, 1122, 175, 175, 360, 1122, 175, 175, 539, 1122, 175, 175, 718, 1122, 175, 175, 181, 1301, 175, 175, 181, 1480, 175, 175, 181, 1659, 175, 175, 360, 1301, 229, 231, 593, 1301, 229, 231, 360, 1536, 490, 492], offsets: [156, 156, 233, 231, 236, 231, 239, 231, 242, 231, 246, 231, 248, 231, 251, 231, 254, 231, 257, 231, 257, 231, 253, 231, 250, 231, 247, 231, 243, 231, 240, 231, 237, 231, 234, 231, 230, 230, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184,
                184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 155, 159, 155, 159, 27, 21]},
            {id: ResourceId.IMG_HUD_STAR, preCutWidth: 100, preCutHeight: 100, rects: [0,0,84,85,84,0,58,85,142,0,32,85,174,0,12,85,186,0,28,85,214,0,44,85,258,0,60,85,318,0,70,85,388,0,78,85,466,0,84,85,550,0,84,85], offsets: [6,5,19,5,32,5,42,5,34,5,26,5,18,5,13,5,9,5,6,5,6,5] },
            {id: 69,
                preCutWidth: 833, preCutHeight: 250, rects: [0, 0, 453, 93], offsets: [191, 76]},
            {id: 70, preCutWidth: 833, preCutHeight: 250, rects: [0, 0, 333, 93], offsets: [251, 79]},
            {id: 71, preCutWidth: 833, preCutHeight: 250, rects: [0, 0, 212, 93], offsets: [310, 79]},
            {id: 72, preCutWidth: 640, preCutHeight: 640, rects: [0, 0, 291, 302, 291, 0, 363, 409, 654, 0, 383, 309, 1037, 0, 314, 335, 1351, 0, 326, 334, 1677, 0, 374, 330, 291, 409, 405, 492, 2051, 0, 350, 334, 0, 302, 201, 207, 0, 509, 201, 206, 0, 715, 201, 207, 0, 922, 201, 207, 0, 1129, 201, 206, 0, 1335, 201, 210, 0, 1545, 201, 213, 0, 1758, 201, 215,
                0, 1973, 201, 219, 0, 2192, 201, 222, 0, 2414, 201, 223, 0, 2637, 201, 222, 0, 2859, 201, 224, 0, 3083, 201, 224, 0, 3307, 201, 224, 0, 3531, 201, 221, 0, 3752, 201, 218, 2401, 0, 201, 214, 2602, 0, 201, 211, 2803, 0, 222, 196, 2803, 196, 210, 201, 3025, 0, 203, 209, 3228, 0, 197, 214, 3425, 0, 193, 217, 3618, 0, 194, 219, 3812, 0, 197, 210, 291, 901, 200, 202, 3812, 210, 204, 197, 291, 1103, 208, 199, 291, 1302, 212, 202, 291, 1504, 214, 205, 291, 1709, 216, 206, 291, 1915, 204, 226, 291, 2141, 203, 213, 495, 1915, 201, 210, 494, 2141, 201, 208, 291, 2354, 201, 203, 3025, 209, 201, 200, 491, 901, 201, 200, 492, 2354, 201,
                200, 291, 2557, 201, 200, 291, 2757, 201, 207, 291, 2964, 201, 216, 492, 2557, 204, 194, 291, 3180, 221, 171, 291, 3351, 249, 197, 291, 3548, 225, 201, 291, 3749, 205, 205, 492, 2964, 202, 213, 696, 409, 201, 222, 897, 409, 201, 222, 1098, 409, 205, 221, 1303, 409, 229, 213, 1532, 409, 243, 205, 0, 3970, 153, 86, 654, 309, 140, 89, 1775, 409, 212, 196, 1987, 409, 208, 209, 496, 3749, 197, 233, 696, 631, 186, 247, 882, 631, 181, 245, 1063, 631, 186, 243, 1249, 631, 194, 234, 1443, 631, 199, 230, 2195, 409, 199, 221, 1642, 631, 199, 223, 1841, 631, 199, 226, 2040, 631, 199, 228, 2239, 631, 199, 230, 2438, 631, 199,
                230, 2637, 631, 199, 230, 2836, 631, 195, 234, 3031, 631, 190, 238, 3221, 631, 194, 231, 2394, 409, 201, 217, 2595, 409, 207, 209, 2802, 409, 218, 202, 3020, 409, 211, 206, 3231, 409, 206, 210, 3437, 409, 210, 213, 3647, 409, 215, 218, 3415, 631, 220, 223, 3862, 409, 224, 216, 3635, 631, 219, 212, 492, 2757, 195, 205, 3854, 631, 195, 207, 696, 878, 195, 210, 891, 878, 217, 214, 891, 1092, 225, 215, 891, 1307, 219, 215, 696, 1088, 195, 215, 696, 1303, 195, 215, 696, 1518, 195, 215, 891, 1522, 217, 215, 891, 1737, 225, 215, 891, 1952, 223, 218, 891, 2170, 219, 222, 891, 2392, 215, 224, 891, 2616, 216, 216, 1108, 878,
                210, 206, 1318, 878, 218, 202, 1536, 878, 205, 190, 1741, 878, 203, 194, 1944, 878, 202, 206, 2146, 878, 203, 214, 891, 2832, 204, 217, 891, 3049, 204, 216, 2349, 878, 204, 213, 2553, 878, 205, 204, 2758, 878, 205, 192, 2401, 214, 201, 195, 2963, 878, 201, 206, 3164, 878, 204, 212, 3368, 878, 205, 214, 891, 3265, 207, 215, 891, 3480, 208, 216, 3573, 878, 205, 205, 3778, 878, 226, 202, 891, 3696, 212, 204, 696, 1733, 195, 210, 696, 1943, 189, 213, 696, 2156, 190, 218, 696, 2374, 193, 222, 1116, 1092, 198, 213, 1314, 1092, 203, 210, 1517, 1092, 207, 202, 1724, 1092, 211, 204, 1935, 1092, 214, 207, 2149, 1092, 216,
                211, 2365, 1092, 216, 212, 2581, 1092, 216, 212, 2797, 1092, 216, 212, 3013, 1092, 216, 212, 3229, 1092, 216, 212, 3445, 1092, 216, 212, 3661, 1092, 216, 212, 3877, 1092, 216, 212, 1116, 1305, 216, 212, 1116, 1517, 212, 216, 1116, 1733, 206, 222, 1116, 1955, 202, 225, 1116, 2180, 205, 222, 1116, 2402, 210, 213, 1116, 2615, 212, 208, 1116, 2823, 198, 223, 1116, 3046, 196, 232, 696, 2596, 195, 236, 1116, 3278, 198, 236, 1116, 3514, 206, 238, 1116, 3752, 211, 226, 1332, 1305, 217, 207, 891, 3900, 213, 195, 1549, 1305, 212, 179, 1761, 1305, 221, 178, 1982, 1305, 221, 181, 2203, 1305, 215, 180, 2418, 1305, 212,
                179, 2630, 1305, 221, 177, 2851, 1305, 221, 180, 3072, 1305, 215, 180, 3287, 1305, 212, 179, 3499, 1305, 221, 177, 3720, 1305, 221, 180, 1332, 1512, 215, 180, 1332, 1692, 212, 179, 1547, 1512, 216, 176, 1332, 1871, 213, 175, 696, 2832, 194, 205, 696, 3037, 188, 236, 696, 3273, 194, 233, 1332, 2046, 210, 202, 1332, 2248, 204, 194, 1332, 2442, 202, 204], offsets: [173, 274, 137, 156, 133, 207, 152, 237, 154, 244, 134, 240, 107, 104, 148, 242, 220, 227, 220, 227, 220, 227, 220, 227, 220, 227, 220, 224, 220, 221, 220, 218, 220, 215, 220, 212, 220, 211, 220, 211, 220, 210, 220, 210, 220, 210, 220, 213, 220, 216, 220,
                220, 220, 223, 211, 238, 216, 233, 220, 225, 223, 220, 225, 217, 224, 215, 223, 224, 221, 232, 219, 237, 217, 235, 215, 232, 214, 229, 213, 228, 217, 208, 218, 221, 219, 224, 219, 226, 219, 231, 219, 234, 219, 234, 219, 234, 219, 234, 219, 226, 219, 218, 218, 240, 210, 263, 192, 237, 204, 233, 217, 229, 220, 221, 220, 212, 220, 212, 217, 212, 202, 221, 195, 229, 240, 264, 249, 265, 215, 238, 217, 225, 223, 201, 228, 187, 231, 189, 228, 191, 223, 200, 220, 204, 220, 213, 220, 211, 220, 208, 220, 206, 220, 204, 220, 204, 220, 204, 222, 200, 225, 196, 223, 203, 220, 217, 216, 225, 211, 232, 216, 228, 218, 224, 212, 221, 208,
                216, 205, 211, 202, 218, 208, 222, 232, 229, 232, 227, 232, 224, 210, 220, 202, 219, 208, 219, 232, 219, 232, 219, 232, 219, 210, 219, 202, 219, 202, 216, 205, 212, 207, 210, 208, 218, 217, 228, 211, 232, 216, 244, 218, 240, 219, 227, 220, 219, 220, 216, 220, 217, 220, 221, 218, 230, 216, 242, 220, 239, 220, 228, 217, 222, 216, 220, 214, 219, 213, 218, 216, 229, 207, 232, 214, 230, 224, 224, 227, 221, 226, 216, 225, 212, 222, 220, 219, 224, 218, 231, 216, 229, 214, 226, 213, 222, 213, 221, 213, 221, 213, 221, 213, 221, 213, 221, 213, 221, 213, 221, 213, 221, 213, 221, 215, 217, 218, 211, 220, 208, 218, 212, 216, 221, 215,
                226, 222, 210, 223, 201, 223, 197, 222, 197, 219, 195, 216, 206, 211, 225, 213, 238, 213, 254, 207, 255, 207, 252, 210, 253, 213, 254, 207, 256, 207, 253, 210, 253, 213, 254, 207, 256, 207, 253, 210, 253, 213, 254, 211, 257, 214, 258, 227, 227, 230, 195, 226, 199, 215, 232, 218, 239, 220, 230]},
            {id: 73, preCutWidth: 275, preCutHeight: 275, rects: [0, 0, 137, 139, 137, 0, 105, 100, 0, 139, 235, 235, 0, 374, 240, 246], offsets: [65, 70, 84, 86, 18, 19, 13, 14]},
            {id: 74, preCutWidth: 833, preCutHeight: 250, rects: [0, 0, 507, 85, 0, 85, 507, 100, 0, 185, 507, 100, 0, 285, 507, 97, 0, 382, 507, 92], offsets: [163, 82,
                163, 73, 163, 71, 163, 70, 163, 75]},
            {id: 75, preCutWidth: 276, preCutHeight: 276, rects: [0, 0, 84, 128, 84, 0, 68, 109, 152, 0, 50, 109, 0, 128, 140, 121, 0, 249, 148, 147]},
            {id: 76, preCutWidth: 761, preCutHeight: 761, rects: [0, 0, 220, 239, 0, 239, 206, 290, 0, 529, 205, 290, 0, 819, 220, 246, 0, 1065, 237, 219, 0, 1284, 226, 231, 220, 0, 18, 17, 238, 0, 11, 11, 206, 239, 38, 39], offsets: [271, 262, 286, 232, 287, 232, 271, 249, 262, 272, 268, 266, 382, 376, 385, 379, 372, 365]},
            {id: 77, preCutWidth: 998, preCutHeight: 1058, rects: [3, 3, 148, 12, 3, 21,
                39, 187, 48, 21, 182, 150, 236, 21, 144, 130, 3, 214, 244, 233, 253, 214, 159, 143, 157, 3, 49, 9, 386, 21, 89, 84, 3, 453, 102, 148, 3, 607, 260, 315], offsets: [585, 767, 160, 241, 412, 548, 307, 397, 184, 663, 530, 224, 676, 540, 540, 425, 351, 211, 684, 200]},
            {id: 78, preCutWidth: 431, preCutHeight: 431, rects: [0, 0, 294, 335, 294, 0, 297, 336, 0, 335, 291, 252, 591, 0, 307, 293, 0, 587, 276, 224], offsets: [53, 6, 51, 3, 55, 78, 47, 57, 63, 98]},
            {id: 79, preCutWidth: 833, preCutHeight: 250, rects: [0, 0, 194, 127, 0, 127, 201, 103, 0, 230, 204, 96, 0, 326, 193, 142, 0, 468, 194, 111], offsets: [319, 69, 316, 81,
                314, 87, 319, 55, 319, 77]},
            {id: 80, preCutWidth: 833, preCutHeight: 250, rects: [0, 0, 302, 123, 302, 0, 319, 99, 621, 0, 322, 95, 302, 99, 292, 136, 0, 123, 302, 111], offsets: [268, 70, 260, 85, 258, 87, 273, 58, 268, 77]},
            {id: 58,
                preCutWidth: 2028, preCutHeight: 1597, rects: [0, 0, 239, 239], offsets: [811, 344]},
            {id: 3, preCutWidth: 936, preCutHeight: 418, rects: [0, 0, 735, 174, 0, 174, 727, 173], offsets: [102, 116, 107, 117]},
            {id: 6, preCutWidth: 2560, preCutHeight: 1440, rects: [2, 2, 58, 1303, 64, 2, 65, 1303, 133, 2, 305, 405, 133, 411, 835, 789], offsets: [1216, 67, 1274, 67, 1096, 31, 455, 578]},
            {id: 40, preCutWidth: 2560, preCutHeight: 1440, rects: [2, 2, 2560, 1440, 2566, 2, 2560, 920, 5130, 2, 1781, 1781], offsets: [0, 0, 0, 520, 388, -172]},
            {id: 41, preCutWidth: 193, preCutHeight: 193, rects: [0, 0, 187,
                149, 0, 149, 187, 149], offsets: [4, 23, 4, 23]},
            {id: 42, preCutWidth: 2560, preCutHeight: 1440, rects: [0, 0, 792, 792, 792, 0, 7, 8, 799, 0, 7, 8, 806, 0, 7, 7, 813, 0, 7, 7, 820, 0, 7, 8], offsets: [880, 283, 1289, 403, 1289, 665, 1289, 770, 1289, 1026, 1289, 531]},
            {id: 43, preCutWidth: 833, preCutHeight: 250, rects: [0, 0, 128, 93], offsets: [133, 76]},
            {id: 44, preCutWidth: 1076, preCutHeight: 983, rects: [0, 0, 868, 795, 0, 795, 267, 300, 267, 795, 305, 135], offsets: [101, 113, 434, 316, 408, 405]},
            {id: 45, preCutWidth: 218, preCutHeight: 218, rects: [0, 0, 190, 196, 0, 196, 202, 208, 0, 404, 143,
                77, 0, 481, 141, 77, 0, 558, 143, 77, 0, 635, 142, 77], offsets: [10, 10, 5, 7, 58, 139, 59, 139, 58, 139, 58, 139]},
            {id: 46, preCutWidth: 1129, preCutHeight: 1092, rects: [2, 2, 229, 183, 235, 2, 166, 174, 405, 2, 146, 69, 405, 75, 146, 85, 555, 2, 144, 140, 703, 2, 173, 166, 2, 189, 162, 168, 2, 361, 169, 170, 2, 535, 166, 171, 2, 710, 166, 175, 172, 710, 166, 172, 342, 710, 166, 174, 512, 710, 166, 174, 682, 710, 166, 174, 2, 889, 477, 221, 880, 2, 58, 58, 2, 1114, 434, 472, 2, 1590, 445, 481, 440, 1114, 434, 472, 451, 1590, 434, 472, 2, 2075, 434, 481, 440, 2075, 434, 473, 2, 2560, 434, 473, 440, 2560, 467, 473, 2, 3037,
                436, 459, 168, 189, 103, 124, 880, 64, 84, 103, 275, 189, 103, 124, 382, 189, 83, 103], offsets: [464, 598, 499, 616, 513, 714, 511, 695, 513, 622, 496, 632, 503, 645, 497, 627, 499, 619, 499, 617, 499, 618, 499, 616, 499, 616, 499, 616, 339, 558, 548, 667, 362, 365, 358, 364, 362, 364, 362, 364, 362, 364, 362, 363, 362, 363, 329, 363, 360, 363, 533, 586, 543, 595, 534, 586, 543, 595]},
            {id: 47, preCutWidth: 679, preCutHeight: 206, rects: [0, 0, 208, 206, 0, 206, 205, 204, 0, 410, 92, 95, 92, 410, 92, 95], offsets: [0, -2, 1, 1, 457, 90, 563, 90]},
            {id: 48, preCutWidth: 679, preCutHeight: 206, rects: [0, 0, 291,
                49], offsets: [143, 106]},
            {id: 49, preCutWidth: 936, preCutHeight: 418, rects: [0, 0, 420, 174, 0, 174, 419, 173], offsets: [262, 115, 263, 115]},
            {id: 50, preCutWidth: 2560, preCutHeight: 122, rects: [0, 0, 96, 97, 96, 0, 96, 97], offsets: [2237, 9, 2237, 9]},
            {id: 54, preCutWidth: 2560, preCutHeight: 380, rects: [0, 0, 2560, 310]},
            {id: 55, preCutWidth: 2560, preCutHeight: 1597, rects: [1, 1, 7, 7, 10, 1, 7, 7, 19, 1, 7, 7, 28, 1, 7, 7, 37, 1, 7, 7, 46, 1, 8, 7, 56, 1, 7, 7, 65, 1, 7, 7, 74, 1, 7, 7, 83, 1, 7, 7, 92, 1, 7, 7, 101, 1, 7, 7, 110, 1, 7, 7, 1, 10, 228, 218, 231, 10, 210, 203, 1, 230, 626, 11, 1, 243, 889, 398],
                offsets: [1052, 556, 1261, 556, 1467, 556, 1261, 378, 1264, 771, 1117, 721, 1461, 721, 1264, 721, 1264, 840, 1261, 1186, 1495, 993, 1052, 993, 1617, 768, 816, 350, 825, 358, 603, 465, 153, 89]},
            {id: 108, rects: [0, 0, 198, 194, 0, 194, 198, 194, 0, 388, 198, 194]},

            // rotateable spikes
            {id: 111, rects: [0, 0, 202, 81], offsets: [316, 87], preCutWidth: 833, preCutHeight: 250 },
            {id: 112, rects: [0, 0, 319, 83], offsets: [260, 87], preCutWidth: 833, preCutHeight: 250 },
            {id: 113, rects: [0, 0, 444, 86], offsets: [195, 81], preCutWidth: 833, preCutHeight: 250 },
            {id: 114, rects: [0, 0, 559, 89], offsets: [137, 77], preCutWidth: 833, preCutHeight: 250 },
            {id: 115, rects: [0, 0, 101, 102, 0, 102, 101, 102, 0, 204, 101, 102, 0, 306, 101, 102], offsets: [219, 29, 218, 29, 219, 29, 219, 29], preCutWidth: 534, preCutHeight: 160 },

            // bee and pollen
            {id: 116, preCutWidth: 275, preCutHeight: 275, rects: [0, 0, 4, 4, 2, 2, 89, 116, 95, 2, 177, 37, 276, 2, 187, 77, 95, 43, 87, 61], offsets: [143, 196, 93, 75, 50, 84, 45, 39, 95, 35]},
            {id: 117, preCutWidth: 25, preCutHeight: 25, rects: [0, 0, 25, 25]},

            // character supports (platform OmNom sits on)
            { id: ResourceId.IMG_CHAR_SUPPORTS, rects: [0, 0, 291, 302, 0, 302, 363, 409, 363, 302, 383, 309, 0, 711, 405, 492, 405, 711, 374, 330, 0, 1203, 350, 334, 0, 1537, 314, 335, 350, 1203, 326, 334, 291, 0, 339, 288, 630, 0, 275, 162, 350, 1537, 372, 317], offsets: [173, 274, 137, 156, 133, 207, 107, 104, 134, 240, 148, 242, 152, 237, 154, 244, 145, 259, 181, 392, 123, 236], preCutWidth: 640, preCutHeight: 640 },

            // vinyl record
            { id: ResourceId.IMG_OBJ_VINIL, rects: [1, 1, 1064, 1064, 1067, 1, 532, 495, 1601, 1, 145, 286, 1601, 289, 38, 38, 1748, 1, 204, 174, 1748, 177, 183, 154], offsets: [55, 154, 55, 686, 442, 543, 568, 667, 484, 1083, 493, 1093], preCutWidth: 1174, preCutHeight: 1498 },

            // background images
            { id: ResourceId.IMG_BGR_01_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2305},
            { id: ResourceId.IMG_BGR_01_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 708], offsets: [0, 896], preCutWidth: 2048, preCutHeight: 2305},
            { id: ResourceId.IMG_BGR_02_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_02_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 874], offsets: [0, 835], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_03_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_03_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1052], offsets: [0, 756], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_04_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_04_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1072], offsets: [0, 768], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_05_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_05_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1124], offsets: [0, 624], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_06_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866},
            { id: ResourceId.IMG_BGR_06_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 948], offsets: [0, 760], preCutWidth: 2048, preCutHeight: 1866},
            { id: ResourceId.IMG_BGR_07_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_07_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 886], offsets: [0, 881], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_08_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152, 0, 0, 4, 3], offsets: [0, 0, 1028.5, 581], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_08_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 642, 0, 0, 4, 3], offsets: [0, 889, 1028.5, 581], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_09_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_09_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 858], offsets: [0, 780], preCutWidth: 2048, preCutHeight: 1638},
            { id: ResourceId.IMG_BGR_10_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_10_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 887], offsets: [0, 792], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_11_P1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1153], offsets: [0, -1], preCutWidth: 2048, preCutHeight: 2304},
            { id: ResourceId.IMG_BGR_11_P2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 980], offsets: [0, 802], preCutWidth: 2048, preCutHeight: 2304},

            { id: ResourceId.IMG_BGR_IE, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866},

            { id: ResourceId.IMG_TIME_BGR_1, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866},
            { id: ResourceId.IMG_TIME_BGR_2, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866},
            { id: ResourceId.IMG_TIME_BGR_3, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866},
            { id: ResourceId.IMG_TIME_BGR_4, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866},
            { id: ResourceId.IMG_TIME_BGR_5, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866},
            { id: ResourceId.IMG_TIME_BGR_6, resScale: 1.25, skipOffsetAdjustment: true, rects: [0, 0, 2048, 1152], offsets: [0, 0], preCutWidth: 2048, preCutHeight: 1866}
        ];

        return RES_INFO_2560;
    }
);

define('resources/ResEntry',
    [],
    function () {

        /**
         * ResEntry constructor
         * @param path {string} location of the file
         * @param type {ResourceType} resource type (IMAGE, SOUND, etc)
         */
        var ResEntry = function (path, type) {
            this.path = path;
            this.type = type;
        };

        return ResEntry;
    }
);


define('resources/ResourceType',
    [],
    function () {

        /**
         * @enum {number}
         */
        var ResourceType = {
            IMAGE: 0,
            SOUND: 1,
            FONT: 2,
            STRINGS: 3
        };

        return ResourceType;
    }
);


define('resources/ResData',
    [
        'resources/ResEntry',
        'resources/ResourceType',
        'resources/ResourceId'
    ],
    function (ResEntry, ResourceType, ResourceId) {

        var RES_DATA = [];


        RES_DATA[ResourceId.IMG_DEFAULT] = new ResEntry("zeptolab.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_LOADERBAR_FULL] = new ResEntry("loaderbar_full.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_CHILLINGO] = new ResEntry("Default.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_BUTTON_DEFAULT] = new ResEntry("menu_button_default.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.FNT_BIG_FONT] = new ResEntry("big_font.png", ResourceType.FONT);
        RES_DATA[ResourceId.FNT_SMALL_FONT] = new ResEntry("small_font.png", ResourceType.FONT);
        RES_DATA[ResourceId.IMG_MENU_LOADING] = new ResEntry("menu_loading.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.SND_TAP] = new ResEntry("tap", ResourceType.SOUND);
        RES_DATA[ResourceId.STR_MENU] = new ResEntry("menu_strings.xml", ResourceType.STRINGS);
        RES_DATA[ResourceId.SND_BUTTON] = new ResEntry("button", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_BUBBLE_BREAK] = new ResEntry("bubble_break", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_BUBBLE] = new ResEntry("bubble", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_CANDY_BREAK] = new ResEntry("candy_break", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_MONSTER_CHEWING] = new ResEntry("monster_chewing", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_MONSTER_CLOSE] = new ResEntry("monster_close", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_MONSTER_OPEN] = new ResEntry("monster_open", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_MONSTER_SAD] = new ResEntry("monster_sad", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_RING] = new ResEntry("ring", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_ROPE_BLEAK_1] = new ResEntry("rope_bleak_1", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_ROPE_BLEAK_2] = new ResEntry("rope_bleak_2", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_ROPE_BLEAK_3] = new ResEntry("rope_bleak_3", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_ROPE_BLEAK_4] = new ResEntry("rope_bleak_4", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_ROPE_GET] = new ResEntry("rope_get", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_STAR_1] = new ResEntry("star_1", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_STAR_2] = new ResEntry("star_2", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_STAR_3] = new ResEntry("star_3", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_ELECTRIC] = new ResEntry("electric", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PUMP_1] = new ResEntry("pump_1", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PUMP_2] = new ResEntry("pump_2", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PUMP_3] = new ResEntry("pump_3", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PUMP_4] = new ResEntry("pump_4", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_SPIDER_ACTIVATE] = new ResEntry("spider_activate", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_SPIDER_FALL] = new ResEntry("spider_fall", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_SPIDER_WIN] = new ResEntry("spider_win", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_WHEEL] = new ResEntry("wheel", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_WIN] = new ResEntry("win", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_GRAVITY_OFF] = new ResEntry("gravity_off", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_GRAVITY_ON] = new ResEntry("gravity_on", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_CANDY_LINK] = new ResEntry("candy_link", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_BOUNCER] = new ResEntry("bouncer", ResourceType.SOUND);
        RES_DATA[ResourceId.IMG_MENU_BGR] = new ResEntry("menu_bgr.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_BUTTON_CRYSTAL] = new ResEntry("menu_button_crystal.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_POPUP] = new ResEntry("menu_popup.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_BUTTON_CRYSTAL_ICON] = new ResEntry("menu_button_crystal_icon.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_LOGO] = new ResEntry("menu_logo.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_LEVEL_SELECTION] = new ResEntry("menu_level_selection.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_PACK_SELECTION] = new ResEntry("menu_pack_selection.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_EXTRA_BUTTONS] = new ResEntry("menu_extra_buttons.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_EXTRA_BUTTONS_EN] = new ResEntry("menu_extra_buttons_en.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_BUTTON_SHORT] = new ResEntry("menu_button_short.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_HUD_BUTTONS] = new ResEntry("hud_buttons.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_CANDY_01] = new ResEntry("obj_candy_01.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_SPIDER] = new ResEntry("obj_spider.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_CONFETTI_PARTICLES] = new ResEntry("confetti_particles.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_PAUSE] = new ResEntry("menu_pause.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_RESULT] = new ResEntry("menu_result.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.FNT_FONT_NUMBERS_BIG] = new ResEntry("font_numbers_big.png", ResourceType.FONT);
        RES_DATA[ResourceId.IMG_HUD_BUTTONS_EN] = new ResEntry("hud_buttons_en.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_MENU_RESULT_EN] = new ResEntry("menu_result_en.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_STAR_DISAPPEAR] = new ResEntry("obj_star_disappear.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_BUBBLE_FLIGHT] = new ResEntry("obj_bubble_flight.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_BUBBLE_POP] = new ResEntry("obj_bubble_pop.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_HOOK_AUTO] = new ResEntry("obj_hook_auto.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_SPIKES_04] = new ResEntry("obj_spikes_04.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_BUBBLE_ATTACHED] = new ResEntry("obj_bubble_attached.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_HOOK_01] = new ResEntry("obj_hook_01.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_HOOK_02] = new ResEntry("obj_hook_02.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_STAR_IDLE] = new ResEntry("obj_star_idle.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_HUD_STAR] = new ResEntry("hud_star.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_SPIKES_03] = new ResEntry("obj_spikes_03.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_SPIKES_02] = new ResEntry("obj_spikes_02.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_SPIKES_01] = new ResEntry("obj_spikes_01.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_CHAR_ANIMATIONS] = new ResEntry("char_animations.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_HOOK_REGULATED] = new ResEntry("obj_hook_regulated.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_ELECTRODES] = new ResEntry("obj_electrodes.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_HOOK_MOVABLE] = new ResEntry("obj_hook_movable.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_PUMP] = new ResEntry("obj_pump.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_TUTORIAL_SIGNS] = new ResEntry("tutorial_signs.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_SOCKS] = new ResEntry("obj_hat.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_BOUNCER_01] = new ResEntry("obj_bouncer_01.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_BOUNCER_02] = new ResEntry("obj_bouncer_02.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.SND_MENU_MUSIC] = new ResEntry("menu_music", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_GAME_MUSIC] = new ResEntry("game_music", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_GAME_MUSIC2] = new ResEntry("game_music2", ResourceType.SOUND);
        RES_DATA[ResourceId.IMG_DRAWING_HIDDEN] = new ResEntry("obj_drawing_hidden.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_ROTATABLE_SPIKES_01] = new ResEntry("obj_rotatable_spikes_01.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_ROTATABLE_SPIKES_02] = new ResEntry("obj_rotatable_spikes_02.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_ROTATABLE_SPIKES_03] = new ResEntry("obj_rotatable_spikes_03.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_ROTATABLE_SPIKES_04] = new ResEntry("obj_rotatable_spikes_04.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_ROTATABLE_SPIKES_BUTTON] = new ResEntry("obj_rotatable_spikes_button.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_BEE_HD] = new ResEntry("obj_bee_hd.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_POLLEN_HD] = new ResEntry("obj_pollen_hd.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.SND_SPIKE_ROTATE_IN] = new ResEntry("spike_rotate_in", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_SPIKE_ROTATE_OUT] = new ResEntry("spike_rotate_out", ResourceType.SOUND);
        RES_DATA[ResourceId.IMG_CHAR_SUPPORTS] = new ResEntry("char_supports.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_OBJ_VINIL] = new ResEntry("obj_vinil.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.SND_SCRATCH_IN] = new ResEntry("scratch_in", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_SCRATCH_OUT] = new ResEntry("scratch_out", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_BUZZ] = new ResEntry("buzz", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_TELEPORT] = new ResEntry("teleport", ResourceType.SOUND);


        RES_DATA[ResourceId.IMG_BGR_01_P1] = new ResEntry("bgr_01_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_01_P2] = new ResEntry("bgr_01_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_02_P1] = new ResEntry("bgr_02_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_02_P2] = new ResEntry("bgr_02_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_03_P1] = new ResEntry("bgr_03_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_03_P2] = new ResEntry("bgr_03_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_04_P1] = new ResEntry("bgr_04_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_04_P2] = new ResEntry("bgr_04_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_05_P1] = new ResEntry("bgr_05_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_05_P2] = new ResEntry("bgr_05_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_06_P1] = new ResEntry("bgr_06_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_06_P2] = new ResEntry("bgr_06_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_07_P1] = new ResEntry("bgr_07_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_07_P2] = new ResEntry("bgr_07_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_08_P1] = new ResEntry("bgr_08_p1.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_08_P2] = new ResEntry("bgr_08_p2.png", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_09_P1] = new ResEntry("bgr_09_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_09_P2] = new ResEntry("bgr_09_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_10_P1] = new ResEntry("bgr_10_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_10_P2] = new ResEntry("bgr_10_p2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_11_P1] = new ResEntry("bgr_11_p1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_BGR_11_P2] = new ResEntry("bgr_11_p2.jpg", ResourceType.IMAGE);

        // IE box background
        RES_DATA[ResourceId.IMG_BGR_IE] = new ResEntry("bgr_ie.jpg", ResourceType.IMAGE);

        RES_DATA[ResourceId.IMG_TIME_BGR_1] = new ResEntry("bgr_time1.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_TIME_BGR_2] = new ResEntry("bgr_time2.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_TIME_BGR_3] = new ResEntry("bgr_time3.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_TIME_BGR_4] = new ResEntry("bgr_time4.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_TIME_BGR_5] = new ResEntry("bgr_time5.jpg", ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_TIME_BGR_6] = new ResEntry("bgr_time6.jpg", ResourceType.IMAGE);

        RES_DATA[ResourceId.IMG_CAESAR_ANIMATIONS_1] = new ResEntry('Caesar_animations_1_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_CAESAR_ANIMATIONS_2] = new ResEntry('Caesar_animations_2_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_CAESAR_ANIMATIONS_3] = new ResEntry('Caesar_animations_3_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_CAESAR_ANIMATIONS_4] = new ResEntry('Caesar_animations_4_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PAINTER_ANIMATIONS_1] = new ResEntry('Painter_animations_1_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PAINTER_ANIMATIONS_2] = new ResEntry('Painter_animations_2_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PAINTER_ANIMATIONS_3] = new ResEntry('Painter_animations_3_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PAINTER_ANIMATIONS_4] = new ResEntry('Painter_animations_4_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PHARAOH_ANIMATIONS_1] = new ResEntry('Pharaoh_animations_1_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PHARAOH_ANIMATIONS_2] = new ResEntry('Pharaoh_animations_2_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PHARAOH_ANIMATIONS_3] = new ResEntry('Pharaoh_animations_3_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PHARAOH_ANIMATIONS_4] = new ResEntry('Pharaoh_animations_4_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PIRATE_ANIMATIONS_1] = new ResEntry('Pirate_animations_1_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PIRATE_ANIMATIONS_2] = new ResEntry('Pirate_animations_2_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PIRATE_ANIMATIONS_3] = new ResEntry('Pirate_animations_3_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PIRATE_ANIMATIONS_4] = new ResEntry('Pirate_animations_4_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PREHISTORIC_ANIMATIONS_1] = new ResEntry('Prehistoric_animations_1_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PREHISTORIC_ANIMATIONS_2] = new ResEntry('Prehistoric_animations_2_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PREHISTORIC_ANIMATIONS_3] = new ResEntry('Prehistoric_animations_3_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_PREHISTORIC_ANIMATIONS_4] = new ResEntry('Prehistoric_animations_4_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_VIKING_ANIMATIONS_1] = new ResEntry('Viking_animations_1_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_VIKING_ANIMATIONS_2] = new ResEntry('Viking_animations_2_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_VIKING_ANIMATIONS_3] = new ResEntry('Viking_animations_3_hd.png', ResourceType.IMAGE);
        RES_DATA[ResourceId.IMG_VIKING_ANIMATIONS_4] = new ResEntry('Viking_animations_4_hd.png', ResourceType.IMAGE);

        RES_DATA[ResourceId.SND_CANDY_HIT] = new ResEntry("candy_hit", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PREHISTORIC_MONSTER_CHEWING] = new ResEntry("prehistoric_monster_chewing", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PREHISTORIC_MONSTER_CLOSE] = new ResEntry("prehistoric_monster_close", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PREHISTORIC_MONSTER_OPEN] = new ResEntry("prehistoric_monster_open", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_PREHISTORIC_MONSTER_SAD] = new ResEntry("prehistoric_monster_sad", ResourceType.SOUND);
        RES_DATA[ResourceId.SND_TIME_MENU_MUSIC] = new ResEntry("time_menu", ResourceType.SOUND);

        RES_DATA[ResourceId.IMG_TIME_STANDS] = new ResEntry('time-stands.png', ResourceType.IMAGE);

        return RES_DATA;
    }
);
define('resources/ResScale',
    ['core/Vector', 'core/Rectangle'],
    function (Vector, Rectangle) {

        // scales a number and rounds to 4 decimals places of precision
        var scaleNumber = function (value, scale) {
            return Math.round(value * scale * 10000) / 10000;
        };

        var ResScaler = {
            scaleResourceInfos: function (infos, canvasScale) {

                // the canvas scale is ratio of the canvas target size compared
                // to the resolution the original assets were designed for. The
                // resource scale handles resources that were designed for a
                // different resolution. Most of the assets were designed for
                // 2560 x 1440 but a few of the later sprites target 2048 x 1080

                var resScale, i, len, info;
                for (i = 0, len = infos.length; i < len; i++) {
                    info = infos[i];
                    resScale = info.resScale || 1;
                    this.scaleResourceInfo(infos[i], scaleNumber(canvasScale, resScale));
                }
            },

            scaleResourceInfo: function (info, scale) {
                if (info.charOffset) {
                    info.charOffset = scaleNumber(info.charOffset, scale);
                }
                if (info.lineOffset) {
                    info.lineOffset = scaleNumber(info.lineOffset, scale);
                }
                if (info.spaceWidth) {
                    info.spaceWidth = scaleNumber(info.spaceWidth, scale);
                }
                if (info.preCutWidth) {
                    info.preCutWidth = Math.ceil(scaleNumber(info.preCutWidth, scale));
                }
                if (info.preCutHeight) {
                    info.preCutHeight = Math.ceil(scaleNumber(info.preCutHeight, scale));
                }
                if (info.rects) {
                    info.originalRects = this.parseOriginalRects(info.rects);
                    var extra = false;
                    
                    info.rects = this.scaleRects(info.originalRects, scale, info.id);
                }
                info.adjustmentMaxX = 0;
                info.adjustmentMaxX = 0;
                if (info.offsets) {
                    info.originalOffsets = this.parseOriginalOffsets(info.offsets);
                    this.scaleOffsets(info, scale);
                }
            },

            parseOriginalRects: function (rects) {
                var i = 0,
                    len = rects.length,
                    originalRects = [];
                while (i < len) {
                    var rect = new Rectangle(
                        rects[i++],
                        rects[i++],
                        rects[i++],
                        rects[i++]);
                    originalRects.push(rect);
                }
                return originalRects;
            },

            scaleRects: function (originalRects, scale, id) {
                var PADDING = 4,
                    newRects = [],
                    maxWidth = 0,
                    currentY = 0,
                    numRects = originalRects.length;

                if (id === 5) {
                    PADDING = 11.25;
                    scale *= 1.6;
                }

                if (id === 68) {
                    PADDING = 2.78;
                    scale *= 1.5;
                }

                for (var j = 0; j < numRects; j++) {
                    var oldRect = originalRects[j],
                        newRect = new Rectangle(
                            0,
                            currentY,
                            scaleNumber(oldRect.w, scale),
                            scaleNumber(oldRect.h, scale));

                    newRects.push(newRect);
                    currentY += Math.ceil(newRect.h) + PADDING;
                }
                return newRects;
            },

            parseOriginalOffsets: function (offsets) {
                var i = 0,
                    len = offsets.length,
                    originalOffsets = [];
                while (i < len) {
                    var rect = new Vector(
                        offsets[i++],
                        offsets[i++]);
                    originalOffsets.push(rect);
                }
                return originalOffsets;
            },

            scaleOffsets: function (info, scale) {

                // Previously we chopped the decimal portion of offsets and then
                // offset the image by that amount when scaling the sprite sheet.
                // That allows us to always have round offsets to avoid twitchy
                // pixels when we snap to pixel boundaries when drawing for perf.
                // Unfortunately, the GDI+ sprite resizer can't accurately handle
                // the floating point offsets so we'll disable offset adjust for
                // now. Instead we'll use high precision drawing coords (instead
                // of rounding) for moving and animated elements.
                var ALLOW_OFFSET_ADJUSTMENT = false;

                var adjustments = [], // how much to offset the offsets :)
                    oldOffsets = info.originalOffsets,
                    newOffsets = [],
                    scaledOffset, adjustment, i, len;
                for (i = 0, len = oldOffsets.length; i < len; i++) {
                    scaledOffset = oldOffsets[i].copy();
                    scaledOffset.x = scaleNumber(scaledOffset.x, scale);
                    scaledOffset.y = scaleNumber(scaledOffset.y, scale);

                    if (!ALLOW_OFFSET_ADJUSTMENT || info.skipOffsetAdjustment) {
                        // the backgrounds use offsets to place other elements, so
                        // we don't always want to pixel adjust the offset because
                        // the c# resizer can only safely adjust the current image.
                        adjustment = new Vector(0, 0);
                    }
                    else {
                        // find the amount we need to adjust the offset by
                        adjustment = new Vector(
                            scaleNumber(scaledOffset.x - scaledOffset.x | 0, 1),
                            scaleNumber(scaledOffset.y - scaledOffset.y | 0, 1));

                        // remember the biggest adjust we made
                        info.adjustmentMaxX = Math.max(info.adjustmentMaxX, Math.ceil(adjustment.x));
                        info.adjustmentMaxY = Math.max(info.adjustmentMaxY, Math.ceil(adjustment.y));

                        // chop off the decimal portion of the offset
                        scaledOffset.x = scaledOffset.x | 0;
                        scaledOffset.y = scaledOffset.y | 0;
                    }

                    adjustments.push(adjustment);
                    newOffsets.push(scaledOffset);
                }

                info.offsets = newOffsets;
                info.offsetAdjustments = adjustments;
                delete info.originalOffsets;
            }
        };

        return ResScaler;
    }
);
define('boxes',[], function () {
    var boxes = [
        {levels: [
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 156, y: 139},
                {name: 100, x: 159, y: 51, length: 90, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 2, x: 161, y: 427},
                {name: 3, x: 162, y: 230, timeout: -1},
                {name: 3, x: 161, y: 295, timeout: -1},
                {name: 3, x: 161, y: 361, timeout: -1},
                {name: 4, x: 197, y: 41, locale: "en", text: "Slide across to cut the rope", width: 180},
                {name: 4, x: 202, y: 290, locale: "en",
                    text: "Deliver candy to Om Nom", width: 200},
                {name: 5, x: 57, y: 119, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 8, x: 231, y: 416, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 109, y: 119, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 119, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 119, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 119, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 14, x: 73, y: 145, locale: "en", moveSpeed: 100, rotateSpeed: 100, special: 2}
            ], ru: [
                {name: 14,
                    x: 73, y: 153, locale: "ru", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 130, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 57, y: 130, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 130, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 130, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 130, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 177, y: 40, locale: "ru", text: "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0438\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u0443\u0440\u0441\u043e\u0440\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0440\u0435\u0437\u0430\u0442\u044c \u0432\u0435\u0440\u0435\u0432\u043a\u0443",
                    width: 300},
                {name: 8, x: 231, y: 415, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 216, y: 280, locale: "ru", text: "\u0414\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043b\u0435\u0434\u0435\u043d\u0435\u0446 \u0410\u043c \u041d\u044f\u043c\u0443", width: 180}
            ], fr: [
                {name: 14, x: 73, y: 150, locale: "fr", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 124, locale: "fr", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 57, y: 124, locale: "fr", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 124, locale: "fr", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 213, y: 124, locale: "fr", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 124, locale: "fr", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 177, y: 31, locale: "fr", text: "Clique ou fais glisser pour couper la corde", width: 200},
                {name: 8, x: 231, y: 415, locale: "fr", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 196, y: 290, locale: "fr", text: "Donne un bonbon \u00e0 Om Nom", width: 200}
            ], de: [
                {name: 14, x: 73, y: 155, locale: "de", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "de", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "de", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "de", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "de", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "de", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 15, locale: "de", text: "Klicke oder ziehe, um das Seil zu zerschneiden", width: 200},
                {name: 8, x: 231, y: 416, locale: "de", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 210, y: 312, locale: "de", text: "Versorg Om Nom mit Bonbons", width: 200}
            ], es: [
                {name: 14, x: 73, y: 155, locale: "es", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "es", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "es", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "es", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "es", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "es", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 45, locale: "es", text: "Haz clic o desliza para cortar la cuerda", width: 200},
                {name: 8, x: 231, y: 416, locale: "es", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 210, y: 312, locale: "es", text: "Haz llegar el caramelo hasta Om Nom.", width: 200}
            ], br: [
                {name: 14, x: 73, y: 155, locale: "br", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "br", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "br", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "br", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "br", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "br", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 45, locale: "br", text: "Deslize o dedo para cortar a corda", width: 200},
                {name: 8, x: 231, y: 416, locale: "br", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 200, y: 312, locale: "br", text: "Entregue os doces para Om Nom.", width: 200}
            ], ca: [
                {name: 14, x: 73, y: 155, locale: "es", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "es", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "ca", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "ca", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "ca", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "ca", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 45, locale: "ca", text: "Llisca el dit per tallar la corda", width: 200},
                //{name: 8, x: 231, y: 416, locale: "ca", moveSpeed: 100, rotateSpeed: 100},
            ], it: [
                {name: 14, x: 73, y: 155, locale: "it", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "it", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "it", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "it", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "it", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "it", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 20, locale: "it", text: "Scorri per tagliare la corda", width: 200},
                {name: 8, x: 231, y: 436, locale: "it", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 200, y: 305, locale: "it", text: "Porta un bonbon a Om Nom", width: 200}
            ], nl: [
                {name: 14, x: 73, y: 155, locale: "nl", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "nl", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "nl", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "nl", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "nl", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "nl", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 25, locale: "nl", text: "Sleep om het touw door te snijden", width: 200},
                {name: 8, x: 231, y: 416, locale: "nl", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 230, y: 312, locale: "nl", text: "Lever het snoepje af bij Om Nom", width: 200}
            ], ko: [
                {name: 14, x: 73, y: 155, locale: "ko", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "ko", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "ko", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "ko", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "ko", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "ko", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 65, locale: "ko", text: "손가락으로 줄을 *자르세요.", width: 200},
                {name: 8, x: 231, y: 416, locale: "ko", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 230, y: 312, locale: "ko", text: "Om Nom에게 사탕을 *먹이세요.", width: 200}
            ], zh: [
                {name: 14, x: 73, y: 155, locale: "zh", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "zh", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "zh", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "zh", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "zh", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "zh", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 65, locale: "zh", text: "滑动你的手指 即可剪断绳索", width: 200},
                {name: 8, x: 231, y: 416, locale: "zh", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 230, y: 342, locale: "zh", text: "将糖果送给 Om Nom", width: 200}
            ], ja: [
                {name: 14, x: 73, y: 155, locale: "ja", moveSpeed: 100, rotateSpeed: 100, special: 2},
                {name: 5, x: 109, y: 129, locale: "ja", moveSpeed: 100,
                    rotateSpeed: 100},
                {name: 5, x: 57, y: 129, locale: "ja", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 161, y: 129, locale: "ja", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 213, y: 129, locale: "ja", moveSpeed: 100, rotateSpeed: 100},
                {name: 5, x: 265, y: 129, locale: "ja", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 175, y: 60, locale: "ja", text: "指を スライド させて ロープを 切り しましょう", width: 200},
                {name: 8, x: 231, y: 416, locale: "ja", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 240, y: 342, locale: "ja", text: "Om Nomに キャンディを あげ ましょう", width: 200}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 69, y: 203},
                {name: 100, x: 52, y: 70, length: 90, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 162, y: 69, length: 170, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 2, x: 264, y: 419},
                {name: 3, x: 54, y: 250, timeout: -1},
                {name: 3, x: 53, y: 369, timeout: -1},
                {name: 3, x: 280, y: 250, timeout: -1},
                {name: 100, x: 275,
                    y: 69, length: 320, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 4, x: 113, y: 245, locale: "en", text: "Collect as many stars as you can", width: 200},
                {name: 13, x: 113, y: 321, locale: "en", moveSpeed: 100, rotateSpeed: 100}
            ], ru: [
                {name: 13, x: 80, y: 290, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 80, y: 228, locale: "ru", text: "\u0421\u0442\u0430\u0440\u0430\u0439\u0442\u0435\u0441\u044c \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0432\u0435\u0437\u0434",
                    width: 210}
            ], fr: [
                {name: 4, x: 137, y: 245, locale: "fr", text: "Collecte autant d'\u00e9toiles que tu le peux", width: 230},
                {name: 13, x: 113, y: 321, locale: "fr", moveSpeed: 100, rotateSpeed: 100}
            ], de: [
                {name: 4, x: 66, y: 265, locale: "de", text: "Sammle m\u00f6glichst viele Sternchen", width: 170},
                {name: 13, x: 66, y: 321, locale: "de", moveSpeed: 100, rotateSpeed: 100}
            ], es: [
                {name: 4, x: 66, y: 265, locale: "es", text: "Consigue tantas estrellas como puedas.", width: 170},
                {name: 13, x: 66, y: 321, locale: "es", moveSpeed: 100, rotateSpeed: 100}
            ], br: [
                {name: 4, x: 66, y: 265, locale: "br", text: "Colete o máximo de estrelas possível", width: 170},
                {name: 13, x: 66, y: 321, locale: "br", moveSpeed: 100, rotateSpeed: 100}
            ], ca: [
                {name: 4, x: 56, y: 265, locale: "ca", text: "Aconsegueix totes les estrelles que puguis", width: 170},
                {name: 13, x: 36, y: 321, locale: "ca", moveSpeed: 100, rotateSpeed: 100}
            ], it: [
                {name: 4, x: 56, y: 265, locale: "it", text: "Recupera più stelle possibili", width: 170},
                {name: 13, x: 36, y: 321, locale: "it", moveSpeed: 100, rotateSpeed: 100}
            ], nl: [
                {name: 4, x: 56, y: 265, locale: "nl", text: "Verzamel zoveel mogelijk sterren", width: 170},
                {name: 13, x: 36, y: 321, locale: "nl", moveSpeed: 100, rotateSpeed: 100}
            ], ko: [
                {name: 4, x: 56, y: 265, locale: "ko", text: "별을 최대한 *많이 모으세요.", width: 170},
                {name: 13, x: 36, y: 321, locale: "ko", moveSpeed: 100, rotateSpeed: 100}
            ], zh: [
                {name: 4, x: 56, y: 295, locale: "zh", text: "尽可能多地收集星星", width: 200},
                {name: 13, x: 36, y: 321, locale: "zh", moveSpeed: 100, rotateSpeed: 100}
            ], ja: [
                {name: 4, x: 46, y: 265, locale: "ja", text: "できるだけ 多くの スターを 獲得 しましょう", width: 220},
                {name: 13, x: 36, y: 321, locale: "ja", moveSpeed: 100, rotateSpeed: 100}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, special: 1, ropePhysicsSpeed: 1}
            ], objects: [
                {name: 52, x: 158, y: 187},
                {name: 2, x: 262, y: 362},
                {name: 100, x: 161, y: 315,
                    length: 93, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 100, x: 289, y: 186, length: 105, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 100, x: 162, y: 57, length: 93, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 100, x: 33, y: 186, length: 105, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 3, x: 159, y: 226, timeout: -1},
                {name: 3, x: 32, y: 312, timeout: -1},
                {name: 3, x: 161, y: 434, timeout: -1}
            ]},
            {settings: [
                {name: 0,
                    gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 101, y: 238},
                {name: 2, x: 219, y: 431},
                {name: 3, x: 102, y: 326, timeout: -1},
                {name: 3, x: 217, y: 209, timeout: -1},
                {name: 3, x: 217, y: 74, timeout: -1},
                {name: 100, x: 96, y: 139, length: 90, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 54, x: 99, y: 391},
                {name: 100, x: 219, y: 252, length: 140, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 4,
                    x: -25, y: -5, locale: "en", text: "Click to pop the bubble", width: 200, special: 1},
                {name: 4, x: 187, y: 263, locale: "en", text: "The bubble will lift the candy up", width: 200},
                {name: 8, x: 167, y: 391, locale: "en", angle: 15, moveSpeed: 100, rotateSpeed: 100},
                {name: 9, x: 218, y: 78, locale: "en", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 102, x: 269, y: 426, angle: 10, drawing: 1}
            ], ru: [
                {name: 8, x: 167, y: 391, locale: "ru", angle: 20, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 158, y: 225, locale: "ru", text: "\u041f\u0443\u0437\u044b\u0440\u044c \u043f\u043e\u0434\u044b\u043c\u0435\u0442 \u043b\u0435\u0434\u0435\u043d\u0435\u0446 \u0432\u0432\u0435\u0440\u0445",
                    width: 200},
                {name: 4, x: -80, y: 0, locale: "ru", text: "\u041d\u0430\u0436\u043c\u0438\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043b\u043e\u043f\u043d\u0443\u0442\u044c \u043f\u0443\u0437\u044b\u0440\u044c", width: 250, special: 1},
                {name: 9, x: 218, y: 78, locale: "ru", moveSpeed: 100, rotateSpeed: 100, special: 1}
            ], fr: [
                {name: 4, x: 135, y: 241, locale: "fr", text: "La bulle fera monter le bonbon", width: 220},
                {name: 8, x: 167, y: 386, locale: "fr", angle: 15, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -10, y: -10, locale: "fr", text: "\u00c9clate la bulle avec ton doigt",
                    width: 200, special: 1},
                {name: 9, x: 217, y: 78, locale: "fr", moveSpeed: 100, rotateSpeed: 100, special: 1}
            ], de: [
                {name: 4, x: -20, y: 15, locale: "de", text: "Klicke, um die Seifenblase platzen zu lassen", width: 200, special: 1},
                {name: 9, x: 217, y: 77, locale: "de", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 168, y: 248, locale: "de", text: "Die Seifenblase l\u00e4sst den Bonbon schweben", width: 250},
                {name: 8, x: 166, y: 390, locale: "de", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], es: [
                {name: 4, x: -60, y: 15, locale: "es", text: "Haz clic sobre la burbuja para que estalle", width: 220, special: 1},
                {name: 9, x: 217, y: 77, locale: "es", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 168, y: 258, locale: "es", text: "Las burbujas hacen que el caramelo se eleve.", width: 250},
                {name: 8, x: 166, y: 390, locale: "es", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], br: [
                {name: 4, x: -60, y: 15, locale: "br", text: "Clique para estourar a bolha", width: 220, special: 1},
                {name: 9, x: 217, y: 77, locale: "br", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 168, y: 278, locale: "br", text: "A bolha vai erguer os doces.", width: 250},
                {name: 8, x: 166, y: 390, locale: "br", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], ca: [
                {name: 4, x: -35, y: 10, locale: "ca", text: "Fes clic per fer explotar la bombolla", width: 200, special: 1},
                {name: 9, x: 217, y: 77, locale: "ca", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 168, y: 248, locale: "ca", text: "La bombolla llançarà les llaminadures cap amunt", width: 250},
                {name: 8, x: 166, y: 390, locale: "ca", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], it: [
                {name: 4, x: -35, y: -15, locale: "it", text: "Clicca per far esplodere la bolla", width: 200, special: 1},
                {name: 9, x: 217, y: 77, locale: "it", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 138, y: 238, locale: "it", text: "La bolla solleverà il bonbon", width: 250},
                {name: 8, x: 166, y: 390, locale: "it", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], nl: [
                {name: 4, x: -35, y: -15, locale: "nl", text: "Klik om de bel door te prikken", width: 200, special: 1},
                {name: 9, x: 217, y: 77, locale: "nl", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 168, y: 238, locale: "nl", text: "Het snoepje drijft in de bel omhoog", width: 250},
                {name: 8, x: 166, y: 390, locale: "nl", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], ko: [
                {name: 4, x: -35, y: 15, locale: "ko", text: "클릭해서 비눗방울을 *터뜨리세요.", width: 200, special: 1},
                {name: 9, x: 217, y: 77, locale: "ko", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 168, y: 318, locale: "ko", text: "비눗방울이 사탕을 *띄워줘요.", width: 250},
                {name: 8, x: 166, y: 390, locale: "ko", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], zh: [
                {name: 4, x: -35, y: 45, locale: "zh", text: "点击戳破气泡", width: 200, special: 1},
                {name: 9, x: 217, y: 77, locale: "zh", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 148, y: 308, locale: "zh", text: "泡泡将提起糖果", width: 250},
                {name: 8, x: 166, y: 390, locale: "zh", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ], ja: [
                {name: 4, x: -35, y: 0, locale: "ja", text: "クリックして バブルを 破裂 させる", width: 200, special: 1},
                {name: 9, x: 217, y: 77, locale: "ja", moveSpeed: 100, rotateSpeed: 10, special: 1},
                {name: 4, x: 168, y: 210, locale: "ja", text: "泡で キャンディを 持ち上げる ことも できます", width: 250},
                {name: 8, x: 166, y: 390, locale: "ja", angle: 15, moveSpeed: 100, rotateSpeed: 100}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1,
                    ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 266, y: 161},
                {name: 100, x: 155, y: 250, length: 95, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 153, y: 110, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 2, x: 155, y: 430},
                {name: 3, x: 37, y: 264, timeout: -1},
                {name: 3, x: 152, y: 70, timeout: -1},
                {name: 3, x: 276, y: 264, timeout: -1},
                {name: 54, x: 252, y: 367},
                {name: 4, x: -100, y: 280, locale: "en", text: "You can restart the level by pressing the",
                    width: 220},
                {name: 12, x: -30, y: 485, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -15, y: 430, locale: "en", text: "button", width: 100}
            ], ru: [
                {name: 4, x: -100, y: 304, locale: "ru", text: "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0443\u0440\u043e\u0432\u0435\u043d\u044c, \u043d\u0430\u0436\u0430\u0432 \u043a\u043d\u043e\u043f\u043a\u0443", width: 200},
                {name: 12, x: 85, y: 436, locale: "ru", moveSpeed: 100, rotateSpeed: 100}
            ], fr: [
                {name: 4,
                    x: -100, y: 280, locale: "fr", text: "Tu peux recommencer le niveau en touchant le bouton", width: 180},
                {name: 12, x: -70, y: 481, locale: "fr", moveSpeed: 100, rotateSpeed: 100}
            ], de: [
                {name: 12, x: -90, y: 440, locale: "de", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -100, y: 282, locale: "de", text: "Lass den Bonbon nicht aus der Schachtel", width: 200}
            ], es: [
                {name: 12, x: -52, y: 423, locale: "es", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -100, y: 282, locale: "es", text: "Puedes reinciar el nivel tocando el   botón", width: 200}
            ], br: [
                {name: 12, x: -52, y: 466, locale: "br", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -100, y: 282, locale: "br", text: "Você pode reiniciar o nível, pressionando o    botão", width: 200}
            ], ca: [
                {name: 12, x: -60, y: 390, locale: "ca", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -130, y: 282, locale: "ca", text: "Pots reiniciar el nivell prement el     botó", width: 240}
            ], it: [
                {name: 12, x: -32, y: 445, locale: "it", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -130, y: 282, locale: "it", text: "Puoi ricominciare il livello premendo il   tasto", width: 240}
            ], nl: [
                {name: 12, x: -62, y: 485, locale: "nl", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -130, y: 282, locale: "nl", text: "Je kunt het level opnieuw opstarten met een druk op de      knop", width: 240}
            ], ko: [
                {name: 12, x: -90, y: 425, locale: "ko", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -130, y: 282, locale: "ko", text: "버튼을 누르면 *해당 레벨을 처음부터 *다시 시작해요.", width: 240}
            ], zh: [
                {name: 12, x: -28, y: 385, locale: "zh", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -130, y: 282, locale: "zh", text: "按下这个按钮 就可以重新开始这一关", width: 350}
            ], ja: [
                {name: 12, x: -80, y: 430, locale: "ja", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -130, y: 282, locale: "ja", text: "このボタンで ステージを やり直す ことが できます", width: 240}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, special: 1, ropePhysicsSpeed: 1}
            ], objects: [
                {name: 52, x: 161, y: 350},
                {name: 2, x: 163, y: 67},
                {name: 100, x: 243, y: 290, length: 120, wheel: !1,
                    radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 100, x: 83, y: 401, length: 90, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 54, x: 165, y: 421},
                {name: 100, x: 165, y: 123, length: 200, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 3, x: 97, y: 294, timeout: -1},
                {name: 3, x: 166, y: 422, timeout: -1},
                {name: 3, x: 97, y: 228, timeout: -1}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52,
                    x: 64, y: 139},
                {name: 2, x: 163, y: 427},
                {name: 100, x: 162, y: 68, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 66, y: 68, length: 50, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 163, y: 163, length: 90, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 162, y: 259, length: 130, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1,
                    part: "L"},
                {name: 58, x: 161, y: 329, angle: 0, size: 2},
                {name: 58, x: 159, y: 229, angle: 0, size: 2},
                {name: 3, x: 250, y: 165, timeout: -1},
                {name: 3, x: 64, y: 276, timeout: -1},
                {name: 3, x: 248, y: 275, timeout: -1},
                {name: 10, x: 320, y: 357, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 220, y: 359, locale: "en", text: "Keep the candy away from spikes", width: 200}
            ], ru: [
                {name: 10, x: -150, y: 370, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -140, y: 302, locale: "ru", text: "\u041d\u0435 \u0434\u0430\u0439\u0442\u0435 \u043b\u0435\u0434\u0435\u043d\u0446\u0443 \u0440\u0430\u0437\u0431\u0438\u0442\u044c\u0441\u044f \u043e \u0448\u0438\u043f\u044b",
                    width: 200}
            ], fr: [
                {name: 4, x: 210, y: 340, locale: "fr", text: "Garde le bonbon loin des pointes", width: 120},
                {name: 10, x: 266, y: 346, locale: "fr", moveSpeed: 100, rotateSpeed: 100}
            ], de: [
                {name: 10, x: -150, y: 380, locale: "de", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -140, y: 327, locale: "de", text: "Pass auf, dass der Bonbon nicht in die N\u00e4he der Spikes kommt", width: 250}
            ], es: [
                {name: 10, x: -150, y: 356, locale: "es", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -160, y: 327, locale: "es", text: "¡Mantén el caramelo alejado de los pinchos!", width: 250}
            ], br: [
                {name: 10, x: -100, y: 310, locale: "br", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -160, y: 327, locale: "br", text: "Mantenha os doces longe dos espinhos", width: 250}
            ], ca: [
                {name: 10, x: -160, y: 376, locale: "ca", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -150, y: 327, locale: "ca", text: "Mantingues les llaminadures allunyades de les puntes", width: 250}
            ], it: [
                {name: 10, x: -160, y: 376, locale: "it", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -150, y: 327, locale: "it", text: "Tieni il bonbon lontano dagli aculei", width: 250}
            ], nl: [
                {name: 10, x: -130, y: 376, locale: "nl", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -150, y: 327, locale: "nl", text: "Houd het snoepje uit de buurt van pinnen", width: 250}
            ], ko: [
                {name: 10, x: -150, y: 366, locale: "ko", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -150, y: 327, locale: "ko", text: "뾰족 장애물을 *조심하세요.", width: 250}
            ], zh: [
                {name: 10, x: -160, y: 376, locale: "zh", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -130, y: 355, locale: "zh", text: "让糖果避开尖刺", width: 280}
            ], ja: [
                {name: 10, x: -170, y: 376, locale: "ja", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -150, y: 327, locale: "ja", text: "キャンディが トゲに*触れない ように*注意 しましょう", width: 250}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 514, y: 418},
                {name: 100, x: 224, y: 177,
                    length: 100, wheel: !1, gun: !1, radius: 45, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 448, y: 337, length: 100, wheel: !1, gun: !1, radius: 45, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 335, y: 256, length: 100, wheel: !1, gun: !1, radius: 45, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 52, x: 178, y: 103},
                {name: 3, x: 222, y: 269, timeout: -1},
                {name: 3, x: 448, y: 430, timeout: -1},
                {name: 3, x: 328, y: 345, timeout: -1},
                {name: 100, x: 180, y: 31, length: 50, wheel: !1,
                    gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 4, x: 310, y: 39, locale: "en", text: "Automatic ropes appear when candy gets into their area", width: 280},
                {name: 8, x: 316, y: 164, locale: "en", moveSpeed: 100, rotateSpeed: 100}
            ], ru: [
                {name: 8, x: 325, y: 173, locale: "ru", moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 254, y: 48, locale: "ru", text: "\u0410\u0432\u0442\u043e-\u0432\u0435\u0440\u0435\u0432\u043a\u0438 \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u043b\u0435\u0434\u0435\u043d\u0435\u0446 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0432 \u0438\u0445 \u0440\u0430\u0434\u0438\u0443\u0441",
                    width: 400}
            ], fr: [
                {name: 4, x: 283, y: 40, locale: "fr", text: "Les cordes automatiques apparaissent lorsqu'un bonbon arrive dans leur zone", width: 400},
                {name: 8, x: 298, y: 144, locale: "fr", moveSpeed: 100, rotateSpeed: 100}
            ], de: [
                {name: 4, x: 300, y: 47, locale: "de", text: "Sobald der Bonbon in ihrer N\u00e4he ist, erscheinen automatisch Seile", width: 390},
                {name: 8, x: 329, y: 152, locale: "de", moveSpeed: 100, rotateSpeed: 100}
            ], es: [
                {name: 4, x: 280, y: 50, locale: "es", text: "Las autocuerdas aparecen cuando el caramelo se acerca a ellas.", width: 390},
                {name: 8, x: 329, y: 160, locale: "es", moveSpeed: 100, rotateSpeed: 100}
            ], br: [
                {name: 4, x: 280, y: 45, locale: "br", text: "Cordas aparecem automaticamente quando há doces na área delas", width: 390},
                {name: 8, x: 329, y: 152, locale: "br", moveSpeed: 100, rotateSpeed: 100}
            ], ca: [
                {name: 4, x: 300, y: 50, locale: "ca", text: "Les cordes apareixen de forma automàtica quan les llaminadures entren a la seva àrea", width: 400},
                {name: 8, x: 298, y: 144, locale: "ca", moveSpeed: 100, rotateSpeed: 100}
            ], it: [
                {name: 4, x: 300, y: 40, locale: "it", text: "Le corde automatiche compaiono quando un bonbon si avvicina", width: 400},
                {name: 8, x: 298, y: 144, locale: "it", moveSpeed: 100, rotateSpeed: 100}
            ], nl: [
                {name: 4, x: 300, y: 40, locale: "nl", text: "Er verschijnen automatisch touwen wanneer het snoepje er in de buurt komt", width: 400},
                {name: 8, x: 298, y: 144, locale: "nl", moveSpeed: 100, rotateSpeed: 100}
            ], ko: [
                {name: 4, x: 280, y: 70, locale: "ko", text: "사탕이 영역에 들어오면 *자동으로 줄이 이어져요.", width: 400},
                {name: 8, x: 298, y: 144, locale: "ko", moveSpeed: 100, rotateSpeed: 100}
            ], zh: [
                {name: 4, x: 260, y: 70, locale: "zh", text: "自动绳会在糖果进入 相应区域时出现", width: 400},
                {name: 8, x: 298, y: 144, locale: "zh", moveSpeed: 100, rotateSpeed: 100}
            ], ja: [
                {name: 4, x: 300, y: 70, locale: "ja", text: "キャンディが 接近すると 自動式ロープが 現われます", width: 400},
                {name: 8, x: 298, y: 144, locale: "ja", moveSpeed: 100, rotateSpeed: 100}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52,
                    x: 160, y: 316},
                {name: 2, x: 162, y: 439},
                {name: 54, x: 160, y: 317},
                {name: 100, x: 36, y: 413, length: 130, wheel: !1, gun: !1, kickable: !1, kicked: !1, radius: -1, invisible: !1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 291, y: 413, length: 130, wheel: !1, gun: !1, kickable: !1, kicked: !1, radius: -1, invisible: !1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 3, x: 162, y: 381, moveSpeed: 100, rotateSpeed: 0, timeout: -1},
                {name: 100, x: 93, y: 231, length: 100, wheel: !1, gun: !1, kickable: !1, kicked: !1, radius: 70,
                    invisible: !1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 231, y: 231, length: 100, wheel: !1, gun: !1, kickable: !1, kicked: !1, radius: 70, invisible: !1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 59, x: 232, y: 232, angle: 0, size: 3, path: "0,0", moveSpeed: 40, rotateSpeed: 40, toggled: !1},
                {name: 59, x: 90, y: 232, angle: 0, size: 3, path: "0,0", moveSpeed: -40, rotateSpeed: -40, toggled: !1},
                {name: 3, x: 161, y: 32, timeout: -1},
                {name: 3, x: 161, y: 73, timeout: -1}
            ]}
        ]},
        {levels: [
            {settings: [
                {name: 0,
                    gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 161, y: 220},
                {name: 2, x: 254, y: 416},
                {name: 3, x: 299, y: 89, timeout: -1},
                {name: 3, x: 20, y: 89, timeout: -1},
                {name: 3, x: 161, y: 171, timeout: -1},
                {name: 55, x: 42, y: 237, angle: 0},
                {name: 55, x: 277, y: 233, angle: 180},
                {name: 100, x: 162, y: 88, length: 110, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 55, x: 161, y: 334, angle: -90},
                {name: 4, x: 0, y: 357, locale: "en", text: "Tap the Air Cushion to blow objects",
                    width: 200},
                {name: 8, x: 101, y: 347, locale: "en", angle: 180, moveSpeed: 100, rotateSpeed: 100}
            ], ru: [
                {name: 8, x: 104, y: 350, locale: "ru", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 12, y: 337, locale: "ru", text: "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043d\u0430 \u043f\u043e\u0434\u0443\u0448\u043a\u0443, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0443\u0442\u044c \u043d\u0430 \u043b\u0435\u0434\u0435\u043d\u0435\u0446", width: 200}
            ], fr: [
                {name: 8, x: 101, y: 347, locale: "fr", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 10, y: 347, locale: "fr", text: "Touche le coussin d'air pour qu'il souffle sur les objets", width: 200}
            ], de: [
                {name: 8, x: 102, y: 354, locale: "de", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -15, y: 350, locale: "de", text: "Ber\u00fchre den Luftballon, um Objekte wegzupusten", width: 210}
            ], es: [
                {name: 8, x: 102, y: 354, locale: "es", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -25, y: 360, locale: "es", text: "Aprieta el fuelle para soplar aire y mover objetos.", width: 210}
            ], br: [
                {name: 8, x: 102, y: 354, locale: "br", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -25, y: 360, locale: "br", text: "Toque a almofada de ar para soprar objetos", width: 210}
            ], ca: [
                {name: 8, x: 102, y: 354, locale: "ca", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -25, y: 360, locale: "ca", text: "Prem el coixinet inflable per llançar aire", width: 210}
            ], it: [
                {name: 8, x: 102, y: 354, locale: "it", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -25, y: 330, locale: "it", text: "Tocca il cuscino d'aria per soffiare sugli oggetti e spostarli", width: 210}
            ], nl: [
                {name: 8, x: 102, y: 354, locale: "nl", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -55, y: 335, locale: "nl", text: "Tik op het luchtkussen om voorwerpen weg te blazen", width: 250}
            ], ko: [
                {name: 8, x: 102, y: 354, locale: "ko", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -75, y: 375, locale: "ko", text: "공기 주머니를 터치하면 *물건을 날려 보내요.", width: 250}
            ], zh: [
                {name: 8, x: 102, y: 354, locale: "zh", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -65, y: 370, locale: "zh", text: "触按气垫以吹起物品", width: 290}
            ], ja: [
                {name: 8, x: 102, y: 354, locale: "ja", angle: 180, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: -90, y: 325, locale: "ja", text: "エアー クッションを タップ して いろいろな ものを 吹き飛ばし ましょう", width: 250}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, special: 1, ropePhysicsSpeed: 1}
            ], objects: [
                {name: 52, x: 215, y: 210},
                {name: 2, x: 190, y: 322},
                {name: 55, x: 284, y: 208, angle: -180},
                {name: 100, x: 222, y: 82, length: 90, wheel: !1,
                    radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 100, x: 101, y: 209, length: 95, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 3, x: 99, y: 253, timeout: -1},
                {name: 3, x: 98, y: 345, timeout: -1},
                {name: 3, x: 98, y: 82, timeout: -1},
                {name: 55, x: 37, y: 343, angle: 0},
                {name: 57, x: 37, y: 257, angle: 0, size: 1},
                {name: 54, x: 97, y: 144},
                {name: 60, x: 220, y: 256, angle: 0, size: 4}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52,
                    x: 321, y: 213},
                {name: 2, x: 431, y: 386},
                {name: 100, x: 323, y: 47, length: 130, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 55, x: 183, y: 224, angle: 0},
                {name: 100, x: 323, y: 167, length: 200, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 323, y: 105, length: 170, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 3, x: 320, y: 399, timeout: -1},
                {name: 57, x: 323, y: 347, angle: 0, size: 1},
                {name: 3,
                    x: 422, y: 161, timeout: -1},
                {name: 3, x: 322, y: 302, timeout: -1}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 401, y: 423},
                {name: 3, x: 226, y: 158, timeout: -1},
                {name: 3, x: 402, y: 160, timeout: -1},
                {name: 55, x: 151, y: 130, angle: 0},
                {name: 3, x: 529, y: 158, timeout: -1},
                {name: 54, x: 400, y: 351},
                {name: 54, x: 314, y: 351},
                {name: 52, x: 528, y: 225},
                {name: 100, x: 529, y: 87, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 306, y: 87, length: 250, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 529, y: 352, length: 90, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, special: 1, ropePhysicsSpeed: 1}
            ], objects: [
                {name: 2, x: 219, y: 433},
                {name: 54, x: 100, y: 176},
                {name: 54, x: 159, y: 178},
                {name: 54, x: 218, y: 178},
                {name: 54, x: 100, y: 242},
                {name: 54, x: 159, y: 244},
                {name: 54, x: 218, y: 244},
                {name: 54,
                    x: 100, y: 308},
                {name: 54, x: 159, y: 310},
                {name: 54, x: 218, y: 310},
                {name: 54, x: 102, y: 373},
                {name: 54, x: 161, y: 375},
                {name: 54, x: 220, y: 375},
                {name: 54, x: 98, y: 109},
                {name: 54, x: 220, y: 110},
                {name: 3, x: 99, y: 110, timeout: -1},
                {name: 3, x: 218, y: 244, timeout: -1},
                {name: 3, x: 103, y: 372, timeout: -1},
                {name: 52, x: 157, y: 97},
                {name: 55, x: 36, y: 376, angle: -20},
                {name: 55, x: 35, y: 247, angle: -20},
                {name: 55, x: 284, y: 245, angle: 200},
                {name: 55, x: 284, y: 374, angle: 200},
                {name: 54, x: 40, y: 312},
                {name: 54, x: 277, y: 310},
                {name: 54, x: 280, y: 178},
                {name: 54, x: 37, y: 180},
                {name: 100,
                    x: 163, y: 33, length: 50, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1},
                {name: 54, x: 280, y: 111},
                {name: 54, x: 37, y: 108}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 390, y: 125},
                {name: 2, x: 318, y: 431},
                {name: 100, x: 391, y: 46, length: 40, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 323, y: 225, length: 70, wheel: !1, gun: !1, radius: 80, moveLength: -1, moveVertical: !1,
                    moveOffset: 0, spider: !0, part: "L"},
                {name: 57, x: 375, y: 406, angle: 90, size: 1},
                {name: 57, x: 256, y: 407, angle: 90, size: 1},
                {name: 4, x: 381, y: 280, locale: "en", text: "Cut the rope before the spider reaches the candy", width: 240},
                {name: 13, x: 385, y: 330, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 3, x: 372, y: 223, timeout: -1},
                {name: 3, x: 230, y: 287, timeout: -1},
                {name: 3, x: 317, y: 377, timeout: -1}
            ], ru: [
                {name: 4, x: 48, y: 251, locale: "ru", text: "\u041f\u0435\u0440\u0435\u0440\u0435\u0436\u044c\u0442\u0435 \u0432\u0435\u0440\u0435\u0432\u043a\u0443 \u043f\u0440\u0435\u0436\u0434\u0435, \u0447\u0435\u043c \u043f\u0430\u0443\u0447\u043e\u043a \u0434\u043e\u0431\u0435\u0440\u0435\u0442\u0441\u044f \u0434\u043e \u043b\u0435\u0434\u0435\u043d\u0446\u0430",
                    width: 200},
                {name: 13, x: 35, y: 330, locale: "ru", moveSpeed: 100, rotateSpeed: 100}
            ], fr: [
                {name: 4, x: 26, y: 289, locale: "fr", text: "Coupe la corde avant que l'araign\u00e9e n'atteigne le bonbon", width: 260},
                {name: 13, x: 29, y: 344, locale: "fr", moveSpeed: 100, rotateSpeed: 100}
            ], de: [
                {name: 4, x: 79, y: 287, locale: "de", text: "Schneide das Seil durch, bevor die Spinne am Bonbon ist", width: 170},
                {name: 13, x: 150, y: 276, locale: "de", moveSpeed: 100, rotateSpeed: 100}
            ], es: [
                {name: 4, x: 29, y: 287, locale: "es", text: "Corta la cuerda antes de que la araña llegue al caramelo.", width: 200},
                {name: 13, x: 50, y: 316, locale: "es", moveSpeed: 100, rotateSpeed: 100}
            ], br: [
                {name: 4, x: 29, y: 287, locale: "br", text: "Corte a corda antes que a aranha atinja os doces", width: 200},
                {name: 13, x: 20, y: 336, locale: "br", moveSpeed: 100, rotateSpeed: 100}
            ], ca: [
                {name: 4, x: 29, y: 287, locale: "ca", text: "Talla la corda abans que l'aranya arribi a les llaminadures", width: 200},
                {name: 13, x: 20, y: 336, locale: "ca", moveSpeed: 100, rotateSpeed: 100}
            ], it: [
                {name: 4, x: 29, y: 287, locale: "it", text: "Taglia la corda prima che il ragno raggiunga il bonbon", width: 200},
                {name: 13, x: 50, y: 336, locale: "it", moveSpeed: 100, rotateSpeed: 100}
            ], nl: [
                {name: 4, x: 29, y: 267, locale: "nl", text: "Snijd het touw door voordat de spin het snoepje kan bereiken", width: 200},
                {name: 13, x: 40, y: 316, locale: "nl", moveSpeed: 100, rotateSpeed: 100}
            ], ko: [
                {name: 4, x: 29, y: 267, locale: "ko", text: "거미가 사탕에 도착하기 전에 줄을 자르세요!", width: 200},
                {name: 13, x: 15, y: 316, locale: "ko", moveSpeed: 100, rotateSpeed: 100}
            ], zh: [
                {name: 4, x: 29, y: 297, locale: "zh", text: "在蜘蛛到达糖果之前 剪断绳索", width: 300},
                {name: 13, x: 10, y: 316, locale: "zh", moveSpeed: 100, rotateSpeed: 100}
            ], ja: [
                {name: 4, x: 29, y: 267, locale: "ja", text: "クモが キャンディに 到達する 前に ロープを 切り ましょう", width: 200},
                {name: 13, x: 10, y: 316, locale: "ja", moveSpeed: 100, rotateSpeed: 100}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1,
                    special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 442, y: 421},
                {name: 52, x: 181, y: 172},
                {name: 100, x: 345, y: 136, length: 100, wheel: !1, gun: !1, radius: 65, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 185, y: 79, length: 60, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 257, y: 245, length: 100, wheel: !1, gun: !1, radius: 65, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !0, part: "L"},
                {name: 3, x: 256, y: 127, timeout: -1},
                {name: 3, x: 434, y: 336, timeout: -1},
                {name: 3, x: 257, y: 335, timeout: -1},
                {name: 54, x: 345, y: 307},
                {name: 100, x: 436, y: 243, length: 100, wheel: !1, gun: !1, radius: 65, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !0, part: "L"},
                {name: 101, x: 108, y: 273, angle: 20, drawing: 2}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 54, x: 109, y: 257},
                {name: 55, x: 66, y: 72, angle: 50},
                {name: 3, x: 208, y: 191, timeout: -1},
                {name: 3, x: 385, y: 195, timeout: -1},
                {name: 3, x: 546, y: 192, timeout: -1},
                {name: 2, x: 458, y: 416},
                {name: 52, x: 111, y: 174},
                {name: 100, x: 113, y: 83, length: 50, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 55, x: 161, y: 72, angle: 50},
                {name: 55, x: 258, y: 73, angle: 50},
                {name: 55, x: 349, y: 73, angle: 50},
                {name: 100, x: 47, y: 154, length: 460, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !0, part: "L"},
                {name: 55, x: 437, y: 73, angle: 50},
                {name: 60, x: 582, y: 203, angle: 90, size: 4}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1,
                    special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 321, y: 414},
                {name: 52, x: 323, y: 191},
                {name: 100, x: 408, y: 302, length: 140, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !0, part: "L"},
                {name: 100, x: 224, y: 304, length: 140, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !0, part: "L"},
                {name: 100, x: 383, y: 125, length: 80, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !0, part: "L"},
                {name: 100, x: 256, y: 126, length: 80, wheel: !1, gun: !1, radius: -1, moveLength: -1,
                    moveVertical: !1, moveOffset: 0, spider: !0, part: "L"},
                {name: 3, x: 321, y: 275, moveSpeed: 100, rotateSpeed: 100, timeout: -1},
                {name: 3, x: 660, y: 133, path: "-39,-17,-91,-21,-154,-11,-201,10,-240,49,-284,99,-331,159,", moveSpeed: 45, rotateSpeed: 0, timeout: -1},
                {name: 3, x: -20, y: 142, path: "46,-14,93,-26,142,-18,195,3,239,40,282,97,327,151,", moveSpeed: 45, rotateSpeed: 0, timeout: -1},
                {name: 54, x: 319, y: 340}
            ], "layer 2": [
                {name: 60, x: 232, y: 172, angle: 55, size: 4},
                {name: 60, x: 406, y: 172, angle: -55, size: 4}
            ]}
        ]},
        {levels: [
            {settings: [
                {name: 0, gridSize: 32,
                    width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 314, y: 372},
                {name: 82, x: 176, y: 397, angle: 25, size: 2},
                {name: 82, x: 454, y: 396, angle: -25, size: 2},
                {name: 100, x: 320, y: 14, length: 160, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 52, x: 159, y: 123},
                {name: 82, x: 86, y: 352, angle: 25, size: 2},
                {name: 82, x: 548, y: 351, angle: -25, size: 2},
                {name: 3, x: 165, y: 350, timeout: -1},
                {name: 3, x: 471, y: 350, timeout: -1},
                {name: 3, x: 320, y: 319, moveSpeed: 35, timeout: -1},
                {name: 82,
                    x: -6, y: 306, angle: 25, size: 2},
                {name: 82, x: 643, y: 304, angle: -25, size: 2},
                {name: 82, x: 360, y: 440, angle: -25, size: 2},
                {name: 82, x: 267, y: 440, angle: 25, size: 2}
            ], en: [
                {name: 4, x: 210, y: 155, locale: "en", text: "Candy will bounce away from this platform", width: 300},
                {name: 13, x: 207, y: 230, locale: "en", moveSpeed: 100, rotateSpeed: 100}
            ], ru: [
                {name: 4, x: 200, y: 152, locale: "ru", text: "\u041b\u0435\u0434\u0435\u043d\u0435\u0446 \u043e\u0442\u0441\u043a\u0430\u043a\u0438\u0432\u0430\u0435\u0442 \u043e\u0442 \u0442\u0430\u043a\u0438\u0445 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c",
                    width: 300},
                {name: 13, x: 206, y: 220, locale: "ru", moveSpeed: 100, rotateSpeed: 100}
            ], de: [
                {name: 4, x: 210, y: 150, locale: "de", text: "Die S\u00fc\u00dfigkeit wird von der Plattform h\u00fcpfen", width: 270},
                {name: 13, x: 210, y: 219, locale: "de", moveSpeed: 100, rotateSpeed: 100}
            ], fr: [
                {name: 4, x: 218, y: 140, locale: "fr", text: "Le bonbon rebondira hors de cette plate-forme", width: 280},
                {name: 13, x: 209, y: 218, locale: "fr", moveSpeed: 100, rotateSpeed: 100}
            ], es: [
                {name: 4, x: 218, y: 130, locale: "es", text: "El caramelo rebota al tocar esta plataforma.", width: 290},
                {name: 13, x: 190, y: 185, locale: "es", moveSpeed: 100, rotateSpeed: 100}
            ], br: [
                {name: 4, x: 200, y: 130, locale: "br", text: "Doces quicarão para longe desta plataforma.", width: 290},
                {name: 13, x: 190, y: 185, locale: "br", moveSpeed: 100, rotateSpeed: 100}
            ], ca: [
                {name: 4, x: 190, y: 130, locale: "ca", text: "Les llaminadures rebotaran des d'aquesta plataforma", width: 290},
                {name: 13, x: 190, y: 185, locale: "ca", moveSpeed: 100, rotateSpeed: 100}
            ], it: [
                {name: 4, x: 190, y: 130, locale: "it", text: "Il bonbon rimbalzerà fuori dalla piattaforma", width: 290},
                {name: 13, x: 240, y: 180, locale: "it", moveSpeed: 100, rotateSpeed: 100}
            ], nl: [
                {name: 4, x: 190, y: 130, locale: "nl", text: "Het snoepje stuitert tegen dit oppervlak", width: 290},
                {name: 13, x: 190, y: 185, locale: "nl", moveSpeed: 100, rotateSpeed: 100}
            ], ko: [
                {name: 4, x: 190, y: 150, locale: "ko", text: "여기에서는 사탕이 *튕겨요.", width: 290},
                {name: 13, x: 190, y: 185, locale: "ko", moveSpeed: 100, rotateSpeed: 100}
            ], zh: [
                {name: 4, x: 180, y: 140, locale: "zh", text: "糖果将从 这个平台上弹走", width: 290},
                {name: 13, x: 190, y: 185, locale: "zh", moveSpeed: 100, rotateSpeed: 100}
            ], ja: [
                {name: 4, x: 190, y: 100, locale: "ja", text: "この プラットホームに 乗った キャンディは 弾んで 跳ね返ります", width: 290},
                {name: 13, x: 190, y: 185, locale: "ja", moveSpeed: 100, rotateSpeed: 100}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, twoParts: !1}
            ],
                objects: [
                    {name: 82, x: 254, y: 139, angle: -135, size: 2},
                    {name: 82, x: 62, y: 136, angle: 135, size: 2},
                    {name: 82, x: 253, y: 335, angle: -45, size: 2},
                    {name: 82, x: 66, y: 332, angle: 45, size: 2},
                    {name: 2, x: 48, y: 222},
                    {name: 52, x: 159, y: 235},
                    {name: 82, x: 159, y: 373, angle: 0, size: 2},
                    {name: 82, x: 157, y: 101, angle: 180, size: 2},
                    {name: 3, x: 161, y: 327, timeout: -1},
                    {name: 3, x: 160, y: 141, timeout: -1},
                    {name: 3, x: 224, y: 234, timeout: -1},
                    {name: 100, x: 85, y: 429, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                    {name: 100,
                        x: 241, y: 429, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                    {name: 100, x: 91, y: 56, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                    {name: 100, x: 231, y: 57, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                    {name: 55, x: 284, y: 235, angle: 135},
                    {name: 103, x: 290, y: 45, angle: 10, drawing: 3}
                ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1,
                    twoParts: !1}
            ], objects: [
                {name: 52, x: 231, y: 196},
                {name: 55, x: 129, y: 193, angle: 0},
                {name: 55, x: 651, y: 187, angle: 180},
                {name: 82, x: 336, y: 260, angle: 0, size: 2},
                {name: 82, x: 445, y: 260, angle: 0, size: 2},
                {name: 82, x: 548, y: 260, angle: 0, size: 2},
                {name: 3, x: 575, y: 71, path: "0,150", moveSpeed: 30, timeout: 10},
                {name: 3, x: 385, y: 69, path: "0,150", moveSpeed: 20, timeout: 15},
                {name: 3, x: 481, y: 71, path: "0,150", moveSpeed: 10, timeout: 20},
                {name: 2, x: 55, y: 422},
                {name: 82, x: 225, y: 260, angle: 0, size: 2},
                {name: 100, x: 57, y: 308, length: 100, wheel: !1, gun: !1, radius: 70,
                    moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 388, y: 133},
                {name: 2, x: 273, y: 41},
                {name: 81, x: 398, y: 189, angle: -30, size: 1},
                {name: 81, x: 191, y: 229, angle: 40, size: 1},
                {name: 81, x: 404, y: 310, angle: -30, size: 1},
                {name: 81, x: 203, y: 372, angle: 30, size: 1},
                {name: 81, x: 397, y: 448, angle: -30, size: 1},
                {name: 3, x: 403, y: 307, path: "RW40", moveSpeed: 75, rotateSpeed: 0, timeout: -1},
                {name: 100, x: 388, y: 27,
                    length: 70, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L", hidePath: !1},
                {name: 54, x: 308, y: 398},
                {name: 3, x: 204, y: 367, path: "RC40", moveSpeed: 75, rotateSpeed: 0, timeout: -1},
                {name: 3, x: 220, y: 196, timeout: -1}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 140, y: 217},
                {name: 82, x: 131, y: 261, angle: 0, size: 2},
                {name: 55, x: 57, y: 197, angle: 0},
                {name: 2, x: 150, y: 431},
                {name: 3, x: 230, y: 230, timeout: -1},
                {name: 82, x: 281, y: 199,
                    angle: -90, size: 2},
                {name: 54, x: 222, y: 311},
                {name: 55, x: 57, y: 317, angle: 0},
                {name: 3, x: 165, y: 360, timeout: -1},
                {name: 3, x: 165, y: 70, timeout: -1},
                {name: 82, x: 281, y: 315, angle: -90, size: 2}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, twoParts: !1}
            ], objects: [
                {name: 81, x: 211, y: 447, angle: 0, size: 1},
                {name: 81, x: 278, y: 447, angle: 0, size: 1},
                {name: 81, x: 343, y: 448, angle: 0, size: 1},
                {name: 81, x: 407, y: 448, angle: 0, size: 1},
                {name: 81, x: 472, y: 448, angle: 0, size: 1},
                {name: 81, x: 538, y: 448, angle: 0, size: 1},
                {name: 81,
                    x: 601, y: 448, angle: 0, size: 1},
                {name: 81, x: 280, y: 42, angle: 180, size: 1},
                {name: 81, x: 344, y: 49, angle: 190, size: 1},
                {name: 81, x: 405, y: 66, angle: 200, size: 1},
                {name: 81, x: 462, y: 92, angle: 210, size: 1},
                {name: 81, x: 514, y: 129, angle: 220, size: 1},
                {name: 81, x: 558, y: 174, angle: 230, size: 1},
                {name: 81, x: 595, y: 226, angle: 240, size: 1},
                {name: 81, x: 617, y: 283, angle: 260, size: 1},
                {name: 81, x: 624, y: 345, angle: 270, size: 1},
                {name: 2, x: 52, y: 352},
                {name: 100, x: 219, y: 72, length: 220, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1,
                    part: "L"},
                {name: 52, x: 221, y: 313},
                {name: 81, x: 624, y: 407, angle: -90, size: 1},
                {name: 81, x: 216, y: 42, angle: 180, size: 1},
                {name: 3, x: 558, y: 258, path: "-39,-66,-96,-120,-160,-155,", moveSpeed: 70, timeout: -1},
                {name: 3, x: 587, y: 343, timeout: -1},
                {name: 3, x: 277, y: 409, timeout: -1},
                {name: 55, x: 127, y: 345, angle: 0},
                {name: 81, x: 87, y: 447, angle: 0, size: 1},
                {name: 81, x: 149, y: 447, angle: 0, size: 1},
                {name: 81, x: 25, y: 447, angle: 0, size: 1}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, twoParts: !1}
            ], objects: [
                {name: 100,
                    x: 160, y: 270, length: 140, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 82, x: 160, y: 147, angle: 0, size: 2},
                {name: 52, x: 114, y: 107},
                {name: 2, x: 37, y: 387},
                {name: 100, x: 160, y: 270, length: 100, wheel: !1, gun: !1, radius: 120, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 82, x: 210, y: 386, angle: 90, size: 2},
                {name: 54, x: 273, y: 389},
                {name: 3, x: 277, y: 389, timeout: -1},
                {name: 3, x: 267, y: 145, timeout: -1},
                {name: 3, x: 57, y: 143, timeout: -1}
            ]},
            {settings: [
                {name: 0, gridSize: 32,
                    width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 82, x: 106, y: 407, angle: 0, size: 2},
                {name: 82, x: 278, y: 354, angle: -80, size: 2},
                {name: 82, x: 41, y: 353, angle: 80, size: 2},
                {name: 82, x: 23, y: 250, angle: 80, size: 2},
                {name: 82, x: 298, y: 252, angle: -80, size: 2},
                {name: 55, x: 91, y: 348, angle: 0},
                {name: 52, x: 213, y: 339},
                {name: 2, x: 43, y: 95},
                {name: 3, x: 106, y: 240, timeout: -1},
                {name: 3, x: 70, y: 197, timeout: -1},
                {name: 3, x: 127, y: 191, timeout: -1},
                {name: 82, x: 212, y: 407, angle: 0, size: 2},
                {name: 54, x: 100, y: 207}
            ]},
            {settings: [
                {name: 0,
                    gridSize: 32, width: 800, height: 480},
                {name: 1, ropePhysicsSpeed: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 608, y: 346},
                {name: 52, x: 397, y: 255},
                {name: 82, x: 292, y: 313, angle: 0, size: 2},
                {name: 82, x: 394, y: 313, angle: 0, size: 2},
                {name: 82, x: 499, y: 313, angle: 0, size: 2},
                {name: 82, x: 628, y: 185, angle: -90, size: 2},
                {name: 82, x: 496, y: 53, angle: 180, size: 2},
                {name: 82, x: 290, y: 184, angle: 0, size: 2},
                {name: 82, x: 391, y: 185, angle: 0, size: 2},
                {name: 82, x: 252, y: 247, angle: 90, size: 2},
                {name: 82, x: 392, y: 53, angle: 180, size: 2},
                {name: 82, x: 286, y: 53, angle: 180, size: 2},
                {name: 82, x: 57, y: 188, angle: 90, size: 2},
                {name: 82, x: 58, y: 289, angle: 90, size: 2},
                {name: 82, x: 190, y: 424, angle: 0, size: 2},
                {name: 82, x: 298, y: 425, angle: 0, size: 2},
                {name: 82, x: 406, y: 425, angle: 0, size: 2},
                {name: 55, x: 302, y: 254, angle: 0},
                {name: 82, x: 591, y: 277, angle: -45, size: 2},
                {name: 82, x: 590, y: 92, angle: -135, size: 2},
                {name: 82, x: 97, y: 385, angle: 45, size: 2},
                {name: 82, x: 90, y: 94, angle: 135, size: 2},
                {name: 55, x: 502, y: 372, angle: 180},
                {name: 82, x: 510, y: 425, angle: 0, size: 2},
                {name: 3, x: 571, y: 191, timeout: -1},
                {name: 3, x: 219, y: 237, timeout: -1},
                {name: 3,
                    x: 372, y: 369, timeout: -1},
                {name: 82, x: 698, y: 380, angle: -45, size: 2},
                {name: 82, x: 685, y: 283, angle: -135, size: 2},
                {name: 82, x: 180, y: 53, angle: 180, size: 2},
                {name: 82, x: 613, y: 425, angle: 0, size: 2}
            ]}
        ]},
        {levels: [
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 290, y: 384},
                {name: 56, x: 15, y: 367, group: 0, angle: -90},
                {name: 56, x: 290, y: 114, group: 0, angle: 90},
                {name: 52, x: 15, y: 150},
                {name: 100, x: 15, y: 69, length: 90, wheel: !1, gun: !1, radius: -1, moveLength: 0, moveVertical: !1,
                    moveOffset: 0, spider: !1, part: "L"},
                {name: 4, x: 46, y: 50, locale: "en", text: "Drop the candy into the magic hat and it will fall out from the other one", width: 200},
                {name: 8, x: 100, y: 360, locale: "en", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 3, x: 15, y: 328, timeout: -1},
                {name: 3, x: 290, y: 177, timeout: -1},
                {name: 3, x: 290, y: 319, timeout: -1}
            ], fr: [
                {name: 8, x: 118, y: 347, locale: "fr", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 46, y: 10, locale: "fr", text: "D\u00e9pose le bonbon dans le chapeau magique et il tombera de l'autre chapeau",
                    width: 200}
            ], de: [
                {name: 8, x: 118, y: 348, locale: "de", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 32, y: 37, locale: "de", text: "Wirf den Bonbon in den magischen Hut und er kommt aus einem anderen wieder raus", width: 220}
            ], ru: [
                {name: 8, x: 122, y: 354, locale: "ru", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 40, y: 30, locale: "ru", text: "\u041a\u0438\u043d\u044c\u0442\u0435 \u043a\u043e\u043d\u0444\u0435\u0442\u0443 \u0432 \u043e\u0434\u043d\u0443 \u0438\u0437 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u044b\u0445 \u0448\u043b\u044f\u043f, \u0438 \u043e\u043d\u0430 \u0432\u044b\u043b\u0435\u0442\u0438\u0442 \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0439",
                    width: 200}
            ], es: [
                {name: 8, x: 118, y: 348, locale: "es", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 32, y: 37, locale: "es", text: "Al caer en una chistera mágica, el caramelo saldrá por la otra.", width: 210}
            ], br: [
                {name: 8, x: 118, y: 348, locale: "br", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 32, y: 37, locale: "br", text: "Jogue os doces no chapéu mágico e eles cairão do outro chapéu", width: 210}
            ], ca: [
                {name: 8, x: 118, y: 358, locale: "ca", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 32, y: 7, locale: "ca", text: "Deixa caure les llaminadures en el barret màgic i tornaran a caure des de l'altre", width: 210}
            ], it: [
                {name: 8, x: 118, y: 358, locale: "it", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 32, y: 7, locale: "it", text: "Butta il bonbon nel cappello magico e cadrà dall'altro cappello", width: 210}
            ], nl: [
                {name: 8, x: 118, y: 358, locale: "nl", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 32, y: 7, locale: "nl", text: "Laat het snoepje in de toverhoed vallen en het komt er bij de andere weer uit", width: 210}
            ], ko: [
                {name: 8, x: 118, y: 358, locale: "ko", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 32, y: 135, locale: "ko", text: "마술 모자에 사탕을 넣으면 *다른 마술 모자에서 *튀어나와요.", width: 210}
            ], zh: [
                {name: 8, x: 118, y: 358, locale: "zh", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 0, y: 220, locale: "zh", text: "将糖果丢入魔术帽。 它会从另一个魔术帽中掉出", width: 380}
            ], ja: [
                {name: 8, x: 118, y: 358, locale: "ja", angle: 0, moveSpeed: 100, rotateSpeed: 100},
                {name: 4, x: 12, y: 200, locale: "ja", text: "魔法の帽子にキャンディを\n入れると、 他の帽子から\n出てきます", width: 380}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 4, x: 58, y: 0, locale: "en", text: "Candy maintains its speed when teleporting", width: 250},
                {name: 13, x: 39, y: 70, locale: "en", moveSpeed: 100, rotateSpeed: 100},
                {name: 2, x: 253, y: 177},
                {name: 56, x: 241, y: 373, group: 0, angle: -180},
                {name: 56, x: 70, y: 235, group: 0, angle: -60},
                {name: 52, x: 191, y: 379},
                {name: 100, x: 112, y: 378, length: 50, wheel: !1, gun: !1, radius: -1, moveLength: 80, moveVertical: !1, moveOffset: 80,
                    spider: !1, part: "L"},
                {name: 100, x: 266, y: 321, length: 55, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 100, x: 267, y: 432, length: 55, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 3, x: 113, y: 169, path: "RW30", moveSpeed: 70, timeout: -1},
                {name: 3, x: 148, y: 154, path: "RC30", moveSpeed: 80, timeout: -1},
                {name: 3, x: 130, y: 158, path: "RC20", moveSpeed: 80, timeout: -1}
            ], fr: [
                {name: 4, x: -55, y: 13, locale: "fr", text: "La vitesse du bonbon reste identique lorsqu'il est t\u00e9l\u00e9port\u00e9",
                    width: 300},
                {name: 13, x: -50, y: 73, locale: "fr", moveSpeed: 100, rotateSpeed: 100}
            ], de: [
                {name: 4, x: -20, y: 0, locale: "de", text: "Beim Teleportieren beh\u00e4lt der Bonbon seine Geschwindigkeit bei", width: 360},
                {name: 13, x: 0, y: 52, locale: "de", moveSpeed: 100, rotateSpeed: 100}
            ], ru: [
                {name: 4, x: 62, y: 1, locale: "ru", text: "\u041a\u043e\u043d\u0444\u0435\u0442\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0432\u043e\u044e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u0440\u0438 \u0442\u0435\u043b\u0435\u043f\u043e\u0440\u0442\u0430\u0446\u0438\u0438",
                    width: 300},
                {name: 13, x: 43, y: 69, locale: "ru", moveSpeed: 100, rotateSpeed: 100}
            ], es: [
                {name: 4, x: -50, y: 10, locale: "es", text: "El caramelo conserva la velocidad al teletransportarse.", width: 360},
                {name: 13, x: -60, y: 62, locale: "es", moveSpeed: 100, rotateSpeed: 100}
            ], br: [
                {name: 4, x: -20, y: 10, locale: "br", text: "Os doces mantêm sua velocidade ao teleportar", width: 360},
                {name: 13, x: -30, y: 62, locale: "br", moveSpeed: 100, rotateSpeed: 100}
            ], ca: [
                {name: 4, x: -50, y: 10, locale: "ca", text: "Les llaminadures mantenen la seva velocitat durant el teletransport", width: 360},
                {name: 13, x: -75, y: 62, locale: "ca", moveSpeed: 100, rotateSpeed: 100}
            ], it: [
                {name: 4, x: -70, y: 0, locale: "it", text: "I bonbon mantengono la stessa velocità durante il teletrasporto", width: 360},
                {name: 13, x: -75, y: 55, locale: "it", moveSpeed: 100, rotateSpeed: 100}
            ], nl: [
                {name: 4, x: -70, y: 0, locale: "nl", text: "Tijdens het teleporteren behoudt het snoepje dezelfde snelheid", width: 360},
                {name: 13, x: 10, y: 52, locale: "nl", moveSpeed: 100, rotateSpeed: 100}
            ], ko: [
                {name: 4, x: -50, y: 30, locale: "ko", text: "사탕이 순간 이동할 *때도 속도는 변하지 *않아요.", width: 360},
                {name: 13, x: -55, y: 62, locale: "ko", moveSpeed: 100, rotateSpeed: 100}
            ], zh: [
                {name: 4, x: -50, y: 30, locale: "zh", text: "糖果在传送时\n保持速度", width: 360},
                {name: 13, x: 10, y: 62, locale: "zh", moveSpeed: 100, rotateSpeed: 100}
            ], ja: [
                {name: 4, x: -50, y: 30, locale: "ja", text: "テレポートするとき、 キャンディの 速度は 維持 されます", width: 360},
                {name: 13, x: -55, y: 62, locale: "ja", moveSpeed: 100, rotateSpeed: 100}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 320, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 55, y: 333},
                {name: 52, x: 220, y: 340},
                {name: 81, x: 130, y: 405, angle: 0, size: 1},
                {name: 81, x: 210, y: 405, angle: 0, size: 1},
                {name: 100, x: 167, y: 310, length: 50, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 56, x: 260, y: 353, group: 0, angle: -180},
                {name: 56, x: 65, y: 165, group: 0, angle: 0},
                {name: 81, x: 280, y: 225, angle: 0, size: 1},
                {name: 81, x: 200, y: 225, angle: 0, size: 1},
                {name: 81, x: 120, y: 225, angle: 0, size: 1},
                {name: 100, x: 198, y: 113, length: 100, wheel: !1, gun: !1, radius: 65, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 3, x: 269, y: 183, path: "1,-110,", moveSpeed: 50, timeout: -1},
                {name: 3, x: 130, y: 84, path: "1,110,", moveSpeed: 50, timeout: -1},
                {name: 3, x: 198, y: 187, timeout: -1}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2,
                    x: 548, y: 117},
                {name: 52, x: 100, y: 315},
                {name: 54, x: 100, y: 316},
                {name: 100, x: 99, y: 442, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 82, x: 47, y: 213, angle: 90, size: 2},
                {name: 55, x: 159, y: 209, angle: 180},
                {name: 56, x: 146, y: 51, group: 0, angle: 90},
                {name: 82, x: 272, y: 209, angle: 90, size: 2},
                {name: 56, x: 322, y: 399, group: 0, angle: 270},
                {name: 55, x: 385, y: 206, angle: 180},
                {name: 56, x: 544, y: 398, group: 1, angle: 270},
                {name: 56, x: 371, y: 54, group: 1, angle: 90},
                {name: 3, x: 546, y: 210, timeout: -1},
                {name: 3, x: 96, y: 210, timeout: -1},
                {name: 3, x: 323, y: 207, timeout: -1}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 433, y: 284},
                {name: 100, x: 338, y: 50, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L", hidePath: !1},
                {name: 100, x: 338, y: 358, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L", hidePath: !1},
                {name: 100, x: 459, y: 202, length: 100, wheel: !1,
                    gun: !1, radius: -1, moveLength: 100, moveVertical: !1, moveOffset: 0, spider: !1, part: "L", hidePath: !1},
                {name: 100, x: 223, y: 202, length: 100, wheel: !1, gun: !1, radius: -1, moveLength: 100, moveVertical: !1, moveOffset: 100, spider: !1, part: "L", hidePath: !1},
                {name: 52, x: 335, y: 200},
                {name: 3, x: 208, y: 72, timeout: -1},
                {name: 3, x: 432, y: 369, timeout: -1},
                {name: 3, x: 209, y: 369, timeout: -1},
                {name: 56, x: 446, y: 137, group: 0, angle: 180},
                {name: 56, x: 212, y: 427, group: 0, angle: 270},
                {name: 56, x: 433, y: 429, group: 1, angle: 270},
                {name: 56, x: 227, y: 150, group: 1, angle: 0}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 167, y: 358},
                {name: 2, x: 480, y: 76},
                {name: 54, x: 168, y: 358},
                {name: 56, x: 83, y: 281, group: 1, angle: 0},
                {name: 56, x: 84, y: 186, group: 0, angle: 0},
                {name: 56, x: 83, y: 90, group: 1, angle: 0},
                {name: 56, x: 483, y: 374, group: 0, angle: 270},
                {name: 55, x: 243, y: 277, angle: 180},
                {name: 55, x: 242, y: 182, angle: 180},
                {name: 55, x: 243, y: 88, angle: 180},
                {name: 3, x: 112, y: 90, timeout: -1},
                {name: 3, x: 116, y: 280, timeout: -1},
                {name: 3, x: 111, y: 186,
                    timeout: -1},
                {name: 80, x: 478, y: 144, initialDelay: -2, offTime: 2, onTime: 2, angle: 0, size: 5},
                {name: 55, x: 479, y: 187, angle: 90},
                {name: 100, x: 169, y: 447, length: 60, wheel: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 490, y: 57},
                {name: 56, x: 490, y: 402, group: 0, angle: 270},
                {name: 56, x: 130, y: 105, group: 0, angle: 90},
                {name: 55, x: 130, y: 270, angle: 270},
                {name: 52, x: 490, y: 355},
                {name: 100,
                    x: 490, y: 267, length: 50, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 3, x: 310, y: 43, timeout: -1},
                {name: 56, x: 130, y: 404, group: 1, angle: 270},
                {name: 56, x: 310, y: 401, group: 1, angle: 270},
                {name: 3, x: 310, y: 162, path: "0,200", moveSpeed: 80, timeout: -1},
                {name: 3, x: 490, y: 187, timeout: -1}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 52, x: 113, y: 189},
                {name: 100, x: 61, y: 187, length: 50, wheel: !1, radius: -1, moveVertical: !0,
                    spider: !1, part: "L"},
                {name: 100, x: 164, y: 188, length: 50, wheel: !1, radius: -1, moveVertical: !0, spider: !1, part: "L"},
                {name: 3, x: 545, y: 195, timeout: -1},
                {name: 56, x: 111, y: 401, group: 0, angle: 270},
                {name: 2, x: 477, y: 290},
                {name: 54, x: 269, y: 257},
                {name: 56, x: 268, y: 98, group: 0, angle: 90},
                {name: 3, x: 407, y: 191, timeout: -1},
                {name: 3, x: 475, y: 113, timeout: -1},
                {name: 56, x: 273, y: 403, group: 1, angle: 270},
                {name: 56, x: 419, y: 402, group: 1, angle: 270},
                {name: 100, x: 477, y: 195, length: 100, wheel: !1, radius: 50, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1,
                    part: "L"},
                {name: 58, x: 114, y: 74, angle: 0, size: 2}
            ]},
            {settings: [
                {name: 0, gridSize: 32, width: 640, height: 480},
                {name: 1, ropePhysicsSpeed: 1, special: 1, twoParts: !1}
            ], objects: [
                {name: 2, x: 416, y: 314},
                {name: 52, x: 303, y: 136},
                {name: 100, x: 306, y: 75, length: 20, wheel: !1, gun: !1, radius: -1, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 56, x: 226, y: 400, group: 1, angle: -90},
                {name: 56, x: 304, y: 139, group: 1, angle: -180, path: "RW90", moveSpeed: 150, rotateSpeed: -96},
                {name: 56, x: 420, y: 395, group: 0, angle: -90},
                {name: 56, x: 304,
                    y: 139, group: 0, angle: -180, path: "RC90", moveSpeed: 100, rotateSpeed: 64},
                {name: 100, x: 224, y: 281, length: 10, wheel: !1, gun: !1, radius: 35, moveLength: -1, moveVertical: !1, moveOffset: 0, spider: !1, part: "L"},
                {name: 3, x: 212, y: 357, timeout: -1},
                {name: 3, x: 237, y: 356, timeout: -1},
                {name: 3, x: 307, y: 198, timeout: -1}
            ]}
        ]}
    ];
    return boxes;
});
define('resources/ResourcePacks',
    [ 'resources/ResourceId' ],
    function (ResourceId) {

        var ResourcePacks = {};

        ResourcePacks.StandardMenuSounds = [
            ResourceId.SND_MENU_MUSIC,
            ResourceId.SND_BUTTON,
            ResourceId.SND_TAP
        ];

        ResourcePacks.TimeMenuSounds = [
            ResourceId.SND_TIME_MENU_MUSIC,
            ResourceId.SND_BUTTON,
            ResourceId.SND_TAP
        ];

        // game element images
        ResourcePacks.StandardGameImages = [
            ResourceId.IMG_CHAR_ANIMATIONS,
            ResourceId.IMG_OBJ_HOOK_01,
            ResourceId.IMG_OBJ_HOOK_02,
            ResourceId.IMG_OBJ_HOOK_AUTO,
            ResourceId.IMG_OBJ_CANDY_01,
            ResourceId.IMG_OBJ_BOUNCER_01,
            ResourceId.IMG_OBJ_BOUNCER_02,
            ResourceId.IMG_OBJ_BUBBLE_ATTACHED,
            ResourceId.IMG_OBJ_BUBBLE_FLIGHT,
            ResourceId.IMG_OBJ_BUBBLE_POP,
            ResourceId.IMG_OBJ_PUMP,
            ResourceId.IMG_OBJ_SPIDER,
            ResourceId.IMG_OBJ_SPIKES_01,
            ResourceId.IMG_OBJ_SPIKES_02,
            ResourceId.IMG_OBJ_SPIKES_03,
            ResourceId.IMG_OBJ_SPIKES_04,
            ResourceId.IMG_OBJ_STAR_IDLE,
            ResourceId.IMG_OBJ_STAR_DISAPPEAR,
            ResourceId.IMG_HUD_STAR,
            ResourceId.IMG_TUTORIAL_SIGNS,
            ResourceId.IMG_DRAWING_HIDDEN,
            ResourceId.IMG_CHAR_SUPPORTS
        ];

        ResourcePacks.Round5AdditionalGameImages = [
            ResourceId.IMG_OBJ_BEE_HD,
            ResourceId.IMG_OBJ_POLLEN_HD
        ];

        ResourcePacks.Round5AdditionalSounds = [
            ResourceId.SND_BUZZ,
            ResourceId.SND_GRAVITY_OFF,
            ResourceId.SND_GRAVITY_ON
        ];

        ResourcePacks.TimeEditionAdditionalGameImages = [
            ResourceId.IMG_OBJ_SOCKS,
            ResourceId.IMG_OBJ_HOOK_MOVABLE,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_01,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_02,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_03,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_04,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_BUTTON
        ];

        ResourcePacks.TimeEditionAdditionalSounds = [
            ResourceId.SND_SPIKE_ROTATE_IN,
            ResourceId.SND_SPIKE_ROTATE_OUT,
            ResourceId.SND_TELEPORT,
            ResourceId.SND_CANDY_HIT,
            ResourceId.SND_PREHISTORIC_MONSTER_CHEWING,
            ResourceId.SND_PREHISTORIC_MONSTER_OPEN,
            ResourceId.SND_PREHISTORIC_MONSTER_CLOSE,
            ResourceId.SND_PREHISTORIC_MONSTER_SAD
        ];

        ResourcePacks.FullGameAdditionalGameImages = [
            ResourceId.IMG_OBJ_HOOK_MOVABLE,
            ResourceId.IMG_OBJ_HOOK_REGULATED,
            ResourceId.IMG_OBJ_ELECTRODES,
            ResourceId.IMG_OBJ_SOCKS,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_01,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_02,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_03,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_04,
            ResourceId.IMG_OBJ_ROTATABLE_SPIKES_BUTTON,
            ResourceId.IMG_OBJ_BEE_HD,
            ResourceId.IMG_OBJ_POLLEN_HD,
            ResourceId.IMG_OBJ_VINIL
        ];

        ResourcePacks.ChromeLiteAdditionalGameImages = [
            ResourceId.IMG_OBJ_SOCKS,
            ResourceId.IMG_OBJ_HOOK_MOVABLE,
            ResourceId.IMG_OBJ_ELECTRODES
        ];

        ResourcePacks.ChromeLiteAdditionalGameSounds = [
            ResourceId.SND_TELEPORT,
            ResourceId.SND_ELECTRIC
        ];

        // fonts
        ResourcePacks.StandardFonts = [
            ResourceId.FNT_SMALL_FONT,
            ResourceId.FNT_BIG_FONT,
            ResourceId.FNT_FONT_NUMBERS_BIG
        ];

        ResourcePacks.StandardGameSounds = [
            ResourceId.SND_GAME_MUSIC,
            ResourceId.SND_BOUNCER,
            ResourceId.SND_BUBBLE,
            ResourceId.SND_BUBBLE_BREAK,
            ResourceId.SND_CANDY_BREAK,
            ResourceId.SND_CANDY_LINK,
            ResourceId.SND_MONSTER_CHEWING,
            ResourceId.SND_MONSTER_CLOSE,
            ResourceId.SND_MONSTER_OPEN,
            ResourceId.SND_MONSTER_SAD,
            ResourceId.SND_PUMP_1,
            ResourceId.SND_PUMP_2,
            ResourceId.SND_PUMP_3,
            ResourceId.SND_PUMP_4,
            ResourceId.SND_ROPE_BLEAK_1,
            ResourceId.SND_ROPE_BLEAK_2,
            ResourceId.SND_ROPE_BLEAK_3,
            ResourceId.SND_ROPE_BLEAK_4,
            ResourceId.SND_ROPE_GET,
            ResourceId.SND_SPIDER_ACTIVATE,
            ResourceId.SND_SPIDER_FALL,
            ResourceId.SND_SPIDER_WIN,
            ResourceId.SND_STAR_1,
            ResourceId.SND_STAR_2,
            ResourceId.SND_STAR_3,
            ResourceId.SND_WIN
        ];

        ResourcePacks.FullGameAdditionalSounds = [
            ResourceId.SND_ELECTRIC,
            ResourceId.SND_GRAVITY_OFF,
            ResourceId.SND_GRAVITY_ON,
            ResourceId.SND_RING,
            ResourceId.SND_WHEEL,
            ResourceId.SND_SPIKE_ROTATE_IN,
            ResourceId.SND_SPIKE_ROTATE_OUT,
            ResourceId.SND_SCRATCH_IN,
            ResourceId.SND_SCRATCH_OUT,
            ResourceId.SND_BUZZ,
            ResourceId.SND_TELEPORT
        ];

        ResourcePacks.StandardMenuImageFilenames = [
            'bBtn_bgd.png',
            'box_lock.png',
            'box_nav_menu.png',
            'box_omnom.png',
            'boxcutter.png',
            'boxmore_bgd.png',
            'buttonsprite.png',
            'fb.png',
            'fBtn_bgd.png',
            'flags.png',
            'fun-omnom.png',
            'gamecomplete.jpg',
            'lBtn_bgd.png',
            'level_bgd.png',
            'level_bgd_small.png',
            'leveltape.png',
            'leveltape_left.png',
            'leveltape_right.png',
            'mBtn_bgd.png',
            'menu_result_en.png',
            'menu_result_fr.png',
            'menu_result_gr.png',
            'menu_result_ru.png',
            'menubg.jpg',
            'options_stars_bgd.png',
            'options_stars_bgd_small.png',
            'perfect_mark.png',
            'ph_logo.png',
            'result_line.png',
            'sBtn_bgd.png',
            'shadow.png',
            'star_result.png',
            'star_result_small.png',
            'startbg.jpg',
            'taperoll.png'
        ];

        ResourcePacks.DrawingMenuImageFilenames = [
            'drawing-bg.png'
        ];

        ResourcePacks.NetDesignResolutionImageNames = [
            'android.png',
            'box.png',
            'comic.png',
            'facebook.png',
            'footer_dot.png',
            'footer_finger.png',
            'full_version_bg.png',
            'full_version_text.png',
            'game_bg.png',
            'ipad.png',
            'iphone.png',
            'more_close.png',
            'more_text.png',
            'more_wallpaper.png',
            'more_window_bg.png',
            'more.png',
            'papercraft.png',
            'privacy.png',
            'shop_over.png',
            'shop.png',
            'terms.png',
            'twitter.png',
            'video_bg.png',
            'youtube.png',
            'zepto.png',
            'zeptologo.png'
        ];

        return ResourcePacks;
    }
);




define('ui/BoxType',[], function () {
    /**
     * @enum {string}
     */
    var BoxType = {
        NORMAL: "NORMAL",
        IEPINNED: "IEPINNED",
        MORECOMING: "MORECOMING",
        PURCHASE: "PURCHASE",
        TIME: "TIME"
    };

    return BoxType;
});
define('resources/LangId',[], function () {


    var LangId = {
        EN: 0,
        FR: 1,
        DE: 2,
        RU: 3,
        KO: 4, //system
        ZH: 5, 
        JA: 6,
        ES: 7,
        CA: 8,
        BR: 9, //system
        IT: 10,
        NL: 11
    };

    LangId.fromString = function (val) {
        switch (val) {
            case 'de':
                return LangId.DE;
            case 'fr':
                return LangId.FR;
            case 'ru':
                return LangId.RU;
            case 'en':
            case 'en_GB':
            case 'en_US':
                return LangId.EN;
            case 'ko':
                return LangId.KO;
            case 'zh':
                return LangId.ZH;
            case 'ja':
                return LangId.JA;
            case 'es':
                return LangId.ES;
            case 'it':
                return LangId.IT;
            case 'nl':
                return LangId.NL;
            case 'br':
                return LangId.BR;
            case 'ca':
                return LangId.CA;
        }

        // handle BCP-47 style codes, ex: en-US
        if (val.length >= 3) {
            switch(val.substr(0,3)) {
                case 'de-':
                    return LangId.DE;
                case 'fr-':
                    return LangId.FR;
                case 'ru-':
                    return LangId.RU;
                case 'en-':
                    return LangId.EN;
                case 'ko-':
                    return LangId.KO;
                case 'zh-':
                    return LangId.ZH;
                case 'ja-':
                    return LangId.JA;
                case 'es-':
                    return LangId.ES;
                case 'it-':
                    return LangId.IT;
                case 'nl-':
                    return LangId.NL;
                case 'br-':
                    return LangId.BR;
                case 'ca-':
                    return LangId.CA;
            }
        }

        return null;
    };

    LangId.toCountryCode = function (langId) {
        switch (langId) {
            case LangId.DE:
                return 'de';
            case LangId.FR:
                return 'fr';
            case LangId.RU:
                return 'ru';
            case LangId.KO:
                return 'ko';
            case LangId.ZH:
                return 'zh';
            case LangId.JA:
                return 'ja';
            case LangId.ES:
                return 'es';
            case LangId.IT:
                return 'it';
            case LangId.NL:
                return 'nl';
            case LangId.BR:
                return 'br';
            case LangId.CA:
                return 'ca';
            case LangId.EN:
            default:
                return 'en';
        }
    };

    return LangId;
});

define('config/editions/net-edition',
    [
        'boxes',
        'resources/ResourcePacks',
        'resources/ResourceId',
        'ui/BoxType',
        'resources/LangId'
    ],
    function (boxes, ResourcePacks, ResourceId, BoxType, LangId) {

        var netEdition = {

            siteUrl: "http://www.cuttherope.net",

            // no hidden drawings yet
            disableHiddenDrawings: true,

            // the text to display on the box in the box selector
            boxText: [
                { en: "Cardboard Box", ko: "골판지 상자", zh: "纸板盒", ja: "ダンボール箱", nl: "Karton", it: "Cartone", ca: "Cartró", br: "Papelão", es: "Cartón", fr: "Carton", de: "Pappkiste", ru: "\u041a\u0430\u0440\u0442\u043e\u043d\u043d\u0430\u044f"},
                { en: "Fabric Box", ko: "천 상자", zh: "布盒", ja: "布の箱", nl: "Stof", it: "Tessuto", ca: "Tela", br: "Tecido", es: "Tela", fr: "Tissu", de: "Stoffkiste", ru: "\u0422\u043a\u0430\u043d\u0435\u0432\u0430\u044f"},
                { en: "Toy Box", ko: "장난감 상자", zh: "玩具盒", ja: "おもちゃ箱", nl: "Speelgoed", it: "Giochi", ca: "Joguines", br: "Brinquedos", es: "Juguetes", fr: "Jouets", de: "Spielzeugkiste", ru: "\u0418\u0433\u0440\u0443\u0448\u0435\u0447\u043d\u0430\u044f"},
                { en: "Magic Box", ko: "마술 상자", zh: "魔盒", ja: "魔法の箱", nl: "Tover", it: "Magica", ca: "Magica", br: "Mágica", es: "Magia", fr: "Magique", de: "Magiekiste", ru: "\u0412\u043e\u043b\u0448\u0435\u0431\u043d\u0430\u044f"},
                { en: "New levels\ncoming soon!", ko:"새 레벨들이 곧 추가됩니다!", zh:"新关卡 即将到来！", ja: "次の*レベル まで もう すこし!", nl: "Er komen binnenkort nieuwe levels aan!", it: "Nuovi livelli in arrivo!", ca: "Nous nivells pròximament!", br: "Novos níveis em breve!", es: "¡Más niveles próximamente!", fr: "De nouveaux niveaux bient\u00f4t disponibles!", de: "Neue Level\nkommen bald!", ru: "\u041d\u043e\u0432\u044b\u0435 \u0443\u0440\u043e\u0432\u043d\u0438\n\u043d\u0430 \u043f\u043e\u0434\u0445\u043e\u0434\u0435!"}
            ],

            // !LANG
            languages: [
                LangId.EN,
                LangId.FR,
                LangId.IT,
                LangId.DE,
                LangId.NL,
                LangId.RU,
                LangId.ES,
                LangId.BR,
                LangId.CA,
                LangId.KO,
                LangId.ZH,
                LangId.JA
            ],

            // the background image to use for the box in the box selector
            boxImages: ["box1_bgd.png", "box2_bgd.png", "box6_bgd.png", "box4_bgd.png", "boxmore_bgd.png"],

            // no box borders in Chrome theme
            boxBorders: [],

            // images used for the sliding door transitions
            boxDoors: [
                'levelbg1.jpg',
                'levelbg2.jpg',
                'levelbg6.jpg',
                'levelbg4.jpg'
            ],

            // the type of box to create
            boxTypes: [BoxType.NORMAL, BoxType.NORMAL, BoxType.NORMAL, BoxType.NORMAL, BoxType.MORECOMING],

            // how many stars are required to unlock each box
            unlockStars: [0, 20, 40, 60, null],

            // the index of the quad for the support OmNom sits on
            supports: [0, 1, 5, 3, null],

            // determines whether the earth animation is shown
            showEarth: [false, false, false, false, false],

            menuSoundIds: ResourcePacks.StandardMenuSounds,

            gameSoundIds: ResourcePacks.StandardGameSounds.concat(
                ResourcePacks.ChromeLiteAdditionalGameSounds),

            menuImageFilenames: ResourcePacks.StandardMenuImageFilenames,

            loaderPageImages: [
                'loader-bg.jpg',
                'loader-logo.png'
            ],

            gameImageIds: ResourcePacks.StandardGameImages.concat(
                ResourcePacks.ChromeLiteAdditionalGameImages),

            boxes: boxes,

            levelBackgroundIds: [
                ResourceId.IMG_BGR_01_P1,
                ResourceId.IMG_BGR_02_P1,

                // import to use the toy box bg (#6 in full game) so offsets are correct
                ResourceId.IMG_BGR_06_P1,

                // magic box
                ResourceId.IMG_BGR_04_P1
            ],

            // none of the chrome lite levels scroll
            levelOverlayIds: [],

            // hidden drawings are disabled
            drawingImageNames: []
        };

        return netEdition;
    }
);
define('edition',
    [
        'config/editions/net-edition'
    ],
    function (netEdition) {

        var mobileEdition = netEdition;
        mobileEdition.siteUrl = 'http://mozilla.cuttherope.net';

        return mobileEdition;
    }
);
define('utils/PubSub',[], function () {
    var PubSub = {},
        subscriptions = [];

    PubSub.subscribe = function (name, callback) {
        subscriptions.push({name: name, callback: callback});
        return [name, callback];
    };
    PubSub.unsubscribe = function (name, callback) {
        var i, sub;
        for (i = subscriptions.length; i >= 0; i--) {
            sub = subscriptions[i];
            if (sub.name === name && sub.callback === callback) {
                subscriptions.splice(i, 1);
            }
        }
    };
    PubSub.publish = function (name) {
        var callbacks = [],
            args = Array.prototype.slice.call(arguments, 1),
            i, len;
        if (subscriptions.length > 0) {
            for (i = 0, len = subscriptions.length; i < len; i++) {
                if (subscriptions[i].name === name) {
                    callbacks.push(subscriptions[i].callback);
                }
            }
            for (i = 0, len = callbacks.length; i < len; i++) {
                callbacks[i].apply(this, args);
            }
        }
    };

    /**
     * set of well known channels
     * @enum {number}
     */
    PubSub.ChannelId = {
        LevelWon: 0,
        LevelLost: 1,
        OmNomClicked: 2,
        DrawingClicked: 3,
        StarCountChanged: 4,
        ControllerActivated: 5,
        ControllerDeactivateRequested: 6,
        ControllerDeactivated: 7,
        ControllerPaused: 8,
        ControllerUnpaused: 9,
        ControllerViewHidden: 10,
        ControllerViewShow: 11,
        LanguageChanged: 12,
        ShowOptionsPage: 13,
        LoadIntroVideo: 14,
        Share: 15,
        ShowOptions: 16,
        EnableGame: 17,
        DisableGame: 18,
        SetPaidBoxes: 19,
        AppInit: 20,
        AppDomReady: 21,
        AppRun: 22,
        PurchaseBoxesPrompt: 23,
        PauseGame: 24,
        AchievementManager: 25,
        UpdateBoxScore: 26,
        SignIn: 27,
        SignOut: 28,
        UpdateCandyScroller: 29,
        UpdateVisibleBoxes: 30,
        SelectedBoxChanged: 31,
        UserIdChanged: 32,
        RoamingSettingProvider: 33,
        RoamingDataChanged: 34,
        BoxesUnlocked: 35
    };

    return PubSub;
});
define('core/SettingStorage',
    [
        'edition',
        'utils/PubSub'
    ],
    function (edition, PubSub) {

        var editionPrefix = edition.settingPrefix || '',
            prefix = editionPrefix;

        PubSub.subscribe(PubSub.ChannelId.UserIdChanged, function (userId) {
            if (userId) {
                prefix = userId + '-' + editionPrefix;
            } else {
                prefix = editionPrefix;
            }
        });

        var settingCache = {};

        var SettingStorage = {
            get: function (key) {
                if (!window.localStorage) {
                    return null;
                }
                //console.log("GET",key);
                if (key in settingCache) {
                    return settingCache[key];
                }

                return localStorage.getItem(prefix + key);
            },
            set: function (key, value) {
                if (window.localStorage) {
                    //console.log("SET",key,value);
                    if (value == null) {
                        delete settingCache[key];
                        localStorage.removeItem(prefix + key);
                    }
                    else {
                        settingCache[key] = value.toString();
                        localStorage.setItem(prefix + key, value.toString());
                    }
                }
            },
            remove: function (key) {
                if (window.localStorage) {
                    //console.log("REMOVE",key)
                    delete settingCache[key];
                    localStorage.removeItem(prefix + key);
                }
            },
            getBoolOrDefault: function (key, defaultValue) {
                var val = this.get(key);
                if (val == null) {
                    return defaultValue;
                }
                return (val === 'true');
            },
            getIntOrDefault: function (key, defaultValue) {
                var val = this.get(key);
                if (val == null) {
                    return defaultValue;
                }
                return parseInt(val, 10);
            }
        };

        return SettingStorage;

    }
);


define('ui/QueryStrings',
    [],
    function () {

        // putting all query strings in a central location so that we can easily add/remove for ship
        var QueryStrings = new function () {

            // parse the query strings into a dictionary
            function getQueryStrings() {
                var assoc = {},
                    queryString = location.search.substring(1) || '',
                    keyValues = queryString.split('&'),
                    i, len, kv,
                    decode = function(s) {
                        return decodeURIComponent(s.replace(/\+/g, " "));
                    };

                for (i = 0, len = keyValues.length; i < len; i++) {
                    kv = keyValues[i].split('=');
                    if (kv.length > 1) {
                        assoc[decode(kv[0])] = decode(kv[1]);
                    }
                }
                return assoc;
            }

            var qs = getQueryStrings();

            // case insensitive lookup
            var urlContains = function (val) {
                var url = window.location.href.toLowerCase();
                return (url.indexOf(val.toLowerCase()) >= 0);
            };

            // debug querystrings
            if (false || false) {
                this.box = (qs['box'] == null) ? null : parseInt(qs['box'], 10);
                this.level = (qs['level'] == null) ? null : parseInt(qs['level'], 10);
                this.minFps = (qs['minFps'] == null) ? null : parseInt(qs['minFps'], 10);
                this.unlockAllBoxes = (this.box != null && this.level != null) || urlContains('unlockAllBoxes=true');
                this.forcePinnedBox = this.unlockAllBoxes || urlContains('enablePinnedBox=true');
                this.createScoresForBox = (qs['createScoresForBox'] == null) ? null : parseInt(qs['createScoresForBox'], 10);
            }

            // there are ok to leave in for ship
            this.lang = qs['lang'];
            this.showBoxBackgrounds = urlContains('boxBackgrounds=true');
            this.showFrameRate = urlContains('showFrameRate=true');
            this.forceHtml5Audio = urlContains('html5audio=true');

            // for testing
            //this.unlockAllBoxes = true;
            //this.showFrameRate = true;
        };

        return QueryStrings;
    }
);

define('platformLoc',
    [
        'resources/LangId'
    ],
    function (LangId) {

        var DefaultLoc = {
            getDefaultLangId: function () {
                return LangId.EN;
            }
        };

        return DefaultLoc;
    }
);

define('game/CTRSettings',
    [
        'core/SettingStorage',
        'ui/QueryStrings',
        'resources/LangId',
        'platformLoc',
        'utils/PubSub'
    ],
    function (SettingStorage, QueryStrings, LangId, platformLoc, PubSub) {

        var SettingKeys = {
            MUSIC: 'music',
            SOUND: 'sound',
            CLICK_TO_CUT: 'clickToCut',
            IS_HD: 'isHD',
            LANGUAGE: "language"
        };

        var CTRSettings = {

            showMenu: true,

            disableTextSelection: true,

            fpsEnabled: QueryStrings.showFrameRate,

            // OmNom will say hello on first level of every session
            showGreeting: true,

            // game music
            getMusicEnabled: function () {
                return SettingStorage.getBoolOrDefault(SettingKeys.MUSIC, true);
            },
            setMusicEnabled: function (musicEnabled) {
                SettingStorage.set(SettingKeys.MUSIC, musicEnabled);
            },

            // sound effects
            getSoundEnabled: function () {
                return SettingStorage.getBoolOrDefault(SettingKeys.SOUND, true);
            },
            setSoundEnabled: function (soundEnabled) {
                SettingStorage.set(SettingKeys.SOUND, soundEnabled);
            },

            // click-to-cut
            getClickToCut: function () {
                return SettingStorage.getBoolOrDefault(SettingKeys.CLICK_TO_CUT, false);
            },
            setClickToCut: function (clickToCutEnabled) {
                SettingStorage.set(SettingKeys.CLICK_TO_CUT, clickToCutEnabled);
            },

            // locale
            getLangId: function () {
                // first see if a querystring override was specified
                if (QueryStrings.lang) {
                    return LangId.fromString(QueryStrings.lang);
                }

                // next, check the local storage setting
                var langId = SettingStorage.getIntOrDefault(SettingKeys.LANGUAGE, null);
                if (langId == null) {

                    // see if the platform can provide a default language
                    langId = platformLoc.getDefaultLangId();

                    // default to english
                    if (langId == null) {
                        langId = LangId.EN;
                    }
                }

                return langId;
            },
            setLangId: function (langId) {
                SettingStorage.set(SettingKeys.LANGUAGE, langId);
            },

            getIsHD: function () {
                return SettingStorage.getBoolOrDefault(SettingKeys.IS_HD, null);
            },
            // sd or hd resolution
            setIsHD: function (isHD) {
                SettingStorage.set(SettingKeys.IS_HD, isHD);
            },

            clear: function () {
                SettingStorage.remove(SettingKeys.IS_HD);
            }
        };

        return CTRSettings;
    }
);
define('config/resolutions/2560x1440',
    [ 'core/Rectangle' ],
    function (Rectangle) {

        var res2560x1440 = {

            /**
             * @const
             * @type {number}
             */
            CANVAS_WIDTH: 2560,
            /**
             * @const
             * @type {number}
             */
            CANVAS_HEIGHT: 1440,

            /**
             * @const
             * @type {number}
             */
            CANVAS_SCALE: 1,

            /**
             * Platform multiplier (maps the height of the level to the height of the canvas).
             * Map height is 480 and canvas height is 576.
             * @const
             * @type {number}
             */
            PM: 3,

            /**
             * Adjusts the y offset for the level
             * @const
             * @type {number}
             */
            PMY: 0,

            /**
             * @const
             * @type {number}
             */
            BUNGEE_REST_LEN: 105,

            /**
             * @const
             * @type {number}
             */
            DEFAULT_BUNGEE_LINE_WIDTH: 10,

            /**
             * @const
             * @type {number}
             */
            DEFAULT_BUNGEE_WIDTH: 6,

            /**
             * @const
             * @type {number}
             */
            CLICK_TO_CUT_SEARCH_RADIUS: 60,

            /**
             * @const
             * @type {number}
             */
            MOVER_SCALE: 3,

            /**
             * @const
             * @type {number}
             */
            STAR_RADIUS: 42,

            /**
             * @const
             * @type {number}
             */
            MOUTH_OPEN_RADIUS: 200,

            /**
             * @const
             * @type {number}
             */
            OUT_OF_SCREEN_ADJUSTMENT_BOTTOM: 400,

            /**
             * @const
             * @type {number}
             */
            OUT_OF_SCREEN_ADJUSTMENT_TOP: -400,

            /**
             * @const
             * @type {Rectangle}
             */
            STAR_DEFAULT_BB: new Rectangle(22, 20, 30, 30),

            /**
             * @const
             * @type {Rectangle}
             */
            STAR_BB: new Rectangle(70, 64, 82, 82),

            /**
             * @const
             * @type {Rectangle}
             */
            TARGET_BB: new Rectangle(264.0, 350.0, 108.0, 2.0),


            // Prehistoric OmNom uses sprites which require a different bounding box
            TARGET2_BB: new Rectangle(192, 278, 108, 2),

            /**
             * @const
             * @type {number}
             */
            TUTORIAL_HAND_TARGET_X_1: 385,
            /**
             * @const
             * @type {number}
             */
            TUTORIAL_HAND_TARGET_X_2: 700,

            /**
             * @const
             * @type {number}
             */
            BUBBLE_SIZE: 85,
            /**
             * @const
             * @type {number}
             */
            BUBBLE_RADIUS: 160,
            /**
             * @const
             * @type {number}
             */
            BUBBLE_TOUCH_OFFSET: 60,
            /**
             * @const
             * @type {number}
             */
            BUBBLE_TOUCH_SIZE: 200,
            /**
             * @const
             * @type {Rectangle}
             */
            BUBBLE_BB: new Rectangle(48, 48, 152, 152),

            /**
             * @const
             * @type {number}
             */
            BUBBLE_IMPULSE_Y: -35,
            /**
             * @const
             * @type {number}
             */
            BUBBLE_IMPULSE_RD: 14,
            /**
             * @const
             * @type {number}
             */
            STAR_SPIKE_RADIUS: 15,

            /**
             * @const
             * @type {number}
             */
            BOUNCER_RADIUS: 40,

            /**
             * @const
             * @type {number}
             */
            PUMP_POWER_RADIUS: 624,
            /**
             * @const
             * @type {Rectangle}
             */
            PUMP_BB: new Rectangle(250, 250, 300, 300),
            /**
             * @const
             * @type {number}
             */
            PUMP_DIRT_SPEED: 2500,
            /**
             * @const
             * @type {number}
             */
            PUMP_DIRT_PARTICLE_SIZE: 15,
            /**
             * @const
             * @type {number}
             */
            PUMP_DIRT_OFFSET: 100,

            /**
             * @const
             * @type {number}
             */
            CANDY_BUBBLE_TUTORIAL_LIMIT_Y: 300,
            /**
             * @const
             * @type {number}
             */
            CANDY_BUBBLE_TUTORIAL_LIMIT_X: 1400,
            /**
             * @const
             * @type {Rectangle}
             */
            CANDY_BB: new Rectangle(142, 157, 112, 104),
            /**
             * @const
             * @type {Rectangle}
             */
            CANDY_LR_BB: new Rectangle(155, 176, 88, 76),

            /**
             * @const
             * @type {number}
             */
            GRAB_RADIUS_ALPHA: 1,
            /**
             * @const
             * @type {number}
             */
            GRAB_WHEEL_RADIUS: 110,
            /**
             * @const
             * @type {number}
             */
            GRAB_WHEEL_MAX_ROTATION: 5.625,
            /**
             * @const
             * @type {number}
             */
            GRAB_WHEEL_SCALE_DIVISOR: 1400,
            /**
             * @const
             * @type {number}
             */
            GRAB_ROPE_ROLL_MAX_LENGTH: 1650,
            /**
             * @const
             * @type {number}
             */
            GRAB_MOVE_BG_WIDTH: 142,
            /**
             * @const
             * @type {number}
             */
            GRAB_MOVE_BG_X_OFFSET: 74,
            /**
             * @const
             * @type {number}
             */
            GRAB_MOVE_RADIUS: 65,
            /**
             * @const
             * @type {number}
             */
            SPIDER_SPEED: 117,
            /**
             * @const
             * @type {number}
             */
            SOCK_LIGHT_Y: 270,
            /**
             * @const
             * @type {number}
             */
            SOCK_WIDTH: 140,
            /**
             * @const
             * @type {number}
             */
            SOCK_ROTATION_Y_OFFSET: 15,
            /**
             * @const
             * @type {number}
             */
            STAR_SOCK_RADIUS: 40,
            /**
             * @const
             * @type {number}
             */
            SOCK_TELEPORT_Y: -16,

            /**
             * @const
             * @type {number}
             */
            POLLEN_MIN_DISTANCE: 44,
            /**
             * @const
             * @type {number}
             */
            POLLEN_MAX_OFFSET: 4,

            /**
             * @const
             * @type {number}
             */
            RC_CONTROLLER_RADIUS: 90,

            /**
             * @const
             * @type {number}
             */
            IGNORE_TOUCHES_DISTANCE: 100,
            /**
             * @const
             * @type {number}
             */
            PREVIEW_CAMERA_SPEED: 800,
            /**
             * @const
             * @type {number}
             */
            PREVIEW_CAMERA_SPEED2: 400,
            /**
             * @const
             * @type {number}
             */
            MAX_PREVIEW_CAMERA_SPEED: 1000,
            /**
             * @const
             * @type {number}
             */
            MIN_PREVIEW_CAMERA_SPEED: 300,
            /**
             * @const
             * @type {number}
             */
            CAMERA_SPEED_THRESHOLD: 5500,
            /**
             * @const
             * @type {number}
             */
            CAMERA_SPEED: 14,
            /**
             * @const
             * @type {number}
             */
            CUT_MAX_SIZE: 12,
            /**
             * @const
             * @type {number}
             */
            PHYSICS_SPEED_MULTIPLIER: 1.4
        };

        return res2560x1440;
    }
);
define('config/resolutions/scale',
    [ 'core/Rectangle', 'config/resolutions/2560x1440'],
    function (Rectangle, res2560x1440) {

        /**
         * Initializes the target device profile using a base profile. A set of properties
         * will be scaled (based on the difference between the base and target resolutions)
         * and then added to the target.
         * @param base
         * @param target
         */
        function initProfile(base, target) {
            var scale = target.CANVAS_SCALE;

            target.BUNGEE_REST_LEN = base.BUNGEE_REST_LEN * scale;
            target.MOVER_SCALE = base.MOVER_SCALE * scale;
            target.STAR_RADIUS = base.STAR_RADIUS * scale;
            target.MOUTH_OPEN_RADIUS = base.MOUTH_OPEN_RADIUS * scale;
            target.OUT_OF_SCREEN_ADJUSTMENT_TOP = base.OUT_OF_SCREEN_ADJUSTMENT_TOP * scale;
            target.OUT_OF_SCREEN_ADJUSTMENT_BOTTOM = base.OUT_OF_SCREEN_ADJUSTMENT_BOTTOM * scale;
            target.CLICK_TO_CUT_SEARCH_RADIUS = base.CLICK_TO_CUT_SEARCH_RADIUS * scale;

            target.TARGET_BB = Rectangle.scaleCopy(base.TARGET_BB, scale);
            target.TARGET2_BB = Rectangle.scaleCopy(base.TARGET2_BB, scale);

            target.TUTORIAL_HAND_TARGET_X_1 = base.TUTORIAL_HAND_TARGET_X_1 * scale;
            target.TUTORIAL_HAND_TARGET_X_2 = base.TUTORIAL_HAND_TARGET_X_2 * scale;

            target.BUBBLE_SIZE = base.BUBBLE_SIZE * scale;
            target.BUBBLE_TOUCH_OFFSET = base.BUBBLE_TOUCH_OFFSET * scale;
            target.BUBBLE_TOUCH_SIZE = base.BUBBLE_TOUCH_SIZE * scale;
            target.BUBBLE_BB = Rectangle.scaleCopy(base.BUBBLE_BB, scale);
            target.BUBBLE_RADIUS = base.BUBBLE_RADIUS * scale;

            target.STAR_SPIKE_RADIUS = base.STAR_SPIKE_RADIUS * scale;
            target.STAR_BB = Rectangle.scaleCopy(base.STAR_BB, scale);
            target.STAR_DEFAULT_BB = Rectangle.scaleCopy(base.STAR_DEFAULT_BB, scale);

            target.BOUNCER_RADIUS = base.BOUNCER_RADIUS * scale;

            target.PUMP_POWER_RADIUS = target.PUMP_POWER_RADIUS || (base.PUMP_POWER_RADIUS * scale);
            target.PUMP_BB = Rectangle.scaleCopy(base.PUMP_BB, scale);
            target.PUMP_DIRT_SPEED = base.PUMP_DIRT_SPEED * scale;
            target.PUMP_DIRT_PARTICLE_SIZE = base.PUMP_DIRT_PARTICLE_SIZE * scale;
            target.PUMP_DIRT_OFFSET = base.PUMP_DIRT_OFFSET * scale;

            target.CANDY_BB = Rectangle.scaleCopy(base.CANDY_BB, scale);
            target.CANDY_LR_BB = Rectangle.scaleCopy(base.CANDY_LR_BB, scale);
            target.CANDY_BUBBLE_TUTORIAL_LIMIT_Y = base.CANDY_BUBBLE_TUTORIAL_LIMIT_Y * scale;
            target.CANDY_BUBBLE_TUTORIAL_LIMIT_X = base.CANDY_BUBBLE_TUTORIAL_LIMIT_X * scale;

            target.IGNORE_TOUCHES_DISTANCE = base.IGNORE_TOUCHES_DISTANCE * scale;
            target.PREVIEW_CAMERA_SPEED = base.PREVIEW_CAMERA_SPEED * scale;
            target.PREVIEW_CAMERA_SPEED2 = base.PREVIEW_CAMERA_SPEED2 * scale;
            target.MAX_PREVIEW_CAMERA_SPEED = base.MAX_PREVIEW_CAMERA_SPEED * scale;
            target.MIN_PREVIEW_CAMERA_SPEED = base.MIN_PREVIEW_CAMERA_SPEED * scale;
            target.CAMERA_SPEED_THRESHOLD = base.CAMERA_SPEED_THRESHOLD * scale;
            target.CAMERA_SPEED = base.CAMERA_SPEED * scale;

            target.GRAB_WHEEL_MAX_ROTATION = base.GRAB_WHEEL_MAX_ROTATION * scale;
            target.GRAB_WHEEL_SCALE_DIVISOR = base.GRAB_WHEEL_SCALE_DIVISOR * scale;
            target.GRAB_WHEEL_RADIUS = base.GRAB_WHEEL_RADIUS * scale;
            target.GRAB_ROPE_ROLL_MAX_LENGTH = base.GRAB_ROPE_ROLL_MAX_LENGTH * scale;
            target.GRAB_MOVE_BG_WIDTH = base.GRAB_MOVE_BG_WIDTH * scale;
            target.GRAB_MOVE_BG_X_OFFSET = base.GRAB_MOVE_BG_X_OFFSET * scale;
            target.GRAB_MOVE_RADIUS = base.GRAB_MOVE_RADIUS * scale;
            target.SPIDER_SPEED = base.SPIDER_SPEED * scale;

            target.POLLEN_MIN_DISTANCE = base.POLLEN_MIN_DISTANCE * scale;
            target.POLLEN_MAX_OFFSET = base.POLLEN_MAX_OFFSET * scale;

            target.RC_CONTROLLER_RADIUS = base.RC_CONTROLLER_RADIUS * scale;

            target.SOCK_LIGHT_Y = base.SOCK_LIGHT_Y * scale;
            target.SOCK_WIDTH = base.SOCK_WIDTH * scale;
            target.SOCK_ROTATION_Y_OFFSET = base.SOCK_ROTATION_Y_OFFSET * scale;
            target.STAR_SOCK_RADIUS = base.STAR_SOCK_RADIUS * scale;
            target.SOCK_TELEPORT_Y = base.SOCK_TELEPORT_Y * scale;

            target.CUT_MAX_SIZE = base.CUT_MAX_SIZE * scale;

            target.uiScaledNumber = function (n) {
                return Math.round(n * target.UI_IMAGES_SCALE);
            };
        }

        /**
         * Initializes a target profile scaled from the mac version profile
         */
        function initProfileFromMac(target) {
            initProfile(res2560x1440, target);
        }

        return initProfileFromMac;
    }
);


define('config/resolutions/480x270',
    [],
    function () {
        var res480x270 = {

            /**
             * @const
             * @type {number}
             */
            VIDEO_WIDTH: 768,

            /**
             * @const
             * @type {number}
             */
            CANVAS_WIDTH: 480,
            /**
             * @const
             * @type {number}
             */
            CANVAS_HEIGHT: 320,
            /**
             * @const
             * @type {number}
             */
            CANVAS_SCALE: 0.1875,

            /**
             * @const
             * @type {number}
             */
            UI_IMAGES_SCALE: 0.46875,
            /**
             * @const
             * @type {number}
             */
            UI_TEXT_SCALE: 1,
            /**
             * @const
             * @type {number}
             */
            UI_WIDTH: 480,
            /**
             * @const
             * @type {number}
             */
            UI_HEIGHT: 320,

            /**
             * @const
             * @type {number}
             */
            BUNGEE_BEZIER_POINTS: 2,

            /**
             * @const
             * @type {number}
             */
            DEFAULT_BUNGEE_LINE_WIDTH: 2,

            /**
             * @const
             * @type {number}
             */
            DEFAULT_BUNGEE_WIDTH: 2,

            /**
             * Platform multiplier (maps the height of the level to the height of the canvas).
             * Map height is 480 and canvas height is 576.
             * @const
             * @type {number}
             */
            PM: 0.5625,

            /**
             * Adjusts the y offset for the level
             * @const
             * @type {number}
             */
            PMY: 0,

            /**
             * @const
             * @type {number}
             */
            GRAB_RADIUS_ALPHA: 0.8,

            /**
             * Controls ascent speed of bubble
             * @const
             * @type {number}
             */
            BUBBLE_IMPULSE_Y: -9,
            /**
             * Controls descent speed of bubble
             * @const
             * @type {number}
             */
            BUBBLE_IMPULSE_RD: 22,

            /**
             * @const
             * @type {number}
             */
            BOUNCER_MAX_MOVEMENT: 158,

            /**
             * @const
             * @type {number}
             */
            PHYSICS_SPEED_MULTIPLIER: 0.65
        };

        window.resolution = res480x270;

        return res480x270;
    }
);

define('config/resolutions/768x432',
    [],
    function () {

        var res768x432 = {
            /**
             * @const
             * @type {number}
             */
            VIDEO_WIDTH: 768,

            /**
             * @const
             * @type {number}
             */
            CANVAS_WIDTH: 768,
            /**
             * @const
             * @type {number}
             */
            CANVAS_HEIGHT: 432,
            /**
             * @const
             * @type {number}
             */
            CANVAS_SCALE: 0.3,

            /**
             * @const
             * @type {number}
             */
            UI_IMAGES_SCALE: .75,
            /**
             * @const
             * @type {number}
             */
            UI_TEXT_SCALE: 1,
            /**
             * @const
             * @type {number}
             */
            UI_WIDTH: 768,
            /**
             * @const
             * @type {number}
             */
            UI_HEIGHT: 432,

            /**
             * @const
             * @type {number}
             */
            BUNGEE_BEZIER_POINTS: 2,

            /**
             * @const
             * @type {number}
             */
            DEFAULT_BUNGEE_LINE_WIDTH: 3,

            /**
             * @const
             * @type {number}
             */
            DEFAULT_BUNGEE_WIDTH: 2,

            /**
             * Platform multiplier (maps the height of the level to the height of the canvas).
             * Map height is 480 and canvas height is 432.
             * @const
             * @type {number}
             */
            PM: 0.9,

            /**
             * Adjusts the y offset for the level
             * @const
             * @type {number}
             */
            PMY: 0,

            /**
             * @const
             * @type {number}
             */
            GRAB_RADIUS_ALPHA: 0.8,

            /**
             * Controls ascent speed of bubble
             * @const
             * @type {number}
             */
            BUBBLE_IMPULSE_Y: -14,
            /**
             * Controls descent speed of bubble
             * @const
             * @type {number}
             */
            BUBBLE_IMPULSE_RD: 24,

            /**
             * @const
             * @type {number}
             */
            BOUNCER_MAX_MOVEMENT: 255,

            /**
             * @const
             * @type {number}
             */
            PHYSICS_SPEED_MULTIPLIER: 0.80
        };

        return res768x432;
    }
);
define('resolution',
    [
        "game/CTRSettings",
        "config/resolutions/scale",
        "config/resolutions/480x270",
        "config/resolutions/768x432"
    ],
    function (settings, scaleResolution, res480, res768) {

        // decide which resolution to target
        var $w = $(window),
            resolution;

        // Use 480px (for testing in desktop browser)
        resolution = res480;

        scaleResolution(resolution);
        resolution.isHD = false;

        return resolution;
    }
);

define('utils/Log',
    [],
    function () {
        var Log = {
            debug: function (message) {
                if (false && window.console) {
                    console.log(message);
                }
            },
            alert: function(message) {
                if (false) {
                    alert(message);
                    Log.debug(message);
                }
            }
        };

        return Log;
    }
);
define('visual/ImageElement',
    [
        'visual/BaseElement',
        'core/Rectangle',
        'resources/ResData',
        'utils/Canvas',
        'utils/Constants',
        'core/Vector',
        'visual/ActionType',
        'utils/Log'
    ],
    function (BaseElement, Rectangle, RES_DATA, Canvas, Constants, Vector, ActionType, Log) {

        // Note: This class is named Image in the iOS sources but we'll use
        // ImageElement to avoid conflicts with the native JS Image class.

        /**
         * Texture container with the ability to calculate and draw quads
         */
        var ImageElement = BaseElement.extend({
            init: function () {
                this._super();
            },
            /**
             * Set the texture for this image element
             * @param texture {Texture2D}
             */
            initTexture: function (texture) {
                this.texture = texture;
                this.restoreCutTransparency = false;

                if (this.texture.rects.length > 0)
                    this.setTextureQuad(0);
                else
                    this.setDrawFullImage();
            },
            getTexture: function (resId) {
                // using the resMgr would create a circular dependency,
                // so we'll assume its been loaded and fetch directly
                var texture = RES_DATA[resId].texture;
                if (!texture) {
                    Log.debug('Image not loaded: ' + RES_DATA[resId].path)
                }
                return texture;
            },
            initTextureWithId: function (resId) {
                this.resId = resId;
                this.initTexture(this.getTexture(resId));
            },
            setTextureQuad: function (n) {
                this.quadToDraw = n;

                // don't set width / height to quad size if we cut transparency from each quad
                if (!this.restoreCutTransparency) {
                    var rect = this.texture.rects[n];
                    this.width = rect.w;
                    this.height = rect.h;
                }
            },
            setDrawFullImage: function () {
                this.quadToDraw = Constants.UNDEFINED;
                this.width = this.texture.imageWidth;
                this.height = this.texture.imageHeight;
            },
            doRestoreCutTransparency: function () {
                if (this.texture.preCutSize.x !== Vector.undefined.x) {
                    this.restoreCutTransparency = true;
                    this.width = this.texture.preCutSize.x;
                    this.height = this.texture.preCutSize.y;
                }
            },
            draw: function () {
                this.preDraw();

                // only draw if the image is non-transparent
                if (this.color.a !== 0 && this.texture) {
                    if (this.quadToDraw === Constants.UNDEFINED) {
                        var qx = this.drawX,
                            qy = this.drawY;

                        Canvas.context.drawImage(this.texture.image, qx, qy);
                    }
                    else {
                        this.drawQuad(this.quadToDraw);
                    }
                }

                this.postDraw();
            },
            drawQuad: function (n) {
                var rect = this.texture.rects[n],
                    quadWidth = rect.w,
                    quadHeight = rect.h,
                    qx = this.drawX,
                    qy = this.drawY;


                if (this.restoreCutTransparency) {
                    var offset = this.texture.offsets[n];
                    if (offset) {
                        qx += offset.x;
                        qy += offset.y;

                        // the sprites are generated with rounded offsets, so we
                        // need to pad the width and height if there is an offset
                        quadWidth += this.texture.adjustmentMaxX;
                        quadHeight += this.texture.adjustmentMaxY;
                    }
                }

                // if (this.drawSizeIncrement) {
                //     // we need sub-pixel size
                //     quadWidth = ~~(quadWidth / this.drawSizeIncrement) * this.drawSizeIncrement;
                //     quadHeight = ~~(quadHeight / this.drawSizeIncrement) * this.drawSizeIncrement;
                // }
                // else {
                    // otherwise by default we snap to pixel boundaries for perf
                    quadWidth = 1 + quadWidth | 0;
                    quadHeight = 1 + quadHeight | 0;
                //}

                // if (this.drawPosIncrement) {
                //     console.log(this.drawPosIncrement, "WAT")
                //     // we need sub-pixel alignment
                //     qx = ~~(qx / this.drawPosIncrement) * this.drawPosIncrement;
                //     qy = ~~(qy / this.drawPosIncrement) * this.drawPosIncrement;
                // }
                //else {
                    // otherwise by default we snap to pixel boundaries for perf
                    qx = qx | 0;
                    qy = qy | 0;
                //}

                Canvas.context.drawImage(
                    this.texture.image,
                    rect.x, rect.y, quadWidth, quadHeight, // source coordinates
                    qx, qy, quadWidth, quadHeight);         // destination coordinates
            },
            drawTiled: function (q, x, y, width, height) {

                var ctx = Canvas.context,
                    qx = 0,
                    qy = 0,
                    qw, qh, rect, yoff, xoff, wd, hg;

                if (q === Constants.UNDEFINED) {
                    qw = this.texture.imageWidth;
                    qh = this.texture.imageHeight;
                }
                else {
                    rect = this.texture.rects[q];
                    qx = rect.x;
                    qy = rect.y;
                    qw = rect.w;
                    qh = rect.h;
                }

                var xInc = qw | 0,
                    yInc = qh | 0,
                    ceilW, ceilH;

                yoff = 0;
                while (yoff < height) {
                    xoff = 0;
                    while (xoff < width) {
                        wd = width - xoff;
                        if (wd > qw) {
                            wd = qw;
                        }
                        ceilW = Math.ceil(wd);

                        hg = height - yoff;
                        if (hg > qh) {
                            hg = qh;
                        }
                        ceilH = Math.ceil(hg);

                        ctx.drawImage(this.texture.image,
                            qx | 0, qy | 0, ceilW, ceilH, // source coordinates
                            x + xoff | 0, y + yoff | 0, ceilW, ceilH); // dest coordinates

                        xoff += xInc;
                    }

                    yoff += yInc;
                }
            },
            /**
             * Returns true if the point is inside the boundaries of the current quad
             * @param x
             * @param y
             */
            pointInDrawQuad: function (x, y) {
                if (this.quadToDraw === Constants.UNDEFINED) {
                    return Rectangle.pointInRect(
                        x, y,
                        this.drawX, this.drawY,
                        this.texture.width, this.texture.height);
                }
                else {
                    var rect = this.texture.rects[this.quadToDraw],
                        qx = this.drawX,
                        qy = this.drawY;

                    if (this.restoreCutTransparency) {
                        var offset = this.texture.offsets[this.quadToDraw];
                        qx += offset.x;
                        qy += offset.y;
                    }

                    return Rectangle.pointInRect(x, y, qx, qy, rect.w, rect.h);
                }
            },
            /**
             * Returns true if the action was handled
             * @param actionData {ActionData}
             * @return {boolean}
             */
            handleAction: function (actionData) {
                if (this._super(actionData)) {
                    return true;
                }

                if (actionData.actionName === ActionType.SET_DRAWQUAD) {
                    this.setTextureQuad(actionData.actionParam);
                }
                else {
                    return false;
                }

                return true;
            },
            setElementPositionWithOffset: function (resId, index) {
                var texture = this.getTexture(resId),
                    offset = texture.offsets[index];
                this.x = offset.x;
                this.y = offset.y;
            },
            setElementPositionWithCenter: function (resId, index) {
                var texture = this.getTexture(resId),
                    rect = texture.rects[index],
                    offset = texture.offsets[index];
                this.x = offset.x + (rect.w / 2);
                this.y = offset.y + (rect.h / 2);
            }
        });

        ImageElement.create = function (resId, drawQuad) {
            var image = new ImageElement();
            image.initTextureWithId(resId);

            if (drawQuad != null)
                image.setTextureQuad(drawQuad);

            return image;
        };

        return ImageElement;
    }
);

define('visual/Font',
    [
        'visual/ImageElement',
        'utils/Canvas',
        'utils/Constants',
        'utils/Log'
    ],
    function (ImageElement, Canvas, Constants, Log) {
        var Font = ImageElement.extend({
            init: function () {
                this._super();

                this.chars = '';
                this.charOffset = 0;
                this.lineOffset = 0;
                this.spaceWidth = 0;
                this.kerning = null;
            },
            initWithVariableSizeChars: function (chars, charTexture, kerningDictionary) {
                this.chars = chars;
                this.initTexture(charTexture);
                this.kerning = kerningDictionary;
            },
            setOffsets: function (charOffset, lineOffset, spaceWidth) {
                this.charOffset = charOffset;
                this.lineOffset = lineOffset;
                this.spaceWidth = spaceWidth;
            },
            getCharQuad: function (c) {
                var charIndex = this.chars.indexOf(c);
                if (charIndex >= 0) {
                    return charIndex;
                }

                Log.alert('Char not found in font:' + c);

                // replace missing character with a period
                return this.chars.indexOf('.');
            },
            drawQuadWOBind: function (index, x, y) {
                var rect = this.texture.rects[index],
                    quadWidth = Math.ceil(rect.w),
                    quadHeight = Math.ceil(rect.h);

                Canvas.context.drawImage(
                    this.texture.image,
                    rect.x | 0, rect.y | 0, quadWidth, quadHeight, // source coordinates
                    x | 0, y | 0, quadWidth, quadHeight);         // destination coordinates
            },
            stringWidth: function (str) {
                var strWidth = 0,
                    len = str.length,
                    lastOffset = 0;
                for (var c = 0; c < len; c++) {
                    lastOffset = this.getCharOffset(str, c);

                    if (str[c] === ' ') {
                        strWidth += this.spaceWidth + lastOffset;
                    }
                    else {
                        var quadIndex = this.getCharQuad(str[c]),
                            itemWidth = this.texture.rects[quadIndex].w;
                        strWidth += itemWidth + lastOffset;
                    }
                }
                strWidth -= lastOffset;
                return Math.ceil(strWidth);
            },
            fontHeight: function () {
                return this.texture.rects[0].h;
            },
            getCharOffset: function (str, charIndex) {
                // no offset if its the last character
                if (charIndex === str.length - 1) {
                    return 0;
                }

                // use the default offset if no kerning is defined
                if (!this.kerning) {
                    return this.charOffset;
                }

                // see if kerning is specified for char pair or use the default offset
                var chars = str[charIndex] + str[charIndex + 1],
                    v = this.kerning[chars];
                if (v != null) {
                    return v;
                }
                else {
                    return this.charOffset;
                }
            }
        });

        return Font;
    }
);
define('core/Quad2D',
    [],
    function () {
        /**
         * Quad2D constructor
         * @constructor
         * @param x {number}
         * @param y {number}
         * @param w {number} width
         * @param h {number} height
         */
        function Quad2D(x, y, w, h) {
            var rightX = x + w,
                bottomY = y + h;

            // top left
            this.tlX = x;
            this.tlY = y;

            // top right
            this.trX = rightX;
            this.trY = y;

            // bottom left
            this.blX = x;
            this.blY = bottomY;

            // bottom right
            this.brX = rightX;
            this.brY = bottomY;
        }

        return Quad2D;
    }
);

define('core/Texture2D',
    ['core/Vector', 'core/Quad2D'],
    function (Vector, Quad2D) {

        var Texture2D = function (image) {
            this.image = image;
            this.rects = [];
            this.offsets = [];
            this.preCutSize = Vector.newUndefined();

            // use jquery to work around intermittent dimension issues with images
            var $img = $(image);
            this.imageWidth = image.width || $img.width();
            this.imageHeight = image.height || $img.height();
            this._invWidth = 1 / this.imageWidth;
            this._invHeight = 1 / this.imageHeight;

            // sometimes we need to adjust offsets to pixel align
            this.adjustmentMaxX = 0;
            this.adjustmentMaxY = 0;
        };

        Texture2D.prototype.addRect = function (rect) {
            this.rects.push(rect);
            this.offsets.push(new Vector(0, 0));
        };

        Texture2D.prototype.setOffset = function (index, x, y) {
            var offset = this.offsets[index];
            offset.x = x;
            offset.y = y;
        };

        Texture2D.prototype.getCoordinates = function (rect) {
            return new Quad2D(
                this._invWidth * rect.x,
                this._invHeight * rect.y,
                this._invWidth * rect.w,
                this._invHeight * rect.h);
        };

        return Texture2D;
    }
);


define('resources/ResourceMgr',
    [
        'ResInfo',
        'resources/ResData',
        'resources/ResScale',
        'resources/ResourceType',
        'resolution',
        'visual/Font',
        'core/Texture2D',
        'core/Vector',
        'core/Rectangle',
        'utils/Log'
    ],
    function (ResInfo, RES_DATA, ResScaler, ResourceType, resolution, Font, Texture2D, Vector, Rectangle, Log) {

        var ResourceMgr = {
            init: function () {
                // merge info into resource entries
                var infos = ResInfo;
                ResScaler.scaleResourceInfos(infos, resolution.CANVAS_SCALE);
                for (var i = 0, len = infos.length; i < len; i++) {
                    var info = infos[i];
                    delete info.originalRects;
                    delete info.offsetAdjustments;

                    RES_DATA[info.id].info = info;
                }
            },
            onResourceLoaded: function (resId, img) {
                // we store the resource id in the custom id
                var resource = RES_DATA[resId];
                switch (resource.type) {
                    case ResourceType.IMAGE:
                        resource.texture = new Texture2D(img);
                        this.setQuads(resource);
                        break;
                    case ResourceType.FONT:
                        resource.texture = new Texture2D(img);
                        this.setQuads(resource);
                        var font = new Font(),
                            info = resource.info;
                        font.initWithVariableSizeChars(
                            info.chars,
                            resource.texture,
                            info.kerning);
                        font.setOffsets(
                            info.charOffset,
                            info.lineOffset,
                            info.spaceWidth);
                        resource.font = font;
                        break;
                }
            },
            /**
             * Sets texture quads from xml info
             * @param resource {ResEntry}
             */
            setQuads: function (resource) {
                var t = resource.texture,
                    imageWidth = t.imageWidth,
                    imageHeight = t.imageHeight,
                    info = resource.info,
                    rects = info.rects,
                    offsets = info.offsets;

                t.preCutSize = Vector.newUndefined();

                if (!rects) {
                    return;
                }

                // we need to make sure our scaled quad doesn't slightly
                // exceed the dimensions of the image. we pad images with
                // offsets with 1 extra pixel because offsets are pixel aligned
                // so they might need to go slightly beyond the dimensions
                // specified in the rect
                t.adjustmentMaxX = info.adjustmentMaxX ? info.adjustmentMaxX : 0;
                t.adjustmentMaxY = info.adjustmentMaxY ? info.adjustmentMaxY : 0;

                for (var i = 0, len = rects.length; i < len; i++) {
                    // convert it to a Rectangle object
                    var rawRect = rects[i],
                        rect = new Rectangle(rawRect.x, rawRect.y, rawRect.w, rawRect.h);

                    if (rect.w + t.adjustmentMaxX > imageWidth) {
                        rect.w = imageWidth - t.adjustmentMaxX;
                    }
                    if (rect.h + t.adjustmentMaxY > imageHeight) {
                        rect.h = imageHeight - t.adjustmentMaxY;
                    }

                    t.addRect(rect);
                }

                if (offsets) {
                    // set the offsets inside the texture
                    var oCount = offsets.length;
                    for (i = 0; i < oCount; i++) {
                        var offset = offsets[i];
                        t.setOffset(i, offset.x, offset.y);
                    }
                }

                // see if there is a pre-cut size specified
                if (info.preCutWidth && info.preCutHeight) {
                    t.preCutSize.x = info.preCutWidth;
                    t.preCutSize.y = info.preCutHeight;
                }
            },
            getTexture: function (resId) {
                var resEntry = RES_DATA[resId];
                if (resEntry.texture) {
                    return resEntry.texture;
                }

                Log.debug('Image not yet loaded:' + resEntry.path);
                return null;
            },
            getFont: function (resId) {
                var resEntry = RES_DATA[resId];
                if (resEntry.font) {
                    return resEntry.font;
                }

                Log.debug('Font not yet loaded:' + resEntry.path);
                return null;
            }
        };

        return ResourceMgr;
    }
);
define('utils/MathHelper',
    [
        'utils/Constants'
    ],
    function (Constants) {
        var MathHelper = {
            /**
             * Fits value v to [minV, maxV]
             * @param v {number} value
             * @param minV {number}
             * @param maxV {number}
             * @return {number}
             */
            fitToBoundaries: function (v, minV, maxV) {
                return Math.max(Math.min(v, maxV), minV);
            },
            /**
             * Returns true if values have the same sign
             * @param x {number}
             * @param y {number}
             * @return {boolean}
             */
            sameSign: function (x, y) {
                return ((x < 0) === ( y < 0));
            },
            /**
             * Returns a random integer from the interval
             * @param from {number}
             * @param to {number}
             */
            randomRange: function (from, to) {
                return ~~(Math.random() * (to - from + 1) + from);
            },
            randomBool: function () {
                return (Math.random() > 0.5);
            },
            randomMinus1to1: function () {
                return Math.random() * 2 - 1;
            },
            /**
             * Returns the max of 4 numbers
             * @param v1 {number}
             * @param v2 {number}
             * @param v3 {number}
             * @param v4 {number}
             * @return {number}
             */
            maxOf4: function (v1, v2, v3, v4) {
                if (v1 >= v2 && v1 >= v3 && v1 >= v4) return v1;
                if (v2 >= v1 && v2 >= v3 && v2 >= v4) return v2;
                if (v3 >= v2 && v3 >= v1 && v3 >= v4) return v3;
                if (v4 >= v2 && v4 >= v3 && v4 >= v1) return v4;

                return Constants.UNDEFINED;
            },
            /**
             * Returns the minimum of 4 numbers
             * @param v1 {number}
             * @param v2 {number}
             * @param v3 {number}
             * @param v4 {number}
             * @return {number}
             */
            minOf4: function (v1, v2, v3, v4) {
                if (v1 <= v2 && v1 <= v3 && v1 <= v4) return v1;
                if (v2 <= v1 && v2 <= v3 && v2 <= v4) return v2;
                if (v3 <= v2 && v3 <= v1 && v3 <= v4) return v3;
                if (v4 <= v2 && v4 <= v3 && v4 <= v1) return v4;

                return Constants.UNDEFINED;
            },
            /**
             * @param x1 {number}
             * @param y1 {number}
             * @param x2 {number}
             * @param y2 {number}
             * @param x3 {number}
             * @param y3 {number}
             * @param x4 {number}
             * @param y4 {number}
             * @return {boolean}
             */
            lineInLine: function (x1, y1, x2, y2, x3, y3, x4, y4) {

                var DPx, DPy,
                    QAx, QAy,
                    QBx, QBy,
                    d, la, lb;

                DPx = x3 - x1 + x4 - x2;
                DPy = y3 - y1 + y4 - y2;
                QAx = x2 - x1;
                QAy = y2 - y1;
                QBx = x4 - x3;
                QBy = y4 - y3;

                d = QAy * QBx - QBy * QAx;
                la = QBx * DPy - QBy * DPx;
                lb = QAx * DPy - QAy * DPx;

                var absD = Math.abs(d);
                return (Math.abs(la) <= absD && Math.abs(lb) <= absD);
            },

            // round to arbitrary precision
            roundPrecision: function (value, precision) {
                var scalar = Math.pow(10, precision);
                return Math.round(value * scalar) / scalar;
            },

            // round to 2 decimals of precision
            roundP2: function (value) {
                return Math.round(value * 100) / 100;
            }

        };

        return MathHelper;
    }
);


define('visual/Text',
    [
        'visual/BaseElement',
        'utils/Constants',
        'core/Alignment',
        'visual/ImageMultiDrawer',
        'utils/Canvas',
        'resources/ResourceId',
        'resources/ResourceMgr',
        'resolution',
        'utils/MathHelper',
        'game/CTRSettings'
    ],
    function (BaseElement, Constants, Alignment, ImageMultiDrawer, Canvas, ResourceId, ResourceMgr, resolution, MathHelper, settings) {
        //settings.getLangId()
        function FormattedString(str, width) {
            this.string = str;
            this.width = width;
        }

        Text = BaseElement.extend({
            init: function (font) {
                this._super();
                this.font = font;
                this.formattedStrings = [];
                this.width = Constants.UNDEFINED;
                this.height = Constants.UNDEFINED;
                this.align = Alignment.LEFT;
                this.d = new ImageMultiDrawer(font.texture);
                this.wrapLongWords = false;
                this.maxHeight = Constants.UNDEFINED;
            },
            setString: function (newString, width) {
                this.string = newString;
                if (width == null || width === Constants.UNDEFINED) {
                    this.wrapWidth = Math.ceil(this.font.stringWidth(newString));
                }
                else {
                    this.wrapWidth = Math.ceil(width);
                }

                if (this.string) {
                    this.formatText();
                    this.updateDrawerValues();
                }
            },
            updateDrawerValues: function () {
                var dx = 0, dy = 0,
                    itemHeight = this.font.fontHeight(),
                    n = 0,
                    dotsString = '..',
                    dotsOffset = this.font.getCharOffset(dotsString, 0),
                    linesToDraw = (this.maxHeight === Constants.UNDEFINED)
                        ? this.formattedStrings.length
                        : Math.min(this.formattedStrings.length, this.maxHeight / itemHeight + this.font.lineOffset),
                    drawEllipsis = (linesToDraw !== this.formattedStrings.length);

                for (var i = 0; i < linesToDraw; i++) {
                    var fs = this.formattedStrings[i],
                        s = fs.string,
                        len = s.length;

                    if (this.align !== Alignment.LEFT) {
                        if (this.align === Alignment.HCENTER || this.align === Alignment.CENTER) {
                            dx = (this.wrapWidth - fs.width) / 2;
                        }
                        else {
                            dx = this.wrapWidth - fs.width;
                        }
                    }
                    else {
                        dx = 0;
                    }

                    for (var c = 0; c < len; c++) {
                        if (s[c] === ' ') {
                            dx += this.font.spaceWidth + this.font.getCharOffset(s, c);
                        }
                        else {
                            var quadIndex = this.font.getCharQuad(s[c]),
                                itemWidth = this.font.texture.rects[quadIndex].w;
                            this.d.mapTextureQuad(quadIndex, Math.round(dx), Math.round(dy), n++);
                            dx += itemWidth + this.font.getCharOffset(s, c);
                        }

                        if (drawEllipsis && i === (linesToDraw - 1)) {
                            var dotIndex = this.font.getCharQuad('.');
                            var dotWidth = this.font.texture.rects[dotIndex].w;
                            if ((c === len - 1) ||
                                (c === len - 2 && dx + 3 * (dotWidth + dotsOffset) + this.font.spaceWidth > this.wrapWidth)) {
                                this.d.mapTextureQuad(dotIndex, Math.round(dx), Math.round(dy), n++);
                                dx += dotWidth + dotsOffset;
                                this.d.mapTextureQuad(dotIndex, Math.round(dx), Math.round(dy), n++);
                                dx += dotWidth + dotsOffset;
                                this.d.mapTextureQuad(dotIndex, Math.round(dx), Math.round(dy), n++);
                                dx += dotWidth + dotsOffset;
                                break;
                            }
                        }
                    }
                    dy += itemHeight + this.font.lineOffset;
                }

                if (this.formattedStrings.length <= 1) {
                    this.height = this.font.fontHeight();
                    this.width = dx;
                }
                else {
                    this.height = (this.font.fontHeight() + this.font.lineOffset) *
                        this.formattedStrings.length - this.font.lineOffset;
                    this.width = this.wrapWidth;
                }

                if (this.maxHeight !== Constants.UNDEFINED) {
                    this.height = Math.min(this.height, this.maxHeight);
                }
            },
            draw: function () {
                this.preDraw();

                // only draw if the image is non-transparent
                if (this.color.a !== 0) {
                    var len = this.string.length,
                        ctx = Canvas.context;
                    if (len > 0) {
                        ctx.translate(this.drawX, this.drawY);
                        this.d.drawNumberOfQuads(len);
                        ctx.translate(-this.drawX, -this.drawY);
                    }
                }

                this.postDraw();
            },
            formatText: function () {
                var strIdx = [],
                    s = this.string,
                    len = s.length,
                    idx = 0,
                    xc = 0,
                    wc = 0,
                    xp = 0,
                    xpe = 0,
                    wp = 0,
                    dx = 0;

                while (dx < len) {
                    var c = s[dx++];

                    if (c == ' ' || c == '\n') {
                        wp += wc;
                        xpe = dx - 1;
                        wc = 0;
                        xc = dx;

                        if (c == ' ') {
                            xc--;
                            wc = this.font.spaceWidth + this.font.getCharOffset(s, dx - 1);
                        }
                    }
                    else {
                        var quadIndex = this.font.getCharQuad(c),
                            charWidth = this.font.texture.rects[quadIndex].w;
                        wc += charWidth + this.font.getCharOffset(s, dx - 1);
                    }

                    var tooLong = MathHelper.roundP2(wp + wc) > this.wrapWidth;

                    if (this.wrapLongWords && tooLong && xpe == xp) {
                        wp += wc;
                        xpe = dx;
                        wc = 0;
                        xc = dx;
                    }

                    if (MathHelper.roundP2(wp + wc) > this.wrapWidth && xpe != xp || c == '\n') {
                        strIdx[idx++] = xp;
                        strIdx[idx++] = xpe;
                        while (xc < len && s[xc] == ' ') {
                            xc++;
                            wc -= this.font.spaceWidth;
                        }

                        xp = xc;
                        xpe = xp;
                        wp = 0;
                    }
                }

                if (wc != 0) {
                    strIdx[idx++] = xp;
                    strIdx[idx++] = dx;
                }

                var strCount = idx >> 1;

                this.formattedStrings = [];
                for (var i = 0; i < strCount; i++) {
                    var start = strIdx[i << 1],
                        end = strIdx[(i << 1) + 1],
                        str = this.string.substring(start, end),
                        wd = this.font.stringWidth(str),
                        fs = new FormattedString(str, wd);
                    this.formattedStrings.push(fs);
                }
            },
            createFromXml: function (xml) {
                var resId = Xml.attrInt(xml, "font"),
                    font = ResourceMgr.getFont(resId),
                    element = new Text(font);

                if (xml.hasAttribute("align")) {
                    element.align = Alignment.parse(Xml.attr(xml, "align"));
                }

                if (xml.hasAttribute("string")) {
                    var strId = Xml.attrInt("string"),
                        str = ResourceMgr.getString(strId),
                        strWidth = xml.hasAttribute("width") ? Xml.attrFloat(xml, "width") : Constants.UNDEFINED;

                    element.setString(str, strWidth);
                }

                if (xml.hasAttribute("height")) {
                    element.maxHeight = Xml.attrFloat(xml, "height");
                }

                return element;
            }
        });

        function stringToArray (ctx, string, width) {
            // convert string to array of lines then words
            var input = string.split("\n");
            for (var i = 0; i < input.length; ++i) {
                input[i] = input[i].split(" ");
            }

            var i = 0, j = 0, output = [];
            var runningWidth = 0;
            var line = 0;
            
            while (i < input.length) {
                while (j < input[i].length) {
                    if (!output[line]) { output[line] = ""; }

                    var text = input[i][j] + " ";
                    var w = ctx.measureText(text).width;

                    // overflow to a newline
                    if (runningWidth + w > width && runningWidth > 0) {
                        line++;
                        runningWidth = 0;
                    } else {
                        output[line] += text;
                        j++;
                        runningWidth += w;
                    }
                }

                output[line] = output[line].trim();
                i++;
                line++;
                j = 0;
                runningWidth = 0;
            }

            return output;
        }

        function setupFont (ctx, options) {
            var color = options.fontId === 5 ? "#000" : "#fff";
            if (options.alignment !== 1) {
                ctx.textAlign = 'center';
            }

            ctx.fillStyle = color;
            ctx.font = "normal 100 18px 'gooddognew', sans-serif";
            
            if (options.fontId === 4) {
                ctx.strokeStyle = 'rgba(0,0,0,0.7)';
                ctx.lineWidth = 3;
            }

            if (options.alpha) {
                ctx.globalAlpha = options.alpha;
            }
        }

        Text.drawSystem = function (options) {
            var lineHeight = 22;
            var cnv = options.canvas ? options.img : document.createElement("canvas");
            cnv.width = options.width || options.maxScaleWidth || options.text.length * 16;
            cnv.height = lineHeight;

            var ctx = cnv.getContext("2d");
            var x = cnv.width / 2;
            
            if (options.alignment === 1) {
                x = 0;
            }

            setupFont(ctx, options);

            // detect overflow by measuring or newline character
            var metric = ctx.measureText(options.text);
            if (options.text.indexOf("\n") > 0 || (options.width && metric.width > options.width)) {
                var text = stringToArray(ctx, options.text, options.width);
                cnv.height = lineHeight * text.length + 5;
                
                setupFont(ctx, options);

                for (var i = 0; i < text.length; ++i) {
                    
                    if (options.fontId === 4) {
                        ctx.strokeText(text[i], x, (i + 1) * lineHeight)
                    }
                    ctx.fillText(text[i], x, (i + 1) * lineHeight);
                }
            } else {
                // use the measured width
                if (!options.width || !options.maxScaleWidth) {
                    cnv.width = metric.width + 5;
                    setupFont(ctx, options);
                    if (options.alignment !== 1) x = cnv.width / 2;
                }
                if (options.fontId === 4) {
                    ctx.strokeText(options.text, x, 15);
                }
                ctx.fillText(options.text, x, 15);
            }

            if (!options.canvas) { 
                options.img.src = cnv.toDataURL('image/png'); 
                options.img.style.paddingTop = "4px";
                
            }

            options.img.style.height = "auto";
            options.img.style.width = "auto";

            return options.img;
        }

        Text.drawImg = function (options) {

            // get or create the image element
            var img = options.img;
            if (!img && options.imgId) {
                img = document.getElementById(options.imgId);
            }
            if (!img && options.imgSel) {
                img = $(options.imgSel)[0];
            }
            if (!img && options.imgParentId) {
                // obbtains img child or prepends new Image if necessary
                var $parent = $('#' + options.imgParentId),
                    $img = $parent.find(options.canvas ? 'canvas' : 'img');
                if ($img.length === 0) {
                    $img = $( options.canvas ? '<canvas>' : '<img>').prependTo($parent);
                }
                img = $img[0];
            }
            if (!img) {
                img = new Image();
            }

            var lang = settings.getLangId();
            if (lang >= 4 && lang <= 9) {
                $("#lang").addClass("lang-system")
                options.img = img;
                options.text = options.text.toString();
                return Text.drawSystem(options);
            }

            var fontId = options.fontId,
                width = options.width,
                alignment = options.alignment,
                scaleToUI = options.scaleToUI,
                alpha = options.alpha != null ? options.alpha : 1,

                scale = options.scaleToUI
                    ? resolution.UI_TEXT_SCALE
                    : (options.scale || 1),

            // ensure the text is a string (ex: convert number to string)
                text = options.text.toString();

            // save the existing canvas id and switch to the hidden canvas
            var existingCanvas = Canvas.element;

            // create a temporary canvas to use
            Canvas.setTarget(options.canvas ? img : document.createElement('canvas'));

            var font = ResourceMgr.getFont(fontId);
            var t = new Text(font);
            var padding = (24 * resolution.CANVAS_SCALE); // add padding to each side

            // set the text parameters
            t.x = Math.ceil(padding / 2);
            t.y = 0;
            t.align = alignment || Alignment.LEFT;
            t.setString(text, width);

            // set the canvas width and height
            var canvas = Canvas.element,
                ctx = Canvas.context,
                imgWidth = (width || Math.ceil(t.width)) + Math.ceil(t.x * 2),
                imgHeight = Math.ceil(t.height);
            canvas.width = imgWidth;
            canvas.height = imgHeight;

            var previousAlpha = ctx.globalAlpha;
            if (alpha !== previousAlpha) {
                ctx.globalAlpha = alpha;
            }

            // draw the text and get the image data
            t.draw();
            if (!options.canvas)
                img.src = canvas.toDataURL('image/png');

            if (alpha !== previousAlpha) {
                ctx.globalAlpha = previousAlpha;
            }

            // restore the original canvas for the App
            if (existingCanvas) {
                Canvas.setTarget(existingCanvas);
            }

            var finalWidth = imgWidth * scale,
                finalHeight = imgHeight * scale,
                maxScaleWidth = options.maxScaleWidth,
                topPadding, widthScale;

            // do additional scaling if a max scale width was specified and exceeded
            if (maxScaleWidth && finalWidth > maxScaleWidth) {
                widthScale = maxScaleWidth / finalWidth;
                topPadding = Math.round((1 - widthScale) * finalHeight / 2);
                finalWidth *= widthScale;
                finalHeight *= widthScale;
            }

            // When the src is set using image data, the height and width are
            // not immediately available so we'll explicitly set them
            var $img = $(img).width(finalWidth).height(finalHeight).css("padding-top", 0);

            // adjust the top padding if we scaled the image for width
            if (topPadding) {
                $img.css('padding-top', topPadding);
            }

            return img;
        };

        Text.drawSmall = function (options) {
            options.fontId = ResourceId.FNT_SMALL_FONT;
            return Text.drawImg(options);
        };
        Text.drawBig = function (options) {
            options.fontId = ResourceId.FNT_BIG_FONT;
            return Text.drawImg(options);
        };
        Text.drawBigNumbers = function (options) {
            options.fontId = ResourceId.FNT_FONT_NUMBERS_BIG;
            return Text.drawImg(options);
        };

        return Text;
    }
);

define('resources/MenuStringId',[], function () {
    /**
     * @enum {number}
     */
    var MenuId = {

        // extracted from ios sources
        PLAY: 0, OPTIONS: 1, EXTRAS: 2, SURVIVAL: 3, FULL_VERSION: 4, RESET: 5, HELP: 6,
        ABOUT: 7, CREDITS: 8, MUSIC_ON: 9, MUSIC_OFF: 10, SOUNDS_ON: 11, SOUNDS_OFF: 12, RESET_TEXT: 13,
        LEVEL_CLEARED1: 14, LEVEL_CLEARED2: 15, LEVEL_CLEARED3: 16, LEVEL_CLEARED4: 17, LEVEL: 18,
        TIME: 19, STAR_BONUS: 20, FINAL_SCORE: 21, BEST_SCORE: 22, SCORE: 23, YES: 24, NO: 25, REPLAY: 26,
        NEXT: 27, MENU: 28, LOADING: 29, TOTAL_STARS: 30, OK: 31, UNLOCK_HINT: 32, CANT_UNLOCK_TEXT1: 33,
        CANT_UNLOCK_TEXT2: 34, CANT_UNLOCK_TEXT3: 35, GAME_FINISHED_TEXT: 36, GAME_FINISHED_TEXT2: 37,
        ACHIEVEMENT_GAINED: 38, CONTINUE: 39, SKIP_LEVEL: 40, LEVEL_SELECT: 41, MAIN_MENU: 42,

        // added for web version
        LETS_PLAY: 43,
        MORE_CTR_FUN: 44,
        SHARE: 45,
        SHARE_DRAWING: 46,
        SHARE_ELLIPSIS: 47,
        FOUND_DRAWING: 48,
        DRAG_TO_CUT: 49,
        CLICK_TO_CUT: 50,
        SHOW_ME: 51,
        FREE_DOWNLOAD: 52,
        SITE_DESC: 53,
        SITE_TITLE: 54,
        SITE_ACTION: 55,
        LANGUAGE: 56,
        RESET_HOLD_YES: 57,
        RELOAD_SD: 58,
        RELOAD_HD: 59,
        EVERYTHING_OFF: 60,
        SLOW_TITLE: 61,
        SLOW_TEXT: 62,
        CONGRATULATIONS: 63,
        MUSIC: 64,
        SOUNDS: 65,
        LEADERBOARDS: 66,
        ACHIEVEMENTS: 67,
        ACHIEVEMENT_UNLOCKED: 68,
        UPGRADE_TO_FULL: 69,
        NAME: 70,
        AND_TEMPLATE: 71,
        BUY_FULL_GAME: 72,

        // added for winjs version
        SETTINGS: 200,
        PRIVACY: 201
    };

    return MenuId;
});
define('resources/MenuStrings',
    [
        'resources/MenuStringId'
    ],
    function (MenuStringId) {
        var locEntries = [
            {id: 0, en: "Play", zh: "玩游戏", ja: "プレイ", ko: "플레이", nl: "Spelen", it: "Gioca", fr: "Jouer", de: "Spielen", ru: "\u0418\u0433\u0440\u0430\u0442\u044c", es: "Jugar", br: "Jogar", ca: "Jugar"},
            {id: 1, en: "Options", zh: "选项", ja: "オプション", ko: "환경설정", nl: "Opties", it: "Opzioni", fr: "Options", de: "Optionen", ru: "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438", es: "Opciones", br: "Opções", ca: "Opcions"},
            {id: 2, en: "Extras", ja: "エクストラ", zh: "额外内容", ko: "부가 기능", nl: "Extra's", it: "Extra", br: "Extras", es: "Extras", fr: "", de: "Extras", ru: "\u0415\u0449\u0435 \u043a\u043e\u0435 \u0447\u0442\u043e", ca: "Extres"},
            {id: 3, en: "Bonus mode", ko: "", fr: "", de: "Bonusmodus", ru: "\u0411\u043e\u043d\u0443\u0441 \u0440\u0435\u0436\u0438\u043c"},
            {id: 4, en: "Full version", es: "Version completa", fr: "Version compl\u00e8te", de: "Vollversion",
                ru: "\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f"},
            {id: 5, en: "Reset game", zh: "重置游戏", ja: "リセット", ko: "게임 초기화", nl: "Herstarten", it: "Azzera gioco", ca: "Reiniciar joc", br: "Reiniciar jogo", es: "Restablecer", fr: "R\u00e9initialiser", de: "Neu starten", ru: "\u0421\u0431\u0440\u043e\u0441 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430"},
            {id: 6, en: "Help", fr: "", de: "Hilfe", ru: "\u041f\u043e\u043c\u043e\u0449\u044c"},
            {id: 7, en: "About", fr: "À propos", de: "\u00dcber", ru: "\u041e\u0431 \u0438\u0433\u0440\u0435"},
            {id: 8, en: "Credits", es: "Creditos", fr: "Cr\u00e9dits", de: "Mitwirkende", ru: "\u0410\u0432\u0442\u043e\u0440\u044b"},
            {id: 9, en: "Music on", zh: "音乐打开", ja: "音楽：*オン", ko: "배경음 켜기", nl: "Muziek aan", it: "Musica: sì", ca: "Música activada", br: "Música on", es: "Música: si", fr: "Musique oui", de: "Musik an", ru: "\u041c\u0443\u0437\u044b\u043a\u0430 \u0432\u043a\u043b."},
            {id: 10, en: "Music off", zh: "音乐关闭", ja: "音楽：*オフ", ko: "배경음 끄기", nl: "Muziek uit", it: "Musica: no", ca: "Música desactivada", br: "Música off", es: "Música: no", fr: "Musique non", de: "Musik aus ", ru: "\u041c\u0443\u0437\u044b\u043a\u0430 \u0432\u044b\u043a\u043b."},
            {id: 11, en: "Sounds on", zh: "声音打开", ja: "効果音：*オン", ko: "효과음 켜기", nl: "Geluid aan", it: "Suoni: sì", ca: "Sons activats", br: "Sons on", es: "Sonido: si", fr: "Sons oui", de: "Ger\u00e4usche an", ru: "\u0417\u0432\u0443\u043a\u0438 \u0432\u043a\u043b."},
            {id: 12, en: "Sounds off", ja: "効果音：*オフ", zh: "声音关闭", ko: "효과음 끄기", nl: "Geluid uit", it: "Suoni: no", ca: "Sons desactivats", br: "Sons off", es: "Sonido: no", fr: "Sons non", de: "Ger\u00e4usche aus", ru: "\u0417\u0432\u0443\u043a\u0438 \u0432\u044b\u043a\u043b."},
            {id: MenuStringId.RESET_TEXT,
                en: "Are you sure you want to reset your progress?", zh: "你确定要*重置游戏*进度吗？", ja: "本当に*リセット*しますか？", ko: "게임을 초기화하시겠습니까?", nl: "Weet je zeker dat je je voortgang wilt wissen?", it: "Vuoi davvero azzerare i tuoi progressi?", ca: "Estàs segur que desitges reiniciar el teu progrés?", br: "Tem certeza de que deseja descartar seu progresso?", es: "¿Seguro que quieres borrar el progreso realizado?", fr: "Voulez-vous vraiment r\u00e9initialiser votre progression?", de: "M\u00f6chtest du deinen Fortschritt wirklich zur\u00fccksetzen?", ru: "\u0423\u0432\u0435\u0440\u0435\u043d\u044b \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441?"},
            {id: 14, en: "Passable!", zh: "马马虎虎！", ja: "まずまず!", ko: "간신히 통과!", nl: "Voldoende!", it: "Accettabile!", ca: "Acceptable!", br: "Passável!", es: "¡No está mal!", fr: "Passable!", de: "Passabel!", ru: "\u0423\u0440\u043e\u0432\u0435\u043d\u044c \u043f\u0440\u043e\u0439\u0434\u0435\u043d!"},
            {id: 15, en: "Good!", zh: "好！", ja: "グッド!", ko: "좋아요!", nl: "Goed!", it: "Bene!", ca: "Bé!", br: "Bom!", es: "¡Bien!", fr: "Bien!", de: "Gut!", ru: "\u0425\u043e\u0440\u043e\u0448\u043e!"},
            {id: 16, en: "Great!", zh: "真棒！", ja: "グレート!", ko: "잘 했어요!", nl: "Geweldig!", it: "Ottimo!", ca: "Fantàstic!", br: "Ótimo!", es: "¡Genial!", fr: "Super!", de: "Prima!", ru: "\u041e\u0442\u043b\u0438\u0447\u043d\u043e!"},
            {id: 17, en: "Excellent!", zh: "好极了！", ja: "最高!", ko: "아주 잘 했어요!", nl: "Uitstekend!", it: "Eccellente!", ca: "Excellent!", br: "¡Excelente!", es: "Excelente!", fr: "Formidable!", de: "Hervorragend!", ru: "\u0421\u0443\u043f\u0435\u0440!"},
            {id: 18, en: "Level", zh: "关卡", ja: "レベル", ko: "레벨", it: "Livello", ca: "Nivell", br: "Nível", es: "Nivel", fr: "Niveau", de: "Level", ru: "\u0423\u0440\u043e\u0432\u0435\u043d\u044c"},
            {id: 19, en: "Time", zh: "时间", ja: "タイム", ko: "시간", nl: "Tijd", it: "Tempo", ca: "Temps", br: "Tempo", es: "Tiempo", fr: "Temps", de: "Zeit", ru: "\u0412\u0440\u0435\u043c\u044f"},
            {id: 20, en: "Star Bonus", zh: "星星奖励", ja: "スターボーナス", ko: "보너스 별", nl: "Sterrenbonus", it: "Bonus stelle", ca: "Bonus de estrelles", br: "Bônus de estrela", es: "Bonificación estrellas", fr: "Bonus \u00e9toile",
                de: "Sternenbonus", ru: "\u0411\u043e\u043d\u0443\u0441 \u0437\u0430 \u0437\u0432\u0435\u0437\u0434\u044b"},
            {id: 21, en: "Your Final Score", zh: "你的*最终得分", ja: "あなたの*最終*スコア", ko: "최종 점수", nl: "Je eindscore", it: "Punteggio finale", ca: "La teva puntuació final", br: "Sua pontuação final", es: "Puntuación final", fr: "Votre score final", de: "Dein Endpunktestand", ru: "\u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0435 \u043e\u0447\u043a\u0438"},
            {id: 22, en: "Best Score", zh: "最佳得分", ja: "ベスト*スコア", ko: "최고 점수", nl: "Beste score", it: "Miglior punteggio", ca: "Millor puntuació", br: "Melhor pontuação", es: "Mejor puntuación", fr: "Meilleur score", de: "Beste punktzahl", ru: "\u041b\u0443\u0447\u0448\u0438\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442"},
            {id: 23, en: "Score", zh: "得分", ja: "スコア", ko: "점수", nl: "Score", it: "Punteggio", ca: "Puntuació", br: "Pontuação", es: "Puntuación", fr: "Score", de: "Punktzahl", ru: "\u041e\u0447\u043a\u0438"},
            {id: 24, en: "Yes", zh: "是", ja: "はい", ko: "예", nl: "Ja", it: "Sì", ca: "Sí", br: "Sim", es: "Sí", fr: "Oui", de: "Ja", ru: "\u0414\u0430"},
            {id: 25, en: "No", zh: "否", ja: "いいえ", ko: "아니요", nl: "Nee", ca: "No", br: "Não", fr: "Non", de: "Nein", ru: "\u041d\u0435\u0442"},
            {id: 26, en: "Replay", zh: "重玩", ja: "やり直す", ko: "다시 하기", nl: "Opnieuw", it: "Rigioca", ca: "Repetir", br: "Repetir", es: "Repetir", fr: "Rejouer", de: "Wieder", ru: "\u0415\u0449\u0435 \u0440\u0430\u0437"},
            {id: 27, en: "Next", zh: "下一步", ja: "次へ", ko: "다음", nl: "Volgende", it: "Prossimo", ca: "Següent", br: "Próximo", es: "Siguiente", fr: "Suivant", de: "Weiter", ru: "\u0412\u043f\u0435\u0440\u0435\u0434"},
            {id: 28, en: "Menu", zh: "菜单", ja: "メニュー", ko: "메뉴", nl: "Menu", ca: "Menú", es: "Menú", fr: "Menu", de: "Men\u00fc", ru: "\u041c\u0435\u043d\u044e"},
            {id: 29, en: "Loading...", zh: "正在加载...", ja: "ロード中...", ko: "불러오는 중...", nl: "Bezig met laden...", it: "Caricamento in corso...", ca: "Carregant ...", br: "Carregando ...", es: "Cargando...", fr: "Chargement...", de: "Laden...", ru: "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430..."},
            {id: 30, en: "Total: %d", zh: "合计： %d", ja: "合計：%d", ko: "총 점수: %d", nl: "Totaal: %d", it: "Totale: %d", fr: "Total: %d", de: "Gesamt: %d", ru: "\u0412\u0441\u0435\u0433\u043e: %d"},
            {id: 31, en: "Ok", zh: "确定", ja: "OK", ko: "확인", nl: "Oké", ca: "Acceptar", es: "Aceptar", fr: "OK", de: "Ok", ru: "Ok"},
            {id: 32, en: "Collect %d stars to unlock this level pack", zh: "收集 %d 颗*星星*才能解锁*这一*关卡集", ja: "アンロック*するには* %d の*スターが*必要です", ko: "이 레벨 상자를 열려면 별 %d개를 모으세요.", nl: "Verzamel %d sterren om dit levelpakket te ontgrendelen", it: "Raccogli %d stelle per sbloccare questo pacchetto di livelli", ca: "Aconsegueix %d estrelles per desbloquejar aquest paquet de nivells", br: "Colete %d estrelas para liberar este nível", es: "Consigue %d estrellas para desbloquear este pack de niveles.", fr: "Recueillez %d \u00e9toiles pour d\u00e9verrouiller ce pack de niveaux", de: "Sammle %d Sterne, um dieses Levelpaket freizuschalten", ru: "\u0421\u043e\u0431\u0435\u0440\u0438\u0442\u0435 %d \u0437\u0432\u0435\u0437\u0434 \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043a\u043e\u0440\u043e\u0431\u043a\u0443"},
            {id: MenuStringId.CANT_UNLOCK_TEXT1, en: "You are missing", zh: "你还需要", ja: "アンロックには", ko: "이 상자를 열려면", nl: "Je mist", it: "Hai bisogno di", ca: "Encara necessites", br: "Você precisa de", es: "Necesitas", fr: "Il vous en manque", de: "Dir fehlen", ru: "\u0412\u0430\u043c \u043d\u0435\u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0432\u0441\u0435\u0433\u043e"},
            {id: MenuStringId.CANT_UNLOCK_TEXT2, en: "to unlock this box", zh: "才能解锁*这个盒子", ja: "が必要です", ko: "개가 더 필요해요.", nl: "om deze doos te openen", it: "per sblocc. la scatola", ca: "per desbloquejar aquesta caixa", br: "para liberar esta caixa", es: "para abrir esta caja", fr: "pour d\u00e9verrouiller bo\u00eete", de: "um dieses Box freizuschalten", ru: "\u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043a\u043e\u0440\u043e\u0431\u043a\u0443"},
            {id: MenuStringId.CANT_UNLOCK_TEXT3, en: "Get more stars from the earlier levels", zh: "从先前的*关卡中*获取更多*星星", ja: "前の*レベルで*スターを*もっと*獲得*しましょう", ko: "이전 레벨에서 별을 더 모으세요.", nl: "Ontvang meer sterren uit de eerdere levels", it: "Ottieni altre stelle nei primi livelli", ca: "Pots aconseguir més estrelles en els nivells anteriors", br: "Obtenha mais estrelas em níveis anteriores", es: "Vuelve a jugar los niveles anteriores para obtener más estrellas", fr: "Recueillez plus d'\u00e9toiles dans les niveaux pr\u00e9c\u00e9dents",
                de: "Gewinne mehr Sterne in den niedrigeren Leveln", ru: "\u0421\u043e\u0431\u0435\u0440\u0438\u0442\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0432\u0435\u0437\u0434 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0443\u0440\u043e\u0432\u043d\u044f\u0445"},
            {id: 37, en: "Check back for the new levels coming with the updates", fr: "Revenez pour d\u00e9couvrir de nouveaux niveaux avec les mises \u00e0 jour", de: "Komm bald wieder, neue Level kommen mit den Updates", ru: "\u041d\u043e\u0432\u044b\u0435 \u0443\u0440\u043e\u0432\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0441 \u0430\u043f\u0434\u0435\u0439\u0442\u0430\u043c\u0438"},
            {id: MenuStringId.ACHIEVEMENT_GAINED, en: "achievement gained!", es: "Logro obtenido!", fr: "r\u00e9alisation d\u00e9verrouill\u00e9e!",
                de: "Erfolg geschafft!", ru: "\u043e\u0442\u043a\u0440\u044b\u0442\u043e!"},
            {id: 39, en: "Continue", zh: "继续", ja: "続ける", ko: "계속", nl: "Doorgaan", it: "Continua", ca: "Continuar", br: "Continuar", es: "Continuar", fr: "Continuer", de: "Weiter", ru: "\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c"},
            {id: 40, en: "Skip level", zh: "跳过关卡", ja: "スキップ", ko: "레벨 건너뛰기", nl: "Overslaan", it: "Salta livello", ca: "Ometre nivell", br: "Pular nível", es: "Saltar nivel", fr: "Passer", de: "\u00dcberspringen", ru: "\u041f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c"},
            {id: 41, en: "Level select", zh: "选择关卡", ja: "レベル*選択", ko: "레벨 선택", nl: "Level kiezen", it: "Sel. livello", ca: "Selecció de nivell", br: "Escolha nível", es: "Elegir nivel", fr: "Choisir niveau", de: "Levelauswahl", ru: "\u0412\u044b\u0431\u043e\u0440 \u0443\u0440\u043e\u0432\u043d\u044f"},
            {id: 42, en: "Main menu", zh: "主菜单", ja: "メイン*メニュー", ko: "주 메뉴", nl: "Hoofdmenu", it: "Menu di gioco", ca: "Menú principal", br: "Menu Princ.", es: "Menú princ", fr: "Menu principal",
                de: "Hauptmen\u00fc", ru: "\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e"},

            {
                id: MenuStringId.LANGUAGE,
                en: "Language",
                ru: "Язык",
                fr: "Langue",
                de: "Sprache",
                es: "Lingua",
                br: "Idioma",
                ca: "Idioma",
                it: "Lingua",
                nl: "Taal",
                ko: "언어",
                ja: "言語",
                zh: "语言"
            },
            {
                id: MenuStringId.DRAG_TO_CUT,
                en: "Drag to Cut",
                fr: "Glisser",
                de: "Ziehe: Schneide",
                ru: "Перетаскиванием",
                es: ""
            },
            {
                id: MenuStringId.CLICK_TO_CUT,
                en: "Click to Cut",
                fr: "Cliquer",
                de: "Klicke: Schneide",
                ru: "Щелчком",
                es: ""
            },
            {
                id: MenuStringId.LETS_PLAY,
                en: "Let's Play",
                fr: "C'est parti",
                de: "Lass uns spielen",
                ru: "Давайте поиграем",
                es: ""
            },
            {
                id: MenuStringId.MORE_CTR_FUN,
                en: "More Cut the Rope fun!",
                fr: "Toujours plus de Cut the Rope !",
                de: "Mehr Cut the Rope Spaß!",
                ru: "Еще больше веселья в игре Cut the Rope!"
            },
            {
                id: MenuStringId.SHARE_ELLIPSIS,
                en: "Share...",
                fr: "Partager...",
                de: "Teilen...",
                ru: "Поделиться...",
                es: "Compartir..."
            },
            {
                id: MenuStringId.SHARE,
                en: "Share",
                fr: "Partager",
                de: "Teilen",
                ru: "Поделиться",
                es: "Compartir"
            },
            {
                id: MenuStringId.CONGRATULATIONS,
                en: "Congratulations!",
                fr: "Félicitations!",
                de: "Gratulation!",
                ru: "Поздравляем!",
                es: "Enhorabuena!",
                br: "Parabéns!",
                ca: "Enhorabona!",
                it: "Congratulazioni!",
                nl: "Gefeliciteerd!",
                ko: "축하합니다!",
                zh: "恭喜！",
                ja: "おめでとう*ございます！"
            },
            {
                id: MenuStringId.GAME_FINISHED_TEXT,
                en: "You completed the game with %d stars!",
                fr: "Tu as terminé le jeu avec %d étoiles !",
                de: "Du hast das Spiel mit %d Sternen beendet!",
                ru: "Вы завершили игру со следующим количеством звезд: %d!",
                ca: "Has finalitzat el joc amb %d estrelles!",
                ko: "게임이 끝났습니다!",
                zh: "你已完成*该游戏！",
                ja: "ゲームを クリアしました！",
                es: "¡Has terminado el juego!",
                it: "Hai completato il gioco!",
                nl: "Je hebt de game uitgespeeld!",
                br: "Você terminou o jogo!"
            },
            {
                id: MenuStringId.SHARE_DRAWING,
                en: "I just found one of Om Nom's secret drawings!",
                fr: "Je viens juste de trouver un des dessins secrets de Om Nom !",
                de: "Ich habe gerade eine geheime Om-Nom-Zeichnung entdeckt!",
                ru: "Мною только что был обнаружен один из тайных рисунков Ам Няма!"
            },
            {
                id: MenuStringId.FOUND_DRAWING,
                en: "You found a drawing!",
                fr: "Tu as trouvé un dessin !",
                de: "Du hast die Zeichnung gefunden!",
                ru: "Вы обнаружили рисунок!"
            },
            {
                id: MenuStringId.SHOW_ME,
                en: "Show Me",
                fr: "Montre-moi",
                de: "Zeig es mir",
                ru: "Показать мне"
            },
            {
                id: MenuStringId.FREE_DOWNLOAD,
                en: "Free Download",
                fr: "Téléchargement gratuit",
                de: "Kostenloser Download",
                ru: "Бесплатная загрузка"
            },
            {
                id: MenuStringId.SITE_DESC,
                en: "Cut the Rope is a fun game where you feed candy to the curiously cute green monster Om-nom.",
                fr: "Cut the Rope est un jeu amusant dans lequel tu dois nourrir Om Nom, un curieux petit monstre.",
                de: "Cut the Rope ist ein lustiges Spiel, wobei du dem kuriosem, niedlichem Monster Om-nom Süßigkeiten fütterst.",
                ru: "Cut the Rope   это веселая игра, в которой вы кормите сладостями любопытного и милого монстра по имени Ам Ням."
            },
            {
                id: MenuStringId.SITE_TITLE,
                en: "Om Nom is Om Line - Cut the Rope for the Web",
                fr: "Om Nom est om ligne - Cut the Rope version web",
                de: "Om Nom ist Om Line - Cut the Rope für das Internet",
                ru: "Ам Ням в Ам Лайне – веб-версия игры Cut the Rope"
            },
            {
                id: MenuStringId.SITE_ACTION,
                en: 'Play the HTML5 version of "Cut the Rope"!',
                fr: 'Joue à Cut the Rope dans sa version HTML5 !',
                de: 'Spiele die HTML5 Version von "Cut the Rope"!',
                ru: 'Играйте в HTML5-версию игры Cut the Rope!'
            },
            {
                id: MenuStringId.RESET_HOLD_YES,
                en: 'Hold the "yes" button for 3 seconds to reset.',
                fr: 'Maintenir le doigt sur "Oui" pendant 3 secondes pour réinitialiser.',
                de: 'Halte zum Neustarten 3 Sekunden lang "Ja" gedrückt.',
                ru: 'Удерживайте "Да" в течение 3 секунд для сброса прогресса.',
                ko: "“예” 버튼을 3초 동안 누르면 초기화됩니다.",
                zh: "按住“是”按钮 3 秒即可重置。",
                ja: "はい」 のボタンを ３秒間*長押しで リセット",
                es: "Mantén pulsado 'Sí' durante 3 segundos para reiniciar.",
                it: "Tieni premuto il tasto 'sì' per 3 secondi per azzerare.",
                nl: "Houd de Ja-knop 3 seconden ingedrukt om opnieuw te beginnen.",
                br: "Segure o botão 'Sim' por 3 segundos para reiniciar.",
                ca: "Mantingues premut el botó 'sí' durant 3 segons per reiniciar."
            },
            {
                id: MenuStringId.EVERYTHING_OFF,
                en: 'everything off',
                fr: 'tout couper',
                de: 'alles aus',
                ru: 'все отключено',
                es: 'todo apagado',
                br: 'todo off',
                ca: "tot desactivat",
                it: "tutto fuori",
                nl: "alles uit"
            },
            {
                id: MenuStringId.RELOAD_SD,
                en: 'reload the game in SD',
                fr: 'recharger le jeu en SD',
                de: 'Spiel in SD neu laden',
                ru: 'перезагрузить игру в SD'
            },
            {
                id: MenuStringId.RELOAD_HD,
                en: 'reload the game in HD',
                fr: 'recharger le jeu en HD',
                de: 'Spiel in HD neu laden',
                ru: 'перезагрузить игру в HD'
            },
            {
                id: MenuStringId.SLOW_TITLE,
                en: 'A Little Too Slow...',
                fr: 'Un peu trop lent',
                de: 'Etwas zu langsam...',
                ru: 'Как-то не очень быстро...'
            },
            {
                id: MenuStringId.SLOW_TEXT,
                en: "Om Nom is sad because your computer is running slow. We'll do our best, but there may be some slow-downs.",
                fr: "Om Nom est triste. Votre ordinateur est très lent. Nous ferons notre mieux, mais il est possible que le jeu ralentisse.",
                de: "Om Nom ist traurig, weil dein Computer so langsam ist. Wir geben unser Bestes, aber es kann trotzdem zu Verzögerungen kommen.",
                ru: 'Ам Ням опечален: у вас слишком медленный компьютер. Мы, конечно, постараемся, но игра у вас может "притормаживать".'
            },
            {id: MenuStringId.MUSIC, en: "Music", nl: "Muziek", it: "Musica", ca: "Música", fr: "Musique", de: "Musik", ru: "\u041c\u0443\u0437\u044b\u043a\u0430"},
            {id: MenuStringId.SOUNDS, en: "Sounds", nl: "Geluid", it: "Suoni", ca: "Sons", fr: "Sons oui", de: "Ger\u00e4usche", ru: "\u0417\u0432\u0443\u043a\u0438"},
            {
                id: MenuStringId.LEADERBOARDS,
                en: 'Leaderboards',
                fr: 'Classements',
                de: 'Bestenlisten',
                ru: 'Таблица рекордов'
            },
            {
                id: MenuStringId.ACHIEVEMENTS,
                en: 'Achievements',
                fr: 'Succès',
                de: 'Erfolge',
                ru: 'Достижения'
            },
            {
                id: MenuStringId.ACHIEVEMENT_UNLOCKED,
                en: 'Achievement unlocked!',
                fr: 'Succès débloqué !',
                de: 'Erfolg freigeschaltet!',
                ru: 'Новое достижение!'
            },
            {
                 id: MenuStringId.UPGRADE_TO_FULL,
                 en: 'Get MANY MORE\n levels in the full version',
                 fr: 'De NOMBREUX autres niveaux\n sont disponibles dans la version complète!',
                 de: 'In der Vollversion gibt\n es noch VIEL MEHR Level!',
                 ru: 'Получите НАМНОГО БОЛЬШЕ\n уровней в полной версии!'
            },
            {
                 id: MenuStringId.NAME,
                 en: 'Name',
                 fr: 'Nom',
                 de: 'Name',
                 ru: 'Имя'
            },
            {
                 id: MenuStringId.AND_TEMPLATE,
                 en: '{0} & {1}',
                 fr: '{0} et {1}',
                 de: '{0} und {1}',
                 ru: '{0} и {1}',
                 ca: '{0} i {1}',
            },
            {
                id: MenuStringId.BUY_FULL_GAME,
                en: 'Buy Full Game',
                fr: 'Acheter le jeu complet',
                de: 'Vollversion kaufen',
                ru: 'Купить полную версию'
            },
            {
                id: MenuStringId.SETTINGS,
                en: 'Settings',
                fr: 'Paramètres',
                de: 'Einstellungen',
                ru: 'Настройки',
                ca: 'Configuració'
            },
            {
                id: MenuStringId.PRIVACY,
                en: 'Privacy',
                fr: 'Vie privée',
                de: 'Datenschutz',
                ru: 'Конфиденциальность'
            }



            /*
             {
             id: MenuStringId.,
             en: '',
             fr: '',
             de: '',
             ru: ''
             }
             */
        ];

        return locEntries;
    });
define('resources/Lang',
    [
        'edition',
        'game/CTRSettings',
        'resources/LangId',
        'resources/MenuStrings',
        'utils/Log'
    ],
    function (edition, settings, LangId, menuStrings, Log) {

        // helper to return the correct string from a loc entry
        var getLocalizedText = function (locEntry) {

            // note: we default to english if entry is blank
            // !LANG
            switch (settings.getLangId()) {
                case LangId.FR:
                    return locEntry.fr || locEntry.en;
                case LangId.DE:
                    return locEntry.de || locEntry.en;
                case LangId.RU:
                    return locEntry.ru || locEntry.en;
                case LangId.ES:
                    return locEntry.es || locEntry.en;
                case LangId.BR:
                    return locEntry.br || locEntry.en;
                case LangId.CA:
                    return locEntry.ca || locEntry.en;
                case LangId.IT:
                    return locEntry.it || locEntry.en;
                case LangId.NL:
                    return locEntry.nl || locEntry.en;
                case LangId.KO:
                    return locEntry.ko || locEntry.en;
                case LangId.ZH:
                    return locEntry.zh || locEntry.en;
                case LangId.JA:
                    return locEntry.ja || locEntry.en;
                case LangId.EN:
                default:
                    return locEntry.en;
            }
        };

        var Lang = {
            boxText: function (boxIndex, includeNumber) {
                var locEntry = edition.boxText[boxIndex],
                    text = getLocalizedText(locEntry);

                // all boxes except last one get prepended numbers
                if (text && includeNumber) {
                    text = (boxIndex + 1) + '. ' + text;
                }

                return text;
            },
            menuText: function (menuStringId) {

                var locEntry, i,
                    len = menuStrings.length;
                for (i = 0; i < len; i++) {
                    locEntry = menuStrings[i];
                    if (locEntry.id === menuStringId) {
                        return getLocalizedText(locEntry);
                    }
                }

                Log.debug('Missing menu string for id: ' + menuStringId);
                return "";
            },
            getText: getLocalizedText,
            getCurrentId: function() {
                return settings.getLangId();
            }
        };

        return Lang;
    }
);

define('config/platforms/platform-web',
    [
        'visual/Text',
        'resolution',
        'resources/Lang',
        'resources/MenuStringId',
        'core/Alignment',
        'edition'
    ],
    function (Text, resolution, Lang, MenuStringId, Alignment, edition) {

        // loc entries that are specific to the web platform
        var locEntries = {
            GAME_COMPLETE: {
                en: "I just finished playing Cut the Rope on the web with %d (out of %d possible) stars!",
                fr: "",
                de: "",
                ru: ""
            }
        };

        var WebPlatform = {

            /**
             * @const
             * @type {boolean}
             */
            ENABLE_ANALYTICS: true,

            /**
             * @const
             * @type {boolean}
             */
            ENABLE_ZOOM: false,

            ZOOM_BOX_CANVAS: false,

            imageBaseUrl: 'images/',
            resolutionBaseUrl: 'images/' + resolution.UI_WIDTH + '/',
            uiImageBaseUrl: 'images/' + resolution.UI_WIDTH + '/ui/',
            boxImageBaseUrl: 'images/' + resolution.UI_WIDTH + '/' + (edition.boxDirectory || 'ui/'),

            audioBaseUrl: 'audio/',
            getAudioExtension: function () {
                return '.mp3';
            },

            videoBaseUrl: 'video/',
            getVideoExtension: function () {
                return Modernizr.video.h264 ? '.mp4' :
                    Modernizr.video.webm ? '.webm' : null;
            },

            getDrawingBaseUrl: function () {
                var loc = window.location,
                    baseUrl = loc.protocol + '//' + loc.host;
                return baseUrl + '/images/' + resolution.UI_WIDTH + '/ui/';
            },
            getScoreImageBaseUrl: function () {
                var loc = window.location,
                    baseUrl = loc.protocol + '//' + loc.host;
                return baseUrl + "/images/scores/";
            },
            setSoundButtonChange: function($button, callback) {
                $button.click(callback);
            },
            setMusicButtonChange: function($button, callback) {
                $button.click(callback);
            },
            updateSoundOption: function ($el, isSoundOn) {
                $el.toggleClass('disabled', !isSoundOn);
            },
            updateMusicOption: function ($el, isMusicOn) {
                $el.toggleClass('disabled', !isMusicOn);
            },
            toggleLangUI: function (show) {
                $('#langBtn').toggle(show);
            },
            setLangOptionClick: function (callback) {
                $('#langBtn').click(function(e) {
                    var langId = null; // just advance to next supported language
                    callback(langId);
                });
            },
            updateLangSetting: function () {
                WebPlatform.setOptionText($('#langBtn'),
                    Lang.menuText(MenuStringId.LANGUAGE) + ':');

                // Chrome has a layout bug where the css offset on the flag
                // icon is not changed immediately. Retrieving the offset
                // forces the browser to query location which fixes layout.
                $('#flag').offset();
            },
            setCutOptionClick: function (callback) {
                $('#cutBtn').click(callback);
            },
            updateCutSetting: function (isClickToCut) {

                // fonts use game sized assets based on canvas size
                var textWidth = 400 * resolution.CANVAS_SCALE,

                // scale need to take UI size into account
                    scale = 0.8 * resolution.UI_TEXT_SCALE,
                    alignment = Alignment.HCENTER;

                // we update the drag text because language changes just
                // reset the current click state
                Text.drawSmall({
                    text: Lang.menuText(MenuStringId.DRAG_TO_CUT),
                    width: textWidth,
                    imgId: 'dragText',
                    scale: scale,
                    alignment: alignment });

                // now update the click-to-cut text and check mark
                Text.drawSmall({
                    text: Lang.menuText(MenuStringId.CLICK_TO_CUT),
                    width: textWidth,
                    imgId: 'cutText',
                    scale: scale,
                    alignment: alignment });
                $('#cutBtn').toggleClass('disabled', !isClickToCut);
            },
            setResetText: function ($el, text) {
                WebPlatform.setOptionText($el, text);
            },
            setOptionText: function ($button, text) {
                Text.drawBig({ text: text, img: $button.find('img')[0], scaleToUI: true });
            },
            getGameCompleteShareText: function (totalStars, possibleStars) {
                var text = Lang.getText(locEntries.GAME_COMPLETE)
                    .replace('%d', totalStars)
                    .replace('%d', possibleStars);
                return text;
            },
            meetsRequirements: function () {
                // does the browser have the HTML5 features we need?
                var meetsReqs =
                    Modernizr.canvas &&
                        Modernizr.audio &&
                        Modernizr.video &&
                        Modernizr.localstorage &&
                        Modernizr.rgba &&
                        Modernizr.opacity &&
                        Modernizr.fontface &&
                        Modernizr.csstransforms &&
                        Modernizr.hq;

                if (!meetsReqs) {
                    // load the css for the downlevel experience
                    Modernizr.load({
                        'load': 'css!css/nosupport.css?RELEASE_TAG'
                    });

                    // remove youtube video if it exists
                    $(function() {
                        $('#yt-video').remove();
                    });

                    // track views of the ugprade page
                    _gaq.push(['_trackEvent', 'Upgrade', 'View']);
                }
                return meetsReqs;
            }
        };

        return WebPlatform;
    }
);

define('platform',
    [
        'config/platforms/platform-web'
    ],
    function (WebPlatform) {

        var GeckoPlatform = WebPlatform;

        // override audio and video format choices

        GeckoPlatform.getAudioExtension = function () {
            return '.mp3';
        };

        GeckoPlatform.getVideoExtension = function () {
            return '.webm';
        };

        GeckoPlatform.disableSlowWarning = true;

        return GeckoPlatform;
    }
);

define('editionUI',[], function () {
    return {};
});
// manages sounds (SoundManager2 only for now, but eventually PhoneGap as well)

document.addEventListener('mozvisibilitychange', function() {
    //console.log("VISIBILTY", document.mozHidden);
    if (document.mozHidden) {
        for (var key in window.sounds__) {
            window.sounds__[key]._wasPlaying = !window.sounds__[key].paused;
            window.sounds__[key].pause();
        }
    } else {

        for (var key in window.sounds__) {
            window.sounds__[key]._wasPlaying && window.sounds__[key].play();
        }
    }
});

define('resources/Sounds',
    [
        'platform',
        'utils/Log'
    ],
    function (platform, Log) {


        // export a singleton which manages audio using SoundManager2
        var Sounds = {
            onReady: function (callback) {
                callback();
            },
            play: function (soundId, onComplete) {
                var sound = window.sounds__['s' + soundId];
                var id = 's' + soundId;

                // choose and create a backup sound
                if (!sound.paused) {
                    // lazy clone it
                    if (!window.backupSounds__[id]) {
                        window.backupSounds__[id] = window.sounds__[id].cloneNode();
                        window.backupSounds__[id].mozAudioChannelType = 'content';
                    }

                    sound = window.backupSounds__[id];
                }

                sound['addEventListener']("ended", function onendcb () {
                  sound['removeEventListener']("ended", onendcb);
                  if (onComplete) {
                    onComplete();
                  }
                }, false);
                sound['play']();
            },
            isPlaying: function (soundId) {
                var sound = window.sounds__['s' + soundId];
                return !sound['paused'];
            },
            isPaused: function (soundId) {
                var sound = window.sounds__['s' + soundId];
                return sound['paused'];
            },
            pause: function (soundId) {
                var sound = window.sounds__['s' + soundId];
                sound['pause']();
            },
            stop: function (soundId) {
                var sound = window.sounds__['s' + soundId];
                sound['pause']();
                try {
                sound['currentTime'] = sound['initialTime'];
                } catch (e) {}
            },
            setVolume: function (soundId, percent) {
                var sound = window.sounds__['s' + soundId];
                sound['volume'] = percent / 100;
            }
        };

        return Sounds;
    }
);


define('PxLoader',[], function () {

    /**
     * PixelLab Resource Loader
     * Loads resources while providing progress updates.
     */
    function PxLoader(settings) {

        // merge settings with defaults
        settings = settings || {};

        // how frequently we poll resources for progress
        if (settings.statusInterval == null) {
            settings.statusInterval = 5000; // every 5 seconds by default
        }

        // delay before logging since last progress change
        if (settings.loggingDelay == null) {
            settings.loggingDelay = 20 * 1000; // log stragglers after 20 secs
        }

        // stop waiting if no progress has been made in the moving time window
        if (settings.noProgressTimeout == null) {
            settings.noProgressTimeout = Infinity; // do not stop waiting by default
        }

        var entries = [], // holds resources to be loaded with their status
            progressListeners = [],
            timeStarted,
            progressChanged = Date.now();

        /**
         * The status of a resource
         * @enum {number}
         */
        var ResourceState = {
            QUEUED: 0,
            WAITING: 1,
            LOADED: 2,
            ERROR: 3,
            TIMEOUT: 4
        };

        // places non-array values into an array.
        var ensureArray = function (val) {
            if (val == null) {
                return [];
            }

            if (Array.isArray(val)) {
                return val;
            }

            return [ val ];
        };

        // add an entry to the list of resources to be loaded
        this.add = function (resource) {

            // ensure tags are in an array
            resource.tags = ensureArray(resource.tags);

            // ensure priority is set
            if (resource.priority == null) {
                resource.priority = Infinity;
            }

            entries.push({
                resource: resource,
                state: ResourceState.QUEUED
            });
        };

        this.addProgressListener = function (callback, tags) {
            progressListeners.push({
                callback: callback,
                tags: ensureArray(tags)
            });
        };

        this.addCompletionListener = function (callback, tags) {
            progressListeners.push({
                tags: ensureArray(tags),
                callback: function (e) {
                    if (e.completedCount === e.totalCount) {
                        callback();
                    }
                }
            });
        };

        // creates a comparison function for resources
        var getResourceSort = function (orderedTags) {

            // helper to get the top tag's order for a resource
            orderedTags = ensureArray(orderedTags);
            var getTagOrder = function (entry) {
                var resource = entry.resource,
                    bestIndex = Infinity;
                for (var i = 0, len = resource.tags.length; i < len; i++) {
                    var index = orderedTags.indexOf(resource.tags[i]);
                    if (index >= 0 && index < bestIndex) {
                        bestIndex = index;
                    }
                }
                return bestIndex;
            };

            return function (a, b) {
                // check tag order first
                var aOrder = getTagOrder(a),
                    bOrder = getTagOrder(b);
                if (aOrder < bOrder) return -1;
                if (aOrder > bOrder) return 1;

                // now check priority
                if (a.priority < b.priority) return -1;
                if (a.priority > b.priority) return 1;
                return 0;
            }
        };

        this.start = function (orderedTags) {
            timeStarted = Date.now();

            // first order the resources
            var compareResources = getResourceSort(orderedTags);
            entries.sort(compareResources);

            // trigger requests for each resource
            for (var i = 0, len = entries.length; i < len; i++) {
                var entry = entries[i];
                entry.status = ResourceState.WAITING;
                entry.resource.start(this);
            }

            // do an initial status check soon since items may be loaded from the cache
            setTimeout(statusCheck, 100);
        };

        var statusCheck = function () {
            var checkAgain = false,
                noProgressTime = (Date.now()) - progressChanged,
                timedOut = (noProgressTime >= settings.noProgressTimeout),
                shouldLog = (noProgressTime >= settings.loggingDelay);

            for (var i = 0, len = entries.length; i < len; i++) {
                var entry = entries[i];
                if (entry.status !== ResourceState.WAITING) {
                    continue;
                }

                // see if the resource has loaded
                entry.resource.checkStatus();

                // if still waiting, mark as timed out or make sure we check again
                if (entry.status === ResourceState.WAITING) {
                    if (timedOut) {
                        entry.resource.onTimeout();
                    }
                    else {
                        checkAgain = true;
                    }
                }
            }

            // log any resources that are still pending
            if (shouldLog && checkAgain) {
                log();
            }

            if (checkAgain) {
                setTimeout(statusCheck, settings.statusInterval);
            }
        };

        this.isBusy = function () {
            for (var i = 0, len = entries.length; i < len; i++) {
                if (entries[i].status === ResourceState.QUEUED ||
                    entries[i].status === ResourceState.WAITING) {
                    return true;
                }
            }
            return false;
        };

        // helper which returns true if two arrays share at least one item
        var arraysIntersect = function (a, b) {
            for (var i = 0, len = a.length; i < len; i++) {
                if (b.indexOf(a[i]) >= 0) {
                    return true;
                }
            }
            return false;
        };

        var onProgress = function (resource, statusType) {
            // find the entry for the resource
            var entry = null;
            for (var i = 0, len = entries.length; i < len; i++) {
                if (entries[i].resource === resource) {
                    entry = entries[i];
                    break;
                }
            }

            // we have already updated the status of the resource
            if (entry == null || entry.status !== ResourceState.WAITING) {
                return;
            }
            entry.status = statusType;
            progressChanged = Date.now();

            var numResourceTags = resource.tags.length;

            // fire callbacks for interested listeners
            for (var i = 0, numListeners = progressListeners.length; i < numListeners; i++) {
                var listener = progressListeners[i],
                    shouldCall;

                if (listener.tags.length === 0) {
                    // no tags specified so always tell the listener
                    shouldCall = true;
                }
                else {
                    // listener only wants to hear about certain tags
                    shouldCall = arraysIntersect(resource.tags, listener.tags);
                }

                if (shouldCall) {
                    sendProgress(entry, listener);
                }
            }
        };

        this.onLoad = function (resource) {
            onProgress(resource, ResourceState.LOADED);
        };
        this.onError = function (resource) {
            onProgress(resource, ResourceState.ERROR);
        };
        this.onTimeout = function (resource) {
            onProgress(resource, ResourceState.TIMEOUT);
        };

        // sends a progress report to a listener
        var sendProgress = function (updatedEntry, listener) {
            // find stats for all the resources the caller is interested in
            var completed = 0,
                total = 0;
            for (var i = 0, len = entries.length; i < len; i++) {
                var entry = entries[i],
                    includeResource;

                if (listener.tags.length === 0) {
                    // no tags specified so always tell the listener
                    includeResource = true;
                }
                else {
                    includeResource = arraysIntersect(entry.resource.tags, listener.tags);
                }

                if (includeResource) {
                    total++;
                    if (entry.status === ResourceState.LOADED ||
                        entry.status === ResourceState.ERROR ||
                        entry.status === ResourceState.TIMEOUT) {
                        completed++;
                    }
                }
            }

            listener.callback({
                // info about the resource that changed
                resource: updatedEntry.resource,

                // should we expose StatusType instead?
                loaded: (updatedEntry.status === ResourceState.LOADED),
                error: (updatedEntry.status === ResourceState.ERROR),
                timeout: (updatedEntry.status === ResourceState.TIMEOUT),

                // updated stats for all resources
                completedCount: completed,
                totalCount: total
            });
        };

        // prints the status of each resource to the console
        var log = this.log = function (showAll) {
            if (!window.console) {
                return;
            }

            var elapsedSeconds = Math.round((Date.now() - timeStarted) / 1000);
            window.console.log('PxLoader elapsed: ' + elapsedSeconds + ' sec');

            for (var i = 0, len = entries.length; i < len; i++) {
                var entry = entries[i];
                if (!showAll && entry.status !== ResourceState.WAITING) {
                    continue;
                }

                var message = 'PxLoader: #' + i + ' ' + entry.resource.getName();
                switch (entry.status) {
                    case ResourceState.QUEUED:
                        message += ' (Not Started)';
                        break;
                    case ResourceState.WAITING:
                        message += ' (Waiting)';
                        break;
                    case ResourceState.LOADED:
                        message += ' (Loaded)';
                        break;
                    case ResourceState.ERROR:
                        message += ' (Error)';
                        break;
                    case ResourceState.TIMEOUT:
                        message += ' (Timeout)';
                        break;
                }

                if (entry.resource.tags.length > 0) {
                    message += ' Tags: [' + entry.resource.tags.join(',') + ']';
                }

                window.console.log(message);
            }
        };
    }

    // shims to ensure we have newer Array utility methods

    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
    if (!Array.isArray) {
        Array.isArray = function (arg) {
            return Object.prototype.toString.call(arg) == '[object Array]';
        };
    }

    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (searchElement /*, fromIndex */) {

            if (this == null) {
                throw new TypeError();
            }
            var t = Object(this);
            var len = t.length >>> 0;
            if (len === 0) {
                return -1;
            }
            var n = 0;
            if (arguments.length > 0) {
                n = Number(arguments[1]);
                if (n != n) { // shortcut for verifying if it's NaN
                    n = 0;
                } else if (n != 0 && n != Infinity && n != -Infinity) {
                    n = (n > 0 || -1) * Math.floor(Math.abs(n));
                }
            }
            if (n >= len) {
                return -1;
            }
            var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
            for (; k < len; k++) {
                if (k in t && t[k] === searchElement) {
                    return k;
                }
            }
            return -1;
        };
    }

    return PxLoader;
});




define('PxLoaderSound',['PxLoader'], function (PxLoader) {

    window.sounds__ = {};
    window.backupSounds__ = {};

    /**
     * PxLoader plugin to load sound using SoundManager2
     */
    function PxLoaderSound(id, url, tags, priority) {
        var self = this,
            loader = null;

        this.tags = tags;
        this.priority = priority;
        this.sound = new Audio();
        this.sound.mozAudioChannelType = 'content';

        window.sounds__[id] = this.sound;
        this.src = url;

        this.start = function (pxLoader) {
            // we need the loader ref so we can notify upon completion
            loader = pxLoader;


            self.sound['src'] = self.src;
            self.sound.mozAudioChannelType = 'content';
            loader.onLoad(self);
        };

        this.checkStatus = function () {
            switch (self.sound['readyState']) {
                case 0: // uninitialised
                case 1: // loading
                    break;
                case 2: // failed/error
                    loader.onError(self);
                    break;
                case 3: // loaded/success
                    loader.onLoad(self);
                    break;
            }
        };

        this.onTimeout = function () {
            loader.onTimeout(self);
        };

        this.getName = function () {
            return url;
        }
    }

    // add a convenience method to PxLoader for adding a sound
    PxLoader.prototype.addSound = function (id, url, tags, priority) {
        var soundLoader = new PxLoaderSound(id, url, tags, priority);
        this.add(soundLoader);
        return soundLoader.sound;
    };

    return PxLoaderSound;
});
define('resources/SoundLoader',
    [
        'platform',
        'edition',
        'resources/ResData',
        'resources/Sounds',
        'PxLoader',
        'PxLoaderSound'
    ],
    function (platform, edition, resData, Sounds, PxLoader, PxLoaderSound) {

        var completeListeners = [],
            startRequested = false,
            soundManagerReady = false,
            startIfReady = function () {
                // ensure start was requested and we are ready
                if (!startRequested || !soundManagerReady) {
                    return;
                }

                var pxLoader = new PxLoader({ 'noProgressTimeout': 30 * 1000 }), // stop waiting after 30 secs
                    baseUrl = platform.audioBaseUrl,
                    extension = platform.getAudioExtension(),
                    MENU_TAG = 'MENU',
                    i, len, soundId, soundUrl;

                // menu sounds first
                for (i = 0, len = edition.menuSoundIds.length; i < len; i++) {
                    soundId = edition.menuSoundIds[i];
                    soundUrl = baseUrl + resData[soundId].path + extension;

                    // SoundManager2 wants a sound id which a char prefix
                    pxLoader.addSound('s' + soundId, soundUrl, MENU_TAG);
                }

                // now game sounds
                for (i = 0, len = edition.gameSoundIds.length; i < len; i++) {
                    soundId = edition.gameSoundIds[i];
                    soundUrl = baseUrl + resData[soundId].path + extension;

                    // SoundManager2 wants a sound id which a char prefix
                    pxLoader.addSound('s' + soundId, soundUrl);
                }

                // wait for all sounds before showing main menu
                pxLoader.addCompletionListener(function () {
                    for (var i = 0, len = completeListeners.length; i < len; i++) {
                        completeListeners[i]();
                    }
                });

                pxLoader.start();
            };

        var SoundLoader = {
            start: function () {
                startRequested = true;
                startIfReady();
            },
            onMenuComplete: function (callback) {
                completeListeners.push(callback);
            }
        };

        Sounds.onReady(function () {
            soundManagerReady = true;
            startIfReady();
        });

        return SoundLoader;
    }
);


define('PxLoaderImage',['PxLoader'], function (PxLoader) {

    /**
     * PxLoader plugin to load images
     */
    function PxLoaderImage(url, tags, priority) {
        var self = this,
            loader = null;

        this.img = new Image();
        this.tags = tags;
        this.priority = priority;

        var onReadyStateChange = function () {
            if (self.img.readyState == 'complete') {
                removeEventHandlers();
                loader.onLoad(self);
            }
        };

        var onLoad = function () {
            removeEventHandlers();
            loader.onLoad(self);
        };

        var onError = function () {
            removeEventHandlers();
            loader.onError(self);
        };

        var removeEventHandlers = function () {
            self.unbind('load', onLoad);
            self.unbind('readystatechange', onReadyStateChange);
            self.unbind('error', onError);
        };

        this.start = function (pxLoader) {
            // we need the loader ref so we can notify upon completion
            loader = pxLoader;

            // NOTE: Must add event listeners before the src is set. We
            // also need to use the readystatechange because sometimes
            // load doesn't fire when an image is in the cache.
            self.bind('load', onLoad);
            self.bind('readystatechange', onReadyStateChange);
            self.bind('error', onError);

            self.img.src = url;
        };

        // called by PxLoader to check status of image (fallback in case
        // the event listeners are not triggered).
        this.checkStatus = function () {
            if (self.img.complete) {
                removeEventHandlers();
                loader.onLoad(self);
            }
        };

        // called by PxLoader when it is no longer waiting
        this.onTimeout = function () {
            removeEventHandlers();
            if (self.img.complete) {
                loader.onLoad(self);
            }
            else {
                loader.onTimeout(self);
            }
        };

        // returns a name for the resource that can be used in logging
        this.getName = function () {
            return url;
        };

        // cross-browser event binding
        this.bind = function (eventName, eventHandler) {
            if (self.img.addEventListener) {
                self.img.addEventListener(eventName, eventHandler, false);
            } else if (self.img.attachEvent) {
                self.img.attachEvent('on' + eventName, eventHandler);
            }
        };

        // cross-browser event un-binding
        this.unbind = function (eventName, eventHandler) {
            if (self.img.removeEventListener) {
                self.img.removeEventListener(eventName, eventHandler, false);
            } else if (self.img.detachEvent) {
                self.img.detachEvent('on' + eventName, eventHandler);
            }
        };

    }

    // add a convenience method to PxLoader for adding an image
    PxLoader.prototype.addImage = function (url, tags, priority) {
        var imageLoader = new PxLoaderImage(url, tags, priority);
        this.add(imageLoader);

        // return the img element to the caller
        return imageLoader.img;
    };

    return PxLoaderImage;
});
define('LoadAnimation',[], function () {

    // no load animation in packaged chrome application
    return null;
});
// responsible for preloading resources. It would be nice to use a single
// loader for both images and sound, but the init of SoundManager2 prevents
// queuing sounds before its ready. So for now, we'll keep the sound manager
// loading separate so we don't have to wait to begin loading images.

define('resources/PreLoader',
    [
        'platform',
        'edition',
        'editionUI',
        'resolution',
        'resources/ResData',
        'resources/SoundLoader',
        'PxLoader',
        'PxLoaderImage',
        'LoadAnimation',
        'resources/ResourceMgr',
        'resources/ResourcePacks'
    ],
    function (platform, edition, editionUI, resolution, resData, SoundLoader, PxLoader, PxLoaderImage, LoadAnimation, ResourceMgr, ResourcePacks) {

        var menuImagesLoadComplete = false,
            menuSoundLoadComplete = false,
            completeCallback = null,
            checkMenuLoadComplete = function () {
                if (!menuImagesLoadComplete || !menuSoundLoadComplete) {
                    return;
                }

                if (LoadAnimation) {
                    LoadAnimation.notifyLoaded();
                    LoadAnimation.hide();
                }

                if (completeCallback) {
                    // queue the execution of the callback so the loader can
                    // finish notifying listeners first
                    setTimeout(completeCallback, 0);
                }

                // ensure the completion is only run once
                checkMenuLoadComplete = function () {
                };
            };

        var loadImages = function () {
            var pxLoader = new PxLoader({ 'noProgressTimeout': 30 * 1000 }), // stop waiting after 30 secs
                gameBaseUrl = platform.imageBaseUrl + resolution.CANVAS_WIDTH + '/game/',
                MENU_TAG = 'MENU',
                FONT_TAG = 'FONT',
                GAME_TAG = 'GAME',
                i, len, imageUrl;

            // first menu images
            var queueMenuImages = function (imageFilenames, menuBaseUrl) {
                if (!imageFilenames) {
                    return;
                }

                menuBaseUrl = menuBaseUrl || platform.uiImageBaseUrl;
                for (i = 0, len = imageFilenames.length; i < len; i++) {
                    if (!imageFilenames[i]) {
                        continue;
                    }
                    imageUrl = menuBaseUrl + imageFilenames[i];
                    pxLoader.addImage(imageUrl, MENU_TAG);
                }
            };

            // queue page images first, the game can wait (we have a load animation)
            var passwordPath = platform.imageBaseUrl + (editionUI.passwordDirectory || '');
            queueMenuImages(editionUI.passwordImageNames, passwordPath);

            var passwordResolutionPath = platform.resolutionBaseUrl + (editionUI.passwordResolutionDirectory || '');
            queueMenuImages(editionUI.passwordResolutionImageNames, passwordResolutionPath);

            queueMenuImages(editionUI.pageImageNames, platform.imageBaseUrl + 'page/');
            queueMenuImages(editionUI.pageResolutionImageNames, platform.resolutionBaseUrl + 'page/');
            queueMenuImages(edition.menuImageFilenames);
            queueMenuImages(edition.boxImages, platform.boxImageBaseUrl);
            queueMenuImages(edition.boxBorders);
            queueMenuImages(edition.boxDoors);

            queueMenuImages(edition.drawingImageNames);

            var editionBaseUrl = platform.resolutionBaseUrl + (edition.editionImageDirectory || '');
            queueMenuImages(edition.editionImages, editionBaseUrl);

            // only report progress on the menu images and fonts
            pxLoader.addProgressListener(function (e) {
                var p = 100 * (e.completedCount / e.totalCount);
                if (LoadAnimation) {
                    LoadAnimation.notifyLoadProgress(p);
                }

                if (e.completedCount === e.totalCount) {
                    menuImagesLoadComplete = true;
                    checkMenuLoadComplete();
                }
            }, [ MENU_TAG, FONT_TAG ]);

            // next fonts and game images
            var queueForResMgr = function (ids, tag) {
                var i, len, imageId;
                for (i = 0, len = ids.length; i < len; i++) {
                    imageId = ids[i];
                    var pxImage = new PxLoaderImage(
                        gameBaseUrl + resData[imageId].path,
                        tag);

                    // add the resId so we can find it upon completion
                    pxImage.resId = imageId;
                    pxLoader.add(pxImage);
                }
            };
            queueForResMgr(ResourcePacks.StandardFonts, FONT_TAG);
            queueForResMgr(edition.gameImageIds, GAME_TAG);
            queueForResMgr(edition.levelBackgroundIds, GAME_TAG);
            queueForResMgr(edition.levelOverlayIds, GAME_TAG);

            // tell the resource manager about game images and fonts
            pxLoader.addProgressListener(function (e) {
                ResourceMgr.onResourceLoaded(e.resource.resId, e.resource.img);
            }, [ FONT_TAG, GAME_TAG ]);

            pxLoader.start();
        };

        var PreLoader = {
            init: function () {
                ResourceMgr.init();

                // start the loading animation images first
                if (LoadAnimation) {
                    LoadAnimation.init();
                }

                // next start the images
                loadImages();

                // now start the sounds
                SoundLoader.onMenuComplete(function () {
                    menuSoundLoadComplete = true;
                    checkMenuLoadComplete();
                });
                SoundLoader.start();
            },
            domReady: function () {
                if (LoadAnimation) {
                    LoadAnimation.domReady();
                    LoadAnimation.show();
                }
            },
            run: function (onComplete) {
                completeCallback = onComplete;
                checkMenuLoadComplete();
            }
        };

        return PreLoader;
    }
);


define('game/RoamSettings',
    [
        'edition',
        'utils/PubSub'
    ],
    function (edition, PubSub) {

        var currentUserId = '';
        PubSub.subscribe(PubSub.ChannelId.UserIdChanged, function (userId) {
            currentUserId = userId;
        });

        var roamingProvider = null;
        PubSub.subscribe(PubSub.ChannelId.RoamingSettingProvider, function (provider) {

            // copy methods (which will be minified)
            if (provider) {
                roamingProvider = {
                    set: provider['set'],
                    get: provider['get'],
                    remove: provider['remove']
                };
            } else {
                roamingProvider = null;
            }

            PubSub.publish(PubSub.ChannelId.RoamingDataChanged);
        });

        var SCORES_PREFIX = 'scores',
            STARS_PREFIX = 'stars',
            ACHIEVEMENTS_PREFIX = 'achievements';

        // appends the current user's id to the key prefix
        function getFullKey(prefix, boxIndex) {
            var key = prefix;
            if (currentUserId) {
                key += ('-' + currentUserId);
            }

            return key;
        }

        /* Unfortunately Windows doesn't tell us which value changed. Keeping this
           code in case we ever intergrate with another settings store that does
        function onSettingChanged(key, value) {

            var parts = (key || '').split('-');
            if (parts.length === 0) {
                return;
            }

            var userId, boxIndex, levelIndex, achievementIndex;
            switch(parts[0]) {
                case SCORES_PREFIX:
                case STARS_PREFIX:
                    var userId = (parts.length === 3) ? parts[2] : '';
                    if (userId === currentUserId) {
                        // only need to change data for current user
                    }
                    break;
                case ACHIEVEMENTS_PREFIX:
                    var userId = (parts.length === 2) ? parts[1] : '';
                    if (userId === currentUserId) {
                        // only need to change data for current user
                    }
                    break;
            }
        }
        */

        // deserializes hex (and possibly undefined or null values) from a string
        function getHexValues(keyPrefix) {

            if (!roamingProvider) {
                return null;
            }

            var key = getFullKey(keyPrefix),
                values = [],
                rawValues = (roamingProvider.get(key) || '').split(','), // split csv
                len = rawValues.length,
                i, val;

            for (i = 0; i < len; i++) {
                if (i < rawValues.length) {

                    // parse value which is stored in hex
                    val = parseInt(rawValues[i], 16);
                    if (isNaN(val)) {
                        val = null;
                    }
                } else {
                    val = null;
                }
                values.push(val);
            }

            return values;
        }

        // serializes numbers into hex CSVs with compact nulls
        function saveHexValues(keyPrefix, values) {
            if (!roamingProvider) {
                return null;
            }

            var key = getFullKey(keyPrefix);

            if (!values) {
                roamingProvider.remove(key);
            } else {
                var rawValues = [],
                    len = values.length,
                    i, val;

                for (i = 0; i < len; i++) {
                    val = values[i];
                    if (val == null) {
                        // we have limited storage space so we'll shorten null values
                        rawValues.push('');
                    } else {
                        // encode values as hex
                        rawValues.push(val.toString(16))
                    }
                }

                // save comma separated values
                roamingProvider.set(key, rawValues.join(','));
            }
        }

        function getValue(keyPrefix, index) {
            if (!roamingProvider) {
                return null;
            }

            var values = getHexValues(keyPrefix);
            return (values.length > index) ? values[index] : null;
        }

        function saveValue(keyPrefix, index, value) {
            if (!roamingProvider) {
                return;
            }

            var values = getHexValues(keyPrefix),
                prevValue = values[index];

            // only write if value has changed
            if (prevValue !== value) {
                values[index] = value;
                saveHexValues(keyPrefix, values);
            }
        }

        var RoamingSettings = {

            // scores
            getScore: function(boxIndex, levelIndex) {
                return getValue(SCORES_PREFIX + '-' + boxIndex, levelIndex);
            },
            setScore: function(boxIndex, levelIndex, score) {
                saveValue(SCORES_PREFIX + '-' + boxIndex, levelIndex, score);
            },

            // stars
            getStars: function(boxIndex, levelIndex) {
                return getValue(STARS_PREFIX + '-' + boxIndex, levelIndex);
            },
            setStars: function(boxIndex, levelIndex, stars) {
                saveValue(STARS_PREFIX + '-' + boxIndex, levelIndex, stars);
            },

            // achievement counts
            getAchievementCount: function(achievementIndex) {
                return getValue(ACHIEVEMENTS_PREFIX, achievementIndex);
            },
            setAchievementCount: function(achievementIndex, count) {
                saveValue(ACHIEVEMENTS_PREFIX, achievementIndex, count);
            }
        };


        return RoamingSettings;
    }
);
define('ui/ScoreManager',
    [
        'ui/QueryStrings',
        'utils/PubSub',
        'utils/MathHelper',
        'core/SettingStorage',
        'edition',
        'visual/Text',
        'resources/Lang',
        'resources/LangId',
        'resources/MenuStringId',
        'game/RoamSettings'
    ],
    function (QueryStrings, PubSub, MathHelper, SettingStorage, edition, Text,
        Lang, LangId, MenuStringId, RoamSettings) {

        // we use XOR to obfuscate the level scores to discourage cheats. Doesn't
        // prevent hacks - server side code would be necessary for that.

        // make the prefixes hard to find in source code
        var SCORE_PREFIX = String.fromCharCode(98, 112), // 'bp' (short for box-points)
            STARS_PREFIX = String.fromCharCode(98, 115), // 'bs' (short for box-stars)

        // our XOR value is a random number that is stored in an entry that is
        // intended to look similar to the score record for a box
            XOR_KEY = SCORE_PREFIX + String.fromCharCode(50, 51, 57, 48),
            XOR_VALUE = SettingStorage.getIntOrDefault(XOR_KEY, null);

        // create the random value if it doesn't exist
        if (XOR_VALUE == null) {
            XOR_VALUE = MathHelper.randomRange(1000, 10000);
            SettingStorage.set(XOR_KEY, XOR_VALUE);
        }

        // helper functions to get/set score
        var getScoreKey = function (box, level) {
                var val = ((box * 1000) + level) ^ XOR_VALUE,
                    key = SCORE_PREFIX + val;

                // make sure we don't overwrite our XOR key
                if (key === XOR_KEY) {
                    return key + '_';
                }

                return key;
            },
            setScore = function (box, level, points) {

                SettingStorage.set(
                    getScoreKey(box, level),
                    // NOTE: we intentionally swap multiplier to level (key uses box)
                    (points + (level * 1000) + box) ^ XOR_VALUE
                );

                RoamSettings.setScore(box, level, points);
            },
            getScore = function (box, level) {
                // fetch both roaming and local scores
                var roamScore = RoamSettings.getScore(box, level) || 0,
                    localKey = getScoreKey(box, level),
                    localVal = SettingStorage.getIntOrDefault(localKey, null),
                    localScore = (localVal == null) ? 0 :
                        (localVal ^ XOR_VALUE) - box - (level * 1000);

                return Math.max(roamScore, localScore);
            };

        // helper functions to get/set stars
        var STARS_UNKNOWN = -1, // needs to be a number but can't be null
            getStarsKey = function (box, level) {
                // NOTE: we intentionally swap multiplier from whats used for points
                var key = ((level * 1000) + box) ^ XOR_VALUE;
                return STARS_PREFIX + key;
            },
            setStars = function (box, level, stars) {
                var localStars = (stars == null) ? STARS_UNKNOWN : stars;
                SettingStorage.set(
                    getStarsKey(box, level),
                    // NOTE: we intentionally swap multiplier to box (key uses level)
                    (localStars  + (box * 1000) + level) ^ XOR_VALUE
                );

                RoamSettings.setStars(box, level, stars);
            },
            getStars = function (box, level) {

                var roamStars = RoamSettings.getStars(box, level),
                    localKey = getStarsKey(box, level),
                    localVal = SettingStorage.getIntOrDefault(localKey, null),
                    localStars = (localVal == null) ? null :
                        (localVal ^ XOR_VALUE) - level - (box * 1000);

                if (localStars === STARS_UNKNOWN || localStars === null) {
                    return roamStars;
                } else if (roamStars == null) {
                    return localStars;
                }

                return Math.max(roamStars, localStars);
            };

        var resetLevel = function (boxIndex, levelIndex) {
            // first level gets 0 stars, otherwise null
            var stars = (levelIndex === 0) ? 0 : null;

            setStars(boxIndex, levelIndex, stars);
            setScore(boxIndex, levelIndex, 0);
        };

        var ScoreBox = function (levelCount, requiredStars, scores, stars) {
            this.levelCount = levelCount;
            this.requiredStars = requiredStars;
            this.scores = scores || [];
            this.stars = stars || [];
        };

        var ScoreManager = new function () {

            var boxes = [];

            this.load = function() {

                // clear existing boxes
                boxes.length = 0;

                // load box scores
                for (var i = 0, len = edition.boxes.length; i < len; i++) {
                    boxes[i] = loadBox(i);
                }

                // update score text
                if (appReady) {
                    ScoreManager.updateTotalScoreText();
                }
            };

            PubSub.subscribe(PubSub.ChannelId.SignIn, this.load);
            PubSub.subscribe(PubSub.ChannelId.SignOut, this.load);
            PubSub.subscribe(PubSub.ChannelId.RoamingDataChanged, this.load);

            // the score text can only be updated when the app is ready
            var appReady = false;
            PubSub.subscribe(PubSub.ChannelId.AppRun, function() { appReady = true; });

            // load previous scores from local storage
            var loadBox = function (boxIndex) {

                // see if the box exists by checking for a level 1 star record
                var boxExists = (getStars(boxIndex, 0) !== null),
                    levelCount = edition.boxes[boxIndex].levels.length,
                    requiredStars = edition.unlockStars[boxIndex],
                    scores = [],
                    stars = [],
                    levelIndex;

                // get (or create) scores and stars from each level
                for (levelIndex = 0; levelIndex < levelCount; levelIndex++) {

                    // if the box doesn't exist
                    if (!boxExists) {
                        resetLevel(boxIndex, levelIndex);
                    }

                    scores.push(getScore(boxIndex, levelIndex));
                    stars.push(getStars(boxIndex, levelIndex));
                }

                // generate fake (and good) star counts
                if (QueryStrings.createScoresForBox == (boxIndex + 1)) {
                    for (var i = 0; i < levelCount; i++) {
                        ScoreManager.setStars(boxIndex, i, 3, true);
                    }
                }

                return new ScoreBox(levelCount, requiredStars, scores, stars);
            };

            this.getXorValue = function() {
                return XOR_VALUE;
            };

            this.boxCount = function () {
                if (boxes != null) return boxes.length;
                return null;
            };

            this.levelCount = function (boxIndex) {
                var box = boxes[boxIndex];
                if (box != null) return box.levelCount;
                return null;
            };

            this.requiredStars = function (boxIndex) {
                var box = boxes[boxIndex];
                if (box != null) return box.requiredStars;
                return 0;
            };

            this.achievedStars = function (boxIndex) {
                var box = boxes[boxIndex];
                if (box != null) {
                    var count = 0;
                    for (var j = 0; j < box.levelCount; j++) {
                        var stars = box.stars[j];
                        count += stars == null ? 0 : stars;
                    }
                    return count;
                }
                return 0;
            };

            this.totalStars = function () {
                var total = 0;
                for (var i = 0; i < boxes.length; i++) {
                    total += ScoreManager.achievedStars(i);
                }
                return total;
            };

            this.possibleStarsForBox = function (boxIndex) {
                var box = boxes[boxIndex];
                if (box != null) {
                    return box.levelCount * 3;
                }
                return 0;
            };

            this.isBoxLocked = function (boxIndex) {
                if (boxIndex == 0) return false;
                if (QueryStrings.unlockAllBoxes) return false;
                var box = boxes[boxIndex];
                if (box != null && (ScoreManager.totalStars() >= ScoreManager.requiredStars(boxIndex))) {
                    return false;
                }
                return true;
            };

            this.isLevelUnlocked = function (boxIndex, levelindex) {
                var box = boxes[boxIndex];
                if (QueryStrings.unlockAllBoxes) return true;
                if (box != null) {
                    return (box.stars[levelindex] != null);
                }
                return false;
            };

            this.setScore = function (boxIndex, levelIndex, levelScore, overridePrevious) {
                var box = boxes[boxIndex];
                if (box != null) {

                    if (overridePrevious) {
                        box.scores[levelIndex] = levelScore;
                    } else {
                        var prevScore = getScore(boxIndex, levelIndex);
                        box.scores[levelIndex] = Math.max(levelScore, prevScore);
                    }

                    setScore(boxIndex, levelIndex, box.scores[levelIndex]);

                    // sum all scores for the box
                    var numLevels = box.scores.length,
                        boxScore = 0, i;
                    for (i = 0; i < numLevels; i++) {
                        boxScore += box.scores[i];
                    }

                    // always report scores since we may have been offline when the
                    // previous high score was achieved.
                    PubSub.publish(PubSub.ChannelId.UpdateBoxScore, boxIndex, boxScore);
                }
            };

            this.getScore = function (boxIndex, levelIndex) {
                var box = boxes[boxIndex];
                if (box != null) return box.scores[levelIndex];
                return null;
            };

            this.setStars = function (boxIndex, levelIndex, score, overridePrevious) {
                var previousStars = this.totalStars(),
                    box = boxes[boxIndex];
                if (box != null) {

                    //don't override past high score
                    var prevStars = getStars(boxIndex, levelIndex);
                    if (prevStars != null && !overridePrevious) {
                        box.stars[levelIndex] = Math.max(score, prevStars);
                    } else {
                        box.stars[levelIndex] = score;
                    }
                    setStars(boxIndex, levelIndex, box.stars[levelIndex]);
                }

                var newStarCount = this.totalStars();
                if (newStarCount !== previousStars) {
                    PubSub.publish(PubSub.ChannelId.StarCountChanged, newStarCount);
                }
            };

            this.getStars = function (boxIndex, levelIndex) {
                var box = boxes[boxIndex];
                if (box != null) return box.stars[levelIndex];
                return null;
            };

            this.resetGame = function () {
                var boxCount = boxes.length,
                    boxIndex,
                    box,
                    levelIndex,
                    levelCount;

                for (boxIndex = 0; boxIndex < boxCount; boxIndex++) {
                    box = boxes[boxIndex];
                    levelCount = box.levelCount;
                    for (levelIndex = 0; levelIndex < levelCount; levelIndex++) {
                        resetLevel(boxIndex, levelIndex);
                        box.stars[levelIndex] = getStars(boxIndex, levelIndex);
                        box.scores[levelIndex] = getScore(boxIndex, levelIndex);
                    }
                }

                // update score
                this.updateTotalScoreText();
            };

            this.updateTotalScoreText = function () {
                var text = Lang.menuText(MenuStringId.TOTAL_STARS)
                    .replace('%d', ScoreManager.totalStars());
                Text.drawBig({text: text, imgSel: '#boxScore img', scaleToUI: true });
            };

            PubSub.subscribe(PubSub.ChannelId.LanguageChanged, this.updateTotalScoreText);

        };

        return ScoreManager;
    }
);
define('ui/Easing',
    [],
    function () {

        // penner easing (we use for canvas animations)

        var Easing = new function () {

            //@t is the current time (or position) of the tween. This can be seconds or frames, steps, seconds, ms, whatever - as long as the unit is the same as is used for the total time [3].
            //@b is the beginning value of the property.
            //@c is the change between the beginning and destination value of the property.
            //@d is the total time of the tween.

            this.noEase = function (t, b, c, d) {
                return c * t / d + b;
            };
            this.easeOutCirc = function (t, b, c, d) {
                return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
            };
            this.easeInCirc = function (t, b, c, d) {
                return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
            };
            this.easeInOutCirc = function (t, b, c, d) {
                if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
                return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
            };
            this.easeInSine = function (t, b, c, d) {
                return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
            };
            this.easeOutSine = function (t, b, c, d) {
                return c * Math.sin(t / d * (Math.PI / 2)) + b;
            };
            this.easeInOutSine = function (t, b, c, d) {
                return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
            };
            this.easeInCubic = function (t, b, c, d) {
                return c * (t /= d) * t * t + b;
            };
            this.easeOutCubic = function (t, b, c, d) {
                return c * ((t = t / d - 1) * t * t + 1) + b;
            };
            this.easeInOutCubic = function (t, b, c, d) {
                if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
                return c / 2 * ((t -= 2) * t * t + 2) + b;
            };
            this.easeInExpo = function (t, b, c, d) {
                return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
            };
            this.easeOutExpo = function (t, b, c, d) {
                return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
            };
            this.easeInOutExpo = function (t, b, c, d) {
                if (t == 0) return b;
                if (t == d) return b + c;
                if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
                return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
            };
            this.easeInBounce = function (t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                return c * (t /= d) * t * ((s + 1) * t - s) + b;
            };
            this.easeOutBounce = function (t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
            };
            this.easeInOutBounce = function (t, b, c, d, s) {
                if (s == undefined) s = 1.70158;
                if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
                return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
            };
            this.easeInOutQuad = function (t, b, c, d) {
                if ((t /= d / 2) < 1) return c / 2 * t * t + b;
                return -c / 2 * ((--t) * (t - 2) - 1) + b;
            };

        };

        return Easing;
    }
);
define('ui/Box',
    [
        'utils/Class',
        'ui/Easing',
        'visual/Text',
        'resolution',
        'platform',
        'ui/BoxType',
        'utils/PubSub',
        'resources/Lang',
        'core/Alignment',
        'ui/ScoreManager',
        'resources/MenuStringId',
        'edition',
        'game/CTRSettings'
    ],
    function (Class, Easing, Text, resolution, platform, BoxType, PubSub, Lang, Alignment, ScoreManager, MenuStringId, edition, settings) {

        // cache upgrade UI elements
        var $upgradeButton;
        $(function() {
            $upgradeButton = $('#boxUpgradePlate').hide();
        });

        function hidePurchaseButton() {
            if ($upgradeButton) {
                $upgradeButton.fadeOut(200);
            }
        }

        PubSub.subscribe(PubSub.ChannelId.SetPaidBoxes, function(paid) {
            if (paid) {
                hidePurchaseButton();
            }
        });

        // localize UI element text
        PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
            Text.drawBig({
                text: Lang.menuText(MenuStringId.BUY_FULL_GAME),
                imgParentId: 'boxUpgradePlate',
                scale: 0.6 * resolution.UI_TEXT_SCALE
            });
        });

        var boxImageBase = (platform.boxImageBaseUrl || platform.uiImageBaseUrl);

        var Box = Class.extend({

            init: function (boxIndex, bgimg, reqstars, islocked, type) {

                this.index = boxIndex;
                this.islocked = islocked;
                this.visible = true;

                // initially we assume all boxes are included in the game
                this.purchased = true;

                this.bounceStartTime = 0;
                this.opacity = 1.0;
                this.type = type;

                this.boxImg = new Image();

                if (bgimg) {
                    this.boxImg.src = boxImageBase + bgimg;
                }

                var textImg = this.textImg = new Image(),
                    boxWidth = this.boxWidth = resolution.uiScaledNumber(350),
                    boxTextMargin = this.boxTextMargin = resolution.uiScaledNumber(20),
                    self = this;

                
                //     console.log(boxTextMargin)
                //     this.boxTextMargin = 100;
                // }

                this.textRendered = false;
                this.renderText = function () {
                    Text.drawBig({
                        text: Lang.boxText(boxIndex, self.includeBoxNumberInTitle),
                        img: textImg,
                        width: (boxWidth - (boxTextMargin * 2)) / resolution.UI_TEXT_SCALE,
                        alignment: Alignment.HCENTER,
                        scaleToUI: true
                    });
                    
                    self.textRendered = true;
                };

                PubSub.subscribe(PubSub.ChannelId.LanguageChanged, this.renderText);

                this.reqImg = Text.drawBig({ text: reqstars, scaleToUI: true });

                this.omNomImg = new Image();
                this.omNomImg.src = platform.uiImageBaseUrl + 'box_omnom.png';

                this.lockImg = new Image();
                this.lockImg.src = platform.uiImageBaseUrl + 'box_lock.png';

                this.starImg = new Image();
                this.starImg.src = platform.uiImageBaseUrl + 'star_result_small.png';

                this.perfectMark = new Image();
                this.perfectMark.src = platform.uiImageBaseUrl + 'perfect_mark.png';

                this.includeBoxNumberInTitle = true;
            },

            isRequired: function () {
                return true;
            },

            isGameBox: function() {
                return true;
            },

            isClickable: function() {
                return true;
            },

            draw: function(ctx, omnomoffset) {

                var prevAlpha = ctx.globalAlpha;
                if (this.opacity !== prevAlpha) {
                    ctx.globalAlpha = this.opacity;
                }

                // render the box
                this.render(ctx, omnomoffset);

                // restore alpha
                if (this.opacity !== prevAlpha) {
                    ctx.globalAlpha = prevAlpha;
                }
            },

            render: function (ctx, omnomoffset) {

                var isGameBox = this.isGameBox();
                if (isGameBox) {
                    // draw the black area
                    ctx.fillStyle = "rgb(45,45,53)";
                    ctx.fillRect(resolution.uiScaledNumber(130), resolution.uiScaledNumber(200),
                        resolution.uiScaledNumber(140), resolution.uiScaledNumber(100));

                    // draw omnom
                    if (omnomoffset != null) {
                        ctx.drawImage(this.omNomImg, omnomoffset + resolution.uiScaledNumber(4), resolution.uiScaledNumber(215));
                    }
                }

                // draw the box image
                ctx.drawImage(this.boxImg, resolution.uiScaledNumber(25), resolution.uiScaledNumber(0));

                if (isGameBox) {
                    // draw the lock
                    if (this.islocked) {

                        // prefer css dimensions (scaled) for text
                        var textWidth = $(this.reqImg).width() || this.reqImg.width,
                            textHeight = $(this.reqImg).height() || this.reqImg.height,

                        // ok to use raw image width for star (image already scaled)
                            starWidth = this.starImg.width || $(this.starImg).width(),
                            starLeftMargin = resolution.uiScaledNumber(-6),

                        // center the text and star label
                            labelWidth = textWidth + starLeftMargin + starWidth,
                            labelMaxWidth = resolution.uiScaledNumber(125),
                            labelOffsetX = (labelMaxWidth - labelWidth) / 2,
                            labelMinX = resolution.uiScaledNumber(140),
                            labelX = labelMinX + labelOffsetX;

                        // slightly scale the lock image (not quite big enough for our boxes)
                        // TODO: should resize lock images for every resolution and remove scaling
                        // TODO: also need to normalize the size of boxes (which vary)
                        ctx.scale(1.015, 1);
                        ctx.drawImage(this.lockImg, resolution.uiScaledNumber(23),
                            resolution.uiScaledNumber(155));
                        ctx.scale(1 / 1.015, 1);

                        if (this.purchased) {
                            ctx.drawImage(this.reqImg, labelX,
                                resolution.uiScaledNumber(220), textWidth, textHeight);
                            ctx.drawImage(this.starImg, labelX + textWidth + starLeftMargin,
                                resolution.uiScaledNumber(225));
                        }

                        /*
                         // DEBUG: draw red dots to show the label boundaries
                         ctx.fillStyle= 'red';
                         ctx.beginPath();
                         ctx.arc(labelMinX, resolution.uiScaledNumber(220), 5, 0, 2*Math.PI, false);
                         ctx.fill();

                         ctx.beginPath();
                         ctx.arc(labelMinX + labelMaxWidth, resolution.uiScaledNumber(220), 5, 0, 2*Math.PI, false);
                         ctx.fill();
                         */
                    }

                    // draw the perfect mark if user got every star in the box
                    if (ScoreManager.achievedStars(this.index) ===
                        ScoreManager.possibleStarsForBox(this.index)) {
                        ctx.drawImage(
                            this.perfectMark,
                            resolution.uiScaledNumber(260),
                            resolution.uiScaledNumber(250));
                    }
                }

                // draw the text
                if (!this.textRendered) {
                    this.renderText();
                }

                var $textImg = $(this.textImg),
                    textWidth = $textImg.width() || this.textImg.width,
                    textHeight = $textImg.height() || this.textImg.height,
                    x = ~~(resolution.uiScaledNumber(25) + this.boxTextMargin +
                        (this.boxWidth - (this.boxTextMargin * 2) - textWidth) / 2),
                    y = resolution.uiScaledNumber(70);

                ctx.drawImage(this.textImg, x, y);
            },

            bounce: function (ctx) {

                if (!ctx) {
                    return;
                }

                this.bounceStartTime = Date.now();

                // stage boundaries in msec
                var s1 = 100, s2 = 300, s3 = 600,
                    w = resolution.uiScaledNumber(1024),
                    h = resolution.uiScaledNumber(576);

                var self = this,
                    renderBounce = function () {

                    // get the elapsed time
                    t = Date.now() - self.bounceStartTime;

                    var d, x, y;

                    if (t < s1) {
                        d = Easing.easeOutSine(t, 0, 0.05, s1); // to 0.95
                        x = 1.0 - d;
                        y = 1.0 + d;
                    }
                    else if (t < s2) {
                        d = Easing.easeInOutCubic(t - s1, 0, 0.11, s2 - s1); // to 0.95
                        x = 0.95 + d;
                        y = 1.05 - d;
                    }
                    else if (t < s3) {
                        // intentionally not ending at 1.0 prevents the animation from "snapping" at the end.
                        // it's not a great hack, but the animation ends up much smoother (esp. in IE)
                        d = Easing.easeOutCubic(t - s2, 0, 0.05, s3 - s2); // to 0.95
                        x = 1.06 - d;
                        y = 0.94 + d;
                    }

                    var tx = (w - (w * x)) / 2.0,
                        ty = (h - (h * y)) / 2.0,
                        sx = (w - (2.0 * tx)) / w,
                        sy = (h - (2.0 * ty)) / h;

                    if (!isNaN(sx) && !isNaN(sy)) {

                        ctx.save();
                        ctx.setTransform(1, 0, 0, 1, 0, 0);
                        ctx.clearRect(resolution.uiScaledNumber(312), resolution.uiScaledNumber(100),
                            resolution.uiScaledNumber(400), resolution.uiScaledNumber(460));
                        ctx.restore();

                        ctx.save();
                        ctx.scale(sx, sy);
                        ctx.translate(tx, ty);
                        ctx.translate(resolution.uiScaledNumber(312), resolution.uiScaledNumber(130));
                        self.draw(ctx, resolution.uiScaledNumber(140));
                        ctx.restore();
                    }

                    if (t > s3) {
                        self.bounceStartTime = 0;
                    } else {
                        window.requestAnimationFrame(renderBounce);
                    }
                };

                // start the animation
                renderBounce();

            },

            cancelBounce: function () {
                this.bounceStartTime = 0;
            },

            onSelected: function () {
                if (!this.purchased) {
                    $upgradeButton
                        .toggleClass('purchaseBox', this.isPurchaseBox || false)
                        .fadeIn();
                }
            },

            onUnselected: function () {
                hidePurchaseButton();
            }

        });


        return Box;
    }
);
define('analytics',[], function () {

    // just what the filename says: no analytics
    return {};
});
define('ui/PinnedBox',
    [
        'ui/Box',
        'ui/QueryStrings',
        'resources/Lang',
        'resources/MenuStringId',
        'utils/PubSub',
        'edition',
        'visual/Text',
        'platform',
        'analytics',
        'resolution',
        'core/SettingStorage'
    ],
    function (Box, QueryStrings, Lang, MenuStringId, PubSub, edition, Text, platform, analytics, resolution, SettingStorage) {

        /**
         * @enum {number}
         */
        var PinnedStates = {
            UNDEFINED: -1, // unknown pinned state
            HIDDEN: 0, // hidden, probably because the OS d
            VISIBLE: 1, // visible and completely playable
            PROMPT_IE: 2, // visible but with a prompt to install IE
            PROMPT_PIN: 3   // visible but with a prompt to pin the game
        };

        var PinnedBox = Box.extend({
            init: function (boxIndex, bgimg, reqstars, islocked, type) {
                this._super(boxIndex, bgimg, reqstars, islocked, type);
                this.pinnedState = PinnedStates.UNDEFINED;
                this.promptId = null;

                // dom ready init
                var self = this;
                $(document).ready(function() {
                    $('#showMeBtn').click(function () {
                        if (analytics.onShowPinning) {
                            analytics.onShowPinning();
                        }
                        self.showMePinning();
                    });

                    // We'll only get download links for vista and win7. For win8
                    // the url is null and we will hide the button (since the user
                    // already has IE10 installed)
                    var $getIeButton = $('#installieBtn'),
                        ieDownload = getIE9DownloadUrl();
                    if (ieDownload) {
                        $getIeButton.on('click', function (e) {
                            if (analytics.onDownloadIE) {
                                analytics.onDownloadIE();
                            }
                            window.location.href = ieDownload;
                        });

                        PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
                            Text.drawBig({
                                text: Lang.menuText(MenuStringId.FREE_DOWNLOAD),
                                img: $getIeButton.find('img')[0],
                                scaleToUI: true });
                        });
                    }
                    else {
                        $getIeButton.hide();
                    }
                });
            },

            isRequired: function () {
                // returns true if the box is enabled on the platform. this doesn't always
                // mean it is unlocked. For example, in Chrome on Windows, we'll tell
                // the user to install IE. On IE, they need to pin the game first. However
                // there is no IE on mac so the box is completely disabled.

                return (this.pinnedState !== PinnedStates.HIDDEN);
            },

            initPinnedState: function () {
                // returns the version of Internet Explorer or a -1 if another browser
                var getIEVersion = function () {
                    var rv = -1; // Return value assumes failure.
                    return rv;
                    if (navigator.appName == 'Microsoft Internet Explorer' || navigator.appName == "MSAppHost/1.0") {
                        var ua = navigator.userAgent,
                            re = new RegExp("MSIE ([0-9]?[0-9]{1,}[\.0-9]{0,})"),
                            matches = re.exec(ua);
                        if (matches != null && matches.length > 1) {
                            // first entry is the original string followed by matches
                            // so index 1 is the first match
                            rv = parseInt(matches[1], 10);
                        }
                    }
                    return rv;
                };

                // returns a bool indiciating whether IE can run on the current OS
                var getIECapableOS = function () {
                    return false;
                    try {
                        var u = navigator.userAgent;
                        var isWindows = u.indexOf("Windows NT") != -1;
                        var majVersion = isWindows ? parseInt(u[u.indexOf("Windows NT") + 11]) : -1;
                        if (isWindows && majVersion >= 6) return true;
                    }
                    catch (ex) {
                        return false;
                    }
                    return false;
                };

                // what version of IE are we running (or -1 for non-IE)
                var ieVer = getIEVersion();

                // are we using an OS (Vista or up) that supports IE
                var ieCan = getIECapableOS();

                // are we in IE9 or greater
                if (ieVer >= 9 || QueryStrings.forcePinnedBox) {

                    var localStorageIsPinned = (platform.ENABLE_PINNED_MODE || (SettingStorage.get("msIsSiteModeActivated") == "true")),
                        msIsSiteMode = (platform.ENABLE_PINNED_MODE === true);

                    // no way to check if this function exists, we have to use try/catch
                    if (!msIsSiteMode) {
                        try {
                            if (window.external.msIsSiteMode()) {
                                msIsSiteMode = true;
                            }
                        } catch (ex) {
                        }
                    }

                    // is the site pinned
                    if (localStorageIsPinned || msIsSiteMode || QueryStrings.forcePinnedBox) {

                        // show the pinned box with all levels unlocked
                        this.pinnedState = PinnedStates.VISIBLE;
                        this.opacity = 1.0;
                        this.promptId = null;

                        // show the first time after being pinned message and save the state
                        if (!localStorageIsPinned) {
                            SettingStorage.set("msIsSiteModeActivated", "true");
                            if (analytics.onSitePinned) {
                                analytics.onSitePinned();
                            }
                        }
                    }
                    else {
                        // we're in IE9 but not pinned so show instructions for pinning
                        this.pinnedState = PinnedStates.PROMPT_PIN;
                        this.opacity = 0.35;
                        this.promptId = "pinPrompt";

                        var self = this;

                        PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
                            Text.drawBig({
                                text: Lang.menuText(MenuStringId.SHOW_ME),
                                imgSel: '#showMeBtn img',
                                scaleToUI: true });
                        });
                    }
                }
                else if (ieCan) {
                    this.pinnedState = PinnedStates.PROMPT_IE;
                    this.opacity = 0.35;
                    this.promptId = "iePrompt";
                }
                else {
                    // we're not in incompatible OS so do nothing (keep the box hidden) and move the final box forward
                    this.pinnedState = PinnedStates.HIDDEN;
                    this.opacity = 0.35;
                    this.promptId = null;
                }

                // return a bool indicating whether the box should be added to the boxes collection
                if (this.pinnedState == PinnedStates.HIDDEN || this.pinnedState == PinnedStates.UNDEFINED) {
                    return false;
                }
                else {
                    return true;
                }
            },

            onSelected: function () {
                if (this.promptId != null) {
                    $("#pinningContent").stop(true, true).delay(100).fadeIn(800);
                    $("#" + this.promptId).show();
                }
            },

            onUnselected: function () {
                if (this.promptId != null) {
                    $("#pinningContent").stop(true, true).fadeOut(300);
                }
            },

            // runs (and the resets) the "show me" animation for the pinned box
            showMePinning: function () {
                var cursor = $("#pinCursor");
                var omnom = $("#pinOmNom");
                var shadow = $("#pinChairShadow");
                var button = $("#showMeBtn");
                var taskbar = $("#pinTaskBar");
                button.fadeOut().delay(5500).fadeIn(1000);
                shadow.delay(500).fadeOut().delay(6000).fadeIn(300);
                cursor.delay(500).fadeIn().delay(2250).animate({ 'left': resolution.uiScaledNumber(200) }, 500,
                    "easeInOutCirc").fadeOut().animate({ 'top': resolution.uiScaledNumber(65), 'left': resolution.uiScaledNumber(45), scale: '1.0' },
                    0);
                omnom.delay(500).fadeIn().delay(1000).animate({ 'top': resolution.uiScaledNumber(305), 'left': resolution.uiScaledNumber(165) },
                    1000, "easeInOutBack").delay(1500).animate({ scale: '0.65' },
                    200).delay(1500).fadeOut(1000).animate({ 'top': resolution.uiScaledNumber(115), 'left': resolution.uiScaledNumber(-49), scale: '1.0' },
                    50).fadeIn(500);
                taskbar.delay(500).fadeIn().delay(5000).fadeOut(1000);
            }
        });

        return PinnedBox;
    }
);

define('ui/PurchaseBox',
    [
        'ui/Box',
        'utils/PubSub',
        'resources/Lang',
        'visual/Text',
        'resources/MenuStringId',
        'resolution',
        'core/Alignment'
    ],
    function (Box, PubSub, Lang, Text, MenuStringId, resolution, Alignment) {

        // cache upgrade UI elements
        var $upgradePrompt, $upgradeButton;
        $(function() {
            $upgradePrompt = $('#boxUpgradePrompt').hide();
            $upgradeButton = $('#boxUpgradeButton').hide()
                .click(function() {
                    PubSub.publish(PubSub.ChannelId.PurchaseBoxesPrompt);
                });
        });

        // localize UI element text
        PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
            Text.drawBig({
                text: Lang.menuText(MenuStringId.UPGRADE_TO_FULL),
                imgParentId: 'boxUpgradePrompt',
                width: resolution.uiScaledNumber(650),
                alignment: Alignment.CENTER,
                scaleToUI: true
            });

            Text.drawBig({
                text: Lang.menuText(MenuStringId.BUY_FULL_GAME),
                imgParentId: 'boxUpgradeButton',
                scale: 0.6 * resolution.UI_TEXT_SCALE
            });
        });

        var PurchaseBox = Box.extend({
            init: function (boxIndex, bgimg, reqstars, islocked, type) {
                this._super(boxIndex, bgimg, reqstars, islocked, type);
                this.purchased = false;
                this.includeBoxNumberInTitle = false;
                this.isPurchaseBox = true;
            },

            isRequired: function () {
                // not a box required for game completion
                return false;
            },

            isGameBox: function() {
                return false;
            },

            onSelected: function () {
                $upgradePrompt.fadeIn();
                $upgradeButton.fadeIn();
            },

            onUnselected: function () {
                $upgradePrompt.fadeOut();
                $upgradeButton.fadeOut(200);
            }

        });

        return PurchaseBox;
    }
);

define('ui/MoreComingBox',
    [
        'ui/Box'
    ],
    function (Box) {

        var MoreComingBox = Box.extend({
            init: function (boxIndex, bgimg, reqstars, islocked, type) {
                this._super(boxIndex, bgimg, reqstars, islocked, type);
                this.includeBoxNumberInTitle = false;
            },

            isRequired: function () {
                // not a box required for game completion
                return false;
            },

            isGameBox: function() {
                return false;
            },

            isClickable: function() {
                return false;
            }

        });

        return MoreComingBox;
    }
);

define('ui/TimeBox',
    [
        'utils/Class',
        'ui/Box',
        'visual/Text',
        'resolution',
        'platform',
        'ui/BoxType',
        'utils/PubSub',
        'resources/Lang',
        'core/Alignment',
        'ui/ScoreManager',
        'resources/MenuStringId',
        'ui/QueryStrings',
        'edition',
        'utils/MathHelper',
        'core/SettingStorage'
    ],
    function (Class, Box, Text, resolution, platform, BoxType, PubSub, Lang, Alignment, ScoreManager,
        MenuStringId, QueryStrings, edition, MathHelper, SettingStorage) {

        // promotion runs from March 4 - April 14
        // using ticks makes finding hacking more difficult because
        // you can't search the code for well known dates
        var BoxOpenDates = [
            1362384000000,  // Mar 4
            1362985200000,  // Mar 11
            1363590000000,  // Mar 18
            1364194800000,  // Mar 25
            1364799600000,  // Apr 1
            1365404400000   // Apr 8
        ];

        // for testing the date locks
        if (true) {
            BoxOpenDates = [
                new Date(),  // open now
                new Date(),  // open now
                new Date(),  // open now
                1364194800000,  // Mar 25
                1364799600000,  // Apr 1
                1365404400000   // Apr 8
            ];
        };

        // The random seeds that will be XOR'd with the user's unique value
        // to create the keys used to store the status of each box unlock
        var BoxKeySeeds = [
            9240,
            7453,
            3646,
            7305,
            5093,
            3829
        ];

        var LOCK_KEY_PREFIX = String.fromCharCode(98, 107), // prefix is 'bk'
            XOR_VALUE = ScoreManager.getXorValue(),
            getLockKey = function(boxIndex) {
                return LOCK_KEY_PREFIX + (BoxKeySeeds[boxIndex] ^ XOR_VALUE);
            },
            isLocked = function(boxIndex) {
                var key = getLockKey(boxIndex),
                    value = SettingStorage.getIntOrDefault(key, 0),
                    correctValue = (BoxKeySeeds[boxIndex] - 1000) ^ XOR_VALUE;

                return (value !== correctValue && !QueryStrings.unlockAllBoxes);
            },
            unlockBox = function(boxIndex) {
                var key = getLockKey(boxIndex),
                    value = (BoxKeySeeds[boxIndex] - 1000) ^ XOR_VALUE;

                SettingStorage.set(key, value);
            };

        var $enterCodeButton = null;
        $(document).ready(function() {
            $enterCodeButton = $('#boxEnterCodeButton').hide();
        });

        // cache text images shared between boxes
        var availableTextImg = null,
            collectTextImg = null,
            toUnlockTextImg = null,
            bkCodeTextImg = null;

        var MonthNames = [ "January", "February", "March", "April", "May", "June",
            "July", "August", "September", "October", "November", "December" ];

        var TimeBox = Box.extend({

            init: function (boxIndex, bgimg, reqstars, islocked, type) {
                this._super(boxIndex, bgimg, reqstars, islocked, type);
                this.lockedBoxImg = new Image();
                this.lockedBoxImg.src = this.boxImg.src.replace('.png', '_locked.png');
                this.isBkCodeLocked = isLocked(boxIndex);
                this.isTimeLocked = (QueryStrings.unlockAllBoxes !== true) &&
                    (Date.now() < BoxOpenDates[boxIndex]);
                this.dateImg = null;
            },

            isClickable: function() {
                return (!this.isTimeLocked && !this.isBkCodeLocked);
            },

            onSelected: function () {
                if (!this.isTimeLocked && this.isBkCodeLocked) {
                    $enterCodeButton.fadeIn();
                }
            },

            onUnselected: function () {
                $enterCodeButton.hide();
            },

            render: function (ctx, omnomoffset) {

                var locked = this.islocked || this.isTimeLocked || this.isBkCodeLocked;

                // draw the base box image
                ctx.drawImage(
                    locked ? this.lockedBoxImg : this.boxImg,
                    resolution.uiScaledNumber(25),
                    resolution.uiScaledNumber(0));

                if (this.isTimeLocked) {

                    // draw label above date
                    if (!availableTextImg) {
                        availableTextImg = new Image();
                        Text.drawBig({
                            text: 'Available starting from',
                            img: availableTextImg,
                            alignment: Alignment.HCENTER,
                            width: resolution.uiScaledNumber(250)
                        });
                    }
                    if (availableTextImg.complete) {
                        ctx.drawImage(
                            availableTextImg,
                            resolution.uiScaledNumber(100),
                            resolution.uiScaledNumber(120),
                            availableTextImg.width * 0.8 * resolution.UI_TEXT_SCALE,
                            availableTextImg.height * 0.8 * resolution.UI_TEXT_SCALE);
                    }

                    // draw date the box will open
                    if (!this.dateImg) {
                        this.dateImg = new Image();
                        var openDate = new Date(BoxOpenDates[this.index]);
                        Text.drawBig({
                            text: MonthNames[openDate.getMonth()] + ' ' + openDate.getDate(),
                            img: this.dateImg,
                            width: resolution.uiScaledNumber(200),
                            alignment: Alignment.HCENTER
                        });
                    }
                    if (this.dateImg.complete) {
                        ctx.drawImage(
                            this.dateImg,
                            resolution.uiScaledNumber(77),
                            resolution.uiScaledNumber(195),
                            this.dateImg.width * 1.2 * resolution.UI_TEXT_SCALE,
                            this.dateImg.height * 1.2 * resolution.UI_TEXT_SCALE);
                    }

                } else if (this.isBkCodeLocked) {

                    // text label for "Collect"
                    if (!bkCodeTextImg) {
                        bkCodeTextImg = new Image();
                        Text.drawBig({
                            text: 'Visit Burger King to get an\n unlock code!',
                            img: bkCodeTextImg,
                            alignment: Alignment.HCENTER,
                            width: resolution.uiScaledNumber(280)
                        });

                        Text.drawBig({
                            text: 'Enter Code',
                            imgParentId: 'boxEnterCodeButton',
                            scaleToUI: true
                        });
                    }

                    if (bkCodeTextImg.complete) {
                        ctx.drawImage(
                            bkCodeTextImg,
                            resolution.uiScaledNumber(50),
                            resolution.uiScaledNumber(90));
                    }

                } else if (this.islocked) {

                    // text label for "Collect"
                    if (!collectTextImg) {
                        collectTextImg = new Image();
                        Text.drawBig({
                            text: 'Collect',
                            img: collectTextImg,
                            scaleToUI: true
                        });
                    }
                    if (collectTextImg.complete) {
                        ctx.drawImage(
                            collectTextImg,
                            resolution.uiScaledNumber(143),
                            resolution.uiScaledNumber(108));
                    }

                    // prefer css dimensions (scaled) for text
                    var textWidth = ($(this.reqImg).width() || this.reqImg.width) * 1.2,
                        textHeight = ($(this.reqImg).height() || this.reqImg.height) * 1.2,

                        // ok to use raw image width for star (image already scaled)
                        starWidth = this.starImg.width || $(this.starImg).width(),
                        starMargin = resolution.uiScaledNumber(-4),

                        // center the text and star label
                        labelWidth = textWidth + starMargin + starWidth,
                        labelMaxWidth = resolution.uiScaledNumber(125),
                        labelOffsetX = (labelMaxWidth - labelWidth) / 2,
                        labelMinX = resolution.uiScaledNumber(140),
                        labelX = labelMinX + labelOffsetX;

                    ctx.drawImage(this.starImg, labelX,
                        resolution.uiScaledNumber(160));
                    ctx.drawImage(this.reqImg,
                        labelX + starWidth,
                        resolution.uiScaledNumber(150),
                        textWidth, textHeight );


                    // text label for "to unlock"
                    if (!toUnlockTextImg) {
                        toUnlockTextImg = new Image();
                        Text.drawBig({
                            text: 'to unlock',
                            img: toUnlockTextImg,
                            scaleToUI: true
                        });
                    }
                    if (toUnlockTextImg.complete) {
                        ctx.drawImage(
                            toUnlockTextImg,
                            resolution.uiScaledNumber(130),
                            resolution.uiScaledNumber(204));
                    }
                }
            }
        });

        TimeBox.unlockBox = unlockBox;
        TimeBox.isLocked = isLocked;

        return TimeBox;
    }
);
define('ui/PanelId',[], function () {
    var PanelId = {
        MENU: 0,
        BOXES: 1,
        LEVELS: 2,
        GAME: 3,
        GAMEMENU: 4,
        LEVELCOMPLETE: 5,
        GAMECOMPLETE: 6,
        OPTIONS: 7,
        CREDITS: 8,
        LEADERBOARDS: 9,
        ACHIEVEMENTS: 10,
        PASSWORD: 11
    };

    return PanelId;
});

define('ui/Panel',
    [], function () {

        function Panel(id, paneldivid, bgdivid, showshadow) {
            this.id = id;
            this.panelDivId = paneldivid;
            this.bgDivId = bgdivid;
            this.showShadow = showshadow;
        }

        return Panel;
    }
);
define('utils/PointerCapture',
    [],
    function () {

        var singleTouch = false;

        function PointerCapture(settings) {

            this.el = settings.element;
            this.getZoom = settings.getZoom;

            var self = this;

            // save references to the event handlers so they can be removed
            this.startHandler = function (event) {
                self.preventPanning(event);

                if (singleTouch === false) {
                    singleTouch = event.changedTouches && event.changedTouches[0].identifier;
                } else {
                    return false;
                }
                
                if (settings.onStart)
                    return self.translatePosition(event, settings.onStart);
                else
                    return false; // not handled
            };
            this.moveHandler = function (event) {
                self.preventPanning(event);

                if (event.changedTouches && event.changedTouches[0].identifier !== singleTouch)
                    return false;
                
                if (settings.onMove)
                    return self.translatePosition(event, settings.onMove);
                else
                    return false; // not handled
            };
            this.endHandler = function (event) {
                self.preventPanning(event);
                singleTouch = false;

                if (settings.onEnd)
                    return self.translatePosition(event, settings.onEnd);
                else
                    return false; // not handled
            };
            this.outHandler = function (event) {
                if (settings.onOut)
                    return self.translatePosition(event, settings.onOut);
                else
                    return false; // not handled
            };
        }

        // translates from page relative to element relative position
        PointerCapture.prototype.translatePosition = function (event, callback) {
            // get the mouse coordinate relative to the page
            // http://www.quirksmode.org/js/events_properties.html
            var posx = 0,
                posy = 0;
            if (!event) {
                event = window.event;
            }

            if (event.changedTouches && event.changedTouches.length > 0) {
                // iOS removes touches from targetTouches on touchend so we use
                // changedTouches when it's available
                posx = event.changedTouches[0].pageX;
                posy = event.changedTouches[0].pageY;
            }
            else if (event.targetTouches && event.targetTouches.length > 0) {
                posx = event.targetTouches[0].pageX;
                posy = event.targetTouches[0].pageY;
            }
            else if (event.pageX || event.pageY) {
                posx = event.pageX;
                posy = event.pageY;
            }
            else if (event.clientX || event.clientY) {
                posx = event.clientX + document.body.scrollLeft
                    + document.documentElement.scrollLeft;
                posy = event.clientY + document.body.scrollTop
                    + document.documentElement.scrollTop;
            }

            var offset = $(this.el).offset(), // get mouse coordinates relative to the element
                zoom = this.getZoom ? this.getZoom() : 1, // adjust coordinates if the game is zoomed
                mouseX = Math.round((posx - offset.left) / zoom),
                mouseY = Math.round((posy - offset.top) / zoom);

            return callback(mouseX, mouseY);
        };

        // prevent touches from panning the page
        PointerCapture.prototype.preventPanning = function (event) {
            if (event['preventManipulation']) {
                event['preventManipulation']();
            }
            else {
                event.preventDefault();
            }
        };

        PointerCapture.prototype.activate = function () {
            this.el.addEventListener(PointerCapture.startEventName, this.startHandler, false);
            this.el.addEventListener(PointerCapture.moveEventName, this.moveHandler, false);
            this.el.addEventListener(PointerCapture.endEventName, this.endHandler, false);
            this.el.addEventListener(PointerCapture.outEventName, this.outHandler, false);
        };

        PointerCapture.prototype.deactivate = function () {
            this.el.removeEventListener(PointerCapture.startEventName, this.startHandler, false);
            this.el.removeEventListener(PointerCapture.moveEventName, this.moveHandler, false);
            this.el.removeEventListener(PointerCapture.endEventName, this.endHandler, false);
            this.el.removeEventListener(PointerCapture.outEventName, this.outHandler, false);
        };

        // IE10 has pointer events that capture mouse, pen, and touch
        PointerCapture.useMSPointerEvents = window.navigator['msPointerEnabled'];

        // We are not using Modernizr in win8, but sometimes we debug in other browsers
        PointerCapture.useTouchEvents = (typeof Modernizr !== 'undefined' && Modernizr.touch);

        // cache the correct event names to use
        PointerCapture.startEventName =
            PointerCapture.useMSPointerEvents ? 'MSPointerDown' :
                PointerCapture.useTouchEvents ? 'touchstart' : 'mousedown';
        PointerCapture.moveEventName =
            PointerCapture.useMSPointerEvents ? 'MSPointerMove' :
                PointerCapture.useTouchEvents ? 'touchmove' : 'mousemove';
        PointerCapture.endEventName =
            PointerCapture.useMSPointerEvents ? 'MSPointerUp' :
                PointerCapture.useTouchEvents ? 'touchend' : 'mouseup';

        // Unfortunately there is no touchleave event
        PointerCapture.outEventName =
            PointerCapture.useMSPointerEvents ? 'MSPointerOut' : 'mouseout';

        return PointerCapture;
    }
);




define('config/resolutions/ZoomManager',
    [],
    function () {

        function ZoomManager() {

            // cache the target element
            this.$el = null;

            // no zoom by default
            this.zoom = GLOBAL_ZOOM;

            this.transformOrigin = 'top left';

            this.setElementId = function (elementId) {
                this.$el = $('#' + elementId);
            };

            this.setElement = function (element) {
                this.$el = $(element);
            };

            this.updateCss = function (css) {

                css = css || {};

                var cssScale = 'scale(' + this.zoom + ')',
                    prefixes = ['ms', 'o', 'webkit', 'moz'],
                    transformOrigin = this.transformOrigin,
                    i, len, key;

                // clear values if no zoom is required
                if (this.zoom === 1) {
                    cssScale = transformOrigin = '';
                }

                // set the transform scale and origin for each browser prefix
                for (i = 0, len = prefixes.length; i < len; i++) {
                    key = '-' + prefixes[i] + '-transform';
                    css[key] = cssScale;
                    css[key + '-origin'] = transformOrigin;
                }

                this.$el.css(css);
            };

            this.getCanvasZoom = function() {
                return this.zoom || 1;
            };

            this.getUIZoom = function() {
                return this.zoom || 1;
            };

            // begin monitoring the window for dimension changes
            this.autoResize = function () {
                var self = this;
                $(window).resize(function () {
                    self.resize();
                });
                this.resize();
            };

            this.nativeWidth = 0;
            this.nativeHeight = 0;

            var originalHeight = 270;

            this.resize = function (skipZoom) {
                // get the viewport and canvas dimensions
                var $w = $(window),
                    vpWidth = $w.width(),
                    vpHeight = $w.height(),
                    canvasWidth = this.nativeWidth,
                    canvasHeight = this.nativeHeight;

                // choose smallest zoom factor that will maximize one side
                if (!skipZoom) {
                    this.zoom = Math.min(vpWidth / canvasWidth, vpHeight / canvasHeight);
                }

                this.bgZoom = vpHeight / (originalHeight * this.zoom);
                $(".coverBg").css({
                    "-webkit-transform": "scale(" + this.bgZoom + ")",
                    "-moz-transform": "scale(" + this.bgZoom + ")"
                });

                $(".scaleBg").css({
                    "-webkit-transform": "scaleY(" + this.bgZoom + ")",
                    "-moz-transform": "scaleY(" + this.bgZoom + ")"
                });

                // center the game by setting the margin (using auto in css doesn't
                // work because of zoom). Note: there are differences in how margin
                // is applied. IE doesn't consider margin as part of the zoomed element
                // while Chrome does. We can safely ignore this for now as full screen is
                // only necessary in win8 (IE10).
                var marginLeft = Math.round((vpWidth - (canvasWidth * this.zoom)) / 2),
                    marginTop = Math.round((vpHeight - (canvasHeight * this.zoom)) / 2);

                this.updateCss({
                    'margin-top': marginTop,
                    'margin-left': marginLeft
                });
            };
        }

        // we only need a singleton instance
        return new ZoomManager();
    }
);
define('ZoomManager',
    [
        'config/resolutions/ZoomManager',
        'resolution'
    ],
    function (ZoomManager, resolution) {

        ZoomManager.domReady = function () {

            // no scaling, just center the game
            this.setElementId('gameContainer');

            this.nativeWidth = resolution.UI_WIDTH;
            this.nativeHeight = resolution.UI_HEIGHT;
            this.resize(true /* skipZoom */);
        };

        return ZoomManager;
    }
);

define('game/CTRSoundMgr',
    [
        'game/CTRSettings',
        'utils/Log',
        'resources/ResourceId',
        'resources/Sounds'
    ],
    function (settings, Log, ResourceId, Sounds) {
        var SoundMgr = {

            audioPaused: false,
            soundEnabled: settings.getSoundEnabled(),
            musicEnabled: settings.getMusicEnabled(),
            musicId: null, // background music
            playSound: function (soundId) {
                if (this.soundEnabled) {
                    Sounds.play(soundId);
                }
            },
            pauseSound: function (soundId) {
                if (this.soundEnabled && Sounds.isPlaying(soundId)) {
                    Sounds.pause(soundId);
                }
            },
            resumeSound: function (soundId) {
                if (this.soundEnabled && Sounds.isPaused(soundId)) {
                    Sounds.play(soundId);
                }
            },
            playLoopedSound: function (soundId) {
                var self = this;
                if (this.soundEnabled && !Sounds.isPlaying(soundId)) {
                    Sounds.play(soundId, function () {
                        if (!self.audioPaused && self.soundEnabled) {
                            self.playLoopedSound(soundId);
                        }
                    });
                }
            },
            stopSound: function (soundId) {
                Sounds.stop(soundId);
            },
            playMusic: function (soundId) {

                // stop the existing music if different
                if (this.musicId && this.musicId !== soundId) {
                    this.stopMusic(soundId);
                }

                var self = this;
                if (this.musicEnabled && !Sounds.isPlaying(soundId)) {
                    this.musicId = soundId;
                    Sounds.setVolume(soundId, 70);
                    Sounds.play(soundId, function () {
                        if (!self.audioPaused && self.musicEnabled) {
                            self.playMusic(soundId);
                        }
                    });
                }
            },
            pauseAudio: function () {

                //console.log('Paused audio');

                this.audioPaused = true;
                this.pauseMusic();

                // electro is the only looped sound effect for now
                this.pauseSound(ResourceId.SND_ELECTRIC);
            },
            pauseMusic: function () {
                if (this.musicId) {
                    Sounds.pause(this.musicId);
                }
            },
            resumeAudio: function () {
                //console.log('Resumed audio', this.audioPaused);
                if (!this.audioPaused) return;

                this.audioPaused = false;
                this.resumeMusic();
                this.resumeSound(ResourceId.SND_ELECTRIC);
            },
            resumeMusic: function () {
                if (this.musicId) {
                    this.playMusic(this.musicId);
                }
            },
            stopMusic: function () {
                if (this.musicId) {
                    // stop any currently playing background music
                    Sounds.stop(this.musicId);
                }
            },
            setMusicEnabled: function (musicEnabled) {
                this.musicEnabled = musicEnabled;
                settings.setMusicEnabled(musicEnabled);
                if (this.musicEnabled) {
                    this.resumeMusic();
                }
                else {
                    this.pauseMusic();
                }
            },
            setSoundEnabled: function (soundEnabled) {
                this.soundEnabled = soundEnabled;
                settings.setSoundEnabled(soundEnabled);
            }
        };

        return SoundMgr;
    }
);

define('core/ViewController',
    [
        'utils/Class',
        'utils/Constants',
        'utils/PubSub'
    ],
    function (Class, Constants, PubSub) {

        // COMMENTS from iOS sources:
        // controller philosophy
        // - there's a root controller which is notified about every controller state change
        // - only one controller runs (invokes 'update') at a time
        // - controller can control several views (or none)
        // - controller can have childs, when controller's child is active, controller itself is paused
        // - child controller notifies parent after deactivation

        //noinspection JSUnusedLocalSymbols
        /**
         * @constructor
         */
        var ViewController = Class.extend({
            init: function (parent) {
                this.controllerState = ViewController.StateType.INACTIVE;
                this.views = [];
                this.children = [];
                this.activeViewID = Constants.UNDEFINED;
                this.activeChildID = Constants.UNDEFINED;
                this.pausedViewID = Constants.UNDEFINED;
                this.parent = parent;
                this.lastTime = Constants.UNDEFINED;
                this.delta = 0;
                this.frames = 0;
                this.accumDt = 0;
                this.frameRate = 0;

                // like a bank account for frame updates. we try to keep our
                // balance under 1 by doing extra frame updates when above 1
                this.frameBalance = 0;

                // initially assume we are getting 60 fps
                this.avgDelta = 1 / 60;

                // keep the last five deltas (init with target fps)
                this.pastDeltas = [
                    this.avgDelta,
                    this.avgDelta,
                    this.avgDelta,
                    this.avgDelta,
                    this.avgDelta
                ];
            },
            activate: function () {
                //Debug.log('View controller activated');
                this.controllerState = ViewController.StateType.ACTIVE;
                PubSub.publish(PubSub.ChannelId.ControllerActivated, this);
            },
            deactivate: function () {
                PubSub.publish(PubSub.ChannelId.ControllerDeactivateRequested, this);
            },
            deactivateImmediately: function () {
                this.controllerState = ViewController.StateType.INACTIVE;
                if (this.activeViewID !== Constants.UNDEFINED) {
                    this.hideActiveView();
                }
                // notify root and parent controllers
                PubSub.publish(PubSub.ChannelId.ControllerDeactivate, this);
                this.parent.onChildDeactivated(this.parent.activeChildID);
            },
            pause: function () {
                this.controllerState = ViewController.StateType.PAUSED;
                PubSub.publish(PubSub.ChannelId.ControllerPaused, this);

                if (this.activeViewID != Constants.UNDEFINED) {
                    this.pausedViewID = this.activeViewID;
                    this.hideActiveView();
                }
            },
            unpause: function () {
                this.controllerState = ViewController.StateType.ACTIVE;
                if (this.activeChildID !== Constants.UNDEFINED) {
                    this.activeChildID = Constants.UNDEFINED;
                }

                PubSub.publish(PubSub.ChannelId.ControllerUnpaused, this);

                if (this.pausedViewID !== Constants.UNDEFINED) {
                    this.showView(this.pausedViewID);
                }
            },
            update: function () {
                if (this.activeViewID === Constants.UNDEFINED) {
                    return;
                }

                var v = this.activeView();

                // the physics engine needs to be updated at 60fps. we
                // will do up to 3 updates for each frame that is
                // actually rendered. This means we could run as low as 20 fps
                var maxUpdates = Math.min(3, this.frameBalance | 0);
                for (var i = 0; i < maxUpdates; i++) {
                    v.update(0.016);
                    this.frameBalance -= 1;
                }
            },
            resetLastTime: function () {
                this.lastTime = Constants.UNDEFINED;
            },
            calculateTimeDelta: function (time) {
                this.delta = (this.lastTime !== Constants.UNDEFINED)
                    ? (time - this.lastTime) / 1000
                    : 0;
                this.lastTime = time;

                // if the physics engine requires 60 fps, how many frames do
                // we need to update?
                this.frameBalance += this.clampDelta(this.delta) / 0.016;
            },
            /**
             * Make sure a delta doesn't exceed some reasonable bounds
             * Delta changes might be large if we are using requestAnimationFrame
             * and the user switches tabs (the browser will stop calling us to
             * preserve power).
             * @param delta {number}
             */
            clampDelta: function (delta) {
                if (delta < 0.016) {
                    // sometimes we'll get a bunch of frames batched together
                    // but we don't want to go below the 60 fps delta
                    return 0.016;
                }
                else if (delta > 0.05) {
                    // dont go below the delta for 20 fps
                    return 0.05;
                }
                return delta;
            },
            calculateFPS: function () {
                this.frames++;
                this.accumDt += this.delta;

                // update the frame rate every second
                if (this.accumDt > 1) {
                    this.frameRate = this.frames / this.accumDt;
                    this.frames = 0;
                    this.accumDt = 0;

                    // advance the queue of past deltas
                    this.pastDeltas.shift();
                    this.pastDeltas.push(this.clampDelta(1 / this.frameRate));

                    // we use a running average to prevent drastic changes
                    this.avgDelta = 0;
                    for (var i = 0, len = this.pastDeltas.length; i < len; i++) {
                        this.avgDelta += this.pastDeltas[i];
                    }
                    this.avgDelta /= len;
                }
            },
            addView: function (v, index) {
                this.views[index] = v;
            },
            deleteView: function (viewIndex) {
                this.views[viewIndex] = null;
            },
            hideActiveView: function () {
                var previousView = this.views[this.activeViewID];
                if (previousView) {
                    PubSub.publish(PubSub.ChannelId.ControllerViewHidden, previousView);
                    previousView.hide();
                    this.activeViewID = Constants.UNDEFINED;
                }
            },
            showView: function (index) {
                if (this.activeViewID != Constants.UNDEFINED) {
                    this.hideActiveView();
                }
                this.activeViewID = index;
                var v = this.views[index];
                PubSub.publish(PubSub.ChannelId.ControllerViewShow, v);
                v.show();
            },
            activeView: function () {
                return this.views[this.activeViewID];
            },
            getView: function (index) {
                return this.views[index];
            },
            addChildWithID: function (controller, index) {
                this.children[index] = controller;
            },
            deleteChild: function (index) {
                this.children[index] = null;
                if (this.activeChildID === index) {
                    this.activeChildID = Constants.UNDEFINED;
                }
            },
            deactivateActiveChild: function () {
                if (this.activeChildID !== Constants.UNDEFINED) {
                    var prevController = this.children[this.activeChildID];
                    if (prevController) {
                        prevController.deactivate();
                    }
                    this.activeChildID = Constants.UNDEFINED;
                }
            },
            activateChild: function (index) {
                if (this.activeChildID !== Constants.UNDEFINED) {
                    this.deactivateActiveChild();
                }

                this.pause();
                this.activeChildID = index;
                this.children[index].activate();
            },
            onChildDeactivated: function (childType) {
                this.unpause();
            },
            activeChild: function () {
                return this.children[this.activeChildID];
            },
            getChild: function (index) {
                return this.children[index];
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseDown: function (x, y) {
                if (this.activeViewID === Constants.UNDEFINED) {
                    return false;
                }
                return this.views[this.activeViewID].onTouchDown(x, y);
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseUp: function (x, y) {
                if (this.activeViewID === Constants.UNDEFINED) {
                    return false;
                }
                return  this.views[this.activeViewID].onTouchUp(x, y);
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseDragged: function (x, y) {
                if (this.activeViewID === Constants.UNDEFINED) {
                    return false;
                }
                return  this.views[this.activeViewID].onTouchMove(x, y);
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseMoved: function (x, y) {
                // only drag events are used
                return false;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            doubleClick: function (x, y) {
                if (this.activeViewID === Constants.UNDEFINED) {
                    return false;
                }
                return  this.views[this.activeViewID].onDoubleClick(x, y);
            }
        });

        /**
         * @enum {number}
         */
        ViewController.StateType = {
            INACTIVE: 0,
            ACTIVE: 1,
            PAUSED: 2
        };

        return ViewController;
    }
);
define('core/RootControllerBase',
    [
        'core/ViewController',
        'utils/PointerCapture',
        'ZoomManager',
        'utils/Constants',
        'game/CTRSettings',
        'resolution',
        'utils/PubSub',
        'utils/Canvas',
        'core/RGBAColor'
    ],
    function (ViewController, PointerCapture, ZoomManager, Constants, settings, resolution, PubSub, Canvas, RGBAColor) {
        /**
         * @const
         * @type {number}
         */
        var TRANSITION_DEFAULT_DELAY = 0.3;

        /**
         * @enum {number}
         */
        var ViewTransition = {
            SLIDE_HORIZONTAL_RIGHT: 0,
            SLIDE_HORIZONTAL_LEFT: 1,
            SLIDE_VERTICAL_UP: 2,
            SLIDE_VERTICAL_DOWN: 3,
            FADE_OUT_BLACK: 4,
            FADE_OUT_WHITE: 5,
            REVEAL: 6,
            COUNT: 7
        };

        var RootController = ViewController.extend({
            init: function (parent) {
                this._super(parent);
                this.suspended = false;
                this.currentController = null;
                this.viewTransition = Constants.UNDEFINED;
                this.transitionTime = Constants.UNDEFINED;
                this.previousView = null;
                this.transitionDelay = TRANSITION_DEFAULT_DELAY;
                this.deactivateCurrentController = false;

                // when the user holds down the mouse button while moving the mouse
                this.dragMode = false;

                PubSub.subscribe(PubSub.ChannelId.ControllerActivated, $.proxy(this.onControllerActivated, this));
                PubSub.subscribe(PubSub.ChannelId.ControllerDeactivateRequested,
                    $.proxy(this.onControllerDeactivationRequest, this));
                PubSub.subscribe(PubSub.ChannelId.ControllerDeactivated, $.proxy(this.onControllerDeactivated, this));
                PubSub.subscribe(PubSub.ChannelId.ControllerPaused, $.proxy(this.onControllerPaused, this));
                PubSub.subscribe(PubSub.ChannelId.ControllerUnpaused, $.proxy(this.onControllerUnpaused, this));
                PubSub.subscribe(PubSub.ChannelId.ControllerViewHidden, $.proxy(this.onControllerViewHide, this));
                PubSub.subscribe(PubSub.ChannelId.ControllerViewShow, $.proxy(this.onControllerViewShow, this));
            },

            operateCurrentMVC: function (time) {
                if (this.suspended || this.currentController === null) {
                    return;
                }

                // pass control to the active controller
                this.currentController.calculateTimeDelta(time);
                if (this.transitionTime === Constants.UNDEFINED) {
                    this.currentController.update();
                }

                if (this.deactivateCurrentController) {
                    this.deactivateCurrentController = false;
                    this.currentController.deactivateImmediately();
                }

                // draw the active view
                if (this.currentController.activeViewID !== Constants.UNDEFINED) {

                    var activeView = this.currentController.activeView();
                    if (activeView) {
                        activeView.draw();
                    }

                    // always calc fps because we use the avg to adjust delta at runtime
                    this.currentController.calculateFPS();

                    // draw the fps meter
                    if (settings.fpsEnabled) {
                        // make sure we have one cycle of measurements
                        var frameRate = this.currentController.frameRate.toFixed(0);
                        if (frameRate > 0) {
                            // draw the fps frame rate
                            var ctx = Canvas.context;
                            ctx.font = "20px Arial";
                            ctx.fillStyle = RGBAColor.styles.SOLID_OPAQUE;
                            ctx.fillText(frameRate + ' fps', 10, resolution.CANVAS_HEIGHT - 10);
                        }
                    }
                }
            },
            activateMouseEvents: function () {
                // ensure the pointer capture helper has been created
                if (!this.pointerCapture) {
                    this.pointerCapture = new PointerCapture({
                        element: Canvas.element,
                        onStart: $.proxy(this.mouseDown, this),
                        onMove: $.proxy(this.mouseMove, this),
                        onEnd: $.proxy(this.mouseUp, this),
                        onOut: $.proxy(this.mouseOut, this),
                        getZoom: function() {
                            return ZoomManager.getCanvasZoom();
                        }
                    });
                }

                this.pointerCapture.activate();
            },
            deactivateMouseEvents: function () {
                if (this.pointerCapture) {
                    this.pointerCapture.deactivate();
                }
            },
            activate: function () {
                this._super();
                this.activateMouseEvents();

                // called to render a frame
                var self = this,
                    requestAnimationFrame = window['requestAnimationFrame'],
                    animationLoop = function () {
                        var now = Date.now();
                        self.operateCurrentMVC(now);
                        if (!self.stopAnimation) {
                            requestAnimationFrame(animationLoop);
                        }
                    };

                // start the animation loop
                this.stopAnimation = false;
                animationLoop();
            },
            deactivate: function () {
                this._super();

                // set flag to stop animation
                this.stopAnimation = true;

                // remove mouse events
                this.deactivateMouseEvents();
            },

            setCurrentController: function (controller) {
                this.currentController = controller;
                this.currentController.idealDelta = 1 / 60;
            },
            getCurrentController: function () {
                return this.currentController;
            },
            onControllerActivated: function (controller) {
                this.setCurrentController(controller);
            },
            onControllerDeactivated: function (controller) {
                this.currentController = null;
            },
            onControllerPaused: function (controller) {
                this.currentController = null;
            },
            onControllerUnpaused: function (controller) {
                this.setCurrentController(controller);
            },
            onControllerDeactivationRequest: function (controller) {
                this.deactivateCurrentController = true;
            },
            onControllerViewShow: function (view) {
                if (this.viewTransition !== Constants.UNDEFINED && this.previousView != null) {
                    this.currentController.calculateTimeDelta();
                    this.transitionTime = this.currentController.lastTime + this.transitionDelay;
                    var activeView = this.currentController.activeView();
                    if (activeView) {
                        activeView.draw();
                    }
                }
            },
            onControllerViewHide: function (view) {
                this.previousView = view;
                if (this.viewTransition !== Constants.UNDEFINED && this.previousView != null) {
                    this.previousView.draw();
                }
            },
            isSuspended: function () {
                return this.suspended;
            },
            suspend: function () {
                this.suspended = true;
            },
            resume: function () {
                if (this.currentController) {
                    this.currentController.resetLastTime();
                }
                this.suspended = false;
            },
            mouseDown: function (x, y) {
                if (this.currentController && this.currentController != this) {
                    //Log.debug('mouse down at:' + x + ',' + y + ' drag mode was:' + this.dragMode);
                    this.dragMode = true;
                    return this.currentController.mouseDown(x, y);
                }
                return false;
            },
            mouseMove: function (x, y) {
                if (this.currentController && this.currentController != this) {
                    if (this.dragMode) {
                        this.currentController.mouseDragged(x, y);
                    }

                    // fire moved event even if drag event was also fired
                    return this.currentController.mouseMoved(x, y);
                }
                return false;
            },
            mouseUp: function (x, y) {
                if (this.currentController && this.currentController != this) {
                    //Log.debug('mouse up at:' + x + ',' + y + ' drag mode was:' + this.dragMode);
                    var handled = this.currentController.mouseUp(x, y);
                    this.dragMode = false;
                    return handled;
                }
                return false;
            },
            mouseOut: function (x, y) {
                if (this.currentController && this.currentController != this) {
                    // if the mouse leaves the canvas while down, trigger the mouseup
                    // event because we won't get it if the user lets go outside
                    if (this.dragMode) {
                        //Log.debug('mouse out at:' + x + ',' + y);
                        var handled = this.currentController.mouseUp(x, y);
                        this.dragMode = false;
                        return handled;
                    }
                }
                return false;
            },
            doubleClick: function (x, y) {
                if (this.currentController && this.currentController != this) {
                    //Log.debug('double click at:' + x + ',' + y + ' drag mode was:' + this.dragMode);
                    this.currentController.mouseUp(x, y);
                    this.dragMode = false;
                    return this.currentController.doubleClick(x, y);
                }
                return false;
            }
        });

        return RootController;
    }
);

define('visual/Animation',
    [
        'visual/ImageElement',
        'core/Quad2D',
        'visual/TrackType',
        'visual/Timeline',
        'visual/Action',
        'visual/ActionType',
        'visual/KeyFrame',
        'utils/Constants'
    ],
    function (ImageElement, Quad2D, TrackType, Timeline, Action, ActionType, KeyFrame, Constants) {

        /**
         * Animation element based on timeline
         */
        var Animation = ImageElement.extend({
            init: function () {
                this._super();
            },
            /**
             * @param delay {number}
             * @param loop {number}
             * @param start {number}
             * @param end {number}
             * @return {number}
             */
            addAnimationDelay: function (delay, loop, start, end) {
                var index = this.timelines.length;
                this.addAnimationEndpoints(index, delay, loop, start, end);
                return index;
            },
            addAnimationWithDelay: function (delay, loopType, count, sequence) {
                var index = this.timelines.length;
                this.addAnimationSequence(index, delay, loopType, count, sequence);
            },
            addAnimationSequence: function (animationId, delay, loopType, count, sequence) {
                this.addAnimation(animationId, delay, loopType, count, sequence[0], Constants.UNDEFINED, sequence);
            },
            addAnimationEndpoints: function (animationId, delay, loopType, start, end, argumentList) {
                var count = end - start + 1;
                this.addAnimation(animationId, delay, loopType, count, start, end, argumentList);
            },
            /**
             * @param animationId {number}
             * @param delay {number}
             * @param loopType
             * @param count {number}
             * @param start {number}
             * @param end {number}
             * @param argumentList
             */
            addAnimation: function (animationId, delay, loopType, count, start, end, argumentList) {
                var t = new Timeline(),
                    as = [ Action.create(this, ActionType.SET_DRAWQUAD, start, 0) ];

                t.addKeyFrame(KeyFrame.makeAction(as, 0));

                var si = start;
                for (var i = 1; i < count; i++) {
                    if (argumentList) {
                        si = argumentList[i];
                    }
                    else {
                        si++;
                    }

                    as = [ Action.create(this, ActionType.SET_DRAWQUAD, si, 0) ];
                    t.addKeyFrame(KeyFrame.makeAction(as, delay));

                    if (i == count - 1 && loopType === Timeline.StateType.REPLAY) {
                        t.addKeyFrame(KeyFrame.makeAction(as, delay));
                    }
                }

                if (loopType) {
                    t.loopType = loopType;
                }

                this.addTimelineWithID(t, animationId);
            },
            setDelay: function (delay, index, animationId) {
                var timeline = this.getTimeline(animationId),
                    track = timeline.getTrack(TrackType.ACTION),
                    kf = track.keyFrames[index];
                kf.timeOffset = delay;
            },
            setPause: function (index, animationId) {
                this.setAction(ActionType.PAUSE_TIMELINE, this, 0, 0, index, animationId);
            },
            setAction: function (actionName, target, param, subParam, index, animationId) {
                var timeline = this.getTimeline(animationId),
                    track = timeline.getTrack(TrackType.ACTION),
                    kf = track.keyFrames[index],
                    action = Action.create(target, actionName, param, subParam);

                kf.value.actionSet.push(action);
            },
            switchToAnimation: function (a2, a1, delay) {
                var timeline = this.getTimeline(a1),
                    as = [ Action.create(this, ActionType.PLAY_TIMELINE, 0, a2)],
                    kf = KeyFrame.makeAction(as, delay);
                timeline.addKeyFrame(kf);
            },
            /**
             * Go to the specified sequence frame of the current animation
             * @param index {number}
             */
            jumpTo: function (index) {
                var timeline = this.currentTimeline;
                timeline.jumpToTrack(TrackType.ACTION, index);
            }


        });

        return Animation;
    }
);
define('utils/Mover',
    [
        'utils/Class',
        'utils/MathHelper',
        'core/Vector'
    ],
    function (Class, MathHelper, Vector) {

        var Mover = Class.extend({
            init: function (pathCapacity, moveSpeed, rotateSpeed) {
                this.pathCapacity = pathCapacity;
                this.rotateSpeed = rotateSpeed || 0;
                this.path = [];
                if (pathCapacity > 0) {
                    this.moveSpeed = new Array(pathCapacity);
                    for (var i = 0; i < pathCapacity; i++) {
                        this.moveSpeed[i] = moveSpeed || 0;
                    }
                }
                this.pos = new Vector(0, 0);
                this.angle = 0;
                this.paused = false;
                this.reverse = false;
                this.overrun = 0;
            },
            setMoveSpeed: function (speed) {
                for (var i = 0, len = this.pathCapacity; i < len; i++) {
                    this.moveSpeed[i] = speed;
                }
            },
            /**
             * @param path {string}
             * @param start {Vector}
             */
            setPathFromString: function (path, start) {
                if (path[0] === 'R') {
                    var clockwise = (path[1] === 'C'),
                        rad = parseInt(path.substr(2), 10),
                        pointsCount = rad / 2,
                        kIncrement = 2 * Math.PI / pointsCount,
                        theta = 0;

                    if (!clockwise)
                        kIncrement = -kIncrement;

                    for (var i = 0; i < pointsCount; ++i) {
                        var nx = start.x + rad * Math.cos(theta),
                            ny = start.y + rad * Math.sin(theta);

                        this.addPathPoint(new Vector(nx, ny));
                        theta += kIncrement;
                    }
                }
                else {
                    this.addPathPoint(start.copy());

                    // remove the trailing comma
                    if (path[path.length - 1] === ',') {
                        path = path.substr(0, path.length - 1);
                    }

                    var parts = path.split(','),
                        len = parts.length;
                    for (i = 0; i < len; i += 2) {
                        var xs = parseFloat(parts[i]),
                            ys = parseFloat(parts[i + 1]),
                            pathPoint = new Vector(start.x + xs, start.y + ys);
                        this.addPathPoint(pathPoint);
                    }
                }
            },
            /**
             * @param pathPoint {Vector}
             */
            addPathPoint: function (pathPoint) {
                this.path.push(pathPoint);
            },
            start: function () {
                if (this.path.length > 0) {
                    this.pos.copyFrom(this.path[0]);
                    this.targetPoint = 1;
                    this.calculateOffset();
                }
            },
            pause: function () {
                this.paused = true;
            },
            unpause: function () {
                this.paused = false;
            },
            setRotateSpeed: function (rotateSpeed) {
                this.rotateSpeed = rotateSpeed;
            },
            jumpToPoint: function (point) {
                this.targetPoint = point;
                this.pos.copyFrom(this.path[point]);
                this.calculateOffset();
            },
            calculateOffset: function () {
                var target = this.path[this.targetPoint];
                this.offset = Vector.subtract(target, this.pos);
                this.offset.normalize();
                this.offset.multiply(this.moveSpeed[this.targetPoint]);
            },
            setMoveSpeedAt: function (moveSpeed, index) {
                this.moveSpeed[index] = moveSpeed;
            },
            setMoveReverse: function (reverse) {
                this.reverse = reverse;
            },
            update: function (delta) {
                if (this.paused)
                    return;

                if (this.path.length > 0) {
                    var target = this.path[this.targetPoint],
                        switchPoint = false;

                    if (!this.pos.equals(target)) {
                        var rdelta = delta;
                        if (this.overrun !== 0) {
                            rdelta += this.overrun;
                            this.overrun = 0;
                        }

                        this.pos.add(Vector.multiply(this.offset, rdelta));

                        // see if we passed the target
                        if (!MathHelper.sameSign(this.offset.x, target.x - this.pos.x) ||
                            !MathHelper.sameSign(this.offset.y, target.y - this.pos.y)) {
                            this.overrun = Vector.subtract(this.pos, target).getLength();

                            // overrun in seconds
                            this.overrun /= this.offset.getLength();
                            this.pos.copyFrom(target);
                            switchPoint = true;
                        }
                    }
                    else {
                        switchPoint = true;
                    }

                    if (switchPoint) {
                        if (this.reverse) {
                            this.targetPoint--;
                            if (this.targetPoint < 0) {
                                this.targetPoint = this.path.length - 1;
                            }
                        }
                        else {
                            this.targetPoint++;
                            if (this.targetPoint >= this.path.length) {
                                this.targetPoint = 0;
                            }
                        }

                        this.calculateOffset();
                    }
                }

                if (this.rotateSpeed !== 0) {
                    this.angle += this.rotateSpeed * delta;
                }
            }
        });

        // NOTE: sometimes we need the status indicating whether the
        // variable was moved to zero. However, for performance we'll
        // offer another version without status.

        Mover.moveToTarget = function (v, t, speed, delta) {
            if (t !== v) {
                if (t > v) {
                    v += speed * delta;
                    if (v > t) {
                        v = t;
                    }
                }
                else {
                    v -= speed * delta;
                    if (v < t) {
                        v = t;
                    }
                }
            }
            return v;
        };
        /**
         *
         * @param v {number} value
         * @param t {number} target
         * @param speed {number}
         * @param delta {number}
         * @return {Object}
         */
        Mover.moveToTargetWithStatus = function (v, t, speed, delta) {
            var reachedZero = false;
            if (t !== v) {
                if (t > v) {
                    v += speed * delta;
                    if (v > t) {
                        v = t;
                    }
                }
                else {
                    v -= speed * delta;
                    if (v < t) {
                        v = t;
                    }
                }
                if (t === v)
                    reachedZero = true;
            }

            return {
                value: v,
                reachedZero: reachedZero
            };
        };

        /**
         * @const
         * @type {number}
         */
        Mover.MAX_CAPACITY = 100;

        return Mover;
    }
);

define('visual/GameObject',
    [
        'visual/Animation',
        'utils/Mover',
        'core/Rectangle',
        'core/Quad2D',
        'core/Alignment',
        'core/Vector',
        'utils/Radians',
        'utils/Canvas',
        'core/RGBAColor'
    ],
    function (Animation, Mover, Rectangle, Quad2D, Alignment, Vector, Radians, Canvas, RGBAcolor) {

        var GameObject = Animation.extend({
            init: function () {
                this._super();
                this.isDrawBB = false;
            },
            initTexture: function (texture) {
                this._super(texture);
                this.bb = new Rectangle(0, 0, this.width, this.height);
                this.rbb = new Quad2D(this.bb.x, this.bb.y, this.bb.width, this.bb.height);
                this.anchor = Alignment.CENTER;

                this.rotatedBB = false;
                this.topLeftCalculated = false;
            },
            setBBFromFirstQuad: function () {
                var firstOffset = this.texture.offsets[0],
                    firstRect = this.texture.rects[0];
                //noinspection JSSuspiciousNameCombination
                this.bb = new Rectangle(
                    Math.round(firstOffset.x),
                    Math.round(firstOffset.y),
                    firstRect.width,
                    firstRect.height);
                this.rbb = new Quad2D(this.bb.x, this.bb.y, this.bb.width, this.bb.height);
            },
            parseMover: function (item) {
                this.rotation = item.angle || 0;

                var path = item.path;
                if (path) {
                    var moverCapacity = Mover.MAX_CAPACITY;
                    if (path[0] === 'R') {
                        var rad = parseInt(path.substr(2), 10);
                        moverCapacity = Math.round(rad / 2 + 1);
                    }

                    var mover = new Mover(
                        moverCapacity,
                        item.moveSpeed,
                        item.rotateSpeed);
                    mover.angle = this.rotation;
                    mover.setPathFromString(path, new Vector(this.x, this.y));
                    this.setMover(mover);
                    mover.start();
                }
            },
            setMover: function (mover) {
                this.mover = mover;

                // turn high precision coordinates on for moving objects
                this.drawPosIncrement = 0.0001;
            },
            update: function (delta) {
                this._super(delta);

                if (!this.topLeftCalculated) {
                    this.calculateTopLeft();
                    this.topLeftCalculated = true;
                }

                if (this.mover) {
                    this.mover.update(delta);

                    this.x = this.mover.pos.x;
                    this.y = this.mover.pos.y;

                    if (this.rotatedBB)
                        this.rotateWithBB(this.mover.angle);
                    else
                        this.rotation = this.mover.angle;
                }
            },
            rotateWithBB: function (angle) {
                if (!this.rotatedBB) {
                    this.rotatedBB = true;
                }
                this.rotation = angle;

                var bb = this.bb,
                    tl = new Vector(bb.x, bb.y),
                    tr = new Vector(bb.x + bb.w, bb.y),
                    br = new Vector(tr.x, bb.y + bb.h),
                    bl = new Vector(bb.x, br.y);

                // calculate the angle and offset for rotation
                var rad = Radians.fromDegrees(angle),
                    offsetX = this.width / 2 + this.rotationCenterX,
                    offsetY = this.height / 2 + this.rotationCenterY;

                tl.rotateAround(rad, offsetX, offsetY);
                tr.rotateAround(rad, offsetX, offsetY);
                br.rotateAround(rad, offsetX, offsetY);
                tl.rotateAround(rad, offsetX, offsetY);

                var rbb = this.rbb;
                rbb.tlX = tl.x;
                rbb.tlY = tl.y;
                rbb.trX = tr.x;
                rbb.trY = tr.y;
                rbb.brX = br.x;
                rbb.brY = br.y;
                rbb.blX = bl.x;
                rbb.blY = bl.y;

                
                
                
                
            },
            drawBB: function () {
                var ctx = Canvas.context,
                    drawX = this.drawX,
                    drawY = this.drawY,
                    bb = this.bb,
                    rbb = this.rbb;
                ctx.strokeStyle = 'red';
                ctx.lineWidth = 2;
                if (this.rotatedBB) {
                    ctx.beginPath();
                    ctx.moveTo(drawX + rbb.tlX, drawY + rbb.tlY);
                    ctx.lineTo(drawX + rbb.trX, drawY + rbb.trY);
                    ctx.lineTo(drawX + rbb.brX, drawY + rbb.brY);
                    ctx.lineTo(drawX + rbb.blX, drawY + rbb.blY);
                    ctx.stroke();
                    ctx.closePath();
                }
                else {
                    ctx.strokeRect(drawX + bb.x, drawY + bb.y, bb.w, bb.h);
                }
            },
            /**
             * Returns true if the point is inside the object's bounding box
             * @param x {number}
             * @param y {number}
             * @return {boolean}
             */
            pointInObject: function (x, y) {
                var bb = this.bb,
                    ox = this.drawX + bb.x,
                    oy = this.drawY + bb.y;

                return Rectangle.pointInRect(x, y, ox, oy, bb.w, bb.h);
            },
            /**
             * @param r1x {number}
             * @param r1y {number}
             * @param r2x {number}
             * @param r2y {number}
             */
            rectInObject: function (r1x, r1y, r2x, r2y) {
                var ox = this.drawX + this.bb.x,
                    oy = this.drawY + this.bb.y;

                return Rectangle.rectInRect(r1x, r1y, r2x, r2y, ox, oy, ox + this.bb.w, oy + this.bb.h);
            }


        });

        GameObject.intersect = function (o1, o2) {
            var o1x = o1.drawX + o1.bb.x,
                o1y = o1.drawY + o1.bb.y,
                o2x = o2.drawX + o2.bb.x,
                o2y = o2.drawY + o2.bb.y;

            return Rectangle.rectInRect(
                o1x, o1y, o1x + o1.bb.w, o1y + o1.bb.h,
                o2x, o2y, o2x + o2.bb.w, o2y + o2.bb.h);
        };

        return GameObject;
    }
);
define('game/CTRMover',
    ['resolution', 'utils/Mover', 'core/Vector'],
    function (resolution, Mover, Vector) {

        var CTRMover = Mover.extend({
            init: function (pathCapacity, moveSpeed, rotateSpeed) {
                this._super(pathCapacity, moveSpeed, rotateSpeed);
            },
            setPathAndStart: function (path, startX, startY) {
                var i, nx, ny, xs, ys,
                    MOVER_SCALE = resolution.MOVER_SCALE;

                if (path[0] === 'R') {
                    var clockwise = (path[1] === 'C'),
                        rad = parseInt(path.substr(2), 10),
                        pointsCount = Math.round((rad * 3) / 2),
                        k_increment = 2 * Math.PI / pointsCount,
                        theta = 0;

                    // now that the number of points have been calculated we
                    // can scale the radius to match the current resolution
                    rad *= MOVER_SCALE;

                    if (!clockwise)
                        k_increment = -k_increment;

                    for (i = 0; i < pointsCount; i++) {
                        nx = startX + rad * Math.cos(theta);
                        ny = startY + rad * Math.sin(theta);

                        this.addPathPoint(new Vector(nx, ny));
                        theta += k_increment;
                    }
                }
                else {
                    this.addPathPoint(new Vector(startX, startY));
                    if (path[path.length - 1] === ',') {
                        path = path.substr(0, path.length - 1);
                    }
                    var parts = path.split(','),
                        numParts = parts.length;
                    for (i = 0; i < numParts; i += 2) {
                        xs = parts[i];
                        ys = parts[i + 1];

                        this.addPathPoint(
                            new Vector(startX + xs * MOVER_SCALE, startY + ys * MOVER_SCALE));
                    }
                }
            }
        });

        return CTRMover;
    }
);

define('game/CTRGameObject',
    [
        'visual/GameObject',
        'game/CTRMover',
        'resolution',
        'utils/Mover'
    ],
    function (GameObject, CTRMover, resolution, Mover) {

        var CTRGameObject = GameObject.extend({
            init: function () {
                this._super();
            },
            parseMover: function (item) {
                this.rotation = item.angle || 0;

                var path = item.path,
                    MOVER_SCALE = resolution.MOVER_SCALE;
                if (path) {
                    var moverCapacity = Mover.MAX_CAPACITY;
                    if (path[0] === 'R') {
                        // Don't scale the radius when used for capacity
                        // calculation. We want same number of path points
                        // even if the actual radius is smaller
                        var rad = parseInt(path.substr(2), 10);
                        moverCapacity = Math.round(rad * 3 / 2 + 1);
                    }
                    var v = item.moveSpeed,
                        rotateSpeed = item.rotateSpeed,
                        mover = new CTRMover(moverCapacity, v * MOVER_SCALE, rotateSpeed);

                    mover.angle = this.rotation;
                    mover.setPathAndStart(path, this.x, this.y);
                    this.setMover(mover);
                    mover.start();
                }
            }
        });

        return CTRGameObject;
    }
);


define('game/Bouncer',
    [
        'game/CTRGameObject',
        'core/Vector',
        'visual/KeyFrame',
        'visual/ActionType',
        'utils/Radians',
        'utils/Constants',
        'resources/ResourceId',
        'visual/Timeline'
    ],
    function (CTRGameObject, Vector, KeyFrame, ActionType, Radians, Constants, ResourceId, Timeline) {

        /**
         * @const
         * @type {number}
         */
        var BOUNCER_HEIGHT = 10;

        var IMG_OBJ_BOUNCER_01_start = 0;
        var IMG_OBJ_BOUNCER_01_Frame_2 = 1;
        var IMG_OBJ_BOUNCER_01_Frame_3 = 2;
        var IMG_OBJ_BOUNCER_01_Frame_4 = 3;
        var IMG_OBJ_BOUNCER_01_end = 4;

        var IMG_OBJ_BOUNCER_02_start_ = 0;
        var IMG_OBJ_BOUNCER_02_Frame_2 = 1;
        var IMG_OBJ_BOUNCER_02_Frame_3 = 2;
        var IMG_OBJ_BOUNCER_02_Frame_4 = 3;
        var IMG_OBJ_BOUNCER_02_end = 4;


        var Bouncer = CTRGameObject.extend({
            init: function (x, y, width, angle) {
                this._super();

                this.angle = 0;
                this.skip = 0;
                this.t1 = Vector.newZero();
                this.t2 = Vector.newZero();
                this.b1 = Vector.newZero();
                this.b2 = Vector.newZero();

                var imageId = Constants.UNDEFINED;
                if (width === 1) {
                    imageId = ResourceId.IMG_OBJ_BOUNCER_01;
                }
                else if (width === 2) {
                    imageId = ResourceId.IMG_OBJ_BOUNCER_02;
                }
                this.initTextureWithId(imageId);

                this.rotation = angle;
                this.x = x;
                this.y = y;

                this.updateRotation();
                var delay = 0.04,
                    k = this.addAnimationDelay(delay, Timeline.LoopType.NO_LOOP, IMG_OBJ_BOUNCER_01_start,
                        IMG_OBJ_BOUNCER_01_end),
                    t = this.getTimeline(k);
                t.addKeyFrame(KeyFrame.makeSingleAction(this, ActionType.SET_DRAWQUAD, 0, 0, delay));
            },
            updateRotation: function () {
                var x = this.x;
                var y = this.y;
                var width = this.width / 2;

                this.t1.x = x - width;
                this.t2.x = x + width;
                this.t1.y = this.t2.y = y - BOUNCER_HEIGHT / 2.0;

                this.b1.x = this.t1.x;
                this.b2.x = this.t2.x;
                this.b1.y = this.b2.y = y + BOUNCER_HEIGHT / 2.0;

                var angle = this.angle = this.rotation * 0.017453292519943295;

                this.t1.rotateAround(angle, x, y);
                this.t2.rotateAround(angle, x, y);
                this.b1.rotateAround(angle, x, y);
                this.b2.rotateAround(angle, x, y);
            },
            update: function (delta) {
                this._super(delta);
                if (this.mover) {
                    this.updateRotation();
                }
            }
        });

        return Bouncer;
    }
);
define('game/Bubble',
    ['visual/GameObject'],
    function (GameObject) {

        var Bubble = GameObject.extend({
            init: function () {
                this._super();
                this.popped = false;
                this.withoutShadow = false;
            },
            draw: function () {
                if (this.withoutShadow) {
                    // only do transformations and draw children
                    this.preDraw();
                    this.postDraw();
                }
                else {
                    this._super();
                }
            }
        });

        return Bubble;
    }
);

define('visual/Particles',
    [
        'core/Vector',
        'core/RGBAColor',
        'visual/BaseElement',
        'utils/MathHelper',
        'utils/Canvas',
        'resolution',
        'utils/Radians'
    ],
    function (Vector, RGBAColor, BaseElement, MathHelper, Canvas, resolution, Radians) {

        /**
         * @constructor
         */
        function PointSprite(x, y, size) {
            this.x = x;
            this.y = y;
            this.size = size;
        }

        /**
         * @constructor
         */
        function Particle() {
            this.startPos = new Vector(0, 0);
            this.pos = new Vector(0, 0);
            this.dir = new Vector(0, 0);
            this.radialAccel = 0;
            this.tangentialAccel = 0;
            this.color = new RGBAColor(0, 0, 0, 0);
            this.deltaColor = new RGBAColor(0, 0, 0, 0);
            this.size = 0;
            this.life = 0;
            this.deltaAngle = 0;
            this.angle = 0;

            // used in multi-image particles
            this.width = 0;
            this.height = 0;
        }

        var Particles = BaseElement.extend({
            init: function (numParticles) {
                this._super();
                this.width = resolution.CANVAS_WIDTH;
                this.height = resolution.CANVAS_HEIGHT;

                this.totalParticles = numParticles;
                this.particles = [];

                // not active by default
                this.active = false;
                // duration in seconds of the system. -1 is infinity
                this.duration = 0;
                // time elapsed since the start of the system (in seconds)
                this.elapsed = 0;

                /// Gravity of the particles
                this.gravity = new Vector(0, 0);

                // Position variance
                this.posVar = new Vector(0, 0);

                // The angle (direction) of the particles measured in degrees
                this.angle = 0;
                // Angle variance measured in degrees;
                this.angleVar = 0;

                // The speed the particles will have.
                this.speed = 0;
                // The speed variance
                this.speedVar = 0;

                // Tangential acceleration
                this.tangentialAccel = 0;
                // Tangential acceleration variance
                this.tangentialAccelVar = 0;

                // Radial acceleration
                this.radialAccel = 0;
                // Radial acceleration variance
                this.radialAccelVar = 0;

                // Size of the particles
                this.size = 0;
                // Size variance
                this.sizeVar = 0;

                // How many seconds will the particle live
                this.life = 0;
                // Life variance
                this.lifeVar = 0;

                // Start color of the particles
                this.startColor = new RGBAColor(0, 0, 0, 0);
                // Start color variance
                this.startColorVar = new RGBAColor(0, 0, 0, 0);
                // End color of the particles
                this.endColor = new RGBAColor(0, 0, 0, 0);
                // End color variance
                this.endColorVar = new RGBAColor(0, 0, 0, 0);

                // additive color or blend
                this.blendAdditive = false;
                // color modulate
                this.colorModulate = false;

                // How many particles can be emitted per second
                this.emissionRate = 0;
                this.emitCounter = 0;

                // Texture of the particles
                this.texture = null;

                // Array of (x,y,size)
                this.vertices = [];
                // Array of colors
                this.colors = [];

                //  particle idx
                this.particleIdx = 0;

                // callback when particle system has finished
                this.onFinished = null;
            },
            /**
             * Creates and adds a particle to the system
             * @return {boolean} false if the system is full, otherwise true
             */
            addParticle: function () {
                if (this.particles.length == this.totalParticles) {
                    return false;
                }

                var particle = new Particle();
                this.initParticle(particle);
                this.particles.push(particle);
                return true;
            },
            initParticle: function (particle) {
                particle.pos.x = this.x + this.posVar.x * MathHelper.randomMinus1to1();
                particle.pos.y = this.y + this.posVar.y * MathHelper.randomMinus1to1();
                particle.startPos.copyFrom(particle.pos);

                var a = Radians.fromDegrees(this.angle + (this.angleVar * MathHelper.randomMinus1to1())),
                    v = new Vector(Math.cos(a), Math.sin(a)),
                    s = this.speed + this.speedVar * MathHelper.randomMinus1to1();

                // direction
                v.multiply(s);
                particle.dir = v;

                // radial acceleration
                particle.radialAccel = this.radialAccel +
                    this.radialAccelVar * MathHelper.randomMinus1to1();

                // tangential acceleration
                particle.tangentialAccel = this.tangentialAccel +
                    this.tangentialAccelVar * MathHelper.randomMinus1to1();

                // life
                particle.life = this.life + this.lifeVar * MathHelper.randomMinus1to1();

                // color
                var start = new RGBAColor(
                    this.startColor.r + this.startColorVar.r * MathHelper.randomMinus1to1(),
                    this.startColor.g + this.startColorVar.g * MathHelper.randomMinus1to1(),
                    this.startColor.b + this.startColorVar.b * MathHelper.randomMinus1to1(),
                    this.startColor.a + this.startColorVar.a * MathHelper.randomMinus1to1());

                var end = new RGBAColor(
                    this.endColor.r + this.endColorVar.r * MathHelper.randomMinus1to1(),
                    this.endColor.g + this.endColorVar.g * MathHelper.randomMinus1to1(),
                    this.endColor.b + this.endColorVar.b * MathHelper.randomMinus1to1(),
                    this.endColor.a + this.endColorVar.a * MathHelper.randomMinus1to1());

                particle.color = start;
                particle.deltaColor.r = (end.r - start.r) / particle.life;
                particle.deltaColor.g = (end.g - start.g) / particle.life;
                particle.deltaColor.b = (end.b - start.b) / particle.life;
                particle.deltaColor.a = (end.a - start.a) / particle.life;

                // size
                particle.size = this.size + this.sizeVar * MathHelper.randomMinus1to1();
            },
            update: function (delta) {
                this._super(delta);
                if (this.onFinished) {
                    if (this.particles.length === 0 && !this.active) {
                        this.onFinished(this);
                        return;
                    }
                }

                if (this.active && this.emissionRate) {
                    var rate = 1 / this.emissionRate;
                    this.emitCounter += delta;
                    while (this.particles.length < this.totalParticles && this.emitCounter > rate) {
                        this.addParticle();
                        this.emitCounter -= rate;
                    }

                    this.elapsed += delta;
                    if (this.duration !== -1 && this.duration < this.elapsed) {
                        this.stopSystem();
                    }
                }

                this.particleIdx = 0;
                while (this.particleIdx < this.particles.length) {
                    var p = this.particles[this.particleIdx];
                    if (p.life > 0) {

                        this.updateParticleLocation(p, delta);

                        p.color.r += (p.deltaColor.r * delta);
                        p.color.g += (p.deltaColor.g * delta);
                        p.color.b += (p.deltaColor.b * delta);
                        p.color.a += (p.deltaColor.a * delta);

                        p.life -= delta;

                        this.updateParticle(p, this.particleIdx, delta);
                        this.particleIdx++;
                    }
                    else {
                        // remove the particle
                        this.removeParticle(this.particleIdx);
                    }
                }
            },
            updateParticleLocation: function (p, delta) {
                var tmp, radial, tangential;

                // radial acceleration
                if (p.pos.x || p.pos.y) {
                    radial = p.pos.copy();
                    radial.normalize();
                }
                else {
                    radial = new Vector(0, 0);
                }
                tangential = radial.copy();
                radial.multiply(p.radialAccel);

                // tangential acceleration
                var newy = tangential.x;
                tangential.x = -tangential.y;
                tangential.y = newy;
                tangential.multiply(p.tangentialAccel);

                // (gravity + radial + tangential) * delta
                tmp = Vector.add(radial, tangential);
                tmp.add(this.gravity);
                tmp.multiply(delta);
                p.dir.add(tmp);

                tmp.copyFrom(p.dir);
                tmp.multiply(delta);
                p.pos.add(tmp);

                
            },
            updateParticle: function (particle, index) {
                this.vertices[this.particleIdx] = new PointSprite(
                    particle.pos.x,
                    particle.pos.y,
                    particle.size);

                this.colors[this.particleIdx] = particle.color;
            },
            removeParticle: function (index) {
                this.particles.splice(index, 1);
            },
            startSystem: function (initialParticles) {
                this.particles.length = 0;
                for (var i = 0; i < initialParticles; i++) {
                    this.addParticle();
                }
                this.active = true;
            },
            stopSystem: function () {
                this.active = false;
                this.elapsed = this.duration;
                this.emitCounter = 0;
            },
            resetSystem: function () {
                this.elapsed = 0;
                this.emitCounter = 0;
            },
            draw: function () {
                this.preDraw();

                // only draw if the image is non-transparent
                if (this.color.a !== 0) {
                    var ctx = Canvas.context,
                        image = this.texture.image;
                    for (var i = 0, len = this.particleIdx; i < len; i++) {
                        var p = this.particles[i];
                        ctx.drawImage(image, Math.round(p.x), Math.round(p.y));
                    }
                }

                this.postDraw();
            },
            isFull: function () {
                return (this.particles.length === this.totalParticles);
            }
        });

        return Particles;
    }
);
define('visual/MultiParticles',
    [
        'visual/Particles',
        'core/Rectangle',
        'utils/MathHelper',
        'visual/ImageMultiDrawer',
        'resolution'
    ],
    function (Particles, Rectangle, MathHelper, ImageMultiDrawer, resolution) {

        var MultiParticles = Particles.extend({
            init: function (numParticles, texture) {
                this._super(numParticles);

                this.imageGrid = texture;
                this.drawer = new ImageMultiDrawer(texture);
                this.width = resolution.CANVAS_WIDTH;
                this.height = resolution.CANVAS_HEIGHT;
            },
            initParticle: function (particle) {
                var texture = this.imageGrid,
                    n = MathHelper.randomRange(0, texture.rects.length - 1),
                    tquad = texture.rects[n],
                    vquad = new Rectangle(0, 0, 0, 0); // don't draw initially

                this.drawer.setTextureQuad(this.particles.length, tquad, vquad, 1);

                this._super(particle);

                particle.width = tquad.w * particle.size;
                particle.height = tquad.h * particle.size;
            },
            updateParticle: function (particle, index) {
                // update the current position
                this.drawer.vertices[index] = new Rectangle(
                    particle.pos.x - particle.width / 2,
                    particle.pos.y - particle.height / 2,
                    particle.width,
                    particle.height);

                // update the alpha in the drawer
                this.drawer.alphas[index] = particle.color.a;

                // update the color in the particle system
                this.colors[index] = particle.color;
            },
            removeParticle: function (index) {
                this.drawer.removeQuads(index);
                this._super(index);
            },
            draw: function () {
                this.preDraw();

                /* for debugging rotation: draw a line from origin at 0 degrees
                 var ctx = Canvas.context;
                 ctx.save();
                 ctx.lineWidth = 5;
                 ctx.strokeStyle = "blue";
                 ctx.beginPath();
                 ctx.moveTo(this.drawX, this.drawY);
                 ctx.lineTo(this.drawX, this.drawY - 100);
                 ctx.closePath();
                 ctx.stroke();
                 ctx.restore();
                 */

                this.drawer.draw();
                this.postDraw();
            }
        });

        return MultiParticles;
    }
);

define('visual/RotateableMultiParticles',
    [
        'visual/MultiParticles',
        'utils/Radians',
        'utils/MathHelper',
        'core/Vector'
    ],
    function (MultiParticles, Radians, MathHelper, Vector) {
        var RotateableMultiParticles = MultiParticles.extend({
            init: function (numParticles, texture) {
                this._super(numParticles, texture);
                this.drawer.rotationAngles = [];
                this.drawer.rotationPositions = [];
            },
            initParticle: function (particle) {
                this._super(particle);
                particle.angle = 0;
                particle.deltaAngle = Radians.fromDegrees(
                    this.rotateSpeed + this.rotateSpeedVar * MathHelper.randomMinus1to1());

                var index = this.particles.length;
                this.drawer.rotationAngles[index] = 0;
                this.drawer.rotationPositions[index] = new Vector(0, 0);
            },
            rotatePreCalc: function (v, cosA, sinA, cx, cy) {
                v.x -= cx;
                v.y -= cy;

                var nx = v.x * cosA - v.y * sinA,
                    ny = v.x * sinA + v.y * cosA;

                v.x = nx + cx;
                v.y = ny + cy;
            },
            updateParticle: function (particle, index, delta) {
                this._super(particle, index, delta);
                particle.angle += (particle.deltaAngle * delta);

                // we need to save the angle and position for drawing rotation
                this.drawer.rotationAngles[index] = particle.angle;
                this.drawer.rotationPositions[index].copyFrom(particle.pos);
            },

            removeParticle: function (index) {
                this.drawer.rotationAngles.splice(index, 1);
                this.drawer.rotationPositions.splice(index, 1);
                this._super(index);
            }

        });

        return RotateableMultiParticles;
    }
);

define('game/CandyBreak',
    [
        'visual/RotateableMultiParticles',
        'resolution',
        'utils/MathHelper',
        'core/Rectangle'
    ],
    function (RotateableMultiParticles, resolution, MathHelper, Rectangle) {

        var IMG_OBJ_CANDY_01_piece_01 = 3;
        var IMG_OBJ_CANDY_01_piece_02 = 4;
        var IMG_OBJ_CANDY_01_piece_03 = 5;
        var IMG_OBJ_CANDY_01_piece_04 = 6;
        var IMG_OBJ_CANDY_01_piece_05 = 7;

        var CandyBreak = RotateableMultiParticles.extend({
            init: function (numParticles, texture) {
                this._super(numParticles, texture);

                // duration
                this.duration = 2;

                // gravity
                this.gravity.x = 0;
                this.gravity.y = 500.0;

                // angle
                this.angle = -90;
                this.angleVar = 50;

                // speed of particles
                this.speed = 150.0;
                this.speedVar = 70.0;

                // radial
                this.radialAccel = 0;
                this.radialAccelVar = 1;

                // tCTRial
                this.tangentialAccel = 0;
                this.tangentialAccelVar = 1;

                // emitter position
                this.posVar.x = 0.0;
                this.posVar.y = 0.0;

                // life of particles
                this.life = 2;
                this.lifeVar = 0;

                // size, in pixels
                this.size = 1;
                this.sizeVar = 0.0;

                // emits per second
                this.emissionRate = 100;

                // color of particles
                this.startColor.r = 1.0;
                this.startColor.g = 1.0;
                this.startColor.b = 1.0;
                this.startColor.a = 1.0;
                this.startColorVar.r = 0.0;
                this.startColorVar.g = 0.0;
                this.startColorVar.b = 0.0;
                this.startColorVar.a = 0.0;
                this.endColor.r = 1.0;
                this.endColor.g = 1.0;
                this.endColor.b = 1.0;
                this.endColor.a = 1.0;
                this.endColorVar.r = 0.0;
                this.endColorVar.g = 0.0;
                this.endColorVar.b = 0.0;
                this.endColorVar.a = 0.0;

                this.rotateSpeed = 0.0;
                this.rotateSpeedVar = 600;

                // additive
                this.blendAdditive = false;
            },
            initParticle: function (particle) {
                this._super(particle);

                var texture = this.imageGrid,
                    n = MathHelper.randomRange(IMG_OBJ_CANDY_01_piece_01, IMG_OBJ_CANDY_01_piece_05),
                    tquad = texture.rects[n],
                    vquad = new Rectangle(0, 0, 0, 0); // don't draw initially

                this.drawer.setTextureQuad(this.particles.length, tquad, vquad);

                particle.width = tquad.w * this.size;
                particle.height = tquad.h * this.size;
            }
        });

        return CandyBreak;
    }
);
define('game/Drawing',
    [
        'visual/GameObject',
        'resources/ResourceId',
        'utils/PubSub'
    ],
    function (GameObject, ResourceId, PubSub) {

        var Drawing = GameObject.extend({
            init: function (hiddenId, drawingIndex) {
                this._super();
                this.initTextureWithId(ResourceId.IMG_DRAWING_HIDDEN);
                this.setTextureQuad(hiddenId);
                this.ingame = true;
                this.drawingIndex = drawingIndex;
                this.passTransformationsToChilds = false;
            },
            showDrawing: function () {
                PubSub.publish(PubSub.ChannelId.DrawingClicked, this.drawingIndex);
            }
        });

        return Drawing;
    }
);
define('game/FingerCut',
    [],
    function () {

        /**
         * @constructor
         * @param start {Vector}
         * @param end {Vector}
         * @param startSize {number}
         * @param endSize {number}
         * @param color {RGBAColor}
         */
        function FingerCut(start, end, startSize, endSize, color) {
            this.start = start;
            this.end = end;
            this.startSize = startSize;
            this.endSize = endSize;
            this.color = color;
        }

        return FingerCut;
    }
);
define('physics/ConstraintSystem',
    [
        'utils/Class',
        'core/Vector',
        'utils/Log'
    ],
    function (Class, Vector, Log) {

        var ConstraintSystem = Class.extend({
            init: function () {
                this.relaxationTimes = 1;
                
                this.parts = [];
            },
            addPartAtIndex: function (cp, index) {
                // splice with removeLength=0 means we just insert
                // the additional element (cp) at the index
                this.parts.splice(index, 0, cp);
            },
            addPart: function (cp) {
                this.parts[this.parts.length] = cp;
            },
            log: function () {
                Log.debug('Constraint System Log:');
                for (var i = 0, partsLen = this.parts.length; i < partsLen; i++) {
                    var cp = this.parts[i];
                    Log.debug('-- Point: ' + cp.posString());
                    for (var j = 0, constraintsLen = cp.constraints.length; j < constraintsLen; j++) {
                        var c = cp.constraints[j];
                        var cInfo = '---- Constraint: ' + c.cp.posString() + ' len: ' + c.restLength;
                        Log.debug(cInfo);
                    }
                }
            },
            removePartAtIndex: function (index) {
                this.parts.splice(index, 1);
            },
            update: function (delta) {
                var parts = this.parts,
                    numParts = parts.length,
                    relaxationTimes = this.relaxationTimes;

                // update each part
                for (var i = 0; i < numParts; i++) {
                    parts[i].update(delta);
                }

                // satisfy constraints during each relaxation period
                
                satisfyConstraintArray(parts, relaxationTimes)
            }

            // NOTE: base draw() implementation isn't used so we won't port it yet
        });

        return ConstraintSystem;
    }
);
define('physics/ConstraintType',
    [],
    function () {
        /** @enum {number} */
        var ConstraintType = {
            DISTANCE: 0,
            NOT_MORE_THAN: 1,
            NOT_LESS_THAN: 2
        };

        return ConstraintType;
    }
);

define('physics/Gravity',
    [
        'core/Vector',
        'utils/Constants'
    ],
    function (Vector, Constants) {

        var GCONST = (9.8 * Constants.PIXEL_TO_SI_METERS_K);

        var Gravity = {
            /**
             * @const
             * @type {number}
             */
            EARTH_Y: GCONST,
            current: new Vector(0, GCONST),
            toggle: function () {
                Gravity.current.y = -Gravity.current.y;
            },
            isZero: function () {
                return (Gravity.current.y === 0 && Gravity.current.x === 0);
            },
            isNormal: function () {
                return (Gravity.current.y === Gravity.EARTH_Y && Gravity.current.x === 0);
            },
            reset: function () {
                Gravity.current.x = 0;
                Gravity.current.y = GCONST;
            }
        };

        return Gravity;
    }
);
define('physics/MaterialPoint',
    [
        'utils/Class',
        'utils/Constants',
        'core/Vector',
        'physics/Gravity'
    ],
    function (Class, Constants, Vector, Gravity) {

        var MaterialPoint = Class.extend({
            init: function () {
                this.disableGravity = false;
                this.setWeight(1);
                this.resetAll();
            },
            setWeight: function (w) {
                this.weight = w;
                this.invWeight = 1 / w;
                this.gravity = new Vector(0, Constants.EARTH_Y * w);
            },
            resetAll: function () {
                var newZero = Vector.newZero;
                this.v = newZero(); // velocity vector
                this.a = newZero(); // acceleration vector
                this.pos = newZero();
                this.posDelta = newZero();
                this.totalForce = newZero();
            },
            updateWithPrecision: function (delta, precision) {

                // Calculate number Of iterations to be made at this update depending
                // on maxPossible_dt And dt (chop off fractional part and add 1)
                var numIterations = ((delta / precision) >> 0) + 1;

                // update delta based on num of iterations
                if (numIterations != 0) { // avoid division by zero
                    delta = delta / numIterations;
                }

                for (var i = 0; i < numIterations; i++) {
                    this.update(delta);
                }
            },
            update: function (delta) {
                this.totalForce = Vector.newZero();

                // incorporate gravity
                if (!this.disableGravity) {
                    if (!Gravity.isZero()) {
                        this.totalForce.add(
                            Vector.multiply(Gravity.current, this.weight));
                    }
                    else {
                        this.totalForce.add(this.gravity);
                    }
                }

                var adjustedDelta = delta / Constants.TIME_SCALE;
                this.totalForce.multiply(this.invWeight);
                this.a = Vector.multiply(this.totalForce, adjustedDelta);
                this.v.add(this.a);

                this.posDelta = Vector.multiply(this.v, adjustedDelta);
                this.pos.add(this.posDelta);
            },
            applyImpulse: function (impulse, delta) {
                if (!impulse.isZero()) {
                    var im = Vector.multiply(impulse, delta / Constants.TIME_SCALE);
                    this.pos.add(im);
                    
                }
            }
        });

        return MaterialPoint;
    }
);
function satisfyConstraintArray (arr, n) {
    // NOTE: this method is a perf hotspot so be careful with changes
    n = n || 1;

    var len = arr.length;
    var cons;

    if (!len) return;

    //loop over the rest length
    while (n--) {

        for (var cIndex = 0; cIndex < len; ++cIndex) {
            
            cons = arr[cIndex];

            var constraints = cons.constraints,
                num = constraints.length;

            var pin = cons.pin,
                pos = cons.pos,
                invWeight = cons.invWeight,
                tmp1X, tmp1Y,
                tmp2X, tmp2Y;

            if (pin.x !== -1 /* Constants.UNDEFINED */) {
                pos.x = pin.x;
                pos.y = pin.y;
                continue;
            }

            for (var i = 0; i < num; i++) {
                var c = constraints[i],
                    cp = c.cp,
                    cpPos = cp.pos;
                

                tmp1X = cpPos.x - pos.x;
                tmp1Y = cpPos.y - pos.y;

                if (tmp1X === 0 && tmp1Y === 0) {
                    tmp1X = 1;
                    tmp1Y = 1;
                }

                var sqrDeltaLength = (tmp1X * tmp1X + tmp1Y * tmp1Y), // get dot product inline
                    restLength = c.restLength,
                    sqrRestLength = restLength * restLength,
                    cType = c.type;

                if (cType === 1 /* ConstraintType.NOT_MORE_THAN */) {
                    if (sqrDeltaLength <= sqrRestLength)
                        continue;
                }
                else if (cType === 2 /*ConstraintType.NOT_LESS_THAN */) {
                    if (sqrDeltaLength >= sqrRestLength)
                        continue;
                }

                var pinUndefined = (cp.pin.x === -1 /* Constants.UNDEFINED */),
                    invWeight2 = cp.invWeight,
                    deltaLength = Math.sqrt(sqrDeltaLength),
                    minDeltaLength = (deltaLength > 1) ? deltaLength : 1,
                    diff = (deltaLength - restLength) /
                        (minDeltaLength * (invWeight + invWeight2));

                // copy the first position before modification
                if (pinUndefined) {
                    tmp2X = tmp1X;
                    tmp2Y = tmp1Y;
                }

                var tmp1Multiplier = invWeight * diff;
                tmp1X *= tmp1Multiplier;
                tmp1Y *= tmp1Multiplier;

                pos.x += tmp1X;
                pos.y += tmp1Y;

                if (pinUndefined) {
                    var tmp2Multiplier = invWeight2 * diff;
                    cpPos.x -= tmp2X * tmp2Multiplier;
                    cpPos.y -= tmp2Y * tmp2Multiplier;
                }
            }
        }

    }//end while
}

define('physics/ConstrainedPoint',
    [
        'physics/ConstraintType',
        'physics/MaterialPoint',
        'core/Vector',
        'utils/Constants',
        'physics/Gravity'
    ],
    function (ConstraintType, MaterialPoint, Vector, Constants, Gravity) {

        function Constraint(cp, restLength, type) {
            this.cp = cp;
            this.restLength = restLength;
            this.type = type;
        }

        var ConstrainedPoint = MaterialPoint.extend({
            init: function () {
                this.prevPos = new Vector(Constants.INT_MAX, Constants.INT_MAX);
                this.pin = new Vector(Constants.UNDEFINED, Constants.UNDEFINED);
                this.constraints = [];
                this.totalForce = Vector.newZero();
                this._super();
            },

            /**
             * Resets the point by clearing previous position and removing constraints
             */
            resetAll: function () {
                this._super();
                this.prevPos.x = Constants.INT_MAX;
                this.prevPos.y = Constants.INT_MAX;
                this.removeConstraints();
            },
            /**
             * removes all constraints
             */
            removeConstraints: function () {
                this.constraints = [];
            },
            /**
             * Add a new constraint
             * @param cp {ConstrainedPoint}
             * @param restLength {number}
             * @param type {ConstraintType}
             */
            addConstraint: function (cp, restLength, type) {
                var ct = new Constraint(cp, restLength, type);
                this.constraints.push(ct);
            },
            /**
             * Removes the specified constraint
             * @param cp {ConstrainedPoint}
             */
            removeConstraint: function (cp) {
                var constraints = this.constraints,
                    len = constraints.length;
                for (var i = 0; i < len; i++) {
                    if (constraints[i].cp === cp) {
                        constraints.splice(i, 1);
                        return;
                    }
                }
            },
            /**
             * Removes the constraint at the specified index
             * @param index {number}
             */
            removeConstraintAtIndex: function (index) {
                this.constraints.splice(index, 1);
            },
            /**
             * @param fromCp {ConstrainedPoint}
             * @param toCp {ConstrainedPoint}
             */
            changeConstraint: function (fromCp, toCp) {
                var constraints = this.constraints,
                    len = constraints.length;
                for (var i = 0; i < len; i++) {
                    var constraint = constraints[i];
                    if (constraint.cp === fromCp) {
                        constraint.cp = toCp;
                        return;
                    }
                }
            },
            /**
             * Returns true if the constrained point is used by a constraint in the system
             * @param cp {ConstrainedPoint}
             * @return {boolean}
             */
            hasConstraint: function (cp) {
                var constraints = this.constraints,
                    len = constraints.length;
                for (var i = 0; i < len; i++) {
                    if (constraints[i].cp === cp) {
                        return true;
                    }
                }

                return false;
            },
            /**
             * @param cp {ConstrainedPoint}
             * @param restLength {number}
             */
            changeRestLength: function (cp, restLength) {
                var constraints = this.constraints,
                    len = constraints.length;
                for (var i = 0; i < len; i++) {
                    var constraint = constraints[i];
                    if (constraint.cp === cp) {
                        constraint.restLength = restLength;
                        return;
                    }
                }
            },
            /**
             * @param fromCp {ConstrainedPoint}
             * @param toCp {ConstrainedPoint}
             * @param restLength {number}
             */
            changeConstraintAndLength: function (fromCp, toCp, restLength) {
                var constraints = this.constraints,
                    len = constraints.length;
                for (var i = 0; i < len; i++) {
                    var constraint = constraints[i];
                    if (constraint.cp === fromCp) {
                        constraint.cp = toCp;
                        constraint.restLength = restLength;
                        return;
                    }
                }
            },
            /**
             * @param cp {ConstrainedPoint}
             * @return {number}
             */
            restLength: function (cp) {
                var constraints = this.constraints,
                    len = constraints.length;
                for (var i = 0; i < len; i++) {
                    var constraint = constraints[i];
                    if (constraint.cp === cp) {
                        return constraint.restLength;
                    }
                }

                return Constants.UNDEFINED;
            },
            /**
             * @param delta {number}
             */
            update: function (delta) {

                var totalForce = this.totalForce,
                    currentGravity = Gravity.current;

                if (!this.disableGravity) {
                    if (currentGravity.y !== 0 || currentGravity.x !== 0) {
                        totalForce.x = currentGravity.x;
                        totalForce.y = currentGravity.y;
                    }
                    else {
                        totalForce.x = this.gravity.x * this.invWeight;
                        totalForce.y = this.gravity.y * this.invWeight;
                    }

                } else {
                    totalForce.x = 0;
                    totalForce.y = 0;
                }

                var aMultiplier = delta / Constants.TIME_SCALE * delta / Constants.TIME_SCALE;
                this.a.x = this.totalForce.x * aMultiplier;
                this.a.y = this.totalForce.y * aMultiplier;

                if (this.prevPos.x === Constants.INT_MAX) {
                    this.prevPos.x = this.pos.x;
                    this.prevPos.y = this.pos.y;
                }

                this.posDelta.x = this.pos.x - this.prevPos.x + this.a.x;
                this.posDelta.y = this.pos.y - this.prevPos.y + this.a.y;

                if (delta > 0) {
                    var vMultiplier = 1 / delta;
                    this.v.x = this.posDelta.x * vMultiplier;
                    this.v.y = this.posDelta.y * vMultiplier;
                }

                this.prevPos.x = this.pos.x;
                this.prevPos.y = this.pos.y;

                this.pos.x += this.posDelta.x;
                this.pos.y += this.posDelta.y;

            },
            satisfyConstraints: function () {
                // NOTE: this method is a perf hotspot so be careful with changes
                var pin = this.pin,
                    pos = this.pos,
                    invWeight = this.invWeight,
                    tmp1X, tmp1Y,
                    tmp2X, tmp2Y;

                if (pin.x !== -1 /* Constants.UNDEFINED */) {
                    pos.x = pin.x;
                    pos.y = pin.y;
                    return;
                }

                var constraints = this.constraints,
                    num = constraints.length;

                
                for (var i = 0; i < num; i++) {
                    var c = constraints[i],
                        cp = c.cp,
                        cpPos = cp.pos;
                    

                    tmp1X = cpPos.x - pos.x;
                    tmp1Y = cpPos.y - pos.y;

                    if (tmp1X === 0 && tmp1Y === 0) {
                        tmp1X = 1;
                        tmp1Y = 1;
                    }

                    var sqrDeltaLength = (tmp1X * tmp1X + tmp1Y * tmp1Y), // get dot product inline
                        restLength = c.restLength,
                        sqrRestLength = restLength * restLength,
                        cType = c.type;
                    if (cType === 1 /* ConstraintType.NOT_MORE_THAN */) {
                        if (sqrDeltaLength <= sqrRestLength)
                            continue;
                    }
                    else if (cType === 2 /*ConstraintType.NOT_LESS_THAN */) {
                        if (sqrDeltaLength >= sqrRestLength)
                            continue;
                    }

                    var pinUndefined = (cp.pin.x === -1 /* Constants.UNDEFINED */),
                        invWeight2 = cp.invWeight,
                        deltaLength = Math.sqrt(sqrDeltaLength),
                        minDeltaLength = (deltaLength > 1) ? deltaLength : 1,
                        diff = (deltaLength - restLength) /
                            (minDeltaLength * (invWeight + invWeight2));

                    // copy the first position before modification
                    if (pinUndefined) {
                        tmp2X = tmp1X;
                        tmp2Y = tmp1Y;
                    }

                    var tmp1Multiplier = invWeight * diff;
                    tmp1X *= tmp1Multiplier;
                    tmp1Y *= tmp1Multiplier;

                    pos.x += tmp1X;
                    pos.y += tmp1Y;

                    if (pinUndefined) {
                        var tmp2Multiplier = invWeight2 * diff;
                        cpPos.x -= tmp2X * tmp2Multiplier;
                        cpPos.y -= tmp2Y * tmp2Multiplier;
                    }
                }
            },
            qcpUpdate: function (delta) {

                // qcpUpdate only differs from update in that it includes material
                // force calculations, however those don't appear to be used. So
                // for now, qcpUpdate simply calls update

                this.update(delta);
            },
            posString: function () {
                return this.pos.x.toFixed(2) + ', ' + this.pos.y.toFixed(2);
            }
        });

        return ConstrainedPoint;
    }
);
define('game/Bungee',
    [
        'physics/ConstraintSystem',
        'physics/ConstrainedPoint',
        'resolution',
        'utils/Constants',
        'physics/ConstraintType',
        'core/Vector',
        'utils/Canvas',
        'core/RGBAColor',
        'utils/Mover',
        'utils/Log'
    ],
    function (ConstraintSystem, ConstrainedPoint, resolution, Constants, ConstraintType, Vector, Canvas, RGBAColor, Mover, Log) {

        /**
         * @const
         * @type {number}
         */
        var ROLLBACK_K = 0.5;

        /**
         * @const
         * @type {number}
         */
        var BUNGEE_RELAXION_TIMES = 25;

        /**
         * @const
         * @type {number}
         */
        var MAX_BUNGEE_SEGMENTS = 10;

        /**
         * @const
         * @type {number}
         */
        var DEFAULT_PART_WEIGHT = 0.02;

        /**
         * @const
         * @type {number}
         */
        var STRENGTHENED_PART_WEIGHT = 0.5;

        /**
         * @const
         * @type {number}
         */
        var CUT_DISSAPPEAR_TIMEOUT = 2.0;

        /**
         * @const
         * @type {number}
         */
        var WHITE_TIMEOUT = 0.05;


        /** @enum {number} */
        var BungeeMode = {
            NORMAL: 0,
            LOCKED: 1
        };

        // create temp color objects used during draw (to reduce allocations)
        var drawBlack = new RGBAColor(0, 0, 0, 1),
            drawC1 = new RGBAColor(0, 0, 0, 1),
            drawD1 = new RGBAColor(0, 0, 0, 1),
            drawC2 = new RGBAColor(0, 0, 0, 1),
            drawD2 = new RGBAColor(0, 0, 0, 1);
        
        var Bungee = ConstraintSystem.extend({
            /**
             * Create a new Rope
             * @param headCp {ConstrainedPoint} head constrained point
             * @param hx {number} head location: x
             * @param hy {number} head location: y
             * @param tailCp {ConstrainedPoint} tail constrained point
             * @param tx {number} tail location: x
             * @param ty {number} tail location: y
             * @param len {number} length of the rope
             */
            init: function (headCp, hx, hy, tailCp, tx, ty, len) {
                this._super();
                this.relaxed = 0;
                this.relaxationTimes = BUNGEE_RELAXION_TIMES;
                this.lineWidth = resolution.DEFAULT_BUNGEE_LINE_WIDTH;
                this.width = resolution.DEFAULT_BUNGEE_WIDTH;
                this.cut = Constants.UNDEFINED;
                this.cutTime = 0;
                this.bungeeMode = BungeeMode.NORMAL;
                this.highlighted = false;
                this.BUNGEE_REST_LEN = resolution.BUNGEE_REST_LEN;

                this.bungeeAnchor = (headCp != null)
                    ? headCp
                    : new ConstrainedPoint();

                if (tailCp != null)
                    this.tail = tailCp;
                else {
                    this.tail = new ConstrainedPoint();
                    this.tail.setWeight(1);
                }

                this.bungeeAnchor.setWeight(DEFAULT_PART_WEIGHT);
                this.bungeeAnchor.pos.x = hx;
                this.bungeeAnchor.pos.y = hy;

                this.tail.pos.x = tx;
                this.tail.pos.y = ty;
                this.addPart(this.bungeeAnchor);
                this.addPart(this.tail);

                this.tail.addConstraint(
                    this.bungeeAnchor,
                    this.BUNGEE_REST_LEN,
                    ConstraintType.DISTANCE);

                var offset = Vector.subtract(this.tail.pos, this.bungeeAnchor.pos);
                var pointsNum = Math.round(len / this.BUNGEE_REST_LEN + 2);
                offset.divide(pointsNum);

                this.roll(len, offset);
                this.forceWhite = false;
                this.initialCandleAngle = Constants.UNDEFINED;
                this.chosenOne = false;
                this.hideTailParts = false;
                this.dontDrawRedStretch = false;

                this.drawPts = [];

                this.BUNGEE_BEZIER_POINTS = resolution.BUNGEE_BEZIER_POINTS;
            },
            /**
             * @return {number}
             */
            getLength: function () {
                var len = 0,
                    parts = this.parts,
                    numParts = parts.length;
                if (numParts > 0) {
                    var v = parts[0].pos;
                    for (var i = 1; i < numParts; i++) {
                        var part = parts[i];
                        len += v.distance(part.pos);
                        v = part.pos;
                    }
                }
                return len;
            },
            roll: function (rollLen, offset) {
                if (offset == null) {
                    offset = Vector.newZero();
                }

                var parts = this.parts,
                    prev = parts[parts.length - 2],
                    tail = this.tail,
                    heroRestLen = tail.restLength(prev),
                    cp = null;

                while (rollLen > 0) {
                    if (rollLen >= this.BUNGEE_REST_LEN) {
                        prev = parts[parts.length - 2];
                        cp = new ConstrainedPoint();
                        cp.setWeight(DEFAULT_PART_WEIGHT);
                        cp.pos = Vector.add(prev.pos, offset);
                        this.addPartAtIndex(cp, this.parts.length - 1);

                        tail.changeConstraintAndLength(prev, cp, heroRestLen);
                        cp.addConstraint(prev, this.BUNGEE_REST_LEN, ConstraintType.DISTANCE);
                        rollLen -= this.BUNGEE_REST_LEN;
                    }
                    else {
                        var newRestLen = rollLen + heroRestLen;
                        if (newRestLen > this.BUNGEE_REST_LEN) {
                            rollLen = this.BUNGEE_REST_LEN;
                            heroRestLen = newRestLen - this.BUNGEE_REST_LEN;
                        }
                        else {
                            prev = parts[parts.length - 2];
                            tail.changeRestLength(prev, newRestLen);
                            rollLen = 0;
                        }
                    }
                }
            },
            rollBack: function (amount) {
                var rollBackLen = amount,
                    parts = this.parts,
                    partsCount = parts.length,
                    prev = parts[partsCount - 2],
                    tail = this.tail,
                    heroRestLen = tail.restLength(prev),
                    oldAnchor;

                while (rollBackLen > 0) {
                    if (rollBackLen >= this.BUNGEE_REST_LEN) {

                        var oldAnchorIndex = partsCount - 2,
                            newAnchor = parts[partsCount - 3];

                        oldAnchor = parts[oldAnchorIndex];
                        tail.changeConstraintAndLength(oldAnchor, newAnchor, heroRestLen);
                        this.removePartAtIndex(oldAnchorIndex);
                        partsCount--;
                        rollBackLen -= this.BUNGEE_REST_LEN;
                    }
                    else {
                        var newRestLen = heroRestLen - rollBackLen;
                        if (newRestLen < 1) {
                            rollBackLen = this.BUNGEE_REST_LEN;
                            heroRestLen = this.BUNGEE_REST_LEN + newRestLen + 1;
                        }
                        else {
                            oldAnchor = parts[partsCount - 2];
                            tail.changeRestLength(oldAnchor, newRestLen);
                            rollBackLen = 0;
                        }
                    }
                }

                var newTailRestLen = (partsCount - 1) * (this.BUNGEE_REST_LEN + 3);
                var constraints = tail.constraints,
                    numConstraints = constraints.length;
                for (var i = 0; i < numConstraints; i++) {
                    var c = constraints[i];
                    if (c.type === ConstraintType.NOT_MORE_THAN)
                        c.restLength = newTailRestLen;
                }
                return rollBackLen;
            },
            strengthen: function () {
                var parts = this.parts,
                    numParts = parts.length;
                for (var i = 0; i < numParts; i++) {
                    var cp = parts[i];
                    if (this.bungeeAnchor.pin.x != Constants.UNDEFINED) {
                        if (cp != this.tail) {
                            cp.setWeight(STRENGTHENED_PART_WEIGHT);
                        }

                        if (i > 0) {
                            var restLen = i * (this.BUNGEE_REST_LEN + 3);
                            cp.addConstraint(this.bungeeAnchor, restLen, ConstraintType.NOT_MORE_THAN);
                        }
                    }
                }
            },
            /**
             * Updates the rope based on the time delta
             * @param delta {number}
             */
            update: function (delta) {
                if (this.cutTime > 0) {
                    this.cutTime = Mover.moveToTarget(this.cutTime, 0, 1, delta);
                    if (this.cutTime < CUT_DISSAPPEAR_TIMEOUT - WHITE_TIMEOUT && this.forceWhite) {
                        this.removePart(this.cut);
                    }
                }

                var parts = this.parts,
                    numParts = parts.length,
                    relaxationTimes = this.relaxationTimes,
                    tail = this.tail,
                    i, cp, k;

                for (i = 0; i < numParts; i++) {
                    cp = parts[i];
                    if (cp !== tail) {
                        //Log.debug('Before qcpUpdate, [' + i + '] : ' + cp.pos );
                        // NOTE: iOS calls qcpUpdate which is identical to update except
                        // it incorporates material forces. However, those don't appear to
                        // be used so we'll simply call update() instead.
                        cp.update(delta);
                    }
                }

                // satisfy constraints during each relaxation period
                satisfyConstraintArray(parts, relaxationTimes);

                // for (i = 0; i < relaxationTimes; i++) {
                //     for (k = 0; k < numParts; k++) {
                //         parts[k].satisfyConstraints();
                //     }
                // }
            },
            removePart: function (partIndex) {
                this.forceWhite = false;

                var parts = this.parts,
                    p1 = parts[partIndex],
                    p2 = parts[partIndex + 1];

                if (!p2) {
                    p1.removeConstraints();
                }
                else {
                    var p2Constraints = p2.constraints,
                        p2NumConstraints = p2Constraints.length;
                    for (var k = 0; k < p2NumConstraints; k++) {
                        var c = p2Constraints[k];
                        if (c.cp === p1) {
                            p2.removeConstraintAtIndex(k);

                            var np2 = new ConstrainedPoint();
                            np2.setWeight(0.00001);
                            np2.pos.copyFrom(p2.pos);
                            np2.prevPos.copyFrom(p2.prevPos);
                            this.addPartAtIndex(np2, partIndex + 1);
                            np2.addConstraint(p1, this.BUNGEE_REST_LEN, ConstraintType.DISTANCE);
                            break;
                        }
                    }
                }

                for (var i = 0, numParts = parts.length; i < numParts; i++) {
                    var cp = parts[i];
                    if (cp != this.tail)
                        cp.setWeight(0.00001);
                }
            },
            setCut: function (partIndex) {
                this.cut = partIndex;
                this.cutTime = CUT_DISSAPPEAR_TIMEOUT;
                this.forceWhite = true;
                this.highlighted = false;
            },
            draw: function () {
                var parts = this.parts,
                    count = parts.length,
                    ctx = Canvas.context,
                    i, part, prevPart;

                ctx.lineJoin = "round";
                ctx.lineWidth = this.lineWidth;

                if (this.cut === Constants.UNDEFINED) {
                    var pts = new Array(count);
                    for (i = 0; i < count; i++) {
                        pts[i] = parts[i].pos;
                        //Log.debug('Point ' + i + ': ' + pts[i].toString());
                    }
                    this.drawBungee(pts);
                }
                else {
                    var pts1 = [],
                        pts2 = [],
                        part2 = false;
                    for (i = 0; i < count; i++) {
                        part = parts[i];
                        var linked = true;

                        if (i > 0) {
                            prevPart = parts[i - 1];
                            if (!part.hasConstraint(prevPart)) {
                                linked = false;
                            }
                        }

                        if (part.pin.x === Constants.UNDEFINED && !linked) {
                            part2 = true;
                        }

                        if (!part2) {
                            pts1[i] = part.pos;
                        }
                        else {
                            pts2.push(part.pos);
                        }
                    }

                    if (pts1.length > 0) {
                        this.drawBungee(pts1);
                    }
                    if (pts2.length > 0 && !this.hideTailParts) {
                        this.drawBungee(pts2);
                    }
                }
                ctx.lineWidth = 1;
            },
            drawBungee: function (pts) {
                var count = pts.length,
                    points = this.BUNGEE_BEZIER_POINTS,
                    drawPts = this.drawPts;

                // we can't calc the distance for a single point
                if (count < 2)
                    return;

                // set the global alpha
                var alpha = (this.cut === Constants.UNDEFINED || this.forceWhite)
                    ? 1
                    : this.cutTime / (CUT_DISSAPPEAR_TIMEOUT - WHITE_TIMEOUT);

                if (alpha <= 0) {
                    return;
                }

                var firstPoint = pts[0],
                    secondPoint = pts[1],
                    tx = firstPoint.x - secondPoint.x,
                    ty = firstPoint.y - secondPoint.y,
                    ptsDistance = Math.sqrt(tx * tx + ty * ty);

                //Log.debug('DrawBungee - point1: ' + firstPoint + ' point2: ' + secondPoint);

                if (ptsDistance <= this.BUNGEE_REST_LEN + 0.3)
                    this.relaxed = 0;
                else if (ptsDistance <= this.BUNGEE_REST_LEN + 1)
                    this.relaxed = 1;
                else if (ptsDistance < this.BUNGEE_REST_LEN + 4)
                    this.relaxed = 2;
                else
                    this.relaxed = 3;

                if (count < 3)
                    return;

                var black = drawBlack,
                    c1 = drawC1,
                    d1 = drawD1,
                    c2 = drawC2,
                    d2 = drawD2;

                // reset the colors (we're reusing temp color objects)
                black.r = 0; black.g = 0; black.b = 0; black.a = alpha;
                c1.r = 95 / 200; c1.g = 61 / 200; c1.b = 37 / 200; c1.a = alpha;
                d1.r = 95 / 500; d1.g = 61 / 500; d1.b = 37 / 500; d1.a = alpha;
                c2.r = 152 / 225; c2.g = 99 / 225; c2.b = 62 / 225; c2.a = alpha;
                d2.r = 152 / 500; d2.g = 99 / 500; d2.b = 62 / 500; d2.a = alpha;

                if (this.highlighted) {
                    c1.r *= 3;
                    c1.g *= 3;
                    c1.b *= 3;
                    c2.r *= 3;
                    c2.g *= 3;
                    c2.b *= 3;
                    d1.r *= 3;
                    d1.g *= 3;
                    d1.b *= 3;
                    d2.r *= 3;
                    d2.g *= 3;
                    d2.b *= 3;
                }

                if (ptsDistance > this.BUNGEE_REST_LEN + 7 && !this.dontDrawRedStretch) {
                    var f = ptsDistance / this.BUNGEE_REST_LEN * 2;
                    d1.r *= f;
                    d2.r *= f;
                }

                var useC1 = false, // ropes have alternating color segments
                    numVertices = (count - 1) * points;

                // // colors
                // //noinspection UnnecessaryLocalVariableJS
                var b1 = d1,
                    colorDivisor = (numVertices - 1),
                    b1rf = (c1.r - d1.r) / colorDivisor,
                    b1gf = (c1.g - d1.g) / colorDivisor,
                    b1bf = (c1.b - d1.b) / colorDivisor;

                var numSegments = this.BUNGEE_BEZIER_POINTS - 1,
                    lastSegmentIndex = numSegments - 1,
                    ctx = Canvas.context,
                    previousAlpha = ctx.globalAlpha;

                // set the line style
                if (previousAlpha !== alpha) {
                    ctx.globalAlpha = alpha;
                }

                // store the first point in the path
                var firstDrawPoint = drawPts[0];
                if (!firstDrawPoint) {
                    firstDrawPoint = drawPts[0] = firstPoint.copy();
                } else {
                    firstDrawPoint.x = firstPoint.x;
                    firstDrawPoint.y = firstPoint.y;
                }

                var vertex, a, pathVector, currentColor;

                ctx.beginPath();

                var currentColor = b1.rgbaStyle();
                if (ctx.strokeStyle !== currentColor)
                    ctx.strokeStyle = currentColor;

                for (vertex = 1; vertex <= numVertices; vertex++) {
                    a = vertex / numVertices;

                    // use bezier to smooth the draw points
                    pathVector = drawPts[vertex];
                    if (!pathVector) {
                        pathVector = drawPts[vertex] = new Vector(0, 0);
                    }
                    Vector.setCalcPathBezier(pts, a, pathVector);

                    // see if we have all the points for this color section
                    var segmentIndex = (vertex - 1) % numSegments;
                    if (segmentIndex === lastSegmentIndex || vertex === numVertices) {

                        // decide which color to use for this section
                        // if (this.forceWhite) {
                        //     currentColor = RGBAColor.styles.SOLID_OPAQUE;
                        // }
                        // else if (useC1) {
                        //     currentColor = b1.rgbaStyle();
                        // }
                        // else {
                        //     currentColor = b2.rgbaStyle();
                        // }

                        //ctx.strokeStyle = currentColor;

                        // move to the beginning of the color section
                        var currentIndex = vertex - segmentIndex - 1;
                        var point = drawPts[currentIndex++];
                        ctx.moveTo(point.x, point.y);

                        // draw each line segment (2 segments per color section)
                        for (; currentIndex <= vertex; currentIndex++) {
                            point = drawPts[currentIndex];
                            ctx.lineTo(point.x, point.y);
                            //Log.debug('Segment to [' + point.x + ', ' + point.y + '] color: ' + currentColor );
                        }

                        //ctx.stroke();
                        //useC1 = !useC1;

                        var colorMultiplier = segmentIndex + 1;

                        // adjust colors
                        b1.r += b1rf * colorMultiplier;
                        b1.g += b1gf * colorMultiplier;
                        b1.b += b1bf * colorMultiplier;
                    }
                }

                ctx.stroke();

                // reset the alpha
                if (previousAlpha !== alpha) {
                    ctx.globalAlpha = previousAlpha;
                }
            }
        });


        // export const for use in GameScene
        Bungee.BUNGEE_RELAXION_TIMES = BUNGEE_RELAXION_TIMES;

        return Bungee;
    }
);
define('game/LevelState',
    [
        'edition'
    ],
    function (edition) {

        // manages state of the current level
        var LevelState = {
            loadedMap: null,
            pack: 0,
            level: 0,
            survival: false,

            loadLevel: function (pack, level) {
                this.pack = pack - 1;
                this.level = level - 1;

                var box = edition.boxes[this.pack];
                this.loadedMap = box.levels[this.level];
            }
        };

        return LevelState;
    }
);

define('visual/HorizontallyTiledImage',
    [
        'visual/ImageElement',
        'utils/Canvas',
        'core/Alignment'
    ],
    function (ImageElement, Canvas, Alignment) {

        var HorizontallyTiledImage = ImageElement.extend({
            init: function () {
                this._super();
            },
            /**
             * Set the texture for this image element
             * @param texture {Texture2D}
             */
            initTexture: function (texture) {
                this._super(texture);

                this.tiles = [];
                this.offsets = [];
                this.align = Alignment.CENTER;
            },
            setTileHorizontally: function (left, center, right) {
                this.tiles[0] = left;
                this.tiles[1] = center;
                this.tiles[2] = right;

                var h1 = this.texture.rects[left].h,
                    h2 = this.texture.rects[center].h,
                    h3 = this.texture.rects[right].h;

                if (h1 >= h2 && h1 >= h3) {
                    this.height = h1;
                }
                else if (h2 >= h1 && h2 >= h3) {
                    this.height = h2;
                }
                else {
                    this.height = h3;
                }

                this.offsets[0] = ~~((this.height - h1) / 2.0);
                this.offsets[1] = ~~((this.height - h2) / 2.0);
                this.offsets[2] = ~~((this.height - h3) / 2.0);
            },
            draw: function () {
                this.preDraw();

                var left = this.texture.rects[this.tiles[0]],
                    center = this.texture.rects[this.tiles[1]],
                    right = this.texture.rects[this.tiles[2]],
                    tileWidth = this.width - (~~left.w + ~~right.w),
                    ctx = Canvas.context,
                    dx = Math.round(this.drawX),
                    dy = Math.round(this.drawY),
                    leftCeilW = Math.ceil(left.w),
                    leftCeilH = Math.ceil(left.h),
                    rightCeilW = Math.ceil(right.w),
                    rightCeilH = Math.ceil(right.h);

                if (tileWidth >= 0) {
                    ctx.drawImage(this.texture.image,
                        left.x, left.y, leftCeilW, leftCeilH,
                        dx, dy + this.offsets[0], leftCeilW, leftCeilH);
                    this.drawTiled(this.tiles[1],
                        dx + leftCeilW, dy + this.offsets[1], tileWidth, center.h);
                    ctx.drawImage(this.texture.image,
                        right.x, right.y, rightCeilW, rightCeilH,
                        dx + leftCeilW + tileWidth, dy + this.offsets[2], rightCeilW, rightCeilH);
                }
                else {
                    var p1 = left.copy(),
                        p2 = right.copy();
                    p1.w = Math.min(p1.w, this.width / 2);
                    p2.w = Math.min(p2.w, this.width - p1.w);
                    p2.x += (right.w - p2.w);

                    ctx.drawImage(this.texture.image,
                        p1.x, p1.y, p1.w, p1.h,
                        dx, dy + this.offsets[0], p1.w, p1.h);
                    ctx.drawImage(this.texture.image,
                        p2.x, p2.y, p2.w, p2.h,
                        dx + p1.w, dy + this.offsets[2], p2.w, p2.h);
                }

                this.postDraw();
            },

            /**
             * Draw the tile image to an offscreen canvas and return an Image
             */
            getImage: function () {
                // save the existing canvas id and switch to the hidden canvas
                var existingCanvas = Canvas.element;

                // create a temporary canvas to use
                Canvas.setTarget(document.createElement('canvas'));

                // set the canvas width and height
                var canvas = Canvas.element,
                    imgWidth = Math.ceil(this.width),
                    imgHeight = Math.ceil(this.height);
                canvas.width = imgWidth;
                canvas.height = imgHeight;

                this.draw();
                var imageData = canvas.toDataURL('image/png'),
                    img = new Image();

                img.src = imageData;

                // NOTE: important to use jQuery to avoid intermittent dimension issues
                $(img).width(imgWidth).height(imgHeight);

                // restore the original canvas for the App
                if (existingCanvas) {
                    Canvas.setTarget(existingCanvas);
                }

                return img;
            }
        });

        return HorizontallyTiledImage;
    }
);

define('game/GrabMoveBackground',
    [
        'visual/ImageElement',
        'visual/HorizontallyTiledImage',
        'resolution',
        'resources/ResourceId',
        'core/Texture2D'
    ],
    function (ImageElement, HorizontallyTiledImage, resolution, ResourceId, Texture2D) {

        var IMG_OBJ_HOOK_MOVABLE_bottom_tile_left = 0;
        var IMG_OBJ_HOOK_MOVABLE_bottom_tile_right = 1;
        var IMG_OBJ_HOOK_MOVABLE_bottom_tile_middle = 2;

        var GrabMoveBackground = ImageElement.extend({
            init: function (length) {
                this._super();

                // render the tiled image once and cache the image
                var tiledImage = new HorizontallyTiledImage();
                tiledImage.initTextureWithId(ResourceId.IMG_OBJ_HOOK_MOVABLE);
                tiledImage.setTileHorizontally(
                    IMG_OBJ_HOOK_MOVABLE_bottom_tile_left,
                    IMG_OBJ_HOOK_MOVABLE_bottom_tile_middle,
                    IMG_OBJ_HOOK_MOVABLE_bottom_tile_right);

                tiledImage.width = length + resolution.GRAB_MOVE_BG_WIDTH;

                var completeImage = tiledImage.getImage();
                this.initTexture(new Texture2D(completeImage));
            }
        });

        return GrabMoveBackground;
    }
);

define('game/Grab',
    [
        'game/CTRGameObject',
        'utils/Radians',
        'game/Bungee',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'core/Vector',
        'utils/Mover',
        'utils/Constants',
        'resolution',
        'game/LevelState',
        'visual/Animation',
        'core/Alignment',
        'visual/ImageElement',
        'utils/Canvas',
        'core/RGBAColor',
        'visual/Timeline',
        'utils/MathHelper',
        'visual/HorizontallyTiledImage',
        'game/GrabMoveBackground'
    ],
    function (CTRGameObject, Radians, Bungee, SoundMgr, ResourceId, Vector, Mover, Constants, resolution, LevelState, Animation, Alignment, ImageElement, Canvas, RGBAColor, Timeline, MathHelper, HorizontallyTiledImage, GrabMoveBackground) {

        /**
         * @enum {number}
         */
        var SpiderState = {
            START: 0,
            WALK: 1,
            BUSTER: 2,
            CATCH: 3
        };

        /**
         * @enum {number}
         */
        var GunState = {
            SHOW: 0,
            HIDE: 1
        };

        var grabCircleCache = [];

        var Grab = CTRGameObject.extend({
            init: function () {
                this._super();
                this.rope = null;

                this.gun = false;
                this.gunFired = false;
                this.invisible = false;
                this.kicked = false;

                this.wheel = false;
                this.wheelOperating = Constants.UNDEFINED;
                this.lastWheelTouch = Vector.newZero();

                this.moveLength = 0;
                this.moveVertical = false;
                this.moveOffset = 0;
                this.moveBackground = null;
                this.grabMoverHighlight = null;
                this.grabMover = null;
                this.moverDragging = 0;
                this.minMoveValue = 0;
                this.maxMoveValue = 0;

                this.hasSpider = false;
                this.spiderActive = false;
                this.spider = null;
                this.spiderPos = 0;
                this.shouldActivate = false;

                this.wheelDirty = false;

                this.launcher = false;
                this.launcherSpeed = 0;
                this.launcherIncreaseSpeed = false;

                this.hideRadius = false;
                this.radiusAlpha = 0;
                this.radius = 0;

                this.balloon = false;
            },
            /**
             *
             * @param v1 {Vector} start
             * @param v2 {Vector} end
             * @param c {Vector} center
             */
            getRotateAngle: function (v1, v2, c) {
                var m1 = Vector.subtract(v1, c);
                var m2 = Vector.subtract(v2, c);

                var a = m2.normalizedAngle() - m1.normalizedAngle();
                return Radians.toDegrees(a);
            },
            /**
             *
             * @param v {Vector}
             */
            handleWheelTouch: function (x, y) {
                this.lastWheelTouch.x = x;
                this.lastWheelTouch.y = y;
            },
            handleWheelRotate: function (v) {
                SoundMgr.playSound(ResourceId.SND_WHEEL);

                var center = new Vector(this.x, this.y),
                    a = this.getRotateAngle(this.lastWheelTouch, v, center);
                if (a > 180) {
                    a -= 360;
                }
                else if (a < -180) {
                    a += 360;
                }

                this.wheelImage2.rotation += a;
                this.wheelImage3.rotation += a;
                this.wheelHighlight.rotation += a;

                a = (a > 0)
                    ? Math.min(Math.max(1, a), resolution.GRAB_WHEEL_MAX_ROTATION)
                    : Math.max(Math.min(-1, a), -resolution.GRAB_WHEEL_MAX_ROTATION);

                if (this.rope) {
                    if (a > 0) {
                        if (this.rope.getLength() < resolution.GRAB_ROPE_ROLL_MAX_LENGTH) {
                            this.rope.roll(a);
                        }
                    }
                    else if (a !== 0) {
                        if (this.rope.parts.length > 3) {
                            this.rope.rollBack(-a);
                        }
                    }
                    this.wheelDirty = true;
                }
                this.lastWheelTouch.copyFrom(v);
                
            },
            update: function (delta) {
                this._super(delta);

                if (this.launcher && this.rope) {
                    var anchor = this.rope.bungeeAnchor,
                        moveResult;
                    anchor.pos.x = this.x;
                    anchor.pos.y = this.y;
                    anchor.pin.copyFrom(anchor.pos);

                    if (this.launcherIncreaseSpeed) {
                        moveResult = Mover.moveToTargetWithStatus(
                            this.launcherSpeed, 200, 30, delta);
                        this.launcherSpeed = moveResult.value;
                        if (moveResult.reachedZero)
                            this.launcherIncreaseSpeed = false;
                    }
                    else {
                        moveResult = Mover.moveToTargetWithStatus(
                            this.launcherSpeed, 130, 30, delta);
                        this.launcherSpeed = moveResult.value;
                        if (moveResult.reachedZero)
                            this.launcherIncreaseSpeed = true;
                    }

                    this.mover.setMoveSpeed(this.launcherSpeed);
                }

                if (this.hideRadius) {
                    this.radiusAlpha -= 1.5 * delta;
                    if (this.radiusAlpha <= 0) {
                        this.radius = Constants.UNDEFINED;
                        this.hideRadius = false;
                    }
                }

                if (this.bee) {
                    var vt = this.mover.path[this.mover.targetPoint],
                        vp = this.mover.pos,
                        v = Vector.subtract(vt, vp),
                        a = 0,
                        MAX_ANGLE = 10;

                    if (Math.abs(v.x) > 15) {
                        a = (v.x > 0) ? MAX_ANGLE : -MAX_ANGLE;
                    }

                    this.bee.rotation = Mover.moveToTarget(
                        this.bee.rotation, a, 60, delta);
                }

                if (this.wheel && this.wheelDirty && this.rope) {
                    var len = this.rope.getLength() * 0.7;
                    if (len === 0) {
                        this.wheelImage2.scaleX = this.wheelImage2.scaleY = 0;
                    }
                    else {
                        this.wheelImage2.scaleX = this.wheelImage2.scaleY =
                            Math.max(0, Math.min(1.2, 1 - len / resolution.GRAB_WHEEL_SCALE_DIVISOR));
                    }
                }
            },
            updateSpider: function (delta) {
                if (this.hasSpider && this.shouldActivate) {
                    this.shouldActivate = false;
                    this.spiderActive = true;
                    SoundMgr.playSound(ResourceId.SND_SPIDER_ACTIVATE);
                    this.spider.playTimeline(SpiderState.START);
                }

                if (this.hasSpider && this.spiderActive) {
                    if (this.spider.currentTimelineIndex !== SpiderState.START) {
                        this.spiderPos += delta * resolution.SPIDER_SPEED;
                    }

                    var checkingPos = 0,
                        reachedCandy = false;
                        
                    if (this.rope) {
                        var drawPts = this.rope.drawPts,
                            BUNGEE_REST_LEN = resolution.BUNGEE_REST_LEN,
                            a = 2 * BUNGEE_REST_LEN / 3;
                        for (var i = 0, numPts = drawPts.length; i < numPts; i++) {

                            var c1 = drawPts[i],
                                c2 = drawPts[i + 1],
                                b = c1.distance(c2),
                                len = a > b ? a : b;

                            if (this.spiderPos >= checkingPos &&
                                (this.spiderPos < checkingPos + len || i > numPts - 3)) {
                                var overlay = this.spiderPos - checkingPos;
                                var c3 = Vector.subtract(c2, c1);
                                c3.multiply(overlay / len);
                                this.spider.x = c1.x + c3.x;
                                this.spider.y = c1.y + c3.y;

                                if (i > numPts - 3) {
                                    reachedCandy = true;
                                }

                                if (this.spider.currentTimelineIndex !== SpiderState.START) {
                                    this.spider.rotation = c3.normalizedAngle() * 57.29577951308232 + 270;
                                }

                                break;
                            }
                            else {
                                checkingPos += len;
                            }
                        }
                    }

                    if (reachedCandy) {
                        this.spiderPos = Constants.UNDEFINED;
                    }
                }
            },
            drawBack: function () {
                if (this.invisible)
                    return;
                if (this.gun)
                    return;

                if (this.kickable && this.kicked && this.rope) {
                    var pos = this.rope.bungeeAnchor.pos;
                    this.x = pos.x;
                    this.y = pos.y;
                }

                this.preDraw();

                if (this.moveLength > 0) {
                    this.moveBackground.draw();
                }
                else {
                    this.back.draw();
                }

                if (this.radius !== Constants.UNDEFINED || this.hideRadius) {
                    var color = new RGBAColor(0.2, 0.5, 0.9, this.radiusAlpha),
                        drawRadius = (this.radius !== Constants.UNDEFINED) ? this.radius : this.previousRadius;
                    this.drawGrabCircle(this.x, this.y, drawRadius, color);
                }
            },
            drawGrabCircle: function (x, y, radius, color) {

                if (radius < 0) {
                    return;
                }

                //generate a key for the cache
                var key = radius.toString() + "|" + color.rgbaStyle();
                var circleCnv;

                //check the cache first
                if (grabCircleCache[key]) {
                    circleCnv = grabCircleCache[key];
                    //console.log("EXISTS IN CACHE")
                } else {

                    //otherwise create it
                    circleCnv = document.createElement("canvas");
                    circleCnv.width = circleCnv.height = radius * 2 + 4;

                    //document.body.appendChild(circleCnv)

                    var ctx = circleCnv.getContext("2d"),
                        totalRadians = 2 * Math.PI,
                        radiusScaleFactor = (resolution.CANVAS_SCALE * 2),
                        scaledRadius = radius / radiusScaleFactor,
                        segments = Math.max(16, Math.round(scaledRadius));

                    // make sure we have an even number of segments
                    if (segments % 2 !== 0) {
                        segments++;
                    }

                    ctx.lineWidth = 2;
                    ctx.strokeStyle = color.rgbaStyle();

                    var segmentRadians = totalRadians / segments;
                    for (var i = 0; i < segments; i++) {
                        // only draw every other segment for dashed circle
                        if (i % 2 === 0) {
                            var startRadians = (i / segments) * totalRadians;
                            ctx.beginPath();
                            ctx.arc(radius + 2, radius + 2, radius, startRadians, startRadians + segmentRadians, false);
                            ctx.stroke();
                            ctx.closePath();
                        }
                    }

                    grabCircleCache[key] = circleCnv;
                    //console.log("DRAW GRAB CIRCLE", circleCnv)
                }

                var mainCtx = Canvas.context;
                mainCtx.drawImage(circleCnv, x - radius - 2, y - radius - 2)
                
                
            },
            drawBB: function () {
                if (this.wheel) {
                    this.drawGrabCircle(
                        this.x,
                        this.y,
                        resolution.GRAB_WHEEL_RADIUS,
                        RGBAColor.red);
                }
            },
            draw: function () {
                if (this.invisible)
                    return;

                // NOTE: we do pre-draw when drawing the back so the position
                // of the back is adjusted. Otherwise the back can be offset
                // when there are large moves to position (grab is on DJ disc)

                var b = this.rope;

                if (this.wheel) {
                    this.wheelHighlight.visible = (this.wheelOperating !== Constants.UNDEFINED);
                    this.wheelImage3.visible = (this.wheelOperating === Constants.UNDEFINED);
                    this.wheelImage.draw();
                }

                if (this.gun) {
                    this.gunBack.draw();
                    if (!this.gunFired) {
                        this.gunArrow.draw();
                    }
                }

                if (b) {
                    b.draw();
                }

                if (this.moveLength <= 0) {
                    this.front.draw();
                }
                else {
                    if (this.moverDragging != Constants.UNDEFINED) {
                        this.grabMoverHighlight.draw();
                    }
                    else {
                        this.grabMover.draw();
                    }
                }

                if (this.wheel) {
                    this.wheelImage2.draw();
                }

                this.postDraw();
            },
            drawSpider: function () {
                this.spider.draw();
            },
            drawGunCup: function () {
                this.gunCup.draw();
            },
            setRope: function (rope) {
                this.rope = rope;
                this.previousRadius = this.radius;
                this.radius = Constants.UNDEFINED;
                if (this.hasSpider) {
                    this.shouldActivate = true;
                }
            },
            setLauncher: function () {
                this.launcher = true;
                this.launcherIncreaseSpeed = true;
                this.launcherSpeed = 130;
                var m = new Mover(100, this.launcherSpeed, 0);
                m.setPathFromString('RC30', new Vector(this.x, this.y));
                this.setMover(m);
                m.start();
            },
            setRadius: function (radius) {
                this.previousRadius = this.radius;
                this.radius = radius;

                // TODO: handle gun

                if (radius === Constants.UNDEFINED || radius === Constants.CANDY2_FLAG) {
                    var imageId = MathHelper.randomRange(
                        ResourceId.IMG_OBJ_HOOK_01,
                        ResourceId.IMG_OBJ_HOOK_02);
                    this.back = ImageElement.create(imageId, IMG_OBJ_HOOK_01_bottom);
                    this.back.doRestoreCutTransparency();
                    this.back.anchor = this.back.parentAnchor = Alignment.CENTER;
                    this.front = ImageElement.create(imageId, IMG_OBJ_HOOK_01_top);
                    this.front.anchor = this.front.parentAnchor = Alignment.CENTER;
                    this.addChild(this.back);
                    this.addChild(this.front);
                    this.back.visible = false;
                    this.front.visible = false;
                }
                else {
                    this.back = ImageElement.create(ResourceId.IMG_OBJ_HOOK_AUTO, IMG_OBJ_HOOK_AUTO_bottom);
                    this.back.doRestoreCutTransparency();
                    this.back.anchor = this.back.parentAnchor = Alignment.CENTER;
                    this.front = ImageElement.create(ResourceId.IMG_OBJ_HOOK_AUTO, IMG_OBJ_HOOK_AUTO_top);
                    this.front.anchor = this.front.parentAnchor = Alignment.CENTER;
                    this.addChild(this.back);
                    this.addChild(this.front);
                    this.back.visible = false;
                    this.front.visible = false;

                    this.radiusAlpha = resolution.GRAB_RADIUS_ALPHA;
                    this.hideRadius = false;
                }

                if (this.wheel) {
                    this.wheelImage = ImageElement.create(ResourceId.IMG_OBJ_HOOK_REGULATED,
                        IMG_OBJ_HOOK_REGULATED_bottom);
                    this.wheelImage.anchor = this.wheelImage.parentAnchor = Alignment.CENTER;
                    this.addChild(this.wheelImage);
                    this.wheelImage.visible = false;

                    this.wheelImage2 = ImageElement.create(ResourceId.IMG_OBJ_HOOK_REGULATED,
                        IMG_OBJ_HOOK_REGULATED_rope);
                    this.wheelImage2.passTransformationsToChilds = false;

                    this.wheelHighlight = ImageElement.create(ResourceId.IMG_OBJ_HOOK_REGULATED,
                        IMG_OBJ_HOOK_REGULATED_active);
                    this.wheelHighlight.anchor = this.wheelHighlight.parentAnchor = Alignment.CENTER;
                    this.wheelImage2.addChild(this.wheelHighlight);

                    this.wheelImage3 = ImageElement.create(ResourceId.IMG_OBJ_HOOK_REGULATED,
                        IMG_OBJ_HOOK_REGULATED_top);
                    this.wheelImage3.anchor = this.wheelImage3.parentAnchor =
                        this.wheelImage2.anchor = this.wheelImage2.parentAnchor = Alignment.CENTER;
                    this.wheelImage2.addChild(this.wheelImage3);
                    this.addChild(this.wheelImage2);
                    this.wheelImage2.visible = false;
                    this.wheelDirty = false;
                }
            },
            setMoveLength: function (length, vertical, offset) {
                this.moveLength = length;
                this.moveVertical = vertical;
                this.moveOffset = offset;

                if (this.moveLength > 0) {
                    this.moveBackground = new GrabMoveBackground(length);
                    this.moveBackground.rotationCenterX = -Math.round(
                        this.moveBackground.width / 2) + resolution.GRAB_MOVE_BG_X_OFFSET;
                    this.moveBackground.x = -resolution.GRAB_MOVE_BG_X_OFFSET;

                    this.grabMoverHighlight = ImageElement.create(
                        ResourceId.IMG_OBJ_HOOK_MOVABLE, IMG_OBJ_HOOK_MOVABLE_active);
                    this.grabMoverHighlight.visible = false;
                    this.grabMoverHighlight.anchor = this.grabMoverHighlight.parentAnchor = Alignment.CENTER;
                    this.addChild(this.grabMoverHighlight);

                    this.grabMover = ImageElement.create(
                        ResourceId.IMG_OBJ_HOOK_MOVABLE, IMG_OBJ_HOOK_MOVABLE_top);
                    this.grabMover.visible = false;
                    this.grabMover.anchor = this.grabMover.parentAnchor = Alignment.CENTER;
                    this.addChild(this.grabMover);
                    this.grabMover.addChild(this.moveBackground);

                    if (this.moveVertical) {
                        this.moveBackground.rotation = 90;
                        this.moveBackground.y = -this.moveOffset;
                        this.minMoveValue = this.y - this.moveOffset;
                        this.maxMoveValue = this.y + (this.moveLength - this.moveOffset);
                        this.grabMover.rotation = 90;
                        this.grabMoverHighlight.rotation = 90;
                    }
                    else {
                        this.minMoveValue = this.x - this.moveOffset;
                        this.maxMoveValue = this.x + (this.moveLength - this.moveOffset);
                        this.moveBackground.x += -this.moveOffset;
                    }
                    this.moveBackground.anchor = Alignment.VCENTER | Alignment.LEFT;
                    this.moveBackground.x += this.x;
                    this.moveBackground.y += this.y;
                    this.moveBackground.visible = false;
                }

                this.moverDragging = Constants.UNDEFINED;
            },
            setBee: function () {
                this.bee = ImageElement.create(ResourceId.IMG_OBJ_BEE_HD, IMG_OBJ_BEE_HD_obj_bee);
                this.bee.doRestoreCutTransparency();
                this.bee.parentAnchor = Alignment.CENTER;

                var wings = new Animation();
                wings.initTextureWithId(ResourceId.IMG_OBJ_BEE_HD);
                wings.parentAnchor = wings.anchor = Alignment.TOP | Alignment.LEFT;
                wings.doRestoreCutTransparency();
                wings.addAnimationDelay(0.03, Timeline.LoopType.PING_PONG, IMG_OBJ_BEE_HD_wings_start, IMG_OBJ_BEE_HD_wings_end);
                wings.playTimeline(0);
                wings.jumpTo(MathHelper.randomRange(0, IMG_OBJ_BEE_HD_wings_end - IMG_OBJ_BEE_HD_wings_start));
                this.bee.addChild(wings);

                var p = this.bee.texture.offsets[IMG_OBJ_BEE_HD__rotation_center];
                this.bee.x = -p.x;
                this.bee.y = -p.y;

                this.bee.rotationCenterX = p.x - (this.bee.width / 2);
                this.bee.rotationCenterY = p.y - (this.bee.width / 2);
                this.bee.scaleX = this.bee.scaleY = 1 / 1.3;

                this.addChild(this.bee);
            },
            setSpider: function (hasSpider) {
                this.hasSpider = hasSpider;
                this.shouldActivate = false;
                this.spiderActive = false;

                this.spider = new Animation();
                this.spider.initTextureWithId(ResourceId.IMG_OBJ_SPIDER);
                this.spider.doRestoreCutTransparency();
                this.spider.anchor = Alignment.CENTER;
                this.spider.x = this.x;
                this.spider.y = this.y;
                this.spider.visible = false;

                // add spider animations
                this.spider.addAnimationEndpoints(SpiderState.START, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_OBJ_SPIDER_activation_start, IMG_OBJ_SPIDER_activation_end);
                this.spider.setDelay(0.4, 5, SpiderState.START);
                this.spider.addAnimationEndpoints(SpiderState.WALK, 0.1, Timeline.LoopType.REPLAY,
                    IMG_OBJ_SPIDER_crawl_start, IMG_OBJ_SPIDER_crawl_end);
                this.spider.switchToAnimation(SpiderState.WALK, SpiderState.START, 0.05);

                this.addChild(this.spider);
            },
            destroyRope: function () {
                this.rope = null;
            }
        });

        // TODO: move into spider
        var IMG_OBJ_SPIDER_activation_start = 0;
        var IMG_OBJ_SPIDER_activation_end = 6;
        var IMG_OBJ_SPIDER_crawl_start = 7;
        var IMG_OBJ_SPIDER_crawl_end = 10;

        var IMG_OBJ_HOOK_AUTO_bottom = 0;
        var IMG_OBJ_HOOK_AUTO_top = 1;

        var IMG_OBJ_HOOK_01_bottom = 0;
        var IMG_OBJ_HOOK_01_top = 1;

        var IMG_OBJ_HOOK_02_bottom = 0;
        var IMG_OBJ_HOOK_02_top = 1;

        var IMG_OBJ_HOOK_REGULATED_bottom = 0;
        var IMG_OBJ_HOOK_REGULATED_rope = 1;
        var IMG_OBJ_HOOK_REGULATED_active = 2;
        var IMG_OBJ_HOOK_REGULATED_top = 3;

        var IMG_OBJ_HOOK_MOVABLE_active = 3;
        var IMG_OBJ_HOOK_MOVABLE_top = 4;

        // bees
        var IMG_OBJ_BEE_HD__rotation_center = 0;
        var IMG_OBJ_BEE_HD_obj_bee = 1;
        var IMG_OBJ_BEE_HD_wings_start = 2;
        var IMG_OBJ_BEE_HD_wings_end = 4;

        return Grab;
    }
);
define('game/Pump',
    [
        'visual/GameObject',
        'core/Vector',
        'utils/Radians'
    ],
    function (GameObject, Vector, Radians) {

        var Pump = GameObject.extend({
            init: function () {
                this._super();
                this.angle = 0;
                this.t1 = Vector.newZero();
                this.t2 = Vector.newZero();
                this.touchTimer = 0;
                this.touch = 0;
            },
            updateRotation: function () {
                var bbHalfWidth = this.bb.w / 2;

                this.t1.x = this.x - bbHalfWidth;
                this.t2.x = this.x + bbHalfWidth;
                this.t1.y = this.t2.y = this.y;

                this.angle = Radians.fromDegrees(this.rotation);

                this.t1.rotateAround(this.angle, this.x, this.y);
                this.t2.rotateAround(this.angle, this.x, this.y);
            }
        });

        return Pump;
    }
);
define('game/PumpDirt',
    [
        'visual/MultiParticles',
        'resolution',
        'core/Vector',
        'core/Rectangle',
        'utils/MathHelper'
    ],
    function (MultiParticles, resolution, Vector, Rectangle, MathHelper) {

        var IMG_OBJ_PUMP_pump_start = 0,
            IMG_OBJ_PUMP_pump_end = 5,
            IMG_OBJ_PUMP_particle_1 = 6,
            IMG_OBJ_PUMP_particle_2 = 7,
            IMG_OBJ_PUMP_particle_3 = 8;

        var PumpDirt = MultiParticles.extend({
            init: function (numParticles, texture, angle) {
                this._super(numParticles, texture);

                this.angle = angle;
                this.angleVar = 10;

                this.speed = resolution.PUMP_DIRT_SPEED;

                // life of particles
                this.life = 0.6;

                // size in pixels
                this.size = 0.002;

                // emissions per second
                this.emissionRate = 50;

                // color of particles
                this.startColor.r = 1.0;
                this.startColor.g = 1.0;
                this.startColor.b = 1.0;
                this.startColor.a = 0.6;

                this.endColor.r = 1.0;
                this.endColor.g = 1.0;
                this.endColor.b = 1.0;
                this.endColor.a = 0.0;

                this.additive = true;
            },
            initParticle: function (particle) {
                this._super(particle);

                var texture = this.imageGrid,
                    n = MathHelper.randomRange(IMG_OBJ_PUMP_particle_1, IMG_OBJ_PUMP_particle_3),
                    tquad = texture.rects[n],
                    vquad = new Rectangle(0, 0, 0, 0); // don't draw initially

                this.drawer.setTextureQuad(this.particles.length, tquad, vquad, 1);

                var particleSize = resolution.PUMP_DIRT_PARTICLE_SIZE;
                particle.width = particleSize;
                particle.height = particleSize;
            },
            updateParticleLocation: function (p, delta) {
                p.dir.multiply(0.90);
                var tmp = Vector.multiply(p.dir, delta);
                tmp.add(this.gravity);
                p.pos.add(tmp);
            }
        });

        return PumpDirt;
    }
);

define('game/Sock',
    [
        'game/CTRGameObject',
        'visual/Animation',
        'resources/ResourceId',
        'core/Alignment',
        'visual/Timeline',
        'resolution',
        'core/Vector',
        'utils/Mover',
        'utils/Radians',
        'utils/Canvas'
    ],
    function (CTRGameObject, Animation, ResourceId, Alignment, Timeline, resolution, Vector, Mover, Radians, Canvas) {

        var Sock = CTRGameObject.extend({
            init: function () {
                this._super();

                this.group = 0;
                this.angle = 0;
                this.t1 = new Vector(0, 0);
                this.t2 = new Vector(0, 0);
                this.b1 = new Vector(0, 0);
                this.b2 = new Vector(0, 0);

                this.idleTimeout = 0;
            },
            createAnimations: function () {
                this.light = new Animation();
                this.light.initTextureWithId(ResourceId.IMG_OBJ_SOCKS);
                this.light.anchor = Alignment.BOTTOM | Alignment.HCENTER;
                this.light.parentAnchor = Alignment.TOP | Alignment.HCENTER;

                this.light.y = resolution.SOCK_LIGHT_Y;
                this.light.x = 0;
                this.light.addAnimationSequence(0, 0.05, Timeline.LoopType.NO_LOOP, 4,
                    [ Sock.Quads.IMG_OBJ_SOCKS_glow_start, Sock.Quads.IMG_OBJ_SOCKS_glow_start + 1,
                        Sock.Quads.IMG_OBJ_SOCKS_glow_start + 2, Sock.Quads.IMG_OBJ_SOCKS_glow_start + 2]);
                this.light.doRestoreCutTransparency();
                this.light.visible = false;
                this.addChild(this.light);
            },
            updateRotation: function () {
                this.t1.x = this.x - resolution.SOCK_WIDTH / 2;
                this.t2.x = this.x + resolution.SOCK_WIDTH / 2;
                this.t1.y = this.t2.y = this.y;

                this.b1.x = this.t1.x;
                this.b2.x = this.t2.x;
                this.b1.y = this.b2.y = this.y + resolution.SOCK_ROTATION_Y_OFFSET;

                this.angle = Radians.fromDegrees(this.rotation);

                this.t1.rotateAround(this.angle, this.x, this.y);
                this.t2.rotateAround(this.angle, this.x, this.y);
                this.b1.rotateAround(this.angle, this.x, this.y);
                this.b2.rotateAround(this.angle, this.x, this.y);
            },
            draw: function () {
                var tl = this.light.currentTimeline;
                if (tl && tl.state === Timeline.StateType.STOPPED) {
                    this.light.visible = false;
                }
                this._super();
            },
            drawBB: function () {
                // DEBUG: draw bounding lines for transport area
                if (false) {
                    var ctx = Canvas.context;
                    ctx.lineWidth = 3;

                    ctx.beginPath();
                    ctx.strokeStyle = 'red';
                    ctx.moveTo(this.t1.x, this.t1.y);
                    ctx.lineTo(this.t2.x, this.t2.y);
                    ctx.stroke();

                    ctx.beginPath();
                    ctx.strokeStyle = 'blue';
                    ctx.moveTo(this.b1.x, this.b1.y);
                    ctx.lineTo(this.b2.x, this.b2.y);
                    ctx.stroke();
                }
            },
            update: function (delta) {
                this._super(delta);
                if (this.mover) {
                    this.updateRotation();
                }
            }
        });

        /**
         * @enum {number}
         */
        Sock.Quads = {
            IMG_OBJ_SOCKS_hat_01: 0,
            IMG_OBJ_SOCKS_hat_02: 1,
            IMG_OBJ_SOCKS_glow_start: 2,
            IMG_OBJ_SOCKS_level: 3,
            IMG_OBJ_SOCKS_glow_end: 4
        };

        /**
         * @enum {number}
         */
        Sock.StateType = {
            RECEIVING: 0,
            THROWING: 1,
            IDLE: 2
        };

        /**
         * @const {number}
         */
        Sock.IDLE_TIMEOUT = 0.8;

        return Sock;
    }
);

define('visual/GenericButton',
    [
        'visual/BaseElement',
        'visual/ImageElement',
        'core/Rectangle',
        'utils/Constants',
        'core/Alignment'
    ],
    function (BaseElement, ImageElement, Rectangle, Constants, Alignment) {

        var TOUCH_MOVE_AND_UP_ZONE_INCREASE = 15;

        var GenericButton = BaseElement.extend({
            init: function (id) {
                this._super();

                this.buttonId = id;
                this.state = GenericButton.StateType.UP;

                this.touchLeftInc = 0.0;
                this.touchRightInc = 0.0;
                this.touchTopInc = 0.0;
                this.touchBottomInc = 0.0;

                this.onButtonPressed = null;

                this.forcedTouchZone = new Rectangle(
                    Constants.UNDEFINED,
                    Constants.UNDEFINED,
                    Constants.UNDEFINED,
                    Constants.UNDEFINED);
            },
            initWithElements: function (up, down) {
                up.parentAnchor = down.parentAnchor = Alignment.TOP | Alignment.LEFT;
                this.addChildWithID(up, GenericButton.StateType.UP);
                this.addChildWithID(down, GenericButton.StateType.DOWN);
                this.setState(GenericButton.StateType.UP);
            },
            initWithTextures: function (upTexture, downTexture) {

                var up = new ImageElement();
                up.initTexture(upTexture);

                var down = new ImageElement();
                down.initTexture(downTexture);

                this.initWithElements(up, down);
            },
            forceTouchRect: function (rect) {
                this.forcedTouchZone = rect;
            },
            setTouchIncrease: function (left, right, top, bottom) {
                this.touchLeftInc = left;
                this.touchRightInc = right;
                this.touchTopInc = top;
                this.touchBottomInc = bottom;
            },
            setState: function (s) {
                this.state = s;
                var up = this.getChild(GenericButton.StateType.UP),
                    down = this.getChild(GenericButton.StateType.DOWN);

                up.setEnabled(s === GenericButton.StateType.UP);
                down.setEnabled(s === GenericButton.StateType.DOWN);
            },
            isInTouchZone: function (tx, ty, td) {
                var tzIncrease = td ? 0 : TOUCH_MOVE_AND_UP_ZONE_INCREASE;

                if (this.forcedTouchZone.w !== Constants.UNDEFINED) {
                    return Rectangle.pointInRect(tx, ty,
                        this.drawX + this.forcedTouchZone.x - tzIncrease,
                        this.drawY + this.forcedTouchZone.y - tzIncrease,
                        this.forcedTouchZone.w + tzIncrease * 2,
                        this.forcedTouchZone.h + tzIncrease * 2);
                }
                else {
                    return Rectangle.pointInRect(tx, ty,
                        this.drawX - this.touchLeftInc - tzIncrease,
                        this.drawY - this.touchTopInc - tzIncrease,
                        this.width + (this.touchLeftInc + this.touchRightInc) + tzIncrease * 2,
                        this.height + (this.touchTopInc + this.touchBottomInc) + tzIncrease * 2);
                }
            },
            onTouchDown: function (tx, ty) {
                this._super(tx, ty);

                if (this.state === GenericButton.StateType.UP) {
                    if (this.isInTouchZone(tx, ty, true)) {
                        this.setState(GenericButton.StateType.DOWN);
                        return true;
                    }
                }

                return false;
            },
            onTouchUp: function (tx, ty) {
                this._super(tx, ty);

                if (this.state === GenericButton.StateType.DOWN) {
                    this.setState(GenericButton.StateType.UP);
                    if (this.isInTouchZone(tx, ty, false)) {
                        if (this.onButtonPressed) {
                            this.onButtonPressed(this.buttonId);
                        }
                        return true;
                    }
                }

                return false;
            },
            onTouchMove: function (tx, ty) {
                this._super(tx, ty);

                if (this.state === GenericButton.StateType.DOWN) {
                    if (!this.isInTouchZone(tx, ty, false)) {
                        this.setState(GenericButton.StateType.UP);
                    }
                    else {
                        return true;
                    }
                }

                return false;
            },
            addChildWithID: function (child, id) {
                this._super(child, id);

                child.parentAnchor = Alignment.TOP | Alignment.LEFT;

                if (id === GenericButton.StateType.DOWN) {
                    this.width = child.width;
                    this.height = child.height;
                    this.setState(GenericButton.StateType.UP);
                }
            }
        });

        GenericButton.StateType = {
            UP: 0,
            DOWN: 1
        };

        return GenericButton;
    }
);

define('game/Spikes',
    [
        'game/CTRGameObject',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'core/Vector',
        'visual/Timeline',
        'utils/Constants',
        'utils/Radians',
        'utils/Mover',
        'visual/ImageElement',
        'visual/GenericButton',
        'core/Alignment',
        'visual/KeyFrame',
        'utils/Canvas',
        'resolution'
    ],
    function (CTRGameObject, SoundMgr, ResourceId, Vector, Timeline, Constants, Radians, Mover, ImageElement, GenericButton, Alignment, KeyFrame, Canvas, resolution) {

        var Spikes = CTRGameObject.extend({
            init: function (px, py, width, angle, t) {
                this._super();

                // select and load the spikes image
                var imageId;
                if (t !== Constants.UNDEFINED) {
                    imageId = ResourceId.IMG_OBJ_ROTATABLE_SPIKES_01 + width - 1;
                }
                else {
                    switch (width) {
                        case 1:
                            imageId = ResourceId.IMG_OBJ_SPIKES_01;
                            break;
                        case 2:
                            imageId = ResourceId.IMG_OBJ_SPIKES_02;
                            break;
                        case 3:
                            imageId = ResourceId.IMG_OBJ_SPIKES_03;
                            break;
                        case 4:
                            imageId = ResourceId.IMG_OBJ_SPIKES_04;
                            break;
                        case 5:
                            imageId = ResourceId.IMG_OBJ_ELECTRODES;
                            break;
                    }
                }
                this.initTextureWithId(imageId);

                if (t > 0) {
                    this.doRestoreCutTransparency();
                    var normalQuad = IMG_OBJ_ROTATABLE_SPIKES_BUTTON_button_1 + (t - 1) * 2,
                        pressedQuad = IMG_OBJ_ROTATABLE_SPIKES_BUTTON_button_1_pressed + (t - 1) * 2,
                        bup = ImageElement.create(ResourceId.IMG_OBJ_ROTATABLE_SPIKES_BUTTON, normalQuad),
                        bdown = ImageElement.create(ResourceId.IMG_OBJ_ROTATABLE_SPIKES_BUTTON, pressedQuad);

                    bup.doRestoreCutTransparency();
                    bdown.doRestoreCutTransparency();

                    this.rotateButton = new GenericButton(SPIKES_ROTATION_BUTTON);
                    this.rotateButton.initWithElements(bup, bdown);
                    this.rotateButton.onButtonPressed = $.proxy(this.onButtonPressed, this);
                    this.rotateButton.anchor = this.rotateButton.parentAnchor = Alignment.CENTER;
                    this.addChild(this.rotateButton);

                    // restore bounding box without alpha
                    var buttonTexture = bup.texture,
                        vo = buttonTexture.offsets[normalQuad],
                        vr = buttonTexture.rects[normalQuad],
                        vs = new Vector(vr.w, vr.h),
                        vo2 = new Vector(buttonTexture.preCutSize.x, buttonTexture.preCutSize.y);

                    vo2.subtract(vs);
                    vo2.subtract(vo);

                    this.rotateButton.setTouchIncrease(
                        -vo.x + vs.x / 2,
                        -vo2.x + vs.x / 2,
                        -vo.y + vs.y / 2,
                        -vo2.y + vs.y / 2);

                    
                }

                this.passColorToChilds = false;
                this.spikesNormal = false;
                this.originalRotation = this.rotation = angle;

                this.t1 = Vector.newZero();
                this.t2 = Vector.newZero();
                this.b1 = Vector.newZero();
                this.b2 = Vector.newZero();

                this.electro = false;
                this.initialDelay = 0;
                this.onTime = 0;
                this.offTime = 0;
                this.electroOn = false;
                this.electroTimer = 0;

                this.x = px;
                this.y = py;

                this.setToggled(t);
                this.updateRotation();

                if (width === 5) {
                    this.addAnimationEndpoints(
                        SpikeAnimation.ELECTRODES_BASE,
                        0.05,
                        Timeline.LoopType.REPLAY,
                        IMG_OBJ_ELECTRODES_base,
                        IMG_OBJ_ELECTRODES_base);
                    this.addAnimationEndpoints(
                        SpikeAnimation.ELECTRODES_ELECTRIC,
                        0.05,
                        Timeline.LoopType.REPLAY,
                        IMG_OBJ_ELECTRODES_electric_start,
                        IMG_OBJ_ELECTRODES_electric_end);
                    this.doRestoreCutTransparency();
                }
                this.touchIndex = Constants.UNDEFINED;
            },
            updateRotation: function () {
                var pWidth = this.electro
                    ? this.width - (400 * resolution.CANVAS_SCALE)
                    : this.texture.rects[this.quadToDraw].w;

                pWidth /= 2;

                this.t1.x = this.x - pWidth;
                this.t2.x = this.x + pWidth;
                this.t1.y = this.t2.y = this.y - SPIKES_HEIGHT / 2.0;

                this.b1.x = this.t1.x;
                this.b2.x = this.t2.x;
                this.b1.y = this.b2.y = this.y + SPIKES_HEIGHT / 2.0;

                this.angle = Radians.fromDegrees(this.rotation);

                this.t1.rotateAround(this.angle, this.x, this.y);
                this.t2.rotateAround(this.angle, this.x, this.y);
                this.b1.rotateAround(this.angle, this.x, this.y);
                this.b2.rotateAround(this.angle, this.x, this.y);
            },
            turnElectroOn: function () {
                this.electroOn = true;
                this.playTimeline(SpikeAnimation.ELECTRODES_ELECTRIC);
                this.electroTimer = this.onTime;
                SoundMgr.playLoopedSound(ResourceId.SND_ELECTRIC);
            },
            turnElectroOff: function () {
                this.electroOn = false;
                this.playTimeline(SpikeAnimation.ELECTRODES_BASE);
                this.electroTimer = this.offTime;
                SoundMgr.stopSound(ResourceId.SND_ELECTRIC);
            },
            update: function (delta) {
                this._super(delta);

                if (this.mover || this.shouldUpdateRotation) {
                    this.updateRotation();
                }

                if (this.electro) {
                    if (this.electroOn) {
                        this.electroTimer = Mover.moveToTarget(this.electroTimer, 0, 1, delta);
                        if (this.electroTimer === 0) {
                            this.turnElectroOff();
                        }
                    }
                    else {
                        this.electroTimer = Mover.moveToTarget(this.electroTimer, 0, 1, delta);
                        if (this.electroTimer === 0) {
                            this.turnElectroOn();
                        }
                    }
                }
            },
            setToggled: function (t) {
                this.toggled = t;
            },
            getToggled: function () {
                return this.toggled;
            },
            rotateSpikes: function () {
                this.spikesNormal = !this.spikesNormal;
                this.removeTimeline(SpikeAnimation.ROTATION_ADJUSTED);

                var rDelta = this.spikesNormal ? 90 : 0,
                    adjustedRotation = this.originalRotation + rDelta,
                    tl = new Timeline();
                tl.addKeyFrame(KeyFrame.makeRotation(
                    this.rotation,
                    KeyFrame.TransitionType.LINEAR,
                    0));
                tl.addKeyFrame(KeyFrame.makeRotation(
                    adjustedRotation,
                    KeyFrame.TransitionType.EASE_OUT,
                    Math.abs(adjustedRotation - this.rotation) / 90 * 0.3));
                tl.onFinished = $.proxy(this.timelineFinished, this);

                this.addTimelineWithID(tl, SpikeAnimation.ROTATION_ADJUSTED);
                this.playTimeline(SpikeAnimation.ROTATION_ADJUSTED);
                this.shouldUpdateRotation = true;
                this.rotateButton.scaleX = -this.rotateButton.scaleX;
            },
            timelineFinished: function (t) {
                // update rotation one last time now that timeline is complete
                this.updateRotation();
                this.shouldUpdateRotation = false;
            },
            onButtonPressed: function (n) {
                if (n === SPIKES_ROTATION_BUTTON) {
                    if (this.onRotateButtonPressed) {
                        this.onRotateButtonPressed(this.toggled);
                    }

                    if (this.spikesNormal) {
                        SoundMgr.playSound(ResourceId.SND_SPIKE_ROTATE_IN);
                    }
                    else {
                        SoundMgr.playSound(ResourceId.SND_SPIKE_ROTATE_OUT);
                    }
                }
            },
            drawBB: function () {
                var ctx = Canvas.context;
                ctx.beginPath();
                ctx.strokeStyle = 'red';
                ctx.moveTo(this.t1.x, this.t1.y);
                ctx.lineTo(this.t2.x, this.t2.y);
                ctx.lineTo(this.b2.x, this.b2.y);
                ctx.lineTo(this.b1.x, this.b1.y);
                ctx.lineTo(this.t1.x, this.t1.y);
                ctx.closePath();
                ctx.stroke();
            }
        });

        /**
         * @const
         * @type {number}
         */
        var SPIKES_HEIGHT = 10;

        var IMG_OBJ_ELECTRODES_base = 0;
        var IMG_OBJ_ELECTRODES_electric_start = 1;
        var IMG_OBJ_ELECTRODES_electric_end = 4;

        var SPIKES_ROTATION_BUTTON = 0;

        var IMG_OBJ_ROTATABLE_SPIKES_01_Shape_3 = 0;
        var IMG_OBJ_ROTATABLE_SPIKES_02_size_2 = 0;
        var IMG_OBJ_ROTATABLE_SPIKES_03_size_3 = 0;
        var IMG_OBJ_ROTATABLE_SPIKES_04_size_4 = 0;
        var IMG_OBJ_ROTATABLE_SPIKES_BUTTON_button_1 = 0;
        var IMG_OBJ_ROTATABLE_SPIKES_BUTTON_button_1_pressed = 1;
        var IMG_OBJ_ROTATABLE_SPIKES_BUTTON_button_2 = 2;
        var IMG_OBJ_ROTATABLE_SPIKES_BUTTON_button_2_pressed = 3;

        var SpikeAnimation = {
            ELECTRODES_BASE: 0,
            ELECTRODES_ELECTRIC: 1,
            ROTATION_ADJUSTED: 2
        };

        return Spikes;
    }
);


define('game/Star',
    [
        'game/CTRGameObject',
        'visual/Animation',
        'resources/ResourceId',
        'core/Alignment',
        'visual/Timeline',
        'visual/KeyFrame',
        'core/RGBAColor',
        'core/Rectangle',
        'utils/MathHelper',
        'utils/Mover',
        'resolution'
    ],
    function (CTRGameObject, Animation, ResourceId, Alignment, Timeline, KeyFrame, RGBAColor, Rectangle, MathHelper, Mover, resolution) {

        var IMG_OBJ_STAR_IDLE_glow = 0;
        var IMG_OBJ_STAR_IDLE_idle_start = 1;
        var IMG_OBJ_STAR_IDLE_idle_end = 18;
        var IMG_OBJ_STAR_IDLE_timed_start = 19;
        var IMG_OBJ_STAR_IDLE_timed_end = 55;


        var Star = CTRGameObject.extend({
            init: function () {
                this._super();

                this.time = 0;
                this.timeout = 0;
                this.timedAnim = null;

                // typically we pixel align image coordinates, but the star animation
                // occurs along a small distance so we use a smaller increment so they
                // don't appear jerky. It's good to use a value that evenly divides 1
                // so that at least some of the positions are on pixel boundaries.
                this.drawPosIncrement = 0.0001;
            },
            createAnimations: function () {
                var t;
                if (this.timeout > 0) {
                    // create animation
                    this.timedAnim = new Animation();
                    this.timedAnim.initTextureWithId(ResourceId.IMG_OBJ_STAR_IDLE);
                    this.timedAnim.anchor = this.timedAnim.parentAnchor = Alignment.CENTER;
                    var delay = this.timeout / (IMG_OBJ_STAR_IDLE_timed_end - IMG_OBJ_STAR_IDLE_timed_start + 1);
                    this.timedAnim.addAnimationEndpoints(0, delay, Timeline.LoopType.NO_LOOP,
                        IMG_OBJ_STAR_IDLE_timed_start, IMG_OBJ_STAR_IDLE_timed_end);

                    // play and add as child
                    this.timedAnim.playTimeline(0);
                    this.time = this.timeout;
                    this.timedAnim.visible = false;
                    this.addChild(this.timedAnim);

                    // timeline for animation color fade
                    var tt = new Timeline();
                    tt.addKeyFrame(KeyFrame.makeColor(
                        RGBAColor.solidOpaque.copy(),
                        KeyFrame.TransitionType.LINEAR,
                        0));
                    tt.addKeyFrame(KeyFrame.makeColor(
                        RGBAColor.transparent.copy(),
                        KeyFrame.TransitionType.LINEAR,
                        0.5));
                    this.timedAnim.addTimelineWithID(tt, 1);

                    // timeline for element scale and color fade
                    t = new Timeline();
                    t.addKeyFrame(KeyFrame.makeScale(1, 1, KeyFrame.TransitionType.LINEAR, 0));
                    t.addKeyFrame(KeyFrame.makeScale(0, 0, KeyFrame.TransitionType.LINEAR, 0.25));
                    t.addKeyFrame(KeyFrame.makeColor(
                        RGBAColor.solidOpaque.copy(),
                        KeyFrame.TransitionType.LINEAR,
                        0));
                    t.addKeyFrame(KeyFrame.makeColor(
                        RGBAColor.transparent.copy(),
                        KeyFrame.TransitionType.LINEAR,
                        0.25));
                    this.addTimelineWithID(t, 1);
                }

                this.bb = Rectangle.copy(resolution.STAR_DEFAULT_BB);

                // timeline to make the star move up and down slightly
                t = new Timeline();
                t.addKeyFrame(KeyFrame.makePos(this.x, this.y, KeyFrame.TransitionType.EASE_IN, 0));
                t.addKeyFrame(KeyFrame.makePos(this.x, this.y - 3, KeyFrame.TransitionType.EASE_OUT, 0.5));
                t.addKeyFrame(KeyFrame.makePos(this.x, this.y, KeyFrame.TransitionType.EASE_IN, 0.5));
                t.addKeyFrame(KeyFrame.makePos(this.x, this.y + 3, KeyFrame.TransitionType.EASE_OUT, 0.5));
                t.addKeyFrame(KeyFrame.makePos(this.x, this.y, KeyFrame.TransitionType.EASE_IN, 0.5));
                t.loopType = Timeline.LoopType.REPLAY;
                this.addTimelineWithID(t, 0);
                this.playTimeline(0);
                t.update(MathHelper.randomRange(0, 20) / 10);

                // idle star animation
                var sr = new Animation();
                sr.initTextureWithId(ResourceId.IMG_OBJ_STAR_IDLE);
                sr.doRestoreCutTransparency();
                sr.addAnimationDelay(0.05, Timeline.LoopType.REPLAY, IMG_OBJ_STAR_IDLE_idle_start,
                    IMG_OBJ_STAR_IDLE_idle_end);
                sr.playTimeline(0);
                sr.getTimeline(0).update(MathHelper.randomRange(0, 20) / 10);
                sr.anchor = sr.parentAnchor = Alignment.CENTER;
                sr.drawPosIncrement = 0.0001;

                this.addChild(sr);
            },
            update: function (delta) {
                if (this.timeout > 0) {
                    if (this.time > 0) {
                        this.time = Mover.moveToTarget(this.time, 0, 1, delta);
                    }
                }
                this._super(delta);
            },
            draw: function () {
                if (this.timedAnim) {
                    this.timedAnim.draw();
                }

                this._super();
            }
        });

        return Star;
    }
);

define('visual/TextImage',
    [
        'visual/ImageElement',
        'visual/Text',
        'core/Texture2D'
    ],
    function (ImageElement, Text, Texture2D) {
        var TextImage = ImageElement.extend({
            init: function () {
                this._super();
            },
            setText: function (fontId, text, width, alignment) {
                var img = Text.drawImg({
                    fontId: fontId,
                    text: text,
                    width: width,
                    alignment: alignment
                });
                this.initTexture(new Texture2D(img));
            }
        });

        return TextImage;
    }
);

define('game/TutorialText',
    ['visual/TextImage'],
    function (TextImage) {

        var TutorialText = TextImage.extend({
            init: function () {
                this._super();
                this.special = 0;
            }
        });

        return TutorialText;
    }
);
define('visual/Camera2D',
    [
        'utils/Class',
        'core/Vector',
        'utils/Canvas',
        'utils/MathHelper'
    ],
    function (Class, Vector, Canvas, MathHelper) {

        var Camera2D = Class.extend({
            /**
             * Camera2D constructor
             * @param speed {number}
             * @param cameraSpeed {CameraSpeed}
             */
            init: function (speed, cameraSpeed) {
                this.speed = speed;
                this.type = cameraSpeed;
                this.pos = Vector.newZero();
                this.target = Vector.newZero();
                this.offset = Vector.newZero();
            },
            /**
             * Changes the camera position (but doesn't actually transform the canvas)
             * @param x {number}
             * @param y {number}
             * @param immediate {boolean}
             */
            moveTo: function (x, y, immediate) {
                this.target.x = x;
                this.target.y = y;

                if (immediate) {
                    this.pos.copyFrom(this.target);
                }
                else if (this.type === Camera2D.SpeedType.DELAY) {
                    this.offset = Vector.subtract(this.target, this.pos);
                    this.offset.multiply(this.speed);
                }
                else if (this.type === Camera2D.SpeedType.PIXELS) {
                    this.offset = Vector.subtract(this.target, this.pos);
                    this.offset.normalize();
                    this.offset.multiply(this.speed);
                }
            },
            /**
             * @param delta {number} time delta
             */
            update: function (delta) {
                if (!this.pos.equals(this.target)) {

                    // add to the current position and round
                    this.pos.add(Vector.multiply(this.offset, delta));
                    this.pos.round();

                    // see if we passed the target
                    if (!MathHelper.sameSign(this.offset.x, this.target.x - this.pos.x) ||
                        !MathHelper.sameSign(this.offset.y, this.target.y - this.pos.y)) {
                        this.pos.copyFrom(this.target);
                    }

                    //console.log('camera pos update x:' + this.pos.x + ' y:' + this.pos.y);
                }
            },
            applyCameraTransformation: function () {
                if (this.pos.x !== 0 || this.pos.y !== 0) {
                    Canvas.context.translate(-this.pos.x, -this.pos.y);
                }
            },
            cancelCameraTransformation: function () {
                if (this.pos.x !== 0 || this.pos.y !== 0) {
                    Canvas.context.translate(this.pos.x, this.pos.y);
                }
            }
        });

        /**
         * @enum {number}
         */
        Camera2D.SpeedType = {
            PIXELS: 0, // camera will move with speed pixels per second
            DELAY: 1  // camera will reach the target position in 1/speed seconds
        };

        return Camera2D;
    }
);
define('utils/DelayedDispatcher',
    [],
    function () {

        var Dispatch = function (object, callback, param, delay) {
            this.object = object;
            this.callback = callback;
            this.param = param;
            this.delay = delay;
        };

        Dispatch.prototype.dispatch = function () {
            this.callback.apply(
                this.object, // use the object as the context (this) for the callback
                this.param);
        };

        var DelayedDispatcher = {
            dispatchers: [],
            callObject: function (object, callback, param, delay) {
                var dp = new Dispatch(object, callback, param, delay);
                this.dispatchers.push(dp);
            },
            cancelAllDispatches: function () {
                this.dispatchers.length = 0;
            },
            cancelDispatch: function (object, callback, param) {
                for (var i = 0, count = this.dispatchers.length; i < count; i--) {
                    var dp = this.dispatchers[i];
                    if (dp.object === object &&
                        dp.callback === callback &&
                        dp.param === param) {
                        this.dispatchers.splice(i, 1);
                        return;
                    }
                }
            },
            update: function (delta) {

                // take a snapshot of the current dispatchers since
                // the queue may be modified during our update
                var currentDps = this.dispatchers.slice(0);

                // update each of the dispatchers
                for (var i = 0, len = currentDps.length; i < len; i++) {
                    var dp = currentDps[i];

                    // a previous dispatch may have cleared the queue,
                    // so make sure it still exists
                    var dpIndex = this.dispatchers.indexOf(dp);
                    if (dpIndex < 0) {
                        continue;
                    }

                    // update the time and see if its time to fire
                    dp.delay -= delta;
                    if (dp.delay <= 0) {
                        // remove the object from the real pool first
                        this.dispatchers.splice(dpIndex, 1);

                        // now we can run the callback
                        dp.dispatch();
                    }
                }
            }

        };

        return DelayedDispatcher;
    }
);
define('utils/MapItem',[], function () {
    /**
     * @enum {number}
     */
    var MapItem = {
        MAP: 0,
        GAME_DESIGN: 1,
        TARGET: 2,
        STAR: 3,
        TUTORIAL_TEXT: 4,
        TUTORIAL_01: 5,
        TUTORIAL_02: 6,
        TUTORIAL_03: 7,
        TUTORIAL_04: 8,
        TUTORIAL_05: 9,
        TUTORIAL_06: 10,
        TUTORIAL_07: 11,
        TUTORIAL_08: 12,
        TUTORIAL_09: 13,
        TUTORIAL_10: 14,
        TUTORIAL_11: 15,
        TUTORIAL_12: 16,
        TUTORIAL_13: 17,
        TUTORIAL_14: 18,
        // leave space for future tutorial elements
        // (which the game assumes are sequentially numbered)

        CANDY_L: 50,
        CANDY_R: 51,
        CANDY: 52,
        GRAVITY_SWITCH: 53,
        BUBBLE: 54,
        PUMP: 55,
        SOCK: 56,
        SPIKE_1: 57,
        SPIKE_2: 58,
        SPIKE_3: 59,
        SPIKE_4: 60,
        SPIKES_SWITCH: 61,
        // leave space for future spike elements

        ELECTRO: 80,
        BOUNCER1: 81,
        BOUNCER2: 82,
        // leave space for future bouncers

        GRAB: 100,
        HIDDEN_01: 101,
        HIDDEN_02: 102,
        HIDDEN_03: 103,
        // leave space for additional hidden

        ROTATED_CIRCLE: 120,
        TARGET_2: 121,
        CANDY_2: 122

    };

    function getMapItem(name) {
        switch (name) {
            case 'map':
                return MapItem.MAP;
            case 'gameDesign':
                return MapItem.GAME_DESIGN;
            case 'target':
                return MapItem.TARGET;
            case 'target2':
                return MapItem.TARGET_2;
            case 'star':
                return MapItem.STAR;
            case 'tutorialText':
                return MapItem.TUTORIAL_TEXT;
            case 'tutorial01':
                return MapItem.TUTORIAL_01;
            case 'tutorial02':
                return MapItem.TUTORIAL_02;
            case 'tutorial03':
                return MapItem.TUTORIAL_03;
            case 'tutorial04':
                return MapItem.TUTORIAL_04;
            case 'tutorial05':
                return MapItem.TUTORIAL_05;
            case 'tutorial06':
                return MapItem.TUTORIAL_06;
            case 'tutorial07':
                return MapItem.TUTORIAL_07;
            case 'tutorial08':
                return MapItem.TUTORIAL_08;
            case 'tutorial09':
                return MapItem.TUTORIAL_09;
            case 'tutorial10':
                return MapItem.TUTORIAL_10;
            case 'tutorial11':
                return MapItem.TUTORIAL_11;
            case 'tutorial12':
                return MapItem.TUTORIAL_12;
            case 'tutorial13':
                return MapItem.TUTORIAL_13;
            case 'tutorial14':
                return MapItem.TUTORIAL_14;
            case 'candyL':
                return MapItem.CANDY_L;
            case 'candyR':
                return MapItem.CANDY_R;
            case 'candy':
                return MapItem.CANDY;
            case 'candy2':
                return MapItem.CANDY_2;
            case 'gravitySwitch':
                return MapItem.GRAVITY_SWITCH;
            case 'bubble':
                return MapItem.BUBBLE;
            case 'pump':
                return MapItem.PUMP;
            case 'sock':
                return MapItem.SOCK;
            case 'spike1':
                return MapItem.SPIKE_1;
            case 'spike2':
                return MapItem.SPIKE_2;
            case 'spike3':
                return MapItem.SPIKE_3;
            case 'spike4':
                return MapItem.SPIKE_4;
            case 'spikesSwitch':
                return MapItem.SPIKES_SWITCH;
            case 'electro':
                return MapItem.ELECTRO;
            case 'bouncer1':
                return MapItem.BOUNCER1;
            case 'bouncer2':
                return MapItem.BOUNCER2;
            case 'grab':
                return MapItem.GRAB;
            case 'hidden01':
                return MapItem.HIDDEN_01;
            case 'hidden02':
                return MapItem.HIDDEN_02;
            case 'hidden03':
                return MapItem.HIDDEN_03;
            case 'rotatedCircle':
                return MapItem.ROTATED_CIRCLE;
            default:
                alert('Unknown map item:' + name);
                return null;
        }
    }

    MapItem.fromName = getMapItem;

    return MapItem;
});


define('visual/AnimationPool',
    ['visual/BaseElement'],
    function (BaseElement) {
        /**
         * Container to hold animated objects which can be automatically deleted
         * once their animation timelines have completed
         */
        var AnimationPool = BaseElement.extend({
            init: function () {
                this._super();

                // keeps track of child elements whose timeline has completed
                // and can be removed
                this.removeList = [];
            },
            update: function (delta) {
                // remove the children
                for (var i = 0, len = this.removeList.length; i < len; i++) {
                    this.removeChild(this.removeList[i]);
                }

                // clear the remove list
                this.removeList.length = 0;
                this._super(delta);
            },
            /**
             * @param timeline {Timeline}
             */
            timelineFinished: function (timeline) {
                this.removeList.push(timeline.element);
            },
            /**
             * Returns a delegate that can be invoked when a timeline finishes
             */
            timelineFinishedDelegate: function () {
                // save a reference to ourselves since we may be called in a
                // different context (typically by another class)
                var self = this;
                return function (timeline) {
                    self.timelineFinished(timeline);
                }
            },
            /**
             * @param particles {Particles}
             */
            particlesFinished: function (particles) {
                this.removeList.push(particles);
            },
            /**
             * Returns a delegate that can be invoked when a particle system finishes
             */
            particlesFinishedDelegate: function () {
                // save a reference to ourselves since we may be called in a
                // different context (typically by another class)
                var self = this;
                return function (particles) {
                    self.particlesFinished(particles);
                }
            }
        });

        return AnimationPool;
    }
);

// @depend BaseElement.js
// @depend ImageMultiDrawer.js
// @depend ../core/Rectangle.js

define('visual/TileMap',
    [
        'visual/BaseElement',
        'visual/ImageMultiDrawer',
        'core/Rectangle',
        'resolution',
        'utils/Constants',
        'utils/MathHelper',
        'core/Vector'
    ],
    function (BaseElement, ImageMultiDrawer, Rectangle, resolution, Constants, MathHelper, Vector) {

        /**
         * An entry in the tile map
         * @constructor
         * @param drawerIndex {number}
         * @param quadIndex {number}
         */
        function TileEntry(drawerIndex, quadIndex) {
            this.drawerIndex = drawerIndex;
            this.quad = quadIndex;
        }

        var TileMap = BaseElement.extend({
            init: function (rows, columns) {
                this._super();

                this.rows = rows;
                this.columns = columns;

                this.cameraViewWidth = resolution.CANVAS_WIDTH;
                this.cameraViewHeight = resolution.CANVAS_HEIGHT;

                this.parallaxRatio = 1;

                this.drawers = [];
                this.tiles = [];

                this.matrix = [];
                for (var i = 0; i < columns; i++) {
                    var column = this.matrix[i] = [];
                    for (var k = 0; k < rows; k++) {
                        column[k] = Constants.UNDEFINED;
                    }
                }

                this.repeatedVertically = TileMap.RepeatType.NONE;
                this.repeatedHorizontally = TileMap.RepeatType.NONE;
                this.horizontalRandom = false;
                this.verticalRandom = false;
                this.restoreTileTransparency = true;
                this.randomSeed = MathHelper.randomRange(1000, 2000);
            },
            addTile: function (texture, quadIndex) {
                if (quadIndex === Constants.UNDEFINED) {
                    this.tileWidth = texture.imageWidth;
                    this.tileHeight = texture.imageHeight;
                }
                else {
                    var rect = texture.rects[quadIndex];
                    this.tileWidth = rect.w;
                    this.tileHeight = rect.h;
                }

                this.updateVars();

                var drawerId = Constants.UNDEFINED;
                for (var i = 0, len = this.drawers.length; i < len; i++) {
                    if (this.drawers[i].texture === texture) {
                        drawerId = i;
                        break;
                    }
                }

                if (drawerId === Constants.UNDEFINED) {
                    var d = new ImageMultiDrawer(texture);
                    drawerId = this.drawers.length;
                    this.drawers.push(d);
                }

                var entry = new TileEntry(drawerId, quadIndex);
                this.tiles.push(entry);
            },
            updateVars: function () {
                this.maxColsOnScreen = 2 + ~~(this.cameraViewWidth / (this.tileWidth + 1));
                this.maxRowsOnScreen = 2 + ~~(this.cameraViewHeight / (this.tileHeight + 1));

                if (this.repeatedVertically === TileMap.RepeatType.NONE) {
                    this.maxRowsOnScreen = Math.min(this.maxRowsOnScreen, this.rows);
                }

                if (this.repeatedHorizontally === TileMap.RepeatType.NONE) {
                    this.maxColsOnScreen = Math.min(this.maxColsOnScreen, this.columns);
                }

                this.width = this.tileMapWidth = this.columns * this.tileWidth;
                this.height = this.tileMapHeight = this.rows * this.tileHeight;
            },
            /**
             * Fills the tilemap matrix with the specified tile entry index
             * @param startRow {number}
             * @param startCol {number}
             * @param numRows {number}
             * @param numCols {number}
             * @param tileIndex {number}
             */
            fill: function (startRow, startCol, numRows, numCols, tileIndex) {
                for (var i = startCol, colEnd = startCol + numCols; i < colEnd; i++) {
                    for (var k = startRow, rowEnd = startRow + numRows; k < rowEnd; k++) {
                        this.matrix[i][k] = tileIndex;
                    }
                }
            },
            setParallaxRation: function (ratio) {
                this.parallaxRatio = ratio;
            },
            /**
             * @param repeatType {TileMap.RepeatType}
             */
            setRepeatHorizontally: function (repeatType) {
                this.repeatedHorizontally = repeatType;
                this.updateVars();
            },
            setRepeatVertically: function (repeatType) {
                this.repeatedVertically = repeatType;
                this.updateVars();
            },
            /**
             * Updates the tile map based on the current camera position
             * @param pos {Vector}
             */
            updateWithCameraPos: function (pos) {
                var mx = Math.round(pos.x / this.parallaxRatio),
                    my = Math.round(pos.y / this.parallaxRatio),
                    tileMapStartX = this.x,
                    tileMapStartY = this.y,
                    a, i, len, v;

                if (this.repeatedVertically !== TileMap.RepeatType.NONE) {
                    var ys = tileMapStartY - my;
                    a = ~~(ys) % this.tileMapHeight;
                    if (ys < 0) {
                        tileMapStartY = a + my;
                    }
                    else {
                        tileMapStartY = a - this.tileMapHeight + my;
                    }
                }

                if (this.repeatedHorizontally !== TileMap.RepeatType.NONE) {
                    var xs = tileMapStartX - mx;
                    a = ~~(xs) % this.tileMapWidth;
                    if (xs < 0) {
                        tileMapStartX = a + mx;
                    }
                    else {
                        tileMapStartX = a - this.tileMapWidth + mx;
                    }
                }

                // see if tile map is in the camera view
                if (!Rectangle.rectInRect(
                    mx, my, mx + this.cameraViewWidth, my + this.cameraViewHeight,
                    tileMapStartX,
                    tileMapStartY,
                    tileMapStartX + this.tileMapWidth,
                    tileMapStartY + this.tileMapHeight)) {
                    return;
                }

                var cameraInTilemap = Rectangle.rectInRectIntersection(
                    tileMapStartX, tileMapStartY, this.tileMapWidth, this.tileMapHeight, // tile map rect
                    mx, my, this.cameraViewWidth, this.cameraViewHeight); // camera rect

                var checkPoint = new Vector(
                    Math.max(0, cameraInTilemap.x),
                    Math.max(0, cameraInTilemap.y));

                //noinspection JSSuspiciousNameCombination
                var startPos = new Vector(
                    ~~(~~(checkPoint.x) / this.tileWidth),
                    ~~(~~(checkPoint.y) / this.tileHeight));

                var highestQuadY = tileMapStartY + startPos.y * this.tileHeight,
                    currentQuadPos = new Vector(tileMapStartX + startPos.x * this.tileWidth, highestQuadY);

                // reset the number of quads to draw
                for (i = 0, len = this.drawers.length; i < len; i++) {
                    this.drawers[i].numberOfQuadsToDraw = 0;
                }

                var maxColumn = startPos.x + this.maxColsOnScreen - 1,
                    maxRow = startPos.y + this.maxRowsOnScreen - 1;

                if (this.repeatedVertically === TileMap.RepeatType.NONE) {
                    maxRow = Math.min(this.rows - 1, maxRow);
                }
                if (this.repeatedHorizontally === TileMap.RepeatType.NONE) {
                    maxColumn = Math.min(this.columns - 1, maxColumn);
                }

                for (i = startPos.x; i <= maxColumn; i++) {
                    currentQuadPos.y = highestQuadY;
                    for (var j = startPos.y; j <= maxRow; j++) {
                        if (currentQuadPos.y >= my + this.cameraViewHeight) {
                            break;
                        }

                        // find intersection rectangle between camera rectangle and every tiled
                        // texture rectangle
                        var resScreen = Rectangle.rectInRectIntersection(
                            mx, my, this.cameraViewWidth, this.cameraViewHeight,
                            currentQuadPos.x, currentQuadPos.y, this.tileWidth, this.tileHeight);

                        var resTexture = new Rectangle(
                            (mx - currentQuadPos.x) + resScreen.x,
                            (my - currentQuadPos.y) + resScreen.y,
                            resScreen.w,
                            resScreen.h);

                        var ri = Math.round(i),
                            rj = Math.round(j);

                        if (this.repeatedVertically === TileMap.RepeatType.EDGES) {
                            if (currentQuadPos.y < y) {
                                rj = 0;
                            }
                            else if (currentQuadPos.y >= this.y + this.tileMapHeight) {
                                rj = this.rows - 1;
                            }
                        }

                        if (this.repeatedHorizontally === TileMap.RepeatType.EDGES) {
                            if (currentQuadPos.x < this.x) {
                                ri = 0;
                            }
                            else if (currentQuadPos.x >= this.x + this.tileMapWidth) {
                                ri = this.columns - 1;
                            }
                        }

                        if (this.horizontalRandom) {
                            v = Math.sin(currentQuadPos.x) * this.randomSeed;
                            ri = Math.abs(~~(v) % this.columns);
                        }

                        if (this.verticalRandom) {
                            v = Math.sin(currentQuadPos.y) * this.randomSeed;
                            rj = Math.abs(~~(v) % this.rows);
                        }

                        if (ri >= this.columns) {
                            ri = ri % this.columns;
                        }

                        if (rj >= this.rows) {
                            rj = rj % this.rows;
                        }

                        var tile = this.matrix[ri][rj];
                        if (tile >= 0) {
                            var entry = this.tiles[tile],
                                drawer = this.drawers[entry.drawerIndex],
                                texture = drawer.texture;

                            if (entry.quad !== Constants.UNDEFINED) {
                                var rect = texture.rects[entry.quad];
                                resTexture.x += rect.x;
                                resTexture.y += rect.y;
                            }

                            var vertRect = new Rectangle(
                                pos.x + resScreen.x,
                                pos.y + resScreen.y,
                                resScreen.w,
                                resScreen.h);

                            drawer.setTextureQuad(drawer.numberOfQuadsToDraw++, resTexture, vertRect);
                        }
                        currentQuadPos.y += this.tileHeight;
                    }
                    currentQuadPos.x += this.tileWidth;

                    if (currentQuadPos.x >= mx + this.cameraViewWidth) {
                        break;
                    }
                }
                
                
            },
            draw: function () {
                this.preDraw();
                for (var i = 0, len = this.drawers.length; i < len; i++) {
                    this.drawers[i].draw();
                }
                this.postDraw();
            }
        });

        /**
         * @enum {number}
         */
        TileMap.RepeatType = {
            NONE: 0,
            ALL: 1,
            EDGES: 2
        };

        return TileMap;
    }
);
define('visual/BackgroundTileMap',
    [
        'visual/TileMap',
        'core/Vector'
    ],
    function (TileMap, Vector) {
        var BackgroundTileMap = TileMap.extend({
            init: function (rows, columns) {
                this._super(rows, columns);
                this.lastCameraPos = Vector.newUndefined();
            },
            updateWithCameraPos: function (pos) {
                if (!this.lastCameraPos.equals(pos)) {
                    this._super(pos);
                    this.lastCameraPos.copyFrom(pos);
                }
            },
            draw: function () {
                /* seems like this should be taken care of in BaseElement.preDraw?

                 var rotationOffsetX = this.back.drawX + (this.back.width >> 1) + this.back.rotationCenterX,
                 rotationOffsetY = this.back.drawY + (this.back.height >> 1) + this.back.rotationCenterY,
                 ctx = Canvas.context;

                 // TODO: skip scaling if unnecessary
                 ctx.translate(rotationOffsetX, rotationOffsetY);
                 ctx.scale(this.back.scaleX, this.back.scaleY);
                 ctx.translate(-rotationOffsetX, -rotationOffsetY);
                 */

                this._super();
            }
        });

        return BackgroundTileMap;
    }
);




define('visual/ToggleButton',
    [
        'visual/BaseElement',
        'visual/GenericButton',
        'core/Alignment'
    ],
    function (BaseElement, GenericButton, Alignment) {

        var ToggleButtonId = {
            FACE1: 0,
            FACE2: 1
        };

        var ToggleButton = BaseElement.extend({
            init: function (up1, down1, up2, down2, id) {
                this._super();

                this.buttonId = id;

                this.b1 = new GenericButton(ToggleButtonId.FACE1);
                this.b1.initWithElements(up1, down1);

                this.b2 = new GenericButton(ToggleButtonId.FACE2);
                this.b2.initWithElements(up2, down2);

                this.b1.parentAnchor = this.b2.parentAnchor = Alignment.TOP | Alignment.LEFT;
                this.width = this.b1.width;
                this.height = this.b1.height;

                this.addChildWithID(this.b1, ToggleButtonId.FACE1);
                this.addChildWithID(this.b2, ToggleButtonId.FACE2);

                this.b2.setEnabled(false);
                this.b1.onButtonPressed = $.proxy(this.onButtonPressed, this);
                this.b2.onButtonPressed = $.proxy(this.onButtonPressed, this);
            },
            onButtonPressed: function (n) {
                switch (n) {
                    case ToggleButtonId.FACE1:
                    case ToggleButtonId.FACE2:
                        this.toggle();
                        break;
                }
                if (this.onButtonPressed) {
                    this.onButtonPressed(this.buttonId);
                }
            },
            setTouchIncrease: function (left, right, top, bottom) {
                this.b1.setTouchIncrease(left, right, top, bottom);
                this.b2.setTouchIncrease(left, right, top, bottom);
            },
            toggle: function () {
                this.b1.setEnabled(!this.b1.isEnabled());
                this.b2.setEnabled(!this.b2.isEnabled());
            },
            isOn: function () {
                return this.b2.isEnabled();
            }
        });

        return ToggleButton;
    }
);

define('game/GravityButton',
    [
        'visual/ImageElement',
        'visual/ToggleButton',
        'resources/ResourceId'
    ],
    function (ImageElement, ToggleButton, ResourceId) {

        var IMG_OBJ_STAR_IDLE_gravity_down = 56;
        var IMG_OBJ_STAR_IDLE_gravity_up = 57;

        var GravityButton = ToggleButton.extend({
            init: function () {
                var itn = ImageElement.create(ResourceId.IMG_OBJ_STAR_IDLE, IMG_OBJ_STAR_IDLE_gravity_down),
                    itp = ImageElement.create(ResourceId.IMG_OBJ_STAR_IDLE, IMG_OBJ_STAR_IDLE_gravity_down),
                    itn2 = ImageElement.create(ResourceId.IMG_OBJ_STAR_IDLE, IMG_OBJ_STAR_IDLE_gravity_up),
                    itp2 = ImageElement.create(ResourceId.IMG_OBJ_STAR_IDLE, IMG_OBJ_STAR_IDLE_gravity_up);

                this._super(itn, itp, itn2, itp2, GravityButton.DefaultId);

                this.setTouchIncrease(10, 10, 10, 10);
            }
        });

        GravityButton.DefaultId = 0;

        return GravityButton;
    }
);

define('game/EarthImage',
    [
        'visual/ImageElement',
        'visual/Timeline',
        'visual/KeyFrame',
        'core/Alignment',
        'resources/ResourceId'
    ],
    function (ImageElement, Timeline, KeyFrame, Alignment, ResourceId) {

        var IMG_BGR_08_P1__position_window = 1;
        var IMG_OBJ_STAR_IDLE_window = 58;

        var EarthImage = ImageElement.extend({
            init: function (offsetX, offsetY) {
                this._super();
                this.initTextureWithId(ResourceId.IMG_OBJ_STAR_IDLE);
                this.setTextureQuad(IMG_OBJ_STAR_IDLE_window);
                this.anchor = Alignment.CENTER;

                var t = new Timeline();
                t.addKeyFrame(KeyFrame.makeRotation(0, KeyFrame.TransitionType.LINEAR, 0));
                t.addKeyFrame(KeyFrame.makeRotation(180, KeyFrame.TransitionType.EASE_OUT, 0.3));
                this.addTimelineWithID(t, EarthImage.TimelineId.UPSIDE_DOWN);

                var t2 = new Timeline();
                t2.addKeyFrame(KeyFrame.makeRotation(180, KeyFrame.TransitionType.LINEAR, 0));
                t2.addKeyFrame(KeyFrame.makeRotation(0, KeyFrame.TransitionType.EASE_OUT, 0.3));
                this.addTimelineWithID(t2, EarthImage.TimelineId.NORMAL);

                this.setElementPositionWithOffset(ResourceId.IMG_BGR_08_P1, IMG_BGR_08_P1__position_window);
                this.x += offsetX;
                this.y += offsetY;
            }
        });

        /**
         * @enum {number}
         */
        EarthImage.TimelineId = {
            NORMAL: 0,
            UPSIDE_DOWN: 1
        };

        return EarthImage;
    }
);
define('game/PollenDrawer',
    [
        'visual/BaseElement',
        'core/Vector',
        'resources/ResourceId',
        'visual/ImageElement',
        'visual/ImageMultiDrawer',
        'resources/ResourceMgr',
        'utils/Mover',
        'utils/MathHelper',
        'resolution',
        'core/Rectangle'
    ],
    function (BaseElement, Vector, ResourceId, ImageElement, ImageMultiDrawer, ResourceMgr, Mover, MathHelper, resolution, Rectangle) {

        function Pollen() {
            this.parentIndex = 0;
            this.x = 0;
            this.y = 0;

            this.scaleX = 1;
            this.startScaleX = 1;
            this.endScaleX = 1;

            this.scaleY = 1;
            this.startScaleY = 1;
            this.endScaleY = 1;

            this.alpha = 1;
            this.startAlpha = 1;
            this.endAlpha = 1;
        }

        var PollenDrawer = BaseElement.extend({
            init: function () {
                this._super();

                var pollen = ResourceMgr.getTexture(ResourceId.IMG_OBJ_POLLEN_HD);

                this.qw = pollen.imageWidth;
                this.qh = pollen.imageHeight;

                this.drawer = new ImageMultiDrawer(pollen);
                this.drawer.drawPosIncrement = 0.1;

                this.pollens = [];
            },
            addPollen: function (v, pi) {
                var sX = 1,
                    sY = 1,
                    size = [ 0.3, 0.3, 0.5, 0.5, 0.6 ],
                    sizeCounts = size.length,
                    rx = size[MathHelper.randomRange(0, sizeCounts - 1)],
                    ry = rx;

                if (MathHelper.randomBool()) {
                    rx *= 1 + (MathHelper.randomRange(0, 1) / 10);
                }
                else {
                    ry *= 1 + (MathHelper.randomRange(0, 1) / 10);
                }

                sX *= rx;
                sY *= ry;

                var w = this.qw * sX,
                    h = this.qh * sY,
                    maxScale = 1,
                    d = Math.min(maxScale - sX, maxScale - sY),
                    delta = Math.random(),
                    pollen = new Pollen();

                pollen.parentIndex = pi;
                pollen.x = v.x;
                pollen.y = v.y;
                pollen.startScaleX = d + sX;
                pollen.startScaleY = d + sY;
                pollen.scaleX = pollen.startScaleX * delta;
                pollen.scaleY = pollen.startScaleY * delta;
                pollen.endScaleX = sX;
                pollen.endScaleY = sY;
                pollen.endAlpha = 0.3;
                pollen.startAlpha = 1;
                pollen.alpha = (0.7 * delta) + 0.3;

                var tquad = this.drawer.texture.rects[IMG_OBJ_POLLEN_HD_obj_pollen],
                    vquad = new Rectangle(v.x - w / 2, v.y - h / 2, w, h);

                this.drawer.setTextureQuad(
                    this.pollens.length, tquad, vquad, pollen.alpha);
                this.pollens.push(pollen);
            },
            fillWithPollenFromPath: function (fromIndex, toIndex, grab) {
                var MIN_DISTANCE = resolution.POLLEN_MIN_DISTANCE,
                    v1 = grab.mover.path[fromIndex],
                    v2 = grab.mover.path[toIndex],
                    v = Vector.subtract(v2, v1),
                    vLen = v.getLength(),
                    times = ~~(vLen / MIN_DISTANCE),
                    POLLEN_MAX_OFFSET = resolution.POLLEN_MAX_OFFSET,
                    i, vn;

                v.normalize();

                for (i = 0; i <= times; i++) {
                    vn = Vector.add(v1, Vector.multiply(v, i * MIN_DISTANCE));
                    vn.x += MathHelper.randomRange(-POLLEN_MAX_OFFSET, POLLEN_MAX_OFFSET);
                    vn.y += MathHelper.randomRange(-POLLEN_MAX_OFFSET, POLLEN_MAX_OFFSET);
                    this.addPollen(vn, fromIndex);
                }
            },
            update: function (delta) {
                this._super(delta);
                this.drawer.update(delta);

                var len = this.pollens.length,
                    i, pollen, temp, w, h, moveResult, a;

                for (i = 0; i < len; i++) {
                    pollen = this.pollens[i];

                    // increment the scale
                    moveResult = Mover.moveToTargetWithStatus(pollen.scaleX, pollen.endScaleX, 1, delta);
                    pollen.scaleX = moveResult.value;
                    if (moveResult.reachedZero) {
                        // swap the start and end values
                        temp = pollen.startScaleX;
                        pollen.startScaleX = pollen.endScaleX;
                        pollen.endScaleX = temp;
                    }

                    moveResult = Mover.moveToTargetWithStatus(pollen.scaleY, pollen.endScaleY, 1, delta);
                    pollen.scaleY = moveResult.value;
                    if (moveResult.reachedZero) {
                        // swap the start and end values
                        temp = pollen.startScaleY;
                        pollen.startScaleY = pollen.endScaleY;
                        pollen.endScaleY = temp;
                    }

                    w = this.qw * pollen.scaleX;
                    h = this.qh * pollen.scaleY;

                    // update the current position
                    this.drawer.vertices[i] = new Rectangle(
                        pollen.x - w / 2,
                        pollen.y - h / 2,
                        w, h);

                    // increment the alpha
                    moveResult = Mover.moveToTargetWithStatus(pollen.alpha, pollen.endAlpha, 1, delta);
                    pollen.alpha = moveResult.value;
                    if (moveResult.reachedZero) {
                        // swap the start and end values
                        temp = pollen.startAlpha;
                        pollen.startAlpha = pollen.endAlpha;
                        pollen.endAlpha = temp;
                    }

                    // update the alpha in the drawer
                    this.drawer.alphas[i] = pollen.alpha;
                }
            },
            draw: function () {
                this.preDraw();
                this.drawer.draw();
                this.postDraw();
            }
        });

        var IMG_OBJ_POLLEN_HD_obj_pollen = 0;

        return PollenDrawer;
    }
);
define('game/RotatedCircle',
    [
        'visual/BaseElement',
        'utils/Constants',
        'visual/ImageElement',
        'resources/ResourceId',
        'core/Alignment',
        'resolution',
        'core/Vector',
        'utils/Radians',
        'utils/Canvas'
    ],
    function (BaseElement, Constants, ImageElement, ResourceId, Alignment, resolution, Vector, Radians, Canvas) {

        var CONTOUR_ALPHA = 0.2,
            CONTROLLER_MIN_SCALE = 0.75,
            STICKER_MIN_SCALE = 0.4,
            CENTER_SCALE_FACTOR = 0.5,
            HUNDRED_PERCENT_SCALE_SIZE = 167.0,
            CIRCLE_VERTEX_COUNT = 80,
            INNER_CIRCLE_WIDTH = 15 * resolution.PM,
            OUTER_CIRCLE_WIDTH = 7 * resolution.PM,
            ACTIVE_CIRCLE_WIDTH = 3 * resolution.PM,
            CONTROLLER_SHIFT_PARAM1 = 22.5 * resolution.PM,
            CONTROLLER_SHIFT_PARAM2 = 0.03 * resolution.PM;

        var StickerImage = ImageElement.extend({
            init: function () {
                this._super();
                this.initTextureWithId(ResourceId.IMG_OBJ_VINIL);
                this.setTextureQuad(IMG_OBJ_VINIL_odj_vinil_sticker);
            }
        });

        var RotatedCircle = BaseElement.extend({
            init: function () {
                this._super();
                this.containedObjects = [];
                this.circles = [];
                this.soundPlaying = Constants.UNDEFINED;
                this.lastTouch = Vector.newUndefined();

                this.vinilStickerL = new StickerImage();
                this.vinilStickerL.anchor = Alignment.RIGHT | Alignment.VCENTER;
                this.vinilStickerL.scaleX = 1;
                this.vinilStickerL.parentAnchor = Alignment.CENTER;
                this.vinilStickerL.rotationCenterX = this.vinilStickerL.width / 2 + 0.5;
                this.vinilStickerL.drawPosIncrement = 0.001;

                this.vinilStickerR = new StickerImage();
                this.vinilStickerR.scaleX = -1;
                this.vinilStickerR.anchor = Alignment.RIGHT | Alignment.VCENTER;
                this.vinilStickerR.parentAnchor = Alignment.CENTER;
                this.vinilStickerR.rotationCenterX = (this.vinilStickerR.width / 2) - 0.5;
                this.vinilStickerR.drawPosIncrement = 0.001;

                this.vinilCenter = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_vinil_center);
                this.vinilCenter.anchor = Alignment.CENTER;

                this.vinilHighlightL = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_vinil_highlight);
                this.vinilHighlightL.anchor = Alignment.TOP | Alignment.RIGHT;

                this.vinilHighlightR = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_vinil_highlight);
                this.vinilHighlightR.scaleX = -1;
                this.vinilHighlightR.anchor = Alignment.TOP | Alignment.LEFT;

                this.vinilControllerL = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_controller);
                this.vinilControllerL.anchor = Alignment.CENTER;
                this.vinilControllerL.rotation = 90.0;

                this.vinilControllerR = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_controller);
                this.vinilControllerR.anchor = Alignment.CENTER;
                this.vinilControllerR.rotation = -90.0;

                this.vinilActiveControllerL = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_controller_active);
                this.vinilActiveControllerL.anchor = this.vinilControllerL.anchor;
                this.vinilActiveControllerL.rotation = this.vinilControllerL.rotation;
                this.vinilActiveControllerL.visible = false;

                this.vinilActiveControllerR = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_controller_active);
                this.vinilActiveControllerR.anchor = this.vinilControllerR.anchor;
                this.vinilActiveControllerR.rotation = this.vinilControllerR.rotation;
                this.vinilActiveControllerR.visible = false;

                this.vinil = ImageElement.create(
                    ResourceId.IMG_OBJ_VINIL, IMG_OBJ_VINIL_obj_vinil);
                this.vinil.anchor = Alignment.CENTER;

                this.passColorToChilds = false;

                this.addChild(this.vinilStickerL);
                this.addChild(this.vinilStickerR);
                this.addChild(this.vinilActiveControllerL);
                this.addChild(this.vinilActiveControllerR);
                this.addChild(this.vinilControllerL);
                this.addChild(this.vinilControllerR);
            },
            setSize: function (value) {
                this.size = value;

                var newScale = this.size / HUNDRED_PERCENT_SCALE_SIZE;
                this.vinilHighlightL.scaleX = this.vinilHighlightL.scaleY =
                    this.vinilHighlightR.scaleY = newScale;
                this.vinilHighlightR.scaleX = -newScale;

                this.vinil.scaleX = this.vinil.scaleY = newScale;

                var newStickerScale = (newScale >= STICKER_MIN_SCALE)
                    ? newScale : STICKER_MIN_SCALE;
                this.vinilStickerL.scaleX = this.vinilStickerL.scaleY = this.vinilStickerR.scaleY = newStickerScale;
                this.vinilStickerR.scaleX = -newStickerScale;

                var newControllerScale = (newScale >= CONTROLLER_MIN_SCALE)
                    ? newScale : CONTROLLER_MIN_SCALE;
                this.vinilControllerL.scaleX = this.vinilControllerL.scaleY =
                    this.vinilControllerR.scaleX = this.vinilControllerR.scaleY = newControllerScale;
                this.vinilActiveControllerL.scaleX = this.vinilActiveControllerL.scaleY =
                    this.vinilActiveControllerR.scaleX = this.vinilActiveControllerR.scaleY = newControllerScale;

                this.vinilCenter.scaleX = 1.0 - (1.0 - newStickerScale) * CENTER_SCALE_FACTOR;
                this.vinilCenter.scaleY = this.vinilCenter.scaleX;

                this.sizeInPixels = this.vinilHighlightL.width * this.vinilHighlightL.scaleX;

                this.updateChildPositions();
            },

            hasOneHandle: function () {
                return (!this.vinilControllerL.visible);
            },
            setHasOneHandle: function (value) {
                this.vinilControllerL.visible = !value;
            },
            isLeftControllerActive: function () {
                return this.vinilActiveControllerL.visible;
            },
            setIsLeftControllerActive: function (value) {
                this.vinilActiveControllerL.visible = value;
            },
            isRightControllerActive: function () {
                return this.vinilActiveControllerR.visible;
            },
            setIsRightControllerActive: function (value) {
                this.vinilActiveControllerR.visible = value;
            },
            containsSameObjectWithAnotherCircle: function () {
                var len = this.circles.length,
                    i, anotherCircle;
                for (i = 0; i < len; i++) {
                    anotherCircle = this.circles[i];
                    if (anotherCircle != this &&
                        this.containsSameObjectWithCircle(anotherCircle)) {
                        return true;
                    }
                }
                return false;
            },
            draw: function () {
                var ctx = Canvas.context;
                if (this.isRightControllerActive() || this.isLeftControllerActive()) {
                    var lineWidth = (ACTIVE_CIRCLE_WIDTH + resolution.PM) * this.vinilControllerL.scaleX,
                        radius = this.sizeInPixels + ~~(lineWidth / 2);
                    ctx.beginPath();
                    ctx.lineWidth = lineWidth;
                    ctx.arc(this.x, this.y, radius, 0, 2 * Math.PI, false);
                    ctx.stroke();
                }

                this.vinilHighlightL.color = this.color;
                this.vinilHighlightR.color = this.color;
                this.vinilControllerL.color = this.color;
                this.vinilControllerR.color = this.color;
                this.vinil.color = this.color;
                this.vinil.draw();

                var len = this.circles.length,
                    i, anotherCircle,
                    selfIndex = this.circles.indexOf(this),
                    previousAlpha = ctx.globalAlpha;

                if (previousAlpha !== CONTOUR_ALPHA) {
                    ctx.globalAlpha = CONTOUR_ALPHA;
                }

                for (i = 0; i < len; i++) {
                    anotherCircle = this.circles[i];
                    if (anotherCircle != this &&
                        anotherCircle.containsSameObjectWithAnotherCircle() &&
                        (this.circles.indexOf(anotherCircle) < selfIndex )) {

                        this.drawCircleIntersection(
                            this.x, this.y, this.sizeInPixels,
                            anotherCircle.x, anotherCircle.y, anotherCircle.sizeInPixels,
                            (OUTER_CIRCLE_WIDTH * anotherCircle.vinilHighlightL.scaleX) * 0.5);
                    }
                }

                if (previousAlpha !== CONTOUR_ALPHA) {
                    ctx.globalAlpha = previousAlpha;
                }

                this.vinilHighlightL.draw();
                this.vinilHighlightR.draw();

                this._super();

                this.vinilCenter.draw();
            },
            drawCircleIntersection: function (cx1, cy1, radius1, cx2, cy2, radius2, width) {
                var circleDistance = Vector.distance(cx1, cy1, cx2, cy2);
                if ((circleDistance >= radius1 + radius2) ||
                    (radius1 >= circleDistance + radius2)) {
                    return;
                }

                //circleDistance = a + b
                var a = (radius1 * radius1 - radius2 * radius2 + circleDistance * circleDistance) / (2 * circleDistance),
                    b = circleDistance - a,
                    beta = Math.acos(b / radius2),

                    diff = new Vector(cx1 - cx2, cy1 - cy2),
                    centersAngle = diff.angle(),
                    startAngle = centersAngle - beta,
                    endAngle = centersAngle + beta;

                if (cx2 > cx1) {
                    startAngle += Math.PI;
                    endAngle += Math.PI;
                }

                var ctx = Canvas.context;
                ctx.beginPath();
                ctx.lineWidth = width;
                ctx.arc(cx2, cy2, radius2, startAngle, endAngle, false);
                ctx.stroke();

                
            },
            updateChildPositions: function () {
                this.vinil.x = this.vinilCenter.x = this.x;
                this.vinil.y = this.vinilCenter.y = this.y;

                var highlightDeltaX = this.vinilHighlightL.width / 2 * (1.0 - this.vinilHighlightL.scaleX),
                    highlightDeltaY = this.vinilHighlightL.height / 2 * (1.0 - this.vinilHighlightL.scaleY),
                    controllerDeltaX = this.sizeInPixels -
                        (CONTROLLER_SHIFT_PARAM1 - CONTROLLER_SHIFT_PARAM2 * this.size) +
                        (1.0 - this.vinilControllerL.scaleX) * (this.vinilControllerL.width / 2);

                this.vinilHighlightL.x = this.x + highlightDeltaX;
                this.vinilHighlightR.x = this.x - highlightDeltaX;
                this.vinilHighlightL.y = this.vinilHighlightR.y = this.y - highlightDeltaY;

                this.vinilControllerL.x = this.x - controllerDeltaX;
                this.vinilControllerR.x = this.x + controllerDeltaX;
                this.vinilControllerL.y = this.vinilControllerR.y = this.y;

                this.vinilActiveControllerL.x = this.vinilControllerL.x;
                this.vinilActiveControllerL.y = this.vinilControllerL.y;
                this.vinilActiveControllerR.x = this.vinilControllerR.x;
                this.vinilActiveControllerR.y = this.vinilControllerR.y;
            },
            containsSameObjectWithCircle: function (anotherCircle) {

                // check for copy of self
                if (this.x === anotherCircle.x &&
                    this.y === anotherCircle.y &&
                    this.size === anotherCircle.size) {
                    return false;
                }

                var len = this.containedObjects.length,
                    i, object;
                for (i = 0; i < len; i++) {
                    if (anotherCircle.containedObjects.indexOf(
                        this.containedObjects[i]) >= 0) {
                        return true;
                    }
                }
                return false;
            },
            copy: function (zone) {
                var copiedCircle = new RotatedCircle();
                copiedCircle.zone = zone;
                copiedCircle.x = this.x;
                copiedCircle.y = this.y;
                copiedCircle.rotation = this.rotation;
                copiedCircle.circles = this.circles;
                copiedCircle.containedObjects = this.containedObjects;
                copiedCircle.operating = Constants.UNDEFINED;

                var copiedSize = this.size * resolution.PM,
                    copiedRadians = Radians.fromDegrees(copiedCircle.rotation);
                copiedCircle.handle1 = new Vector(
                    copiedCircle.x - copiedSize, copiedCircle.y);
                copiedCircle.handle2 = new Vector(
                    copiedCircle.x + copiedSize, copiedCircle.y);
                copiedCircle.handle1.rotateAround(
                    copiedRadians, copiedCircle.x, copiedCircle.y);
                copiedCircle.handle2.rotateAround(
                    copiedRadians, copiedCircle.x, copiedCircle.y);

                copiedCircle.setSize(this.size);
                copiedCircle.setHasOneHandle(this.hasOneHandle());

                // circle controllers should not be visible
                copiedCircle.vinilControllerL.visible = false;
                copiedCircle.vinilControllerR.visible = false;

                return copiedCircle;
            }
        });

        var IMG_OBJ_VINIL_obj_vinil = 0;
        var IMG_OBJ_VINIL_obj_vinil_highlight = 1;
        var IMG_OBJ_VINIL_odj_vinil_sticker = 2;
        var IMG_OBJ_VINIL_obj_vinil_center = 3;
        var IMG_OBJ_VINIL_obj_controller_active = 4;
        var IMG_OBJ_VINIL_obj_controller = 5;

        return RotatedCircle;
    }
);

define('achievements/AchievementId',[], function () {

    // This is the internal CTR id for achievements. XBOX and other
    // game centers may use different ids.
    var AchievementId = {
        BRONZE_SCISSORS: 0,
        SILVER_SCISSORS: 1,
        GOLDEN_SCISSORS: 2,
        ROPE_CUTTER: 3,
        ROPE_CUTTER_MANIAC: 4,
        ULTIMATE_ROPE_CUTTER: 5,
        BUBBLE_POPPER: 6,
        BUBBLE_MASTER: 7,
        SPIDER_BUSTER: 8,
        SPIDER_TAMER: 9,
        SPIDER_LOVER: 10,
        WEIGHT_LOSER: 11,
        CALORIE_MINIMIZER: 12,
        QUICK_FINGER: 13,
        MASTER_FINGER: 14,
        TUMMY_TEASER: 15,
        CANDY_JUGGLER: 16,
        ROMANTIC_SOUL: 17,
        MAGICIAN: 18
    };

    return AchievementId;

});


define('Achievements',
    [],
    function () {

        var NoAchievements = {
            increment: function(achievementId) {
                // no-op
            }
        };

        return NoAchievements;
    }
);
define('GameScene',
    [
        'game/Bouncer',
        'game/Bubble',
        'game/CandyBreak',
        'game/Drawing',
        'game/FingerCut',
        'game/Grab',
        'game/Pump',
        'game/PumpDirt',
        'game/Sock',
        'game/Spikes',
        'game/Star',
        'game/TutorialText',
        'utils/MathHelper',
        'utils/Mover',
        'visual/Camera2D',
        'utils/DelayedDispatcher',
        'utils/MapItem',
        'visual/AnimationPool',
        'visual/BaseElement',
        'visual/BackgroundTileMap',
        'resources/ResourceMgr',
        'game/CTRSettings',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'utils/Constants',
        'visual/Animation',
        'visual/Timeline',
        'core/Vector',
        'core/RGBAColor',
        'visual/KeyFrame',
        'resolution',
        'utils/PubSub',
        'game/LevelState',
        'edition',
        'core/Alignment',
        'visual/TileMap',
        'physics/ConstrainedPoint',
        'visual/GameObject',
        'game/CTRGameObject',
        'visual/TextImage',
        'game/Bungee',
        'utils/Radians',
        'core/Rectangle',
        'utils/Canvas',
        'visual/ImageElement',
        'physics/ConstraintType',
        'visual/ActionType',
        'game/GravityButton',
        'physics/Gravity',
        'game/EarthImage',
        'resources/LangId',
        'resources/Lang',
        'resources/MenuStringId',
        'game/PollenDrawer',
        'utils/Log',
        'game/RotatedCircle',
        'achievements/AchievementId',
        'Achievements'
    ],
    function (Bouncer, Bubble, CandyBreak, Drawing, FingerCut, Grab, Pump, PumpDirt,
        Sock, Spikes, Star, TutorialText, MathHelper, Mover, Camera2D,
        DelayedDispatcher, MapItem, AnimationPool, BaseElement, BackgroundTileMap,
        ResourceMgr, settings, SoundMgr, ResourceId, Constants, Animation, Timeline,
        Vector, RGBAColor, KeyFrame, resolution, PubSub, LevelState, edition, Alignment,
        TileMap, ConstrainedPoint, GameObject, CTRGameObject, TextImage, Bungee, Radians,
        Rectangle, Canvas, ImageElement, ConstraintType, ActionType, GravityButton, Gravity,
        EarthImage, LangId, Lang, MenuStringId, PollenDrawer, Log, RotatedCircle,
        AchievementId, Achievements) {


        /**
         * Tutorial elements can have a special id specified in the level xml
         * @const
         * @type {number}
         */
        var LEVEL1_ARROW_SPECIAL_ID = 2;

        /**
         * @enum {number}
         */
        var RestartState = {
            FADE_IN: 0,
            FADE_OUT: 1
        };

        /**
         * @enum {number}
         */
        var CameraMove = {
            TO_CANDY_PART: 0,
            TO_CANDY: 1
        };

        /**
         * @enum {number}
         */
        var ButtonMode = {
            GRAVITY: 0,
            SPIKES: 1
        };

        /**
         * @enum {number}
         */
        var PartsType = {
            SEPARATE: 0,
            DISTANCE: 1,
            NONE: 2
        };

        /**
         * @const
         * @type {number}
         */
        var SCOMBO_TIMEOUT = 0.2;

        /**
         * @const
         * @type {number}
         */
        var SCUT_SCORE = 10;

        /**
         * @const
         * @type {number}
         */
        var MAX_LOST_CANDIES = 3;

        /**
         * @const
         * @type {number}
         */
        var ROPE_CUT_AT_ONCE_TIMEOUT = 0.1;

        // Candy Juggler: keep candy without ropes or bubbles for 30 secs
        var CANDY_JUGGLER_TIME = 30;

        /**
         * @const
         * @type {number}
         */
        var BLINK_SKIP = 3;

        /**
         * @const
         * @type {number}
         */
        var MOUTH_OPEN_TIME = 1;

        /**
         * @const
         * @type {number}
         */
        var PUMP_TIMEOUT = 0.05;

        /**
         * @const
         * @type {number}
         */
        var SOCK_SPEED_K = 0.9;

        /**
         * @const
         * @type {number}
         */
        var SOCK_COLLISION_Y_OFFSET = 25;

        /**
         * @enum {number}
         */
        var CandyBlink = {
            INITIAL: 0,
            STAR: 1
        };

        /**
         * @enum {number}
         */
        var TutorialAnimation = {
            SHOW: 0,
            HIDE: 1
        };

        /**
         * @enum {number}
         */
        var EarthAnimation = {
            NORMAL: 0,
            UPSIDE_DOWN: 1
        };

        /**
         * Animations for Om-nom character
         * @enum {number}
         */
        var CharAnimation = {
            IDLE: 0,
            IDLE2: 1,
            IDLE3: 2,
            EXCITED: 3,
            PUZZLED: 4,
            FAIL: 5,
            WIN: 6,
            MOUTH_OPEN: 7,
            MOUTH_CLOSE: 8,
            CHEW: 9,
            GREETING: 10
        };

        /**
         * @const
         * @type {number}
         */
        var HUD_STARS_COUNT = 3;

        /**
         * @const
         * @type {number}
         */
        var HUD_CANDIES_COUNT = 3;

        /**
         * @const
         * @type {number}
         */
        var IMG_BGR_01_bgr = 0;
        /**
         * @const
         * @type {number}
         */
        var IMG_BGR_01_P2_vert_transition = 0;
        var IMG_BGR_02_vert_transition = 1;

        var starDisappearPool = [];
        var bubbleDisappear;


        function applyStarImpulse (star, rd, yImpulse, delta) {
            star.applyImpulse(
                new Vector(
                    -star.v.x / rd,
                    -star.v.y / rd + yImpulse
                ),
                delta
            );
        }

        function isCandyHit (bouncer, star, bouncer_radius) {
            var bouncer_radius_double = bouncer_radius * 2;
            return (
                Rectangle.lineInRect(
                    bouncer.t1.x, bouncer.t1.y,
                    bouncer.t2.x, bouncer.t2.y,
                    star.pos.x - bouncer_radius, star.pos.y - bouncer_radius,
                    bouncer_radius_double, bouncer_radius_double) ||
                    Rectangle.lineInRect(
                        bouncer.b1.x, bouncer.b1.y,
                        bouncer.b2.x, bouncer.b2.y,
                        star.pos.x - bouncer_radius, star.pos.y - bouncer_radius,
                        bouncer_radius_double, bouncer_radius_double));
        }

        var currentPack = -1;

        var GameScene = BaseElement.extend({
            init: function () {
                this._super();

                this.dd = DelayedDispatcher;

                this.initialCameraToStarDistance = Constants.UNDEFINED;
                this.restartState = Constants.UNDEFINED;

                // create animation pools
                this.aniPool = new AnimationPool();
                this.aniPool.visible = false;
                this.addChild(this.aniPool);

                this.staticAniPool = new AnimationPool();
                this.staticAniPool.visible = false;
                this.addChild(this.staticAniPool);

                this.camera = new Camera2D(resolution.CAMERA_SPEED, Camera2D.SpeedType.DELAY);

                this.starsCollected = 0;
                this.hudStars = [];
                starDisappearPool = [];

                for (var i = 0; i < HUD_STARS_COUNT; i++) {
                    var hs = this.hudStars[i] = new Animation();
                    hs.initTextureWithId(ResourceId.IMG_HUD_STAR);
                    hs.doRestoreCutTransparency();
                    hs.addAnimationDelay(0.05, Timeline.LoopType.NO_LOOP, IMG_HUD_STAR_Frame_1, IMG_HUD_STAR_Frame_10);
                    hs.setPause(IMG_HUD_STAR_Frame_10 - IMG_HUD_STAR_Frame_1, 0);
                    //TODO: + canvas.xOffsetScaled on next line?
                    hs.x = 10 + (hs.width + 5) * i;
                    hs.y = 8;
                    this.addChild(hs);
                }

                this.slastTouch = Vector.newZero();
                this.fingerCuts = [];
                for (i = 0; i < Constants.MAX_TOUCHES; i++) {
                    this.fingerCuts[i] = [];
                }

                this.clickToCut = settings.getClickToCut();

                this.PM = resolution.PM;
                this.PMY = resolution.PMY;
                this.PMX = 0;

                this.earthAnims = [];

                this.lastCandyRotateDelta = 0;
                this.lastCandyRotateDeltaL = 0;
                this.lastCandyRotateDeltaR = 0;

                this.attachCount = 0;
                this.juggleTimer = 0;

                this.dragging = new Array(Constants.MAX_TOUCHES);
                this.startPos = new Array(Constants.MAX_TOUCHES);
                this.prevStartPos = new Array(Constants.MAX_TOUCHES);
                for (i = 0; i < Constants.MAX_TOUCHES; i++) {
                    this.dragging[i] = false;
                    this.startPos[i] = Vector.newZero();
                    this.prevStartPos[i] = Vector.newZero();
                }
            },
            /**
             * @param p {ConstrainedPoint}
             * @return {boolean}
             */
            pointOutOfScreen: function (p) {
                var bottomY = this.mapHeight + resolution.OUT_OF_SCREEN_ADJUSTMENT_BOTTOM,
                    topY = resolution.OUT_OF_SCREEN_ADJUSTMENT_TOP,
                    outOfScreen = (p.pos.y > bottomY || p.pos.y < topY);
                return outOfScreen;
            },
            restart: function () {
                this.hide();
                this.show();
            },
            showGreeting: function () {
                this.target.playTimeline(CharAnimation.GREETING);
            },
            shouldSkipTutorialElement: function (element) {
                var langId = settings.getLangId(),
                    tl = element.locale;

                if (LangId.fromString(tl) !== langId) {
                    return true;
                }

                return false;
            },
            show: function () {
                starDisappearPool = [];

                //create bubble animation
                bubbleDisappear = new Animation();
                bubbleDisappear.initTextureWithId(ResourceId.IMG_OBJ_BUBBLE_POP);
                bubbleDisappear.doRestoreCutTransparency();
                bubbleDisappear.anchor = Alignment.CENTER;

                var a = bubbleDisappear.addAnimationDelay(0.05, Timeline.LoopType.NO_LOOP, IMG_OBJ_BUBBLE_POP_Frame_1,
                    IMG_OBJ_BUBBLE_POP_Frame_12);
                bubbleDisappear.getTimeline(a).onFinished = this.aniPool.timelineFinishedDelegate();

                this.aniPool.removeAllChildren();
                this.staticAniPool.removeAllChildren();
                this.dd.cancelAllDispatches();

                this.attachCount = 0;
                this.juggleTimer = 0;

                // load the background image and overlay
                var bgrID = edition.levelBackgroundIds[LevelState.pack],
                    overlayId = edition.levelOverlayIds[LevelState.pack];
                
                if (currentPack != LevelState.pack) {
                    this.bgTexture = ResourceMgr.getTexture(bgrID);
                    $("#canvasBackground").css({
                        "background": "url('" + this.bgTexture.image.src + "')"
                    }).show();

                    currentPack = LevelState.pack;
                }

                // there may not be an overlay specified if none of the box levels scroll
                // this.overlayTexture = overlayId
                //     ? ResourceMgr.getTexture(overlayId)
                //     : this.bgTexture;

                // this.back = new BackgroundTileMap(1, 1);
                // this.back.setRepeatHorizontally(TileMap.RepeatType.NONE);
                // this.back.setRepeatVertically(TileMap.RepeatType.ALL);
                // this.back.addTile(this.bgTexture, IMG_BGR_01_bgr);
                // this.back.fill(0, 0, 1, 1, 0);

                this.gravityButton = null;
                this.gravityTouchDown = Constants.UNDEFINED;

                this.twoParts = PartsType.NONE;
                this.partsDist = 0;

                this.targetSock = null;

                SoundMgr.stopSound(ResourceId.SND_ELECTRIC);

                this.bungees = [];
                this.razors = [];
                this.spikes = [];
                this.stars = [];
                this.bubbles = [];
                this.pumps = [];
                this.rockets = [];
                this.socks = [];
                this.tutorialImages = [];
                this.tutorials = [];
                this.drawings = [];
                this.bouncers = [];
                this.rotatedCircles = [];
                this.pollenDrawer = null;

                this.star = new ConstrainedPoint();
                this.star.setWeight(1);
                this.starL = new ConstrainedPoint();
                this.starL.setWeight(1);
                this.starR = new ConstrainedPoint();
                this.starR.setWeight(1);

                // candy
                this.candy = new GameObject();
                this.candy.initTextureWithId(ResourceId.IMG_OBJ_CANDY_01);
                this.candy.setTextureQuad(IMG_OBJ_CANDY_01_candy_bottom);
                this.candy.doRestoreCutTransparency();
                this.candy.anchor = Alignment.CENTER;
                this.candy.bb = Rectangle.copy(resolution.CANDY_BB);
                this.candy.passTransformationsToChilds = false;
                this.candy.scaleX = this.candy.scaleY = 0.71;
                this.candy.drawPosIncrement = 0.0001;

                // candy main
                this.candyMain = new GameObject();
                this.candyMain.initTextureWithId(ResourceId.IMG_OBJ_CANDY_01);
                this.candyMain.setTextureQuad(IMG_OBJ_CANDY_01_candy_main);
                this.candyMain.doRestoreCutTransparency();
                this.candyMain.anchor = this.candyMain.parentAnchor = Alignment.CENTER;
                this.candy.addChild(this.candyMain);
                this.candyMain.scaleX = this.candyMain.scaleY = 0.71;
                this.candyMain.drawPosIncrement = 0.0001;

                // candy top
                this.candyTop = new GameObject();
                this.candyTop.initTextureWithId(ResourceId.IMG_OBJ_CANDY_01);
                this.candyTop.setTextureQuad(IMG_OBJ_CANDY_01_candy_top);
                this.candyTop.doRestoreCutTransparency();
                this.candyTop.anchor = this.candyTop.parentAnchor = Alignment.CENTER;
                this.candy.addChild(this.candyTop);
                this.candyTop.scaleX = this.candyTop.scaleY = 0.71;
                this.candyTop.drawPosIncrement = 0.0001;

                // candy blink
                this.candyBlink = new Animation();
                this.candyBlink.initTextureWithId(ResourceId.IMG_OBJ_CANDY_01);
                this.candyBlink.doRestoreCutTransparency();
                this.candyBlink.addAnimationEndpoints(
                    CandyBlink.INITIAL,
                    0.07,
                    Timeline.LoopType.NO_LOOP,
                    IMG_OBJ_CANDY_01_highlight_start,
                    IMG_OBJ_CANDY_01_highlight_end);
                this.candyBlink.addAnimationSequence(
                    CandyBlink.STAR,
                    0.3, // delay
                    Timeline.LoopType.NO_LOOP,
                    2, // count
                    [ IMG_OBJ_CANDY_01_glow, IMG_OBJ_CANDY_01_glow ]);
                var gt = this.candyBlink.getTimeline(CandyBlink.STAR);
                gt.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 0));
                gt.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0.2));
                this.candyBlink.visible = false;
                this.candyBlink.anchor = this.candyBlink.parentAnchor = Alignment.CENTER;
                this.candyBlink.scaleX = this.candyBlink.scaleY = 0.71;
                this.candy.addChild(this.candyBlink);
                this.candyBlink.drawPosIncrement = 0.0001;

                // candy bubble
                this.candyBubbleAnimation = new Animation();
                this.candyBubbleAnimation.initTextureWithId(ResourceId.IMG_OBJ_BUBBLE_FLIGHT);
                this.candyBubbleAnimation.x = this.candy.x;
                this.candyBubbleAnimation.y = this.candy.y;
                this.candyBubbleAnimation.parentAnchor = this.candyBubbleAnimation.anchor = Alignment.CENTER;
                this.candyBubbleAnimation.addAnimationDelay(0.05, Timeline.LoopType.REPLAY,
                    IMG_OBJ_BUBBLE_FLIGHT_Frame_1, IMG_OBJ_BUBBLE_FLIGHT_Frame_13);
                this.candyBubbleAnimation.playTimeline(0);
                this.candy.addChild(this.candyBubbleAnimation);
                this.candyBubbleAnimation.visible = false;
                this.candyBubbleAnimation.drawPosIncrement = 0.0001;

                for (var i = 0; i < HUD_STARS_COUNT; i++) {
                    var hs = this.hudStars[i];
                    if (hs.currentTimeline) {
                        hs.currentTimeline.stop();
                    }
                    hs.setTextureQuad(IMG_HUD_STAR_Frame_1);
                }

                var map = LevelState.loadedMap;
                this.loadMap(map);

                // add the animations for the bubbles
                if (this.twoParts !== PartsType.NONE) {
                    this.candyBubbleAnimationL = new Animation();
                    this.candyBubbleAnimationL.initTextureWithId(ResourceId.IMG_OBJ_BUBBLE_FLIGHT);
                    this.candyBubbleAnimationL.parentAnchor = this.candyBubbleAnimationL.anchor = Alignment.CENTER;
                    this.candyBubbleAnimationL.addAnimationDelay(0.05, Timeline.LoopType.REPLAY,
                        IMG_OBJ_BUBBLE_FLIGHT_Frame_1, IMG_OBJ_BUBBLE_FLIGHT_Frame_13);
                    this.candyBubbleAnimationL.playTimeline(0);
                    this.candyL.addChild(this.candyBubbleAnimationL);
                    this.candyBubbleAnimationL.visible = false;
                    this.candyBubbleAnimationL.drawPosIncrement = 0.0001;

                    this.candyBubbleAnimationR = new Animation();
                    this.candyBubbleAnimationR.initTextureWithId(ResourceId.IMG_OBJ_BUBBLE_FLIGHT);
                    this.candyBubbleAnimationR.parentAnchor = this.candyBubbleAnimationR.anchor = Alignment.CENTER;
                    this.candyBubbleAnimationR.addAnimationDelay(0.05, Timeline.LoopType.REPLAY,
                        IMG_OBJ_BUBBLE_FLIGHT_Frame_1, IMG_OBJ_BUBBLE_FLIGHT_Frame_13);
                    this.candyBubbleAnimationR.playTimeline(0);
                    this.candyR.addChild(this.candyBubbleAnimationR);
                    this.candyBubbleAnimationR.visible = false;
                    this.candyBubbleAnimationR.drawPosIncrement = 0.0001;
                }

                var len = this.rotatedCircles.length, r;
                for (i = 0; i < len; i++) {
                    r = this.rotatedCircles[i];
                    r.operating = Constants.UNDEFINED;
                    r.circles = this.rotatedCircles;
                }

                this.startCamera();

                this.tummyTeasers = 0;

                this.starsCollected = 0;
                this.candyBubble = null;
                this.candyBubbleL = null;
                this.candyBubbleR = null;

                this.mouthOpen = false;
                this.noCandy = (this.twoParts !== PartsType.NONE);
                this.noCandyL = false;
                this.noCandyR = false;
                this.blink.playTimeline(0);
                this.spiderTookCandy = false;
                this.time = 0;
                this.score = 0;

                this.gravityNormal = true;
                Gravity.reset();

                this.dimTime = 0;

                this.ropesCutAtOnce = 0;
                this.ropesAtOnceTimer = 0;

                // delay start candy blink
                this.dd.callObject(this, this.doCandyBlink, null, 1);

                var levelLabel = new TextImage(),
                    levelText = (LevelState.pack + 1) + ' - ' + (LevelState.level + 1);
                levelLabel.setText(ResourceId.FNT_BIG_FONT, levelText);
                levelLabel.anchor = Alignment.BOTTOM | Alignment.LEFT;
                levelLabel.x = 37 * resolution.CANVAS_SCALE;
                levelLabel.y = resolution.CANVAS_HEIGHT - (5 * resolution.CANVAS_SCALE);

                var levelLabelTitle = new TextImage();
                levelLabelTitle.setText(ResourceId.FNT_BIG_FONT, Lang.menuText(MenuStringId.LEVEL));
                levelLabelTitle.anchor = Alignment.BOTTOM | Alignment.LEFT;
                levelLabelTitle.parentAnchor = Alignment.TOP | Alignment.LEFT;
                levelLabelTitle.y = 60 * resolution.CANVAS_SCALE;
                levelLabelTitle.rotationCenterX -= levelLabelTitle.width / 2;
                levelLabelTitle.scaleX = levelLabelTitle.scaleY = 0.7;
                levelLabel.addChild(levelLabelTitle);

                var tl = new Timeline();
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0.5));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 0.5));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 1));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0.5));
                levelLabel.addTimelineWithID(tl, 0);
                levelLabel.playTimeline(0);
                tl.onFinished = this.staticAniPool.timelineFinishedDelegate();
                this.staticAniPool.addChild(levelLabel);

                if (this.clickToCut) {
                    this.resetBungeeHighlight();
                }
            },
            startCamera: function () {
                var SCREEN_WIDTH = resolution.CANVAS_WIDTH,
                    SCREEN_HEIGHT = resolution.CANVAS_HEIGHT;

                if (this.mapWidth > SCREEN_WIDTH || this.mapHeight > SCREEN_HEIGHT) {
                    this.ignoreTouches = true;
                    this.fastenCamera = false;
                    this.camera.type = Camera2D.SpeedType.PIXELS;
                    this.camera.speed = 10;
                    this.cameraMoveMode = CameraMove.TO_CANDY_PART;

                    var startX, startY,
                        cameraTarget = (this.twoParts !== PartsType.NONE) ? this.starL : this.star;

                    if (this.mapWidth > SCREEN_WIDTH) {
                        if (cameraTarget.pos.x > this.mapWidth / 2) {
                            startX = 0;
                            startY = 0;
                        }
                        else {
                            startX = this.mapWidth - SCREEN_WIDTH;
                            startY = 0;
                        }
                    }
                    else {
                        if (cameraTarget.pos.y > this.mapHeight / 2) {
                            startX = 0;
                            startY = 0;
                        }
                        else {
                            startX = 0;
                            startY = this.mapHeight - SCREEN_HEIGHT;
                        }
                    }

                    var xScroll = cameraTarget.pos.x - SCREEN_WIDTH / 2,
                        yScroll = cameraTarget.pos.y - SCREEN_HEIGHT / 2,
                        targetX = MathHelper.fitToBoundaries(xScroll, 0, this.mapWidth - SCREEN_WIDTH),
                        targetY = MathHelper.fitToBoundaries(yScroll, 0, this.mapHeight - SCREEN_HEIGHT);

                    this.camera.moveTo(startX, startY, true);

                    this.initialCameraToStarDistance = this.camera.pos.distance(new Vector(targetX, targetY));
                }
                else {
                    this.ignoreTouches = false;
                    this.camera.moveTo(0, 0, true);
                }
            },
            doCandyBlink: function () {
                this.candyBlink.playTimeline(CandyBlink.INITIAL);
            },

            /**
             * Loads the map object
             * @param map {Object}
             */
            loadMap: function (map) {
                var layers = [],
                    self = this;

                // get all the layers for this map
                for (var layerName in map) {
                    if (map.hasOwnProperty(layerName)) {
                        layers.push(map[layerName]);
                    }
                }

                // var enumLayerChildren = function (layers, childCallback) {
                //     for (var i = 0, numLayers = layers.length; i < numLayers; i++) {
                //         // parse the children
                //         var children = layers[i],
                //             numChildren = children.length;
                //         for (var j = 0; j < numChildren; j++) {
                //             //console.log("CALLBAC", i, j)
                //             childCallback.call(self, children[j]);
                //         }
                //     }
                // };

                // first pass handles basic settings and candy
                for (var i = 0, numLayers = layers.length; i < numLayers; i++) {
                    // parse the children
                    var children = layers[i],
                        numChildren = children.length;
                    for (var j = 0; j < numChildren; j++) {
                        var child = children[j];
                        switch (child.name) {
                            case MapItem.MAP:
                                this.loadMapSettings(child);
                                break;
                            case MapItem.GAME_DESIGN:
                                this.loadGameDesign(child);
                                break;
                            case MapItem.CANDY_L:
                                this.loadCandyL(child);
                                break;
                            case MapItem.CANDY_R:
                                this.loadCandyR(child);
                                break;
                            case MapItem.CANDY:
                                this.loadCandy(child);
                                break;
                        }
                    }
                }

                // second pass handles the rest of the game elements
                for (var i = 0, numLayers = layers.length; i < numLayers; i++) {
                    // parse the children
                    var children = layers[i],
                        numChildren = children.length;
                    for (var j = 0; j < numChildren; j++) {
                        var child = children[j];
                        switch (child.name) {
                            case MapItem.GRAVITY_SWITCH:
                                this.loadGravitySwitch(child);
                                break;
                            case MapItem.STAR:
                                this.loadStar(child);
                                break;
                            case MapItem.TUTORIAL_TEXT:
                                this.loadTutorialText(child);
                                break;
                            case MapItem.TUTORIAL_01:
                            case MapItem.TUTORIAL_02:
                            case MapItem.TUTORIAL_03:
                            case MapItem.TUTORIAL_04:
                            case MapItem.TUTORIAL_05:
                            case MapItem.TUTORIAL_06:
                            case MapItem.TUTORIAL_07:
                            case MapItem.TUTORIAL_08:
                            case MapItem.TUTORIAL_09:
                            case MapItem.TUTORIAL_10:
                            case MapItem.TUTORIAL_11:
                            case MapItem.TUTORIAL_12:
                            case MapItem.TUTORIAL_13:
                            case MapItem.TUTORIAL_14:
                                this.loadTutorialImage(child);
                                break;
                            case MapItem.BUBBLE:
                                this.loadBubble(child);
                                break;
                            case MapItem.PUMP:
                                this.loadPump(child);
                                break;
                            case MapItem.SOCK:
                                this.loadSock(child);
                                break;
                            case MapItem.SPIKE_1:
                            case MapItem.SPIKE_2:
                            case MapItem.SPIKE_3:
                            case MapItem.SPIKE_4:
                            case MapItem.ELECTRO:
                                this.loadSpike(child);
                                break;
                            case MapItem.ROTATED_CIRCLE:
                                this.loadRotatedCircle(child);
                                break;
                            case MapItem.BOUNCER1:
                            case MapItem.BOUNCER2:
                                this.loadBouncer(child);
                                break;
                            case MapItem.GRAB:
                                this.loadGrab(child);
                                break;
                            case MapItem.TARGET:
                                this.loadTarget(child);
                                break;
                            case MapItem.HIDDEN_01:
                            case MapItem.HIDDEN_02:
                            case MapItem.HIDDEN_03:
                                this.loadHidden(child);
                                break;
                        }
                    }
                };
            },
            /**
             * Loads the map settings for the map node (inside settings layer)
             * @param item
             */
            loadMapSettings: function (item) {
                this.mapWidth = item.width;
                this.mapHeight = item.height;
                this.PMX = (resolution.CANVAS_WIDTH - (this.mapWidth * this.PM)) / 2;
                this.mapWidth *= this.PM;
                this.mapHeight *= this.PM;

                if (edition.showEarth[LevelState.pack]) {
                    if (this.mapWidth > resolution.CANVAS_WIDTH) {
                        this.earthAnims.push(new EarthImage(resolution.CANVAS_WIDTH, 0));
                    }
                    if (this.mapHeight > resolution.CANVAS_HEIGHT) {
                        this.earthAnims.push(new EarthImage(0, resolution.CANVAS_HEIGHT));
                    }
                    this.earthAnims.push(new EarthImage(0, 0));
                }
            },
            loadGameDesign: function (item) {
                this.special = item.special || 0;
                this.ropePhysicsSpeed = item.ropePhysicsSpeed;
                this.nightLevel = item.nightLevel;
                this.twoParts = item.twoParts ? PartsType.SEPARATE : PartsType.NONE;
                this.ropePhysicsSpeed *= resolution.PHYSICS_SPEED_MULTIPLIER;
            },
            loadGrab: function (item) {
                var gx = item.x * this.PM + this.PMX,
                    gy = item.y * this.PM + this.PMY,
                    l = item.length * this.PM,
                    r = item.radius,
                    wheel = item.wheel,
                    kickable = item.kickable,
                    invisible = item.invisible,
                    ml = item.moveLength * this.PM || -1,
                    v = item.moveVertical,
                    o = item.moveOffset * this.PM || 0,
                    spider = item.spider,
                    left = (item.part === "L"),
                    hidePath = item.hidePath,
                    gun = item.gun,
                    g = new Grab();

                g.x = gx;
                g.y = gy;
                g.wheel = wheel;
                g.gun = gun;
                g.kickable = kickable;
                g.invisible = invisible;
                g.setSpider(spider);
                g.parseMover(item);

                if (g.mover) {
                    g.setBee();

                    if (!hidePath) {
                        var d = 3,
                            isCircle = (item.path[0] === 'R');

                        // create pollen drawer if needed
                        if (!this.pollenDrawer) {
                            this.pollenDrawer = new PollenDrawer();
                        }

                        for (var i = 0, len = g.mover.path.length - 1; i < len; i++) {
                            if (!isCircle || i % d === 0) {
                                this.pollenDrawer.fillWithPollenFromPath(i, i + 1, g);
                            }
                        }

                        if (g.mover.path.length > 2) {
                            this.pollenDrawer.fillWithPollenFromPath(0, g.mover.path.length - 1, g);
                        }

                    }
                }

                if (r !== Constants.UNDEFINED)
                    r *= this.PM;

                if (r === Constants.UNDEFINED && !gun) {
                    var tail = this.star;
                    if (this.twoParts !== PartsType.NONE) {
                        tail = left ? this.starL : this.starR;
                    }

                    var b = new Bungee(null, gx, gy, tail, tail.pos.x, tail.pos.y, l);
                    b.bungeeAnchor.pin.copyFrom(b.bungeeAnchor.pos);
                    g.setRope(b);
                    this.attachCandy();
                }

                g.setRadius(r);
                g.setMoveLength(ml, v, o);

                this.bungees.push(g);
            },
            loadCandyL: function (item) {
                this.starL.pos.x = item.x * this.PM + this.PMX;
                this.starL.pos.y = item.y * this.PM + this.PMY;

                this.candyL = new GameObject();
                this.candyL.initTextureWithId(ResourceId.IMG_OBJ_CANDY_01);
                this.candyL.setTextureQuad(IMG_OBJ_CANDY_01_part_1);
                this.candyL.scaleX = this.candyL.scaleY = 0.71;
                this.candyL.passTransformationsToChilds = false;
                this.candyL.doRestoreCutTransparency();
                this.candyL.anchor = Alignment.CENTER;
                this.candyL.x = this.starL.pos.x;
                this.candyL.y = this.starL.pos.y;
                this.candyL.bb = Rectangle.copy(resolution.CANDY_LR_BB);
            },
            loadCandyR: function (item) {
                this.starR.pos.x = item.x * this.PM + this.PMX;
                this.starR.pos.y = item.y * this.PM + this.PMY;

                this.candyR = new GameObject();
                this.candyR.initTextureWithId(ResourceId.IMG_OBJ_CANDY_01);
                this.candyR.setTextureQuad(IMG_OBJ_CANDY_01_part_2);
                this.candyR.scaleX = this.candyR.scaleY = 0.71;
                this.candyR.passTransformationsToChilds = false;
                this.candyR.doRestoreCutTransparency();
                this.candyR.anchor = Alignment.CENTER;
                this.candyR.x = this.starR.pos.x;
                this.candyR.y = this.starR.pos.y;
                this.candyR.bb = Rectangle.copy(resolution.CANDY_LR_BB);
            },
            loadCandy: function (item) {
                this.star.pos.x = item.x * this.PM + this.PMX;
                this.star.pos.y = item.y * this.PM + this.PMY;
            },
            loadGravitySwitch: function (item) {
                this.gravityButton = new GravityButton();
                this.gravityButton.onButtonPressed = $.proxy(this.onButtonPressed, this);
                this.gravityButton.visible = false;
                this.gravityButton.touchable = false;
                this.addChild(this.gravityButton);
                this.gravityButton.x = item.x * this.PM + this.PMX;
                this.gravityButton.y = item.y * this.PM + this.PMY;
                this.gravityButton.anchor = Alignment.CENTER;
            },
            loadStar: function (item) {
                var s = new Star();
                s.initTextureWithId(ResourceId.IMG_OBJ_STAR_IDLE);
                s.x = item.x * this.PM + this.PMX;
                s.y = item.y * this.PM + this.PMY;
                s.timeout = item.timeout;
                s.createAnimations();

                s.bb = Rectangle.copy(resolution.STAR_BB);
                s.parseMover(item);

                // let stars move the starting position of mover
                s.update(0);

                var l = this.stars.push(s);

                //init the star disappear animations
                var sd = starDisappearPool[l - 1] = new Animation();
                sd.initTextureWithId(ResourceId.IMG_OBJ_STAR_DISAPPEAR);
                sd.doRestoreCutTransparency();
                sd.anchor = Alignment.CENTER;
                
                sd.addAnimationDelay(0.05, Timeline.LoopType.NO_LOOP, IMG_OBJ_STAR_DISAPPEAR_Frame_1,
                                    IMG_OBJ_STAR_DISAPPEAR_Frame_13);
            },
            loadTutorialText: function (item) {
                if (this.shouldSkipTutorialElement(item)) {
                    return;
                }

                if (item.text == null || item.text === '') {
                    Log.debug('Missing tutorial text');
                    return;
                }

                var t = new TutorialText();
                t.x = item.x * this.PM + this.PMX;
                t.y = item.y * this.PM + this.PMY;
                t.special = item.special || 0;
                t.align = Alignment.HCENTER;
                //t.scaleX = 1.3;
                //t.scaleY = 1.3;
                
                var text = item.text,
                    textWidth = Math.ceil(item.width * this.PM);
                t.setText(ResourceId.FNT_SMALL_FONT, text, textWidth, Alignment.HCENTER);
                t.color = RGBAColor.transparent.copy();

                var tl = new Timeline(),
                    isFirstLevel = (LevelState.pack === 0 && LevelState.level === 0);
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 1));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR,
                    isFirstLevel ? 10 : 5));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, .5));
                t.addTimelineWithID(tl, 0);

                if (t.special === 0) {
                    t.playTimeline(0);
                }

                this.tutorials.push(t);
            },
            loadTutorialImage: function (item) {
                if (this.shouldSkipTutorialElement(item)) {
                    return;
                }

                var v = item.name - MapItem.TUTORIAL_01, // gets the tutorial number
                    s = new CTRGameObject();

                s.initTextureWithId(ResourceId.IMG_TUTORIAL_SIGNS);
                s.setTextureQuad(v);
                s.color = RGBAColor.transparent.copy();
                s.x = item.x * this.PM + this.PMX;
                s.y = item.y * this.PM + this.PMY;
                s.rotation = item.angle || 0;
                s.special = item.special || 0;
                s.parseMover(item);

                var tl = new Timeline();
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 1));

                if (LevelState.pack === 0 && LevelState.level === 0) {
                    tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR,
                        10));
                }
                else {
                    tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR,
                        5.2));
                }

                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0.5));
                s.addTimelineWithID(tl, 0);

                if (s.special === 0) {
                    s.playTimeline(0);
                }
                else if (s.special === LEVEL1_ARROW_SPECIAL_ID) {
                    var tl2 = new Timeline();
                    tl2.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR,
                        0));
                    tl2.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR,
                        0.5));
                    tl2.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR,
                        1));
                    tl2.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR,
                        1.1));
                    tl2.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR,
                        0.5));

                    tl2.addKeyFrame(KeyFrame.makePos(s.x, s.y, KeyFrame.TransitionType.LINEAR, 0));
                    tl2.addKeyFrame(KeyFrame.makePos(s.x, s.y, KeyFrame.TransitionType.LINEAR, 0.5));
                    tl2.addKeyFrame(KeyFrame.makePos(s.x, s.y, KeyFrame.TransitionType.LINEAR, 1));
                    tl2.addKeyFrame(KeyFrame.makePos(s.x + resolution.TUTORIAL_HAND_TARGET_X_1, s.y,
                        KeyFrame.TransitionType.LINEAR, 0.5));
                    tl2.addKeyFrame(KeyFrame.makePos(s.x + resolution.TUTORIAL_HAND_TARGET_X_2, s.y,
                        KeyFrame.TransitionType.LINEAR, 0.5));

                    tl2.loopsLimit = 2;
                    tl2.loopType = Timeline.LoopType.REPLAY;

                    s.addTimelineWithID(tl2, 1);
                    s.playTimeline(1);
                }

                this.tutorialImages.push(s);
            },
            loadHidden: function (item) {
                // get the hidden image index
                var v = item.name - MapItem.HIDDEN_01,
                    drawingId = item.drawing - 1;

                var alreadyUnlocked = false;
                if (!alreadyUnlocked && !edition.disableHiddenDrawings) {
                    var s = new Drawing(v, drawingId);
                    s.x = item.x * this.PM + this.PMX;
                    s.y = item.y * this.PM + this.PMY;
                    s.rotation = item.angle || 0;
                    this.drawings.push(s);
                }
            },
            loadBubble: function (item) {
                var at = MathHelper.randomRange(
                        IMG_OBJ_BUBBLE_ATTACHED_stain_01, IMG_OBJ_BUBBLE_ATTACHED_stain_03),
                    s = new Bubble();
                s.initTextureWithId(ResourceId.IMG_OBJ_BUBBLE_ATTACHED);
                s.setTextureQuad(at);
                s.doRestoreCutTransparency();

                s.bb = Rectangle.copy(resolution.BUBBLE_BB);
                s.x = item.x * this.PM + this.PMX;
                s.y = item.y * this.PM + this.PMY;
                s.anchor = Alignment.CENTER;
                s.popped = false;

                var bubble = new ImageElement();
                bubble.initTextureWithId(ResourceId.IMG_OBJ_BUBBLE_ATTACHED);
                bubble.setTextureQuad(IMG_OBJ_BUBBLE_ATTACHED_bubble);
                bubble.doRestoreCutTransparency();
                bubble.parentAnchor = bubble.anchor = Alignment.CENTER;
                s.addChild(bubble);
                this.bubbles.push(s);


            },
            loadPump: function (item) {
                var s = new Pump();
                s.initTextureWithId(ResourceId.IMG_OBJ_PUMP);
                s.doRestoreCutTransparency();
                s.addAnimationWithDelay(0.05, Timeline.LoopType.NO_LOOP, 4, [1, 2, 3, 0]);

                s.bb = Rectangle.copy(resolution.PUMP_BB);
                s.x = item.x * this.PM + this.PMX;
                s.y = item.y * this.PM + this.PMY;
                s.rotation = item.angle + 90;
                s.updateRotation();
                s.anchor = Alignment.CENTER;
                this.pumps.push(s);
            },
            loadSock: function (item) {
                var s = new Sock();
                s.initTextureWithId(ResourceId.IMG_OBJ_SOCKS);
                s.scaleX = s.scaleY = 0.7;
                s.createAnimations();
                s.doRestoreCutTransparency();

                s.x = item.x * this.PM + this.PMX;
                s.y = item.y * this.PM + this.PMY;
                s.group = item.group;

                s.anchor = Alignment.TOP | Alignment.HCENTER;
                s.rotationCenterY -= s.height / 2 - SOCK_COLLISION_Y_OFFSET;

                s.setTextureQuad((s.group === 0)
                    ? Sock.Quads.IMG_OBJ_SOCKS_hat_01
                    : Sock.Quads.IMG_OBJ_SOCKS_hat_02);

                s.state = Sock.StateType.IDLE;
                s.parseMover(item);
                s.rotation += 90;
                if (s.mover) {
                    s.mover.angle += 90;
                }

                s.updateRotation();
                this.socks.push(s);
            },
            loadSpike: function (item) {
                var px = item.x * this.PM + this.PMX,
                    py = item.y * this.PM + this.PMY,
                    w = item.size,
                    a = parseFloat(item.angle) || 0,
                    tg = (item.toggled === false)
                        ? Constants.UNDEFINED
                        : item.toggled || Constants.UNDEFINED,
                    s = new Spikes(px, py, w, a, tg);
                s.parseMover(item);

                if (tg) {
                    s.onRotateButtonPressed = $.proxy(this.rotateAllSpikesWithId, this);
                }

                if (item.name === MapItem.ELECTRO) {
                    s.electro = true;
                    s.initialDelay = item.initialDelay;
                    s.onTime = item.onTime;
                    s.offTime = item.offTime;
                    s.electroTimer = 0;

                    s.turnElectroOff();
                    s.electroTimer += s.initialDelay;
                    s.updateRotation();
                }
                else {
                    s.electro = false;
                }
                this.spikes.push(s);
            },
            loadRotatedCircle: function (item) {
                var px = item.x * this.PM + this.PMX,
                    py = item.y * this.PM + this.PMY,
                    size = item.size,
                    handleAngle = parseFloat(item.handleAngle) || 0,
                    handleRadians = Radians.fromDegrees(handleAngle),
                    oneHandle = item.oneHandle,
                    l = new RotatedCircle();

                l.anchor = Alignment.CENTER;
                l.x = px;
                l.y = py;
                l.rotation = handleAngle;
                l.handle1 = new Vector(l.x - size * this.PM, l.y);
                l.handle2 = new Vector(l.x + size * this.PM, l.y);

                l.handle1.rotateAround(handleRadians, l.x, l.y);
                l.handle2.rotateAround(handleRadians, l.x, l.y);

                l.setSize(size);
                l.setHasOneHandle(oneHandle);

                this.rotatedCircles.push(l);
            },
            loadBouncer: function (item) {
                var px = item.x * this.PM + this.PMX,
                    py = item.y * this.PM + this.PMY,
                    w = item.size,
                    a = item.angle,
                    bouncer = new Bouncer(px, py, w, a);
                bouncer.parseMover(item);
                this.bouncers.push(bouncer);
            },
            loadTarget: function (item) {
                var target = new GameObject();
                this.target = target;

                target.initTextureWithId(ResourceId.IMG_CHAR_ANIMATIONS);
                target.doRestoreCutTransparency();

                target.bb = Rectangle.copy(resolution.TARGET_BB);
                target.drawPosIncrement = 0.0001;

                target.addAnimationEndpoints(CharAnimation.GREETING, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_greeting_start, IMG_CHAR_ANIMATIONS_greeting_end);
                target.addAnimationEndpoints(CharAnimation.IDLE, 0.05, Timeline.LoopType.REPLAY,
                    IMG_CHAR_ANIMATIONS_idle_start, IMG_CHAR_ANIMATIONS_idle_end);
                target.addAnimationEndpoints(CharAnimation.IDLE2, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_idle2_start, IMG_CHAR_ANIMATIONS_idle2_end);
                target.addAnimationSequence(CharAnimation.IDLE3, 0.05, Timeline.LoopType.NO_LOOP,
                    (IMG_CHAR_ANIMATIONS_idle3_end - IMG_CHAR_ANIMATIONS_idle3_start + 1) * 2,
                    [ 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
                        109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124 ]);
                target.addAnimationEndpoints(CharAnimation.EXCITED, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_excited_start, IMG_CHAR_ANIMATIONS_excited_end);
                target.addAnimationEndpoints(CharAnimation.PUZZLED, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_puzzled_start, IMG_CHAR_ANIMATIONS_puzzled_end);
                target.addAnimationEndpoints(CharAnimation.FAIL, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_fail_start, IMG_CHAR_ANIMATIONS_fail_end);
                target.addAnimationEndpoints(CharAnimation.WIN, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_mouth_close_start, IMG_CHAR_ANIMATIONS_mouth_close_end);
                target.addAnimationEndpoints(CharAnimation.MOUTH_OPEN, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_mouth_open_start, IMG_CHAR_ANIMATIONS_mouth_open_end);
                target.addAnimationEndpoints(CharAnimation.MOUTH_CLOSE, 0.05, Timeline.LoopType.NO_LOOP,
                    IMG_CHAR_ANIMATIONS_mouth_close_start, IMG_CHAR_ANIMATIONS_mouth_close_end);
                target.addAnimationEndpoints(CharAnimation.CHEW, 0.05, Timeline.LoopType.REPLAY,
                    IMG_CHAR_ANIMATIONS_chew_start, IMG_CHAR_ANIMATIONS_chew_end);
                target.switchToAnimation(CharAnimation.CHEW, CharAnimation.WIN, 0.05);
                target.switchToAnimation(CharAnimation.PUZZLED, CharAnimation.MOUTH_CLOSE, 0.05);
                target.switchToAnimation(CharAnimation.IDLE, CharAnimation.GREETING, 0.05);
                target.switchToAnimation(CharAnimation.IDLE, CharAnimation.IDLE2, 0.05);
                target.switchToAnimation(CharAnimation.IDLE, CharAnimation.IDLE3, 0.05);
                target.switchToAnimation(CharAnimation.IDLE, CharAnimation.EXCITED, 0.05);
                target.switchToAnimation(CharAnimation.IDLE, CharAnimation.PUZZLED, 0.05);

                // delay greeting by Om-nom
                if (settings.showGreeting) {
                    this.dd.callObject(this, this.showGreeting, null, 2);
                    settings.showGreeting = false;
                }

                target.playTimeline(CharAnimation.IDLE);

                var idle = target.getTimeline(CharAnimation.IDLE);
                idle.onKeyFrame = $.proxy(this.onIdleOmNomKeyFrame, this);

                target.setPause(
                    (IMG_CHAR_ANIMATIONS_mouth_open_end - IMG_CHAR_ANIMATIONS_mouth_open_start),
                    CharAnimation.MOUTH_OPEN);
                this.blink = new Animation();
                this.blink.initTextureWithId(ResourceId.IMG_CHAR_ANIMATIONS);
                this.blink.parentAnchor = Alignment.TOP | Alignment.LEFT;

                this.blink.visible = false;
                this.blink.addAnimationSequence(0, 0.05, Timeline.LoopType.NO_LOOP, 4,
                    [ IMG_CHAR_ANIMATIONS_blink_start, IMG_CHAR_ANIMATIONS_blink_end, IMG_CHAR_ANIMATIONS_blink_end, IMG_CHAR_ANIMATIONS_blink_end ]);
                this.blink.setAction(ActionType.SET_VISIBLE, this.blink, 0, 0, 2, 0);
                this.blinkTimer = BLINK_SKIP;

                this.blink.doRestoreCutTransparency();
                target.addChild(this.blink);

                var supportQuadID = edition.supports[LevelState.pack];
                this.support = ImageElement.create(ResourceId.IMG_CHAR_SUPPORTS, supportQuadID);
                this.support.doRestoreCutTransparency();
                this.support.anchor = Alignment.CENTER;

                var sx = item.x,
                    sy = item.y;

                this.target.x = this.support.x = sx * this.PM + this.PMX | 0;
                this.target.y = this.support.y = sy * this.PM + this.PMY | 0;

                this.idlesTimer = MathHelper.randomRange(5, 20);
            },
            onIdleOmNomKeyFrame: function (timeline, keyFrame, index) {

                if (index === 1) {

                    // om-nom blink
                    this.blinkTimer--;
                    if (this.blinkTimer === 0) {
                        this.blink.visible = true;
                        this.blink.playTimeline(0);
                        this.blinkTimer = BLINK_SKIP;
                    }

                    // om-nom idle action
                    this.idlesTimer--;
                    if (this.idlesTimer === 0) {
                        if (MathHelper.randomRange(0, 1) === 1) {
                            this.target.playTimeline(CharAnimation.IDLE2);
                        }
                        else {
                            this.target.playTimeline(CharAnimation.IDLE3);
                        }
                        this.idlesTimer = MathHelper.randomRange(5, 20);
                    }
                }
            },
            onRotatedCircleTimelineFinished: function (t) {
                var circleToRemove = t.element;
                circleToRemove.removeOnNextUpdate = true;
            },
            update: function (delta) {
                var i, len, moveResult;
                for (i = 0, len = this.drawings.length; i < len; i++) {
                    this.drawings[i].update(delta);
                }

                this._super(delta);
                this.dd.update(delta);

                if (this.pollenDrawer) {
                    this.pollenDrawer.update(delta);
                }

                for (i = 0; i < Constants.MAX_TOUCHES; i++) {
                    var cuts = this.fingerCuts[i],
                        numCuts = cuts.length,
                        k = 0;

                    while (k < numCuts) {
                        var fc = cuts[k];
                        moveResult = Mover.moveToTargetWithStatus(fc.color.a, 0, 10, delta);
                        fc.color.a = moveResult.value;
                        if (moveResult.reachedZero) {
                            cuts.splice(k, 1);
                            numCuts--;
                        }
                        else {
                            k++;
                        }
                    }
                }

                for (i = 0, len = this.earthAnims.length; i < len; i++) {
                    this.earthAnims[i].update(delta);
                }

                this.ropesAtOnceTimer = Mover.moveToTarget(this.ropesAtOnceTimer, 0, 1, delta);

                if (this.attachCount === 0) {
                    this.juggleTimer += delta;

                    // has it been 30 secs since the candy was attached?
                    if (this.juggleTimer > CANDY_JUGGLER_TIME) {

                        //Achievements.increment(AchievementId.CANDY_JUGGLER);

                        // reset the timer
                        this.juggleTimer = 0;
                    }
                }

                var SCREEN_WIDTH = resolution.CANVAS_WIDTH,
                    SCREEN_HEIGHT = resolution.CANVAS_HEIGHT,
                    cameraTarget = (this.twoParts != PartsType.NONE) ? this.starL : this.star,
                    xScroll = cameraTarget.pos.x - SCREEN_WIDTH / 2,
                    yScroll = cameraTarget.pos.y - SCREEN_HEIGHT / 2,
                    targetX = MathHelper.fitToBoundaries(xScroll, 0, this.mapWidth - SCREEN_WIDTH),
                    targetY = MathHelper.fitToBoundaries(yScroll, 0, this.mapHeight - SCREEN_HEIGHT);

                this.camera.moveTo(targetX, targetY, false);

                // NOTE: mac sources indicate this is temporary?
                if (!(this.freezeCamera && this.camera.type === Camera2D.SpeedType.DELAY)) {
                    this.camera.update(delta);
                }

                if (this.camera.type === Camera2D.SpeedType.PIXELS) {
                    var IGNORE_TOUCHES_DISTANCE = resolution.IGNORE_TOUCHES_DISTANCE,
                        PREVIEW_CAMERA_SPEED = resolution.PREVIEW_CAMERA_SPEED,
                        PREVIEW_CAMERA_SPEED2 = resolution.PREVIEW_CAMERA_SPEED2,
                        MAX_PREVIEW_CAMERA_SPEED = resolution.MAX_PREVIEW_CAMERA_SPEED,
                        MIN_PREVIEW_CAMERA_SPEED = resolution.MIN_PREVIEW_CAMERA_SPEED;

                    var starDistance = this.camera.pos.distance(new Vector(targetX, targetY));
                    if (starDistance < IGNORE_TOUCHES_DISTANCE) {
                        this.ignoreTouches = false;
                    }

                    if (this.fastenCamera) {
                        if (this.camera.speed < resolution.CAMERA_SPEED_THRESHOLD) {
                            this.camera.speed *= 1.5;
                        }
                    }
                    else {
                        if (starDistance > this.initialCameraToStarDistance / 2.0) {
                            this.camera.speed += delta * PREVIEW_CAMERA_SPEED;
                            this.camera.speed = Math.min(MAX_PREVIEW_CAMERA_SPEED, this.camera.speed);
                        }
                        else {
                            this.camera.speed -= delta * PREVIEW_CAMERA_SPEED2;
                            this.camera.speed = Math.max(MIN_PREVIEW_CAMERA_SPEED, this.camera.speed);
                        }
                    }

                    if (Math.abs(this.camera.pos.x - targetX) < 1 &&
                        Math.abs(this.camera.pos.y - targetY) < 1) {
                        this.camera.type = Camera2D.SpeedType.DELAY;
                        this.camera.speed = resolution.CAMERA_SPEED;
                    }
                }
                else {
                    this.time += delta;
                }

                var numGrabs = this.bungees.length;
                if (numGrabs > 0) {
                    var handledRotation = false,
                        handledRotationL = false,
                        handledRotationR = false;

                    for (i = 0; i < numGrabs; i++) {
                        // yes, its a little confusing that the bungees array
                        // actually holds grabs
                        var g = this.bungees[i];
                        g.update(delta);

                        var b = g.rope;

                        if (g.mover) {
                            if (b) {
                                b.bungeeAnchor.pos.x = g.x;
                                b.bungeeAnchor.pos.y = g.y;
                                b.bungeeAnchor.pin.copyFrom(b.bungeeAnchor.pos);
                            }
                        }

                        if (b) {
                            if (b.cut !== Constants.UNDEFINED && b.cutTime === 0) {
                                g.destroyRope();
                                continue;
                            }

                            b.update(delta * this.ropePhysicsSpeed);

                            if (g.hasSpider) {
                                if (this.camera.type != Camera2D.SpeedType.PIXELS || !this.ignoreTouches) {
                                    g.updateSpider(delta);
                                }

                                if (g.spiderPos === Constants.UNDEFINED) {
                                    this.spiderWon(g);
                                    break;
                                }
                            }
                        }

                        if (g.radius !== Constants.UNDEFINED && !g.rope) {
                            // shared code for creating a rope with a star
                            var STAR_RADIUS = resolution.STAR_RADIUS,
                                createRope = $.proxy(function (star) {
                                    var l = new Vector(g.x, g.y).distance(star.pos);
                                    if (l <= g.radius + STAR_RADIUS) {
                                        var b = new Bungee(
                                            null, g.x, g.y, // head
                                            star, star.pos.x, star.pos.y, // tail
                                            g.radius + STAR_RADIUS);
                                        b.bungeeAnchor.pin.copyFrom(b.bungeeAnchor.pos);
                                        g.hideRadius = true;
                                        g.setRope(b);

                                        this.attachCandy();

                                        SoundMgr.playSound(ResourceId.SND_ROPE_GET);
                                        if (g.mover) {
                                            SoundMgr.playSound(ResourceId.SND_BUZZ);
                                        }
                                    }
                                }, this);

                            if (this.twoParts !== PartsType.NONE) {
                                if (!this.noCandyL) {
                                    createRope(this.starL);
                                }
                                if (!this.noCandyR && g.rope == null) {
                                    createRope(this.starR)
                                }
                            }
                            else {
                                createRope(this.star);
                            }
                        }

                        if (b) {
                            var prev = b.bungeeAnchor,
                                tail = b.parts[b.parts.length - 1],
                                v = Vector.subtract(prev.pos, tail.pos),
                                hasCandy = false;

                            if (!handledRotation) {
                                if (this.twoParts !== PartsType.NONE) {
                                    if (tail === this.starL && !this.noCandyL && !handledRotationL) {
                                        hasCandy = true;
                                    }
                                    else if (tail === this.starR && !this.noCandyR && !handledRotationR) {
                                        hasCandy = true;
                                    }
                                }
                                else if (!this.noCandy && !handledRotation) {
                                    hasCandy = true;
                                }
                            }

                            if (b.relaxed !== 0 && b.cut === Constants.UNDEFINED && hasCandy) {
                                var a = Radians.toDegrees(v.normalizedAngle());
                                if (this.twoParts !== PartsType.NONE) {
                                    var candyPart = (tail === this.starL) ? this.candyL : this.candyR;
                                    if (!b.chosenOne) {
                                        b.initialCandleAngle = candyPart.rotation - a;
                                    }

                                    if (tail === this.starL) {
                                        this.lastCandyRotateDeltaL = a + b.initialCandleAngle - candyPart.rotation;
                                        handledRotationL = true;
                                    }
                                    else {
                                        this.lastCandyRotateDeltaR = a + b.initialCandleAngle - candyPart.rotation;
                                        handledRotationR = true;
                                    }
                                    candyPart.rotation = a + b.initialCandleAngle;
                                }
                                else {
                                    if (!b.chosenOne) {
                                        b.initialCandleAngle = this.candyMain.rotation - a;
                                    }
                                    this.lastCandyRotateDelta = a + b.initialCandleAngle - this.candyMain.rotation;
                                    this.candyMain.rotation = a + b.initialCandleAngle;
                                    handledRotation = true;
                                }

                                b.chosenOne = true;
                            }
                            else {
                                b.chosenOne = false;
                            }
                        }
                    }

                    if (this.twoParts !== PartsType.NONE) {
                        if (!handledRotationL && !this.noCandyL) {
                            this.candyL.rotation += Math.min(5, this.lastCandyRotateDeltaL);
                            this.lastCandyRotateDeltaL *= 0.98;
                        }
                        if (!handledRotationR && !this.noCandyR) {
                            this.candyR.rotation += Math.min(5, this.lastCandyRotateDeltaR);
                            this.lastCandyRotateDeltaR *= 0.98;
                        }
                    }
                    else {
                        if (!handledRotation && !this.noCandy) {
                            this.candyMain.rotation += Math.min(5, this.lastCandyRotateDelta);
                            this.lastCandyRotateDelta *= 0.98;
                        }
                    }
                }

                if (!this.noCandy) {
                    this.candy.update(delta);
                    this.star.update(delta * this.ropePhysicsSpeed);
                }

                if (this.twoParts !== PartsType.NONE) {
                    var ropeDelta = delta * this.ropePhysicsSpeed;
                    this.candyL.update(delta);
                    this.starL.update(ropeDelta);
                    this.candyR.update(delta);
                    this.starR.update(ropeDelta);
                    if (this.twoParts === PartsType.DISTANCE) {
                        for (i = 0; i < Bungee.BUNGEE_RELAXION_TIMES; i++) {
                            this.starL.satisfyConstraints();
                            this.starR.satisfyConstraints();
                        }
                    }
                    if (this.partsDist > 0) {
                        moveResult = Mover.moveToTargetWithStatus(this.partsDist, 0, 200, delta);
                        this.partsDist = moveResult.value;
                        if (moveResult.reachedZero) {
                            SoundMgr.playSound(ResourceId.SND_CANDY_LINK);
                            this.twoParts = PartsType.NONE;
                            this.noCandy = false;
                            this.noCandyL = true;
                            this.noCandyR = true;

                            //Achievements.increment(AchievementId.ROMANTIC_SOUL);

                            if (this.candyBubbleL || this.candyBubbleR) {
                                this.candyBubble = (this.candyBubbleL ? this.candyBubbleL : this.candyBubbleR);
                                this.candyBubbleAnimation.visible = true;
                            }

                            this.lastCandyRotateDelta = 0;
                            this.lastCandyRotateDeltaL = 0;
                            this.lastCandyRotateDeltaR = 0;

                            this.star.pos.x = this.starL.pos.x;
                            this.star.pos.y = this.starL.pos.y;
                            this.candy.x = this.star.pos.x;
                            this.candy.y = this.star.pos.y;
                            this.candy.calculateTopLeft();

                            var lv = Vector.subtract(this.starL.pos, this.starL.prevPos),
                                rv = Vector.subtract(this.starR.pos, this.starR.prevPos),
                                sv = new Vector((lv.x + rv.x) / 2, (lv.y + rv.y) / 2);
                            this.star.prevPos.copyFrom(this.star.pos);
                            this.star.prevPos.subtract(sv);

                            for (var i = 0, count = this.bungees.length; i < count; i++) {
                                var g = this.bungees[i],
                                    b = g.rope;
                                if (b && b.cut !== b.parts.length - 3 &&
                                    (b.tail === this.starL || b.tail === this.starR)) {
                                    var prev = b.parts[b.parts.length - 2],
                                        heroRestLen = b.tail.restLength(prev);
                                    this.star.addConstraint(prev, heroRestLen, ConstraintType.DISTANCE);
                                    b.tail = this.star;
                                    b.parts[b.parts.length - 1] = this.star;
                                    b.initialCandleAngle = 0;
                                    b.chosenOne = false;
                                }
                            }

                            var transform = new Animation();
                            transform.initTextureWithId(ResourceId.IMG_OBJ_CANDY_01);
                            transform.doRestoreCutTransparency();
                            transform.x = this.candy.x;
                            transform.y = this.candy.y;
                            transform.anchor = Alignment.CENTER;
                            var a = transform.addAnimationDelay(0.05, Timeline.LoopType.NO_LOOP,
                                IMG_OBJ_CANDY_01_part_fx_start, IMG_OBJ_CANDY_01_part_fx_end);
                            transform.getTimeline(a).onFinished = this.aniPool.timelineFinishedDelegate();
                            transform.playTimeline(0);
                            this.aniPool.addChild(transform);

                            
                            
                            
                        }
                        else {
                            this.starL.changeRestLength(this.starR, this.partsDist);
                            this.starR.changeRestLength(this.starL, this.partsDist);
                        }
                    }

                    if (!this.noCandyL && !this.noCandyR &&
                        this.twoParts === PartsType.SEPARATE &&
                        GameObject.intersect(this.candyL, this.candyR)) {
                        this.twoParts = PartsType.DISTANCE;
                        this.partsDist = this.starL.pos.distance(this.starR.pos);
                        this.starL.addConstraint(this.starR, this.partsDist, ConstraintType.NOT_MORE_THAN);
                        this.starR.addConstraint(this.starL, this.partsDist, ConstraintType.NOT_MORE_THAN);
                    }
                }

                this.target.update(delta);

                if (this.camera.type !== Camera2D.SpeedType.PIXELS || !this.ignoreTouches) {
                    for (i = 0, len = this.stars.length; i < len; i++) {
                        var s = this.stars[i];
                        if (!s) continue;
                        s.update(delta);

                        if (s.timeout > 0 && s.time === 0) {
                            s.getTimeline(1).onFinished = this.aniPool.timelineFinishedDelegate();
                            this.aniPool.addChild(s);
                            this.stars.splice(i, 1);
                            s.timedAnim.playTimeline(1);
                            s.playTimeline(1);
                            break;
                        }
                        else {
                            var hits = false;
                            if (this.twoParts !== PartsType.NONE) {
                                hits = (GameObject.intersect(this.candyL, s) && !this.noCandyL) ||
                                    (GameObject.intersect(this.candyR, s) && !this.noCandyR);
                            }
                            else {
                                hits = GameObject.intersect(this.candy, s) && !this.noCandy;
                            }

                            if (hits) {
                                
                                this.candyBlink.playTimeline(CandyBlink.STAR);
                                this.starsCollected++;
                                this.hudStars[this.starsCollected - 1].playTimeline(0);
                                
                                var starDisappear = starDisappearPool[i];
                                starDisappear.x = s.x;
                                starDisappear.y = s.y;
                                
                                starDisappear.playTimeline(0);
                                this.aniPool.addChild(starDisappear);

                                
                                this.stars[i] = null;
                                SoundMgr.playSound(ResourceId.SND_STAR_1 + this.starsCollected - 1);
                                
                                if (this.target.currentTimelineIndex === CharAnimation.IDLE) {
                                    this.target.playTimeline(CharAnimation.EXCITED);
                                }
                                
                                break;
                            }
                        }
                    }
                }

                for (i = 0, len = this.bubbles.length; i < len; i++) {
                    b = this.bubbles[i];
                    b.update(delta);

                    if (!b.popped) {

                        if (this.twoParts != PartsType.NONE) {

                            if (!this.noCandyL && this.isBubbleCapture(b, this.candyL, this.candyBubbleL, this.candyBubbleAnimationL)) {
                                this.candyBubbleL = b;
                                break;
                            }

                            if (!this.noCandyR && this.isBubbleCapture(b, this.candyR, this.candyBubbleR, this.candyBubbleAnimationR)) {
                                this.candyBubbleR = b;
                                break;
                            }
                        } else {

                            if (!this.noCandy && this.isBubbleCapture(b, this.candy, this.candyBubble, this.candyBubbleAnimation)) {
                                this.candyBubble = b;
                                break;
                            }
                        }
                    }

                    if (!b.withoutShadow) {
                        var numRotatedCircles = this.rotatedCircles.length;
                        for (j = 0; j < numRotatedCircles; j++) {
                            var rc = this.rotatedCircles[j],
                                distanceToCircle = Vector.distance(b.x, b.y, rc.x, rc.y);
                            if (distanceToCircle < rc.sizeInPixels) {
                                b.withoutShadow = true;
                            }
                        }
                    }
                }

                // tutorial text
                for (i = 0, len = this.tutorials.length; i < len; i++) {
                    var t = this.tutorials[i];
                    t.update(delta);
                }

                // tutorial images
                for (i = 0, len = this.tutorialImages.length; i < len; i++) {
                    t = this.tutorialImages[i];
                    t.update(delta);
                }

                var removeCircleIndex = -1;
                for (i = 0, len = this.rotatedCircles.length; i < len; i++) {
                    rc = this.rotatedCircles[i];

                    for (j = 0; j < numGrabs; j++) {
                        var g = this.bungees[j],
                            gIndex = rc.containedObjects.indexOf(g),
                            distance = Vector.distance(g.x, g.y, rc.x, rc.y);

                        if (distance <= rc.sizeInPixels + 5 * this.PM) {
                            if (gIndex < 0) {
                                rc.containedObjects.push(g);
                            }
                        }
                        else if (gIndex >= 0) {
                            rc.containedObjects.splice(g, 1);
                        }
                    }

                    var numBubbles = this.bubbles.length;
                    for (j = 0; j < numBubbles; j++) {
                        var b = this.bubbles[j],
                            distance = Vector.distance(b.x, b.y, rc.x, rc.y),
                            bIndex = rc.containedObjects.indexOf(b);

                        if (distance <= rc.sizeInPixels + 10 * this.PM) {
                            if (bIndex < 0) {
                                rc.containedObjects.push(b);
                            }
                        }
                        else if (bIndex >= 0) {
                            rc.containedObjects.splice(b, 1);
                        }
                    }

                    if (rc.removeOnNextUpdate) {
                        removeCircleIndex = i;
                    }

                    rc.update(delta);
                }

                if (removeCircleIndex >= 0) {
                    this.rotatedCircles.splice(removeCircleIndex, 1);
                }

                // rockets
                for (i = 0, len = this.rockets.length; i < len; i++) {
                    r = this.rockets[i];
                    r.update(delta);
                    // TODO: finish
                }

                // socks
                for (i = 0, len = this.socks.length; i < len; i++) {
                    s = this.socks[i];
                    s.update(delta);
                    var moveStatus = Mover.moveToTargetWithStatus(s.idleTimeout, 0, 1, delta);
                    s.idleTimeout = moveStatus.value;
                    if (moveStatus.reachedZero) {
                        s.state = Sock.StateType.IDLE;
                    }

                    var savedRotation = s.rotation;
                    s.rotation = 0;
                    s.updateRotation();
                    var rs = this.star.posDelta.copy();
                    rs.rotate(Radians.fromDegrees(-savedRotation));
                    s.rotation = savedRotation;
                    s.updateRotation();

                    var bbX = this.star.pos.x - resolution.STAR_SOCK_RADIUS,
                        bbY = this.star.pos.y - resolution.STAR_SOCK_RADIUS,
                        bbW = resolution.STAR_SOCK_RADIUS * 2,
                        bbH = bbW;

                    /*
                     // DEBUG: draw the star bounding box
                     var ctx = Canvas.context;
                     ctx.lineWidth = 1;
                     ctx.strokeStyle = 'red';
                     ctx.strokeRect(bbX, bbY, bbW, bbH);
                     */

                    if (rs.y >= 0 &&
                        (Rectangle.lineInRect(
                            s.t1.x, s.t1.y, s.t2.x, s.t2.y,
                            bbX, bbY, bbW, bbH) ||
                            Rectangle.lineInRect(
                                s.b1.x, s.b1.y, s.b2.x, s.b2.y,
                                bbX, bbY, bbW, bbH))) {

                        if (s.state === Sock.StateType.IDLE) {

                            // look for a recieving sock
                            for (var j = 0; j < len; j++) {
                                var n = this.socks[j];
                                if (n !== s && n.group === s.group) {
                                    s.state = Sock.StateType.RECEIVING;
                                    n.state = Sock.StateType.THROWING;
                                    this.releaseAllRopes(false);

                                    this.savedSockSpeed = SOCK_SPEED_K * this.star.v.getLength() *
                                        resolution.PHYSICS_SPEED_MULTIPLIER;
                                    this.targetSock = n;

                                    s.light.playTimeline(0);
                                    s.light.visible = true;
                                    SoundMgr.playSound(ResourceId.SND_TELEPORT);
                                    this.dd.callObject(this, this.teleport, null, 0.1);
                                    break;
                                }
                            }
                            break;
                        }

                    }
                    else {
                        if (s.state !== Sock.StateType.IDLE && s.idleTimeout === 0) {
                            s.idleTimeout = Sock.IDLE_TIMEOUT;
                        }
                    }

                }

                // pumps
                for (i = 0, len = this.pumps.length; i < len; i++) {
                    var p = this.pumps[i];
                    p.update(delta);

                    var moveStatus = Mover.moveToTargetWithStatus(p.touchTimer, 0, 1, delta);
                    p.touchTimer = moveStatus.value;
                    if (moveStatus.reachedZero) {
                        this.operatePump(p, delta);
                    }
                }

                // razors
                for (i = 0, len = this.razors.length; i < len; i++) {
                    var r = this.razors[i];
                    r.update(delta);
                    this.cut(r, null, null, false);
                }

                // spikes
                var star_spike_radius = resolution.STAR_SPIKE_RADIUS,
                    star_spike_radius_double = star_spike_radius * 2;
                    // isCandyHit = function (spike, star) {
                    //     return (
                    //         Rectangle.lineInRect(
                    //             spike.t1.x, spike.t1.y,
                    //             spike.t2.x, spike.t2.y,
                    //             star.pos.x - star_spike_radius, star.pos.y - star_spike_radius,
                    //             star_spike_radius_double, star_spike_radius_double) ||
                    //             Rectangle.lineInRect(
                    //                 spike.b1.x, spike.b1.y,
                    //                 spike.b2.x, spike.b2.y,
                    //                 star.pos.x - star_spike_radius, star.pos.y - star_spike_radius,
                    //                 star_spike_radius_double, star_spike_radius_double));
                    // };

                for (i = 0, len = this.spikes.length; i < len; i++) {
                    s = this.spikes[i];

                    //only update if something happens
                    if (s.mover || s.shouldUpdateRotation || s.electro) {
                        s.update(delta);
                    }

                    if (!s.electro || s.electroOn) {
                        var candyHits = false,
                            left = false;
                        if (this.twoParts !== PartsType.NONE) {
                            candyHits = !this.noCandyL && isCandyHit(s, this.starL, star_spike_radius);
                            if (candyHits) {
                                left = true;
                            }
                            else {
                                candyHits = !this.noCandyR && isCandyHit(s, this.starR, star_spike_radius);
                            }
                        }
                        else {
                            candyHits = !this.noCandy && isCandyHit(s, this.star, star_spike_radius);
                        }

                        if (candyHits) {
                            if (this.twoParts !== PartsType.NONE) {
                                if (left) {
                                    if (this.candyBubbleL) {
                                        this.popCandyBubble(true);
                                    }
                                }
                                else {
                                    if (this.candyBubbleR) {
                                        this.popCandyBubble(false);
                                    }
                                }
                            }
                            else if (this.candyBubble) {
                                this.popCandyBubble(false);
                            }

                            var candyTexture = ResourceMgr.getTexture(ResourceId.IMG_OBJ_CANDY_01),
                                b = new CandyBreak(5, candyTexture);
                            if (this.gravityButton && !this.gravityNormal) {
                                b.gravity.y = -500;
                                b.angle = 90;
                            }

                            b.onFinished = this.aniPool.particlesFinishedDelegate();

                            if (this.twoParts != PartsType.NONE) {
                                if (left) {
                                    b.x = this.candyL.x;
                                    b.y = this.candyL.y;
                                    this.noCandyL = true;
                                }
                                else {
                                    b.x = this.candyR.x;
                                    b.y = this.candyR.y;
                                    this.noCandyR = true;
                                }
                            }
                            else {
                                b.x = this.candy.x;
                                b.y = this.candy.y;
                                this.noCandy = true;
                            }

                            b.startSystem(5);
                            this.aniPool.addChild(b);
                            SoundMgr.playSound(ResourceId.SND_CANDY_BREAK);
                            this.releaseAllRopes(left);

                            if (this.restartState !== RestartState.FADE_IN) {
                                this.dd.callObject(this, this.gameLost, null, 0.3);
                            }

                            return;
                        }
                    }
                }

                // bouncers
                var bouncer_radius = resolution.BOUNCER_RADIUS,
                    bouncer_radius_double = bouncer_radius * 2;

                for (i = 0, len = this.bouncers.length; i < len; i++) {
                    var bouncer = this.bouncers[i];
                    //if (bouncer.mover) {
                        bouncer.update(delta);
                    //}

                    candyHits = false;
                    left = false;
                    if (this.twoParts !== PartsType.NONE) {
                        candyHits = !this.noCandyL && isCandyHit(bouncer, this.starL, bouncer_radius);
                        if (candyHits) {
                            left = true;
                        }
                        else {
                            candyHits = !this.noCandyR && isCandyHit(bouncer, this.starR, bouncer_radius);
                        }
                    }
                    else {
                        candyHits = !this.noCandy && isCandyHit(bouncer, this.star, bouncer_radius);
                    }

                    if (candyHits) {
                        if (this.twoParts !== PartsType.NONE) {
                            if (left) {
                                this.handleBounce(bouncer, this.starL, delta);
                            }
                            else {
                                this.handleBounce(bouncer, this.starR, delta);
                            }
                        }
                        else {
                            this.handleBounce(bouncer, this.star, delta);
                        }
                        
                        break; //stop after hit
                    }
                    else {
                        bouncer.skip = false;
                    }
                }

                // apply force to bubbles
                var gravityMultiplier = (this.gravityButton && !this.gravityNormal) ? -1 : 1,
                    yImpulse = resolution.BUBBLE_IMPULSE_Y * gravityMultiplier,
                    rd = resolution.BUBBLE_IMPULSE_RD;

                // apply candy impulse
                if (this.twoParts === PartsType.SEPARATE) {
                    if (this.candyBubbleL) {
                        applyStarImpulse(this.starL, rd, yImpulse, delta);
                    }
                    if (this.candyBubbleR) {
                        applyStarImpulse(this.starR, rd, yImpulse, delta);
                    }
                }
                if (this.twoParts === PartsType.DISTANCE) {
                    if (this.candyBubbleL || this.candyBubbleR) {
                        applyStarImpulse(this.starL, rd, yImpulse, delta);
                        applyStarImpulse(this.starR, rd, yImpulse, delta);
                    }
                }
                else {
                    if (this.candyBubble) {
                        applyStarImpulse(this.star, rd, yImpulse, delta);
                    }
                }

                var targetVector;
                if (!this.noCandy) {
                    var MOUTH_OPEN_RADIUS = resolution.MOUTH_OPEN_RADIUS;
                    if (!this.mouthOpen) {
                        targetVector = new Vector(this.target.x, this.target.y);
                        if (this.star.pos.distance(targetVector) < MOUTH_OPEN_RADIUS) {
                            this.mouthOpen = true;
                            this.target.playTimeline(CharAnimation.MOUTH_OPEN);
                            SoundMgr.playSound(ResourceId.SND_MONSTER_OPEN);
                            this.mouthCloseTimer = MOUTH_OPEN_TIME;
                        }
                    }
                    else {
                        if (this.mouthCloseTimer > 0) {
                            this.mouthCloseTimer = Mover.moveToTarget(
                                this.mouthCloseTimer, 0, 1, delta);

                            if (this.mouthCloseTimer <= 0) {
                                targetVector = new Vector(this.target.x, this.target.y);
                                if (this.star.pos.distance(targetVector) > MOUTH_OPEN_RADIUS) {
                                    this.mouthOpen = false;
                                    this.target.playTimeline(CharAnimation.MOUTH_CLOSE);
                                    SoundMgr.playSound(ResourceId.SND_MONSTER_CLOSE);

                                    // this.tummyTeasers++;
                                    // if (this.tummyTeasers === 10) {
                                    //     Achievements.increment(AchievementId.TUMMY_TEASER);
                                    // }
                                }
                                else {
                                    this.mouthCloseTimer = MOUTH_OPEN_TIME;
                                }
                            }
                        }
                    }

                    if (this.restartState !== RestartState.FADE_IN) {
                        if (GameObject.intersect(this.candy, this.target)) {
                            this.gameWon();
                            return;
                        }
                    }
                }

                var outOfScreen = (this.twoParts === PartsType.NONE && this.pointOutOfScreen(this.star) && !this.noCandy),
                    outOfScreenL = (this.twoParts !== PartsType.NONE && this.pointOutOfScreen(this.starL) && !this.noCandyL),
                    outOfScreenR = (this.twoParts !== PartsType.NONE && this.pointOutOfScreen(this.starR) && !this.noCandyR);

                if (outOfScreen || outOfScreenL || outOfScreenR) {

                    if (outOfScreen) {
                        this.noCandy = true;
                    }
                    if (outOfScreenL) {
                        this.noCandyL = true;
                    }
                    if (outOfScreenR) {
                        this.noCandyR = true;
                    }

                    if (this.restartState !== RestartState.FADE_IN) {

                        // lost candy achievements
                        // Achievements.increment(AchievementId.WEIGHT_LOSER);
                        // Achievements.increment(AchievementId.CALORIE_MINIMIZER);

                        if (this.twoParts != PartsType.NONE && this.noCandyL && this.noCandyR) {
                            return;
                        }
                        this.gameLost();
                        return;
                    }
                }

                if (this.special !== 0) {
                    if (this.special === 1) {
                        if (!this.noCandy &&
                            this.candyBubble != null &&
                            this.candy.y < resolution.CANDY_BUBBLE_TUTORIAL_LIMIT_Y &&
                            this.candy.x > resolution.CANDY_BUBBLE_TUTORIAL_LIMIT_X) {
                            this.special = 0;

                            // tutorial text
                            for (i = 0, len = this.tutorials.length; i < len; i++) {
                                t = this.tutorials[i];
                                if (t.special === 1) {
                                    t.playTimeline(0);
                                }
                            }

                            // tutorial images
                            for (i = 0, len = this.tutorialImages.length; i < len; i++) {
                                t = this.tutorialImages[i];
                                if (t.special === 1) {
                                    t.playTimeline(0);
                                }
                            }
                        }
                    }
                }

                if (this.clickToCut && !this.ignoreTouches) {
                    this.resetBungeeHighlight();

                    // first see if there is a nearby bungee
                    var cv = new Vector(0, 0),
                        pos = Vector.add(this.slastTouch, this.camera.pos),
                        grab = this.getNearestBungeeGrabByBezierPoints(cv, pos.x, pos.y);
                    b = grab ? grab.rope : null;
                    if (b) {

                        // now see if there is an active element that would override
                        // bungee selection
                        var activeElement = false;
                        if (this.gravityButton) {
                            var c = this.gravityButton.getChild(
                                this.gravityButton.isOn() ? 1 : 0);
                            if (c.isInTouchZone(pos.x, pos.y, true)) {
                                activeElement = true;
                            }
                        }

                        if (this.candyBubble ||
                            (this.twoParts != PartsType.NONE && (this.candyBubbleL || this.candyBubbleR))) {
                            for (i = 0, len = this.bubbles.length; i < len; i++) {
                                var s = this.bubbles[i],
                                    BUBBLE_RADIUS = resolution.BUBBLE_RADIUS,
                                    BUBBLE_DIAMETER = BUBBLE_RADIUS * 2;
                                if (this.candyBubble) {
                                    if (Rectangle.pointInRect(pos.x, pos.y,
                                        this.star.pos.x - BUBBLE_RADIUS,
                                        this.star.pos.y - BUBBLE_RADIUS,
                                        BUBBLE_DIAMETER, BUBBLE_DIAMETER)) {
                                        activeElement = true;
                                        break;
                                    }
                                }

                                if (this.candyBubbleL) {
                                    if (Rectangle.pointInRect(pos.x, pos.y,
                                        this.starL.pos.x - BUBBLE_RADIUS,
                                        this.starL.pos.y - BUBBLE_RADIUS,
                                        BUBBLE_DIAMETER, BUBBLE_DIAMETER)) {
                                        activeElement = true;
                                        break;
                                    }
                                }

                                if (this.candyBubbleR) {
                                    if (Rectangle.pointInRect(pos.x, pos.y,
                                        this.starR.pos.x - BUBBLE_RADIUS,
                                        this.starR.pos.y - BUBBLE_RADIUS,
                                        BUBBLE_DIAMETER, BUBBLE_DIAMETER)) {
                                        activeElement = true;
                                        break;
                                    }
                                }
                            }
                        }

                        for (i = 0, len = this.spikes.length; i < len; i++) {
                            var s = this.spikes[i];
                            if (s.rotateButton &&
                                s.rotateButton.isInTouchZone(pos.x, pos.y, true)) {
                                activeElement = true;
                            }
                        }

                        for (i = 0, len = this.pumps.length; i < len; i++) {
                            if (this.pumps[i].pointInObject(pos.x, pos.y)) {
                                activeElement = true;
                                break;
                            }
                        }

                        for (i = 0, len = this.rotatedCircles.length; i < len; i++) {
                            var rc = this.rotatedCircles[i];
                            if (rc.isLeftControllerActive() || rc.isRightControllerActive()) {
                                activeElement = true;
                                break;
                            }

                            if (Vector.distance(pos.x, pos.y, rc.handle2.x, rc.handle2.y) <= resolution.RC_CONTROLLER_RADIUS ||
                                Vector.distance(pos.x, pos.y, rc.handle2.x, rc.handle2.y) <= resolution.RC_CONTROLLER_RADIUS) {
                                activeElement = true;
                                break;
                            }
                        }

                        for (i = 0, len = this.bungees.length; i < len; i++) {
                            var g = this.bungees[i];
                            if (g.wheel) {
                                if (Rectangle.pointInRect(pos.x, pos.y,
                                    g.x - resolution.GRAB_WHEEL_RADIUS,
                                    g.y - resolution.GRAB_WHEEL_RADIUS,
                                    resolution.GRAB_WHEEL_RADIUS * 2,
                                    resolution.GRAB_WHEEL_RADIUS * 2)) {
                                    activeElement = true;
                                    break;
                                }
                            }

                            if (g.moveLength > 0) {
                                if (Rectangle.pointInRect(pos.x, pos.y,
                                    g.x - resolution.GRAB_MOVE_RADIUS,
                                    g.y - resolution.GRAB_MOVE_RADIUS,
                                    resolution.GRAB_MOVE_RADIUS * 2,
                                    resolution.GRAB_MOVE_RADIUS * 2) || g.moverDragging !== Constants.UNDEFINED) {
                                    activeElement = true;
                                    break;
                                }
                            }
                        }

                        if (!activeElement) {
                            b.highlighted = true;
                        }
                    }
                }

                moveResult = Mover.moveToTargetWithStatus(this.dimTime, 0, 1, delta);
                this.dimTime = moveResult.value;
                if (moveResult.reachedZero) {
                    if (this.restartState === RestartState.FADE_IN) {
                        this.restartState = RestartState.FADE_OUT;
                        this.hide();
                        this.show();
                        this.dimTime = Constants.DIM_TIMEOUT;
                    }
                    else {
                        this.restartState = Constants.UNDEFINED;
                    }
                }
            },

            isBubbleCapture: function(b, candy, candyBubble, candyBubbleAnimation) {

                var bubbleSize = resolution.BUBBLE_SIZE,
                    bubbleSizeDouble = bubbleSize * 2;

                if (Rectangle.pointInRect(
                    candy.x, candy.y, b.x - bubbleSize, b.y - bubbleSize,
                    bubbleSizeDouble, bubbleSizeDouble)) {

                    if (candyBubble) {
                        this.popBubble(b.x, b.y);
                    }
                    candyBubbleAnimation.visible = true;

                    SoundMgr.playSound(ResourceId.SND_BUBBLE);

                    b.popped = true;
                    b.removeChildWithID(0);

                    this.attachCandy();

                    return true;
                }
                return false;
            },
            teleport: function () {
                if (!this.targetSock) {
                    return;
                }

                this.targetSock.light.playTimeline(0);
                this.targetSock.light.visible = true;

                var off = new Vector(0, resolution.SOCK_TELEPORT_Y);
                off.rotate(Radians.fromDegrees(this.targetSock.rotation));

                this.star.pos.x = this.targetSock.x;
                this.star.pos.y = this.targetSock.y;
                this.star.pos.add(off);

                this.star.prevPos.copyFrom(this.star.pos);

                this.star.v.x = 0;
                this.star.v.y = -1;
                this.star.v.rotate(Radians.fromDegrees(this.targetSock.rotation));
                this.star.v.multiply(this.savedSockSpeed);

                this.star.posDelta.copyFrom(this.star.v);
                this.star.posDelta.divide(60);
                this.star.prevPos.copyFrom(this.star.pos);
                this.star.prevPos.subtract(this.star.posDelta);
                this.targetSock = null;

                //Achievements.increment(AchievementId.MAGICIAN);

                
            },
            animateLevelRestart: function () {
                this.restartState = RestartState.FADE_IN;
                this.dimTime = Constants.DIM_TIMEOUT;
            },
            isFadingIn: function () {
                return (this.restartState === RestartState.FADE_IN);
            },
            releaseAllRopes: function (left) {
                for (var l = 0, len = this.bungees.length; l < len; l++) {
                    var g = this.bungees[l],
                        b = g.rope;

                    if (b &&
                        (b.tail === this.star ||
                            (b.tail === this.starL && left) ||
                            (b.tail === this.starR && !left))) {
                        if (b.cut === Constants.UNDEFINED) {
                            b.setCut(b.parts.length - 2);
                            this.detachCandy();
                        }
                        else {
                            b.hideTailParts = true;
                        }

                        if (g.hasSpider && g.spiderActive) {
                            this.spiderBusted(g);
                        }
                    }
                }
            },
            attachCandy: function() {
                this.attachCount += 1;
                //console.log('candy attached. count: ' + this.attachCount);
            },
            detachCandy: function() {
                this.attachCount -= 1;
                this.juggleTimer = 0;
                //console.log('candy detached. count: ' + this.attachCount);
            },
            calculateScore: function () {
                this.timeBonus = Math.max(0, 30 - this.time) * 100;
                this.timeBonus /= 10;
                this.timeBonus *= 10;
                this.starBonus = 1000 * this.starsCollected;
                this.score = Math.ceil(this.timeBonus + this.starBonus);
            },
            gameWon: function () {
                this.dd.cancelAllDispatches();

                this.target.playTimeline(CharAnimation.WIN);
                SoundMgr.playSound(ResourceId.SND_MONSTER_CHEWING);

                if (this.candyBubble) {
                    this.popCandyBubble(false);
                }

                this.noCandy = true;

                this.candy.passTransformationsToChilds = true;
                this.candyMain.scaleX = this.candyMain.scaleY = 1;
                this.candyTop.scaleX = this.candyTop.scaleY = 1;

                var tl = new Timeline();
                tl.addKeyFrame(KeyFrame.makePos(this.candy.x, this.candy.y, KeyFrame.TransitionType.LINEAR, 0));
                tl.addKeyFrame(KeyFrame.makePos(this.target.x, this.target.y + 10, KeyFrame.TransitionType.LINEAR,
                    0.1));
                tl.addKeyFrame(KeyFrame.makeScale(0.71, 0.71, KeyFrame.TransitionType.LINEAR, 0));
                tl.addKeyFrame(KeyFrame.makeScale(0, 0, KeyFrame.TransitionType.LINEAR, 0.1));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 0));
                tl.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0.1));
                this.candy.addTimelineWithID(tl, 0);
                this.candy.playTimeline(0);
                tl.onFinished = this.aniPool.timelineFinishedDelegate();
                this.aniPool.addChild(this.candy);

                this.calculateScore();
                this.releaseAllRopes(false);


                var self = this,
                    onLevelWonAppCallback = function () {

                        PubSub.publish(PubSub.ChannelId.LevelWon, {
                            stars: self.starsCollected,
                            time: self.time,
                            score: self.score,
                            fps: (1 / self.gameController.avgDelta)
                        });
                    };

                // the closing doors animation takes 850ms so we want it to
                // finish before the game level deactivates (and freezes)
                if (settings.showMenu) {
                    this.dd.callObject(this, onLevelWonAppCallback, null, 1);
                }

                // stop the electro after 1.5 seconds
                this.dd.callObject(this, function () {
                    // stop the electro spikes sound from looping
                    SoundMgr.stopSound(ResourceId.SND_ELECTRIC);
                }, null, 1.5);

                // fire level won callback after 2 secs
                var onLevelWon = function () {
                    this.gameController.onLevelWon.call(this.gameController);
                };
                this.dd.callObject(this, onLevelWon, null, 1.8);
            },
            gameLost: function () {
                this.dd.cancelAllDispatches();
                this.target.playTimeline(CharAnimation.FAIL);
                SoundMgr.playSound(ResourceId.SND_MONSTER_SAD);

                // fire level lost callback after 1 sec
                var onLevelLost = function () {
                    this.gameController.onLevelLost.call(this.gameController);
                    PubSub.publish(PubSub.ChannelId.LevelLost, { 'time': this.time });
                };
                this.dd.callObject(this, onLevelLost, null, 1);
            },
            draw: function () {

                // reset any canvas transformations and clear everything
                var ctx = Canvas.context;
                ctx.clearRect(0, 0, resolution.CANVAS_WIDTH, resolution.CANVAS_HEIGHT);

                this.preDraw();
                this.camera.applyCameraTransformation();
                //this.back.updateWithCameraPos(this.camera.pos);
                //console.log('back x:' + this.back.x + ' y:' + this.back.y);
                //this.back.draw();

                var overlayCut = 2,
                    q, overlayRect, off;
                if (this.mapHeight > resolution.CANVAS_HEIGHT) {
                    q = IMG_BGR_01_P2_vert_transition;
                    off = this.overlayTexture.offsets[q].y;
                    overlayRect = this.overlayTexture.rects[q];

                    ctx.drawImage(this.overlayTexture.image,
                        overlayRect.x, overlayRect.y + overlayCut, overlayRect.w, overlayRect.h - (overlayCut * 2),
                        0, off + overlayCut, overlayRect.w, overlayRect.h - (overlayCut * 2));
                }

                var i, len;
                for (i = 0, len = this.drawings.length; i < len; i++) {
                    this.drawings[i].draw();
                }

                for (i = 0, len = this.earthAnims.length; i < len; i++) {
                    this.earthAnims[i].draw();
                }

                if (this.pollenDrawer) {
                    this.pollenDrawer.draw();
                }
                if (this.gravityButton) {
                    this.gravityButton.draw();
                }

                this.support.draw();
                this.target.draw();

                // tutorial text
                for (i = 0, len = this.tutorials.length; i < len; i++) {
                    this.tutorials[i].draw();
                }

                // tutorial images
                for (i = 0, len = this.tutorialImages.length; i < len; i++) {
                    var ti = this.tutorialImages[i];

                    // don't draw the level1 arrow now - it needs to be on top
                    if (ti.special !== LEVEL1_ARROW_SPECIAL_ID) {
                        ti.draw();
                    }
                }

                for (i = 0, len = this.razors.length; i < len; i++) {
                    this.razors[i].draw();
                }

                for (i = 0, len = this.rotatedCircles.length; i < len; i++) {
                    this.rotatedCircles[i].draw();
                }

                for (i = 0, len = this.bubbles.length; i < len; i++) {
                    this.bubbles[i].draw();
                }

                for (i = 0, len = this.pumps.length; i < len; i++) {
                    this.pumps[i].draw();
                }

                for (i = 0, len = this.spikes.length; i < len; i++) {
                    this.spikes[i].draw();
                }

                for (i = 0, len = this.bouncers.length; i < len; i++) {
                    this.bouncers[i].draw();
                }

                for (i = 0, len = this.socks.length; i < len; i++) {
                    var sock = this.socks[i];
                    sock.y -= SOCK_COLLISION_Y_OFFSET;
                    sock.draw();
                    sock.y += SOCK_COLLISION_Y_OFFSET;
                }

                var bungees = this.bungees;
                for (i = 0, len = bungees.length; i < len; i++) {
                    bungees[i].drawBack();
                }
                for (i = 0; i < len; i++) {
                    bungees[i].draw();
                }

                for (i = 0, len = this.stars.length; i < len; i++) {
                    this.stars[i] && this.stars[i].draw();
                }

                if (!this.noCandy && !this.targetSock) {
                    this.candy.x = this.star.pos.x;
                    this.candy.y = this.star.pos.y;
                    this.candy.draw();

                    if (this.candyBlink.currentTimeline != null) {
                        this.candyBlink.draw();
                    }
                }

                if (this.twoParts !== PartsType.NONE) {
                    if (!this.noCandyL) {
                        this.candyL.x = this.starL.pos.x;
                        this.candyL.y = this.starL.pos.y;
                        this.candyL.draw();
                    }

                    if (!this.noCandyR) {
                        this.candyR.x = this.starR.pos.x;
                        this.candyR.y = this.starR.pos.y;
                        this.candyR.draw();
                    }
                }

                for (i = 0, len = bungees.length; i < len; i++) {
                    var g = bungees[i];
                    if (g.hasSpider) {
                        g.drawSpider();
                    }
                }

                this.aniPool.draw();
                this.drawCuts();
                this.camera.cancelCameraTransformation();
                this.staticAniPool.draw();

                // draw the level1 arrow last so its on top
                for (i = 0, len = this.tutorialImages.length; i < len; i++) {
                    ti = this.tutorialImages[i];
                    if (ti.special === LEVEL1_ARROW_SPECIAL_ID) {
                        ti.draw();
                    }
                }

                this.postDraw();
            },
            drawCuts: function () {
                var maxSize = resolution.CUT_MAX_SIZE;
                for (var i = 0; i < Constants.MAX_TOUCHES; i++) {
                    var cuts = this.fingerCuts[i],
                        count = cuts.length;
                    if (count > 0) {
                        var perpSize = 1,
                            v = 0,
                            fc = null,
                            pts = [],
                            pc = 0;

                        for (var k = 0; k < count; k++) {
                            fc = cuts[k];
                            if (k === 0) {
                                pts[pc++] = fc.start;
                            }
                            pts[pc++] = fc.end;
                        }

                        var p = null,
                            points = 2,
                            numVertices = count * points,
                            vertices = [],
                            bstep = 1 / numVertices,
                            a = 0;

                        while (true) {
                            if (a > 1) {
                                a = 1;
                            }

                            p = Vector.calcPathBezier(pts, a);
                            vertices.push(p);

                            if (a === 1) {
                                break;
                            }

                            a += bstep;
                        }

                        var step = maxSize / numVertices,
                            verts = [];
                        for (var k = 0, lenMinusOne = numVertices - 1; k < lenMinusOne; k++) {
                            var startSize = perpSize,
                                endSize = (k === numVertices - 1) ? 1 : perpSize + step,
                                start = vertices[k],
                                end = vertices[k + 1];

                            // n is the normalized arrow
                            var n = Vector.subtract(end, start);
                            n.normalize();

                            var rp = Vector.rPerpendicular(n),
                                lp = Vector.perpendicular(n);

                            if (v === 0) {
                                var srp = Vector.add(start, Vector.multiply(rp, startSize)),
                                    slp = Vector.add(start, Vector.multiply(lp, startSize));

                                verts.push(slp);
                                verts.push(srp);
                            }

                            var erp = Vector.add(end, Vector.multiply(rp, endSize)),
                                elp = Vector.add(end, Vector.multiply(lp, endSize));

                            verts.push(elp);
                            verts.push(erp);

                            perpSize += step;
                        }

                        // draw triangle strip
                        Canvas.fillTriangleStrip(verts, RGBAColor.styles.SOLID_OPAQUE);
                    }
                }
            },
            handlePumpFlow: function (p, s, c, delta) {
                var powerRadius = resolution.PUMP_POWER_RADIUS;
                if (c.rectInObject(p.x - powerRadius, p.y - powerRadius, p.x + powerRadius, p.y + powerRadius)) {
                    var tn1 = new Vector(0, 0),
                        tn2 = new Vector(0, 0),
                        h = new Vector(c.x, c.y);

                    tn1.x = p.x - p.bb.w / 2.0;
                    tn2.x = p.x + p.bb.w / 2.0;
                    tn1.y = tn2.y = p.y;

                    if (p.angle != 0) {
                        h.rotateAround(-p.angle, p.x, p.y);
                    }

                    if (h.y < tn1.y &&
                        Rectangle.rectInRect(
                            h.x - c.bb.w / 2.0, h.y - c.bb.h / 2.0,
                            h.x + c.bb.w / 2.0, h.y + c.bb.h / 2.0,
                            tn1.x, tn1.y - powerRadius, tn2.x, tn2.y)) {

                        var maxPower = powerRadius * 2.0,
                            power = maxPower * (powerRadius - (tn1.y - h.y)) / powerRadius,
                            pumpForce = new Vector(0, -power);

                        pumpForce.rotate(p.angle);
                        s.applyImpulse(pumpForce, delta);
                        
                    }

                    
                }
            },
            handleBounce: function (bouncer, star, delta) {
                if (bouncer.skip) {
                    return;
                }

                var v = Vector.subtract(star.prevPos, star.pos),
                    spos = star.prevPos.copy();

                var angle = bouncer.angle,
                    x = bouncer.x,
                    y = bouncer.y;

                spos.rotateAround(-angle, x, y);

                var fromTop = (spos.y < bouncer.y),
                    dir = fromTop ? -1 : 1,
                    a = v.getLength() * 40,
                    b = resolution.BOUNCER_MAX_MOVEMENT,
                    m = (a > b ? a : b) * dir,
                    v2 = Vector.forAngle(bouncer.angle),
                    impulse = Vector.perpendicular(v2);

                impulse.multiply(m);

                star.pos.rotateAround(-angle, x, y);
                star.prevPos.rotateAround(-angle, x, y);
                star.prevPos.y = star.pos.y;
                star.pos.rotateAround(angle, x, y);
                star.prevPos.rotateAround(angle, x, y);

                star.applyImpulse(impulse, delta);
                bouncer.playTimeline(0);

                SoundMgr.playSound(ResourceId.SND_BOUNCER);
            },
            operatePump: function (pump, delta) {
                pump.playTimeline(0);
                var soundId = MathHelper.randomRange(ResourceId.SND_PUMP_1, ResourceId.SND_PUMP_4);
                SoundMgr.playSound(soundId);

                var dirtTexture = ResourceMgr.getTexture(ResourceId.IMG_OBJ_PUMP);
                var b = new PumpDirt(5, dirtTexture, Radians.toDegrees(pump.angle) - 90);
                b.onFinished = this.aniPool.particlesFinishedDelegate();

                var v = new Vector(pump.x + resolution.PUMP_DIRT_OFFSET, pump.y);
                v.rotateAround(pump.angle - Math.PI / 2, pump.x, pump.y);
                b.x = v.x;
                b.y = v.y;

                b.startSystem(5);
                this.aniPool.addChild(b);

                if (!this.noCandy) {
                    this.handlePumpFlow(pump, this.star, this.candy, delta);
                }

                if (this.twoParts !== PartsType.NONE) {
                    if (!this.noCandyL) {
                        this.handlePumpFlow(pump, this.starL, this.candyL, delta);
                    }

                    if (!this.noCandyR) {
                        this.handlePumpFlow(pump, this.starR, this.candyR, delta);
                    }
                }

            },
            cut: function (razor, v1, v2, immediate) {
                var cutCount = 0;
                for (var l = 0, len = this.bungees.length; l < len; l++) {
                    var g = this.bungees[l],
                        b = g.rope;

                    if (!b || b.cut !== Constants.UNDEFINED) {
                        continue;
                    }

                    var GRAB_WHEEL_RADIUS = resolution.GRAB_WHEEL_RADIUS,
                        GRAB_WHEEL_DIAMETER = GRAB_WHEEL_RADIUS * 2;
                    for (var i = 0, iLimit = b.parts.length - 1; i < iLimit; i++) {
                        var p1 = b.parts[i],
                            p2 = b.parts[i + 1],
                            cut = false;

                        if (razor) {
                            if (p1.prevPos.x !== Constants.INT_MAX) {
                                var minX = MathHelper.minOf4(p1.pos.x, p1.prevPos.x, p2.pos.x, p2.prevPos.x),
                                    minY = MathHelper.minOf4(p1.pos.y, p1.prevPos.y, p2.pos.y, p2.prevPos.y),
                                    maxX = MathHelper.maxOf4(p1.pos.x, p1.prevPos.x, p2.pos.x, p2.prevPos.x),
                                    maxY = MathHelper.maxOf4(p1.pos.y, p1.prevPos.y, p2.pos.y, p2.prevPos.y);

                                cut = Rectangle.rectInRect(
                                    minX, minY, maxX, maxY,
                                    razor.drawX, razor.drawY, razor.drawX + razor.width, razor.drawY + razor.height);
                            }
                        }
                        else {
                            if (g.wheel && Rectangle.lineInRect(
                                v1.x, v1.y, v2.x, v2.y,
                                g.x - GRAB_WHEEL_RADIUS, g.y - GRAB_WHEEL_RADIUS,
                                GRAB_WHEEL_DIAMETER, GRAB_WHEEL_DIAMETER)) {
                                cut = false;
                            }
                            else {
                                cut = MathHelper.lineInLine(
                                    v1.x, v1.y, v2.x, v2.y,
                                    p1.pos.x, p1.pos.y, p2.pos.x, p2.pos.y);
                            }
                        }

                        if (cut) {
                            cutCount++;

                            if (g.hasSpider && g.spiderActive) {
                                this.spiderBusted(g);
                            }

                            SoundMgr.playSound(ResourceId.SND_ROPE_BLEAK_1 + b.relaxed);

                            b.setCut(i);
                            this.detachCandy();

                            if (immediate) {
                                b.cutTime = 0;
                                b.removePart(i);
                            }

                            return cutCount;
                        }
                    }
                }

                return cutCount;
            },
            spiderBusted: function (g) {

                SoundMgr.playSound(ResourceId.SND_SPIDER_FALL);
                g.hasSpider = false;
                var s = ImageElement.create(ResourceId.IMG_OBJ_SPIDER, IMG_OBJ_SPIDER_busted);
                s.doRestoreCutTransparency();
                var tl = new Timeline();
                if (this.gravityButton && !this.gravityNormal) {
                    tl.addKeyFrame(KeyFrame.makePos(g.spider.x, g.spider.y, KeyFrame.TransitionType.EASE_OUT, 0));
                    tl.addKeyFrame(KeyFrame.makePos(g.spider.x, g.spider.y + 50, KeyFrame.TransitionType.EASE_OUT,
                        0.3));
                    tl.addKeyFrame(KeyFrame.makePos(g.spider.x, g.spider.y - resolution.CANVAS_HEIGHT,
                        KeyFrame.TransitionType.EASE_IN, 1));
                }
                else {
                    tl.addKeyFrame(KeyFrame.makePos(g.spider.x, g.spider.y, KeyFrame.TransitionType.EASE_OUT, 0));
                    tl.addKeyFrame(KeyFrame.makePos(g.spider.x, g.spider.y - 50, KeyFrame.TransitionType.EASE_OUT,
                        0.3));
                    tl.addKeyFrame(KeyFrame.makePos(g.spider.x, g.spider.y + resolution.CANVAS_HEIGHT,
                        KeyFrame.TransitionType.EASE_IN, 1));
                }

                tl.addKeyFrame(KeyFrame.makeRotation(0, 0, 0));
                tl.addKeyFrame(KeyFrame.makeRotation(MathHelper.randomRange(-120, 120), 0, 1));
                s.addTimelineWithID(tl, 0);
                s.playTimeline(0);
                s.x = g.spider.x;
                s.y = g.spider.y;
                s.anchor = Alignment.CENTER;

                tl.onFinished = this.aniPool.timelineFinishedDelegate();
                this.aniPool.addChild(s);

                // spider achievements
                // Achievements.increment(AchievementId.SPIDER_BUSTER);
                // Achievements.increment(AchievementId.SPIDER_TAMER);
            },
            spiderWon: function (sg) {
                SoundMgr.playSound(ResourceId.SND_SPIDER_WIN);

                for (var i = 0, count = this.bungees.length; i < count; i++) {
                    var g = this.bungees[i],
                        b = g.rope;
                    if (b && b.tail === this.star) {
                        if (b.cut !== Constants.UNDEFINED) {
                            g.destroyRope();
                        }
                        else {
                            b.setCut(b.parts.length - 2);
                            this.detachCandy();
                            b.forceWhite = false;
                        }

                        if (g.hasSpider && g.spiderActive && sg !== g) {
                            this.spiderBusted(g);
                        }
                    }
                }

                sg.hasSpider = false;
                this.spiderTookCandy = true;
                this.noCandy = true;

                var s = ImageElement.create(ResourceId.IMG_OBJ_SPIDER, IMG_OBJ_SPIDER_stealing);
                s.doRestoreCutTransparency();
                this.candy.anchor = this.candy.parentAnchor = Alignment.CENTER;
                this.candy.x = 0;
                this.candy.y = -5;

                s.addChild(this.candy);
                var tl = new Timeline();
                if (this.gravityButton && !this.gravityNormal) {
                    tl.addKeyFrame(KeyFrame.makePos(sg.spider.x, sg.spider.y - 10, KeyFrame.TransitionType.EASE_OUT,
                        0));
                    tl.addKeyFrame(KeyFrame.makePos(sg.spider.x, sg.spider.y + 70, KeyFrame.TransitionType.EASE_OUT,
                        0.3));
                    tl.addKeyFrame(KeyFrame.makePos(sg.spider.x, sg.spider.y - resolution.CANVAS_HEIGHT,
                        KeyFrame.TransitionType.EASE_IN, 1));
                }
                else {
                    tl.addKeyFrame(KeyFrame.makePos(sg.spider.x, sg.spider.y - 10, KeyFrame.TransitionType.EASE_OUT,
                        0));
                    tl.addKeyFrame(KeyFrame.makePos(sg.spider.x, sg.spider.y - 70, KeyFrame.TransitionType.EASE_OUT,
                        0.3));
                    tl.addKeyFrame(KeyFrame.makePos(sg.spider.x, sg.spider.y + resolution.CANVAS_HEIGHT,
                        KeyFrame.TransitionType.EASE_IN, 1));
                }

                s.addTimelineWithID(tl, 0);
                s.playTimeline(0);
                s.x = sg.spider.x;
                s.y = sg.spider.y - 10;
                s.anchor = Alignment.CENTER;

                tl.onFinished = this.aniPool.timelineFinishedDelegate();
                this.aniPool.addChild(s);

                if (this.restartState !== RestartState.FADE_IN) {
                    this.dd.callObject(this, this.gameLost, null, 2);
                }

                // Achievements.increment(AchievementId.SPIDER_LOVER);
            },
            popCandyBubble: function (isLeft) {
                if (this.twoParts !== PartsType.NONE) {
                    if (isLeft) {
                        this.candyBubbleL = null;
                        this.candyBubbleAnimationL.visible = false;
                        this.popBubble(this.candyL.x, this.candyL.y);
                    }
                    else {
                        this.candyBubbleR = null;
                        this.candyBubbleAnimationR.visible = false;
                        this.popBubble(this.candyR.x, this.candyR.y);
                    }
                }
                else {
                    this.candyBubble = null;
                    this.candyBubbleAnimation.visible = false;
                    this.popBubble(this.candy.x, this.candy.y);
                }


            },
            popBubble: function (x, y) {

                this.detachCandy();

                SoundMgr.playSound(ResourceId.SND_BUBBLE_BREAK);

                bubbleDisappear.x = x;
                bubbleDisappear.y = y;
                
                bubbleDisappear.playTimeline(0);
                this.aniPool.addChild(bubbleDisappear);
            },
            handleBubbleTouch: function (s, tx, ty) {
                if (Rectangle.pointInRect(
                    tx + this.camera.pos.x, ty + this.camera.pos.y,
                    s.pos.x - resolution.BUBBLE_TOUCH_OFFSET, s.pos.y - resolution.BUBBLE_TOUCH_OFFSET,
                    resolution.BUBBLE_TOUCH_SIZE, resolution.BUBBLE_TOUCH_SIZE)) {
                    this.popCandyBubble(s === this.starL);

                    // Achievements.increment(AchievementId.BUBBLE_POPPER);
                    // Achievements.increment(AchievementId.BUBBLE_MASTER);

                    return true;
                }
                return false;
            },
            resetBungeeHighlight: function () {
                for (var i = 0, len = this.bungees.length; i < len; i++) {
                    var grab = this.bungees[i],
                        bungee = grab.rope;
                    if (!bungee || bungee.cut !== Constants.UNDEFINED) {
                        continue;
                    }
                    bungee.highlighted = false;
                }
            },
            getNearestBungeeGrabByBezierPoints: function (s, tx, ty) {
                var SEARCH_RADIUS = resolution.CLICK_TO_CUT_SEARCH_RADIUS,
                    grab = null,
                    md = SEARCH_RADIUS,
                    tv = new Vector(tx, ty);

                for (var l = 0, numBungees = this.bungees.length; l < numBungees; l++) {
                    var g = this.bungees[l],
                        b = g.rope;

                    if (b) {
                        for (var i = 0, numParts = b.drawPts.length; i < numParts; i++) {

                            var c1 = b.drawPts[i],
                                d = c1.distance(tv);
                            if (d < SEARCH_RADIUS && d < md) {
                                md = d;
                                grab = g;
                                s.copyFrom(c1);
                            }
                        }
                    }
                }

                
                return grab;
            },
            getNearestBungeeSegmentByConstraints: function (s, g) {
                var SEARCH_RADIUS = Number.MAX_VALUE,
                    nb = null,
                    md = SEARCH_RADIUS,
                    sOrig = s.copy(),
                    b = g.rope;

                if (!b || b.cut !== Constants.UNDEFINED) {
                    return null;
                }

                var GRAB_WHEEL_RADIUS = resolution.GRAB_WHEEL_RADIUS,
                    GRAB_WHEEL_DIAMETER = GRAB_WHEEL_RADIUS * 2;
                for (var i = 0, numParts = b.parts.length - 1; i < numParts; i++) {
                    var p1 = b.parts[i],
                        d = p1.pos.distance(sOrig);
                    if (d < md) {
                        if (!g.wheel || Rectangle.pointInRect(
                            p1.pos.x, p1.pos.y,
                            g.x - GRAB_WHEEL_RADIUS, g.y - GRAB_WHEEL_RADIUS,
                            GRAB_WHEEL_DIAMETER, GRAB_WHEEL_DIAMETER)) {

                            md = d;
                            nb = b;
                            s.copyFrom(p1.pos);
                        }
                    }
                }

                return nb;
            },
            touchDown: function (x, y, touchIndex) {
                if (this.ignoreTouches) {
                    if (this.camera.type === Camera2D.SpeedType.PIXELS) {
                        this.fastenCamera = true;
                    }
                    return true;
                }

                if (touchIndex >= Constants.MAX_TOUCHES) {
                    return true;
                }

                this.overOmNom = false;

                // if (this.gravityButton) {
                //     var childIndex = this.gravityButton.isOn() ? 1 : 0,
                //         child = this.gravityButton.getChild(childIndex);
                //     if (child.isInTouchZone(x + this.camera.pos.x, y + this.camera.pos.y, true)) {
                //         this.gravityTouchDown = touchIndex;
                //         return true;
                //     }
                // }

                if (this.candyBubble) {
                    if (this.handleBubbleTouch(this.star, x, y)) {
                        return true;
                    }
                }

                if (this.twoParts !== PartsType.NONE) {
                    if (this.candyBubbleL) {
                        if (this.handleBubbleTouch(this.starL, x, y)) {
                            return true;
                        }
                    }
                    if (this.candyBubbleR) {
                        if (this.handleBubbleTouch(this.starR, x, y)) {
                            return true;
                        }
                    }
                }

                var touch = new Vector(x, y);
                if (!this.dragging[touchIndex]) {
                    this.dragging[touchIndex] = true;
                    this.startPos[touchIndex].copyFrom(touch);
                    this.prevStartPos[touchIndex].copyFrom(touch);
                }

                

                var i, len,
                    cameraPos = this.camera.pos,
                    cameraAdjustedX = x + cameraPos.x,
                    cameraAdjustedY = y + cameraPos.y;

                // handle rotating spikes
                for (i = 0, len = this.spikes.length; i < len; i++) {
                    var spike = this.spikes[i];
                    if (spike.rotateButton &&
                        spike.touchIndex === Constants.UNDEFINED &&
                        spike.rotateButton.onTouchDown(cameraAdjustedX, cameraAdjustedY)) {
                        spike.touchIndex = touchIndex;
                        return true;
                    }
                }

                // handle pump touches
                for (i = 0, len = this.pumps.length; i < len; i++) {
                    var pump = this.pumps[i];
                    if (pump.pointInObject(cameraAdjustedX, cameraAdjustedY)) {
                        pump.touchTimer = PUMP_TIMEOUT;
                        pump.touch = touchIndex;
                        return true;
                    }
                }

                var activeCircle = null,
                    hasCircleInside = false,
                    intersectsAnotherCircle = false;
                for (i = 0, len = this.rotatedCircles.length; i < len; i++) {
                    var r = this.rotatedCircles[i],
                        d1 = Vector.distance(cameraAdjustedX, cameraAdjustedY, r.handle1.x, r.handle1.y),
                        d2 = Vector.distance(cameraAdjustedX, cameraAdjustedY, r.handle2.x, r.handle2.y);
                    if ((d1 < resolution.RC_CONTROLLER_RADIUS &&
                        !r.hasOneHandle()) ||
                        d2 < resolution.RC_CONTROLLER_RADIUS) {

                        //check for overlapping
                        for (var j = i + 1; j < len; j++) {
                            var r2 = this.rotatedCircles[j],
                                d3 = Vector.distance(r2.x, r2.y, r.x, r.y);

                            if ((d3 + r2.sizeInPixels) <= r.sizeInPixels) {
                                hasCircleInside = true;
                            }

                            if (d3 <= r.sizeInPixels + r2.sizeInPixels)
                                intersectsAnotherCircle = true;
                        }

                        r.lastTouch.x = cameraAdjustedX;
                        r.lastTouch.y = cameraAdjustedY;
                        r.operating = touchIndex;

                        if (d1 < resolution.RC_CONTROLLER_RADIUS) {
                            r.setIsLeftControllerActive(true);
                        }
                        if (d2 < resolution.RC_CONTROLLER_RADIUS) {
                            r.setIsRightControllerActive(true);
                        }

                        activeCircle = r;

                        break;
                    }

                }

                // circle fading
                var activeCircleIndex = this.rotatedCircles.indexOf(activeCircle);
                if ((activeCircleIndex != this.rotatedCircles.length - 1) &&
                    intersectsAnotherCircle && !hasCircleInside) {

                    var fadeIn = new Timeline();
                    fadeIn.addKeyFrame(KeyFrame.makeColor(RGBAColor.transparent.copy(), KeyFrame.TransitionType.LINEAR, 0));
                    fadeIn.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 0.2));

                    var fadeOut = new Timeline();
                    fadeOut.addKeyFrame(KeyFrame.makeColor(RGBAColor.solidOpaque.copy(), KeyFrame.TransitionType.LINEAR, 0.2));
                    fadeOut.onFinished = $.proxy(this.onRotatedCircleTimelineFinished, this);

                    var fadingOutCircle = activeCircle.copy();
                    fadingOutCircle.addTimeline(fadeOut);
                    fadingOutCircle.playTimeline(0);

                    activeCircle.addTimeline(fadeIn);
                    activeCircle.playTimeline(0);

                    this.rotatedCircles[activeCircleIndex] = fadingOutCircle;
                    this.rotatedCircles.push(activeCircle);
                    activeCircle = null;
                }

                var GRAB_WHEEL_RADIUS = resolution.GRAB_WHEEL_RADIUS,
                    GRAB_WHEEL_DIAMETER = GRAB_WHEEL_RADIUS * 2,
                    GRAB_MOVE_RADIUS = resolution.GRAB_MOVE_RADIUS,
                    GRAB_MOVE_DIAMETER = GRAB_MOVE_RADIUS * 2;
                for (i = 0, len = this.bungees.length; i < len; i++) {
                    var grab = this.bungees[i];
                    if (grab.wheel) {
                        if (Rectangle.pointInRect(cameraAdjustedX, cameraAdjustedY,
                            grab.x - GRAB_WHEEL_RADIUS, grab.y - GRAB_WHEEL_RADIUS, GRAB_WHEEL_DIAMETER, GRAB_WHEEL_DIAMETER)) {
                            grab.handleWheelTouch(cameraAdjustedX, cameraAdjustedY);
                            grab.wheelOperating = touchIndex;
                        }
                    }

                    if (grab.moveLength > 0) {
                        if (Rectangle.pointInRect(cameraAdjustedX, cameraAdjustedY,
                            grab.x - GRAB_MOVE_RADIUS, grab.y - GRAB_MOVE_RADIUS, GRAB_MOVE_DIAMETER, GRAB_MOVE_DIAMETER)) {
                            grab.moverDragging = touchIndex;
                            return true;
                        }
                    }
                }

                if (this.clickToCut) {
                    var cutPos = Vector.newZero(),
                        grab = this.getNearestBungeeGrabByBezierPoints(
                            cutPos, cameraAdjustedX, cameraAdjustedY),
                        bungee = grab ? grab.rope : null;
                    if (bungee && bungee.highlighted) {
                        if (this.getNearestBungeeSegmentByConstraints(cutPos, grab)) {
                            this.cut(null, cutPos, cutPos, false);
                        }
                    }
                }

                // easter egg check must be last to avoid affecting other elements
                if (this.target.pointInDrawQuad(x, y)) {
                    this.overOmNom = true;
                }

                return true;
            },
            doubleClick: function (x, y, touchIndex) {
                if (this.ignoreTouches) {
                    return true;
                }

                return true;
            },
            touchUp: function (x, y, touchIndex) {
                if (this.ignoreTouches) {
                    return true;
                }

                this.dragging[touchIndex] = false;

                // see if the user clicked on OmNom
                if (this.overOmNom && this.target.pointInDrawQuad(x, y)) {
                    this.overOmNom = false;
                    PubSub.publish(PubSub.ChannelId.OmNomClicked);
                    return;
                }
                else {
                    this.overOmNom = false;
                }

                var i, len,
                    cameraPos = this.camera.pos,
                    cameraAdjustedX = x + cameraPos.x,
                    cameraAdjustedY = y + cameraPos.y;

                // drawings
                for (i = 0, len = this.drawings.length; i < len; i++) {
                    var drawing = this.drawings[i];
                    if (drawing.pointInObject(cameraAdjustedX, cameraAdjustedY)) {
                        drawing.showDrawing();

                        // remove the drawing
                        this.drawings.splice(i, 1);
                        break;
                    }
                }

                if (this.gravityButton && this.gravityTouchDown === touchIndex) {
                    var childIndex = this.gravityButton.isOn() ? 1 : 0,
                        child = this.gravityButton.getChild(childIndex);
                    if (child.isInTouchZone(x + this.camera.pos.x, y + this.camera.pos.y, true)) {
                        this.gravityButton.toggle();
                        this.onButtonPressed(GravityButton.DefaultId);
                    }
                    this.gravityTouchDown = Constants.UNDEFINED;
                }

                // for (i = 0, len = this.spikes.length; i < len; i++) {
                //     var spike = this.spikes[i];
                //     if (spike.rotateButton && spike.touchIndex === touchIndex) {
                //         spike.touchIndex = Constants.UNDEFINED;
                //         if (spike.rotateButton.onTouchUp(x + this.camera.pos.x, y + this.camera.pos.y)) {
                //             return true;
                //         }
                //     }
                // }

                for (i = 0, len = this.rotatedCircles.length; i < len; i++) {
                    var r = this.rotatedCircles[i];
                    if (r.operating === touchIndex) {
                        r.operating = Constants.UNDEFINED;
                        r.soundPlaying = Constants.UNDEFINED;
                        r.setIsLeftControllerActive(false);
                        r.setIsRightControllerActive(false);
                    }
                }

                for (i = 0, len = this.bungees.length; i < len; i++) {
                    var grab = this.bungees[i];
                    if (grab.wheel && grab.wheelOperating === touchIndex) {
                        grab.wheelOperating = Constants.UNDEFINED;
                    }

                    if (grab.moveLength > 0 && grab.moverDragging === touchIndex) {
                        grab.moverDragging = Constants.UNDEFINED;
                    }
                }

                return true;
            },
            touchMove: function (x, y, touchIndex) {
                if (this.ignoreTouches) {
                    return true;
                }

                if (touchIndex >= Constants.MAX_TOUCHES) {
                    return true;
                }

                var touch = new Vector(x, y),
                    i, len;
                if (this.startPos[touchIndex].distance(touch) > 10) {
                    for (i = 0, len = this.pumps.length; i < len; i++) {
                        var pump = this.pumps[i];

                        // cancel pump touch if we moved
                        if (pump.touch === touchIndex && pump.touchTimer !== 0) {
                            pump.touchTimer = 0;
                        }
                    }
                }

                this.slastTouch.copyFrom(touch);
                

                var cameraAdjustedTouch = new Vector(
                    x + this.camera.pos.x, y + this.camera.pos.y);

                for (i = 0, len = this.rotatedCircles.length; i < len; i++) {
                    var r = this.rotatedCircles[i];
                    if (r.operating === touchIndex) {
                        var c = new Vector(r.x, r.y);
                        if (c.distance(cameraAdjustedTouch) < r.sizeInPixels / 10) {
                            r.lastTouch.copyFrom(cameraAdjustedTouch);
                        }

                        var m1 = Vector.subtract(r.lastTouch, c),
                            m2 = Vector.subtract(cameraAdjustedTouch, c),
                            a = m2.normalizedAngle() - m1.normalizedAngle();

                        if (a > Math.PI) {
                            a = a - 2 * Math.PI;
                        }
                        else if (a < -Math.PI) {
                            a = a + 2 * Math.PI;
                        }

                        r.handle1.rotateAround(a, r.x, r.y);
                        r.handle2.rotateAround(a, r.x, r.y);
                        r.rotation += Radians.toDegrees(a);

                        var soundToPlay = (a > 0)
                            ? ResourceId.SND_SCRATCH_IN
                            : ResourceId.SND_SCRATCH_OUT;

                        if (Math.abs(a) < 0.07)
                            soundToPlay = Constants.UNDEFINED;

                        if (r.soundPlaying != soundToPlay && soundToPlay != Constants.UNDEFINED) {
                            SoundMgr.playSound(soundToPlay);
                            r.soundPlaying = soundToPlay;
                        }

                        for (i = 0, len = this.bungees.length; i < len; i++) {
                            var g = this.bungees[i],
                                gn = new Vector(g.x, g.y);
                            if (gn.distance(c) <= (r.sizeInPixels + 5 * this.PM)) {
                                gn.rotateAround(a, r.x, r.y);
                                g.x = gn.x;
                                g.y = gn.y;
                                if (g.rope) {
                                    g.rope.bungeeAnchor.pos.copyFrom(gn);
                                    g.rope.bungeeAnchor.pin.copyFrom(gn);
                                }
                            }
                        }

                        for (i = 0, len = this.pumps.length; i < len; i++) {
                            var g = this.pumps[i],
                                gn = new Vector(g.x, g.y);
                            if (gn.distance(c) <= (r.sizeInPixels + 5 * this.PM)) {
                                gn.rotateAround(a, r.x, r.y);
                                g.x = gn.x;
                                g.y = gn.y;
                                g.rotation += Radians.toDegrees(a);
                                g.updateRotation();
                            }
                        }

                        for (i = 0, len = this.bubbles.length; i < len; i++) {
                            var g = this.bubbles[i],
                                gn = new Vector(g.x, g.y);
                            if (gn.distance(c) <= (r.sizeInPixels + 10 * this.PM) &&
                                g !== this.candyBubble && g !== this.candyBubbleR && g !== this.candyBubbleL) {
                                gn.rotateAround(a, r.x, r.y);
                                g.x = gn.x;
                                g.y = gn.y;
                            }
                        }

                        if (Rectangle.pointInRect(
                            this.target.x, this.target.y,
                            r.x - r.size, r.y - r.size, 2 * r.size, 2 * r.size)) {
                            gn = new Vector(this.target.x, this.target.y);
                            gn.rotateAround(a, r.x, r.y);
                            this.target.x = gn.x;
                            this.target.y = gn.y;
                        }

                        
                        
                        r.lastTouch.copyFrom(cameraAdjustedTouch);
                        
                        return true;
                    }
                }

                for (i = 0, len = this.bungees.length; i < len; i++) {
                    var grab = this.bungees[i];
                    if (!grab) {
                        continue;
                    }

                    if (grab.wheel && grab.wheelOperating === touchIndex) {
                        grab.handleWheelRotate(cameraAdjustedTouch);
                        return true;
                    }

                    if (grab.moveLength > 0 && grab.moverDragging === touchIndex) {
                        if (grab.moveVertical) {
                            grab.y = MathHelper.fitToBoundaries(
                                cameraAdjustedTouch.y, grab.minMoveValue, grab.maxMoveValue);
                        }
                        else {
                            grab.x = MathHelper.fitToBoundaries(
                                cameraAdjustedTouch.x, grab.minMoveValue, grab.maxMoveValue);
                        }

                        if (grab.rope) {
                            var ba = grab.rope.bungeeAnchor;
                            ba.pos.x = ba.pin.x = grab.x;
                            ba.pos.y = ba.pin.y = grab.y;
                        }

                        return true;
                    }
                }

                if (this.dragging[touchIndex]) {
                    var fc = new FingerCut(
                            Vector.add(this.startPos[touchIndex], this.camera.pos),
                            Vector.add(touch, this.camera.pos),
                            5, // start size
                            5, // end size
                            RGBAColor.white.copy()),
                        currentCuts = this.fingerCuts[touchIndex],
                        ropeCuts = 0;

                    currentCuts.push(fc);
                    for (i = 0, len = currentCuts.length; i < len; i++) {
                        var fcc = currentCuts[i];
                        ropeCuts += this.cut(null, fcc.start, fcc.end, false);
                    }

                    if (ropeCuts > 0) {
                        this.freezeCamera = false;

                        if (this.ropesCutAtOnce > 0 && this.ropesAtOnceTimer > 0) {
                            this.ropesCutAtOnce += ropeCuts;
                        }
                        else {
                            this.ropesCutAtOnce = ropeCuts;
                        }
                        this.ropesAtOnceTimer = ROPE_CUT_AT_ONCE_TIMEOUT;

                        // rope cut achievements
                        // Achievements.increment(AchievementId.ROPE_CUTTER);
                        // Achievements.increment(AchievementId.ROPE_CUTTER_MANIAC);
                        // Achievements.increment(AchievementId.ULTIMATE_ROPE_CUTTER);

                        // // concurrent cut rope achievements
                        // if (this.ropesCutAtOnce >= 5) {
                        //     Achievements.increment(AchievementId.MASTER_FINGER);
                        // } else if (this.ropesCutAtOnce >= 3) {
                        //     Achievements.increment(AchievementId.QUICK_FINGER);
                        // }
                    }

                    this.prevStartPos[touchIndex].copyFrom(this.startPos[touchIndex]);
                    this.startPos[touchIndex].copyFrom(touch);
                }

                return true;
            },
            touchDragged: function (x, y, touchIndex) {
                if (touchIndex > Constants.MAX_TOUCHES) {
                    return false;
                }

                this.slastTouch.x = x;
                this.slastTouch.y = y;
                return true;
            },
            onButtonPressed: function (n) {
                Gravity.toggle();
                this.gravityNormal = Gravity.isNormal();
                SoundMgr.playSound(this.gravityNormal
                    ? ResourceId.SND_GRAVITY_OFF
                    : ResourceId.SND_GRAVITY_ON);

                for (var i = 0, len = this.earthAnims.length; i < len; i++) {
                    var earthImage = this.earthAnims[i];
                    if (Gravity.isNormal()) {
                        earthImage.playTimeline(EarthImage.TimelineId.NORMAL);
                    }
                    else {
                        earthImage.playTimeline(EarthImage.TimelineId.UPSIDE_DOWN);
                    }
                }
            },
            rotateAllSpikesWithId: function (sid) {
                for (var i = 0, len = this.spikes.length; i < len; i++) {
                    if (this.spikes[i].getToggled() === sid) {
                        this.spikes[i].rotateSpikes();
                    }
                }
            }
        });

        var IMG_OBJ_CANDY_01_candy_bottom = 0;
        var IMG_OBJ_CANDY_01_candy_main = 1;
        var IMG_OBJ_CANDY_01_candy_top = 2;

        var IMG_OBJ_SPIDER_busted = 11;
        var IMG_OBJ_SPIDER_stealing = 12;

        var IMG_OBJ_CANDY_01_highlight_start = 8;
        var IMG_OBJ_CANDY_01_highlight_end = 17;
        var IMG_OBJ_CANDY_01_glow = 18;
        var IMG_OBJ_CANDY_01_part_1 = 19;
        var IMG_OBJ_CANDY_01_part_2 = 20;
        var IMG_OBJ_CANDY_01_part_fx_start = 21;
        var IMG_OBJ_CANDY_01_part_fx_end = 25;

        var IMG_OBJ_STAR_DISAPPEAR_Frame_1 = 0;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_2 = 1;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_3 = 2;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_4 = 3;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_5 = 4;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_6 = 5;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_7 = 6;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_8 = 7;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_9 = 8;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_10 = 9;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_11 = 10;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_12 = 11;
        var IMG_OBJ_STAR_DISAPPEAR_Frame_13 = 12;

        var IMG_OBJ_BUBBLE_FLIGHT_Frame_1 = 0;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_2 = 1;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_3 = 2;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_4 = 3;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_5 = 4;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_6 = 5;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_7 = 6;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_8 = 7;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_9 = 8;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_10 = 9;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_11 = 10;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_12 = 11;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_13 = 12;
        var IMG_OBJ_BUBBLE_FLIGHT_Frame_14 = 13;

        var IMG_OBJ_BUBBLE_POP_Frame_1 = 0;
        var IMG_OBJ_BUBBLE_POP_Frame_2 = 1;
        var IMG_OBJ_BUBBLE_POP_Frame_3 = 2;
        var IMG_OBJ_BUBBLE_POP_Frame_4 = 3;
        var IMG_OBJ_BUBBLE_POP_Frame_5 = 4;
        var IMG_OBJ_BUBBLE_POP_Frame_6 = 5;
        var IMG_OBJ_BUBBLE_POP_Frame_7 = 6;
        var IMG_OBJ_BUBBLE_POP_Frame_8 = 7;
        var IMG_OBJ_BUBBLE_POP_Frame_9 = 8;
        var IMG_OBJ_BUBBLE_POP_Frame_10 = 9;
        var IMG_OBJ_BUBBLE_POP_Frame_11 = 10;
        var IMG_OBJ_BUBBLE_POP_Frame_12 = 11;

        var IMG_OBJ_BUBBLE_ATTACHED_bubble = 0;
        var IMG_OBJ_BUBBLE_ATTACHED_stain_01 = 1;
        var IMG_OBJ_BUBBLE_ATTACHED_stain_02 = 2;
        var IMG_OBJ_BUBBLE_ATTACHED_stain_03 = 3;

        var IMG_HUD_STAR_Frame_1 = 0;
        var IMG_HUD_STAR_Frame_2 = 1;
        var IMG_HUD_STAR_Frame_3 = 2;
        var IMG_HUD_STAR_Frame_4 = 3;
        var IMG_HUD_STAR_Frame_5 = 4;
        var IMG_HUD_STAR_Frame_6 = 5;
        var IMG_HUD_STAR_Frame_7 = 6;
        var IMG_HUD_STAR_Frame_8 = 7;
        var IMG_HUD_STAR_Frame_9 = 8;
        var IMG_HUD_STAR_Frame_10 = 9;
        var IMG_HUD_STAR_Frame_11 = 10;

        var IMG_CHAR_ANIMATIONS_idle_start = 8;
        var IMG_CHAR_ANIMATIONS_idle_end = 26;
        var IMG_CHAR_ANIMATIONS_fail_start = 27;
        var IMG_CHAR_ANIMATIONS_fail_end = 39;
        var IMG_CHAR_ANIMATIONS_mouth_open_start = 40;
        var IMG_CHAR_ANIMATIONS_mouth_open_end = 48;
        var IMG_CHAR_ANIMATIONS_mouth_close_start = 49;
        var IMG_CHAR_ANIMATIONS_mouth_close_end = 52;
        var IMG_CHAR_ANIMATIONS_chew_start = 53;
        var IMG_CHAR_ANIMATIONS_chew_end = 61;
        var IMG_CHAR_ANIMATIONS_blink_start = 62;
        var IMG_CHAR_ANIMATIONS_blink_end = 63;
        var IMG_CHAR_ANIMATIONS_excited_start = 64;
        var IMG_CHAR_ANIMATIONS_excited_end = 83;
        var IMG_CHAR_ANIMATIONS_idle2_start = 84;
        var IMG_CHAR_ANIMATIONS_idle2_end = 108;
        var IMG_CHAR_ANIMATIONS_idle3_start = 109;
        var IMG_CHAR_ANIMATIONS_idle3_end = 124;
        var IMG_CHAR_ANIMATIONS_puzzled_start = 125;
        var IMG_CHAR_ANIMATIONS_puzzled_end = 151;
        var IMG_CHAR_ANIMATIONS_greeting_start = 152;
        var IMG_CHAR_ANIMATIONS_greeting_end = 180;


        return GameScene;
    }

);

define('core/View',
    ['visual/BaseElement', 'resolution'],
    function (BaseElement, resolution) {

        var View = BaseElement.extend({
            init: function () {
                this._super();
                this.width = resolution.CANVAS_WIDTH;
                this.height = resolution.CANVAS_HEIGHT;
            }
        });

        return View;
    }
);

// @depend ../core/View.js

define('game/GameView',
    [
        'core/View',
        'resolution',
        'core/RGBAColor',
        'utils/Canvas',
        'utils/Constants'
    ],
    function (View, resolution, RGBAColor, Canvas, Constants) {

        var GameView = View.extend({
            init: function () {
                this._super();
            },
            draw: function () {
                var children = this.children,
                    childCount = children.length;
                for (var i = 0; i < childCount; i++) {
                    var c = children[i];
                    if (!c.visible) {
                        continue;
                    }

                    c.draw();
                }

                var gs = this.getChild(GameView.ElementType.GAME_SCENE);
                if (gs.dimTime > 0) {
                    var alpha = gs.dimTime / Constants.DIM_TIMEOUT;
                    if (gs.isFadingIn()) {
                        alpha = 1 - alpha;
                    }

                    var ctx = Canvas.context,
                        color = new RGBAColor(1, 1, 1, alpha);
                    ctx.fillStyle = color.rgbaStyle();
                    ctx.fillRect(0, 0, resolution.CANVAS_WIDTH, resolution.CANVAS_HEIGHT);
                }
            },
            show: function () {
                this._super();

                var gs = this.getChild(GameView.ElementType.GAME_SCENE);
                if (gs.animateRestartDim) {
                    gs.animateLevelRestart();
                }
            }

        });

        /**
         * @enum {number}
         */
        GameView.ElementType = {
            GAME_SCENE: 0,
            PAUSE_BUTTON: 1,
            RESTART_BUTTON: 2,
            NEXT_BUTTON: 3,
            PAUSE_MENU: 4,
            RESULTS: 5
        };

        return GameView;
    }
);
define('game/GameController',
    [
        'core/ViewController',
        'GameScene',
        'game/GameView',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'utils/Constants'
    ],
    function (ViewController, GameScene, GameView, SoundMgr, ResourceId, Constants) {

        /**
         * @enum {number}
         */
        var GameButton = {
            PAUSE_RESUME: 0,
            PAUSE_RESTART: 1,
            PAUSE_SKIP: 2,
            PAUSE_LEVEL_SELECT: 3,
            PAUSE_EXIT: 4,
            WIN_EXIT: 5,
            PAUSE: 6,
            NEXT_LEVEL: 7,
            WIN_RESTART: 8,
            WIN_NEXT_LEVEL: 9
        };

        /**
         * @enum {number}
         */
        var ExitCodeFrom = {
            PAUSE_MENU: 0,
            PAUSE_MENU_LEVEL_SELECT: 1,
            PAUSE_MENU_LEVEL_SELECT_NEXT_PACK: 2
        };

        var GameController = ViewController.extend({
            init: function (parent) {
                this._super(parent);
                this.animateRestart = false;
            },
            activate: function () {
                this._super();
                SoundMgr.playMusic(ResourceId.SND_GAME_MUSIC);
                this.createGameView();
                this.initGameView();
                this.showView(0);
            },
            createGameView: function () {

                var view = new GameView();
                var sc = new GameScene();
                sc.gameController = this;
                sc.animateRestartDim = this.animateRestart;
                this.animateRestart = false;
                view.addChildWithID(sc, GameView.ElementType.GAME_SCENE);

                this.addView(view, 0);
            },

            initGameView: function () {
                this.setPaused(false);
                this.levelFirstStart();
            },
            levelFirstStart: function () {
                this.isGamePaused = false;
            },
            levelStart: function () {
                this.isGamePaused = false;
            },
            onLevelWon: function () {
                SoundMgr.playSound(ResourceId.SND_WIN);
                this.deactivate();
            },
            onLevelLost: function () {
                this.restartLevel();
            },
            setPaused: function (paused) {
                this.isGamePaused = paused;

                var view = this.getView(0);
                if (view) {
                    var gs = view.getChild(GameView.ElementType.GAME_SCENE);
                    if (gs) {
                        gs.touchable = !paused;
                        gs.updateable = !paused;

                        if (paused) {
                            SoundMgr.pauseAudio();
                        }
                        else {
                            SoundMgr.resumeAudio();
                        }
                    }
                }
            },
            pauseLevel: function () {
                var view = this.getView(0);
                if (view) {
                    var gs = view.getChild(GameView.ElementType.GAME_SCENE);
                    if (gs) {
                        gs.dimTime = 0.0;
                        this.setPaused(true);
                    }
                }
            },
            resumeLevel: function () {
                this.setPaused(false);
            },
            restartLevel: function () {
                this.deleteView(0);
                this.animateRestart = true;
                this.activate();
            },

            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseDown: function (x, y) {
                // see if the event was handled by the base class
                var res = this._super(x, y);
                if (res) {
                    return true;
                }

                // see if the game scene is touchable
                var view = this.getView(0);
                if (view) {
                    var gs = view.getChild(GameView.ElementType.GAME_SCENE);
                    if (gs && gs.touchable) {
                        gs.touchDown(x, y, 0);
                        return true;
                    }
                }

                return false;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseDragged: function (x, y) {
                // see if the event was handled by the base class
                var res = this._super(x, y);
                if (res) {
                    return true;
                }

                // see if the game scene is touchable
                var view = this.getView(0);
                if (view) {
                    var gs = view.getChild(GameView.ElementType.GAME_SCENE);
                    if (gs && gs.touchable) {
                        gs.touchDragged(x, y, 0);
                        return true;
                    }
                }

                return false;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseMoved: function (x, y) {
                // see if the event was handled by the base class
                var res = this._super(x, y);
                if (res) {
                    return true;
                }

                // see if the game scene is touchable
                var view = this.getView(0);
                if (view) {
                    var gs = view.getChild(GameView.ElementType.GAME_SCENE);
                    if (gs && gs.touchable) {
                        gs.touchMove(x, y, 0);
                        return true;
                    }
                }

                return false;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            mouseUp: function (x, y) {
                // see if the event was handled by the base class
                var res = this._super(x, y);
                if (res) {
                    return true;
                }

                // see if the game scene is touchable
                var view = this.getView(0);
                if (view) {
                    var gs = view.getChild(GameView.ElementType.GAME_SCENE);
                    if (gs && gs.touchable) {
                        gs.touchUp(x, y, 0);
                        return true;
                    }
                }

                return false;
            },
            /**
             * @param x {number}
             * @param y {number}
             * @return {boolean} true if event was handled
             */
            doubleClick: function (x, y) {
                // see if the event was handled by the base class
                var res = this._super(x, y);
                if (res) {
                    return true;
                }

                var view = this.getView(0);
                if (view) {
                    var gs = view.getChild(GameView.ElementType.GAME_SCENE);
                    if (gs && gs.touchable) {
                        gs.doubleClick(x, y, 0);
                        return true;
                    }
                }

                return false;
            }
        });

        return GameController;
    }
);
define('game/CTRRootController',
    [
        'core/ViewController',
        'core/RootControllerBase',
        'game/GameController',
        'edition',
        'game/LevelState'
    ],
    function (ViewController, RootControllerBase, GameController, edition, LevelState) {

        /**
         * @enum {number}
         */
        var ChildController = {
            START: 0,
            MENU: 1,
            LOADING: 2,
            GAME: 3
        };

        var CTRRootController = RootControllerBase.extend({
            init: function (parent) {
                this._super(parent);
            },
            startLevel: function (pack, level) {
                LevelState.loadLevel(pack, level);

                // activate the root controller if necessary
                if (this.controllerState === ViewController.StateType.INACTIVE) {
                    this.activate();
                }

                // deactivate any existing game that is running
                var gameController = this.getChild(ChildController.GAME);
                if (gameController) {
                    gameController.deactivateImmediately();
                }

                // create and add the new game controller
                gameController = new GameController(this);
                this.addChildWithID(gameController, ChildController.GAME);
                this.activateChild(ChildController.GAME);
            },
            pauseLevel: function () {
                var gameController = this.getChild(ChildController.GAME);
                if (gameController) {
                    gameController.pauseLevel();
                }
            },
            resumeLevel: function () {
                var gameController = this.getChild(ChildController.GAME);
                if (gameController) {
                    gameController.resumeLevel();
                }
            },
            restartLevel: function () {
                var gameController = this.getChild(ChildController.GAME);
                if (gameController) {
                    gameController.restartLevel();
                }
            },
            stopLevel: function () {
                this.deactivateActiveChild();
            },
            isLevelActive: function () {

                // is the root controller active?
                if (this.controllerState === ViewController.StateType.INACTIVE)
                    return false;

                // see if the game controller exists
                var gameController = this.getChild(ChildController.GAME);
                if (!gameController)
                    return false;

                // is the game controller active?
                if (gameController.controllerState === ViewController.StateType.INACTIVE)
                    return false;

                // see if the game is paused
                if (gameController.isGamePaused)
                    return false;

                return true;
            },
            onChildDeactivated: function (childType) {
                this._super(childType);

                if (childType == ChildController.GAME) {
                    this.deleteChild(ChildController.GAME);
                }
            }
        });

        // we only need a singleton instance
        return new CTRRootController();
    }
);



define('ui/Dialogs',
    [
        'game/CTRRootController',
        'ui/PanelId',
        'ui/Panel',
        'resolution',
        'platform',
        'ui/ScoreManager',
        'ui/BoxManager',
        'utils/PubSub',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'resources/Lang',
        'visual/Text',
        'resources/MenuStringId',
        'edition',
        'core/Alignment'
    ],
    function (RootController, PanelId, Panel, resolution, platform, ScoreManager, BoxManager, PubSub, SoundMgr,
        ResourceId, Lang, Text, MenuStringId, edition, Alignment) {

        // show a popup
        var Dialogs = {
            showPopup: function (contentDivId) {
                RootController.pauseLevel();
                $(".popupOuterFrame").hide();
                $(".popupInnerFrame").hide();
                $("#popupWindow").fadeIn(500, function () {
                    $("#" + contentDivId).show();
                    $(".popupOuterFrame").fadeIn(500);
                });
            },

            closePopup: function () {
                SoundMgr.playSound(ResourceId.SND_TAP);
                $("#popupWindow").fadeOut(500, function () {
                    RootController.resumeLevel();
                });
            },

            showPayDialog: function() {
                SoundMgr.playSound(ResourceId.SND_TAP);
                Dialogs.showPopup("payDialog");
            },

            showSlowComputerPopup: function() {
                // remove the text images
                var $slowComputer = $('#slowComputer');
                $slowComputer.children('img').remove();

                // add the title and text
                var $titleImg = $(Text.drawBig({
                        text: Lang.menuText(MenuStringId.SLOW_TITLE),
                        alignment: Alignment.CENTER,
                        width: 1200 * resolution.CANVAS_SCALE,
                        scale: 1.25 * resolution.UI_TEXT_SCALE
                    })),
                    $textImg = $(Text.drawBig({
                        text: Lang.menuText(MenuStringId.SLOW_TEXT),
                        width: 1200 * resolution.CANVAS_SCALE,
                        scale: 0.8 * resolution.UI_TEXT_SCALE
                    }));

                $textImg.css('margin-left', resolution.uiScaledNumber(30));
                $slowComputer.append($titleImg).append($textImg);

                // shrink button text slightly so it will fit in RU and DE
                Text.drawBig({
                    text: Lang.menuText(MenuStringId.LETS_PLAY),
                    imgSel: '#slowComputerBtn img',
                    scale: 0.8 * resolution.UI_TEXT_SCALE
                });

                Dialogs.showPopup('slowComputer');
            }
        };

        function onPayClick() {
            PubSub.publish(PubSub.ChannelId.PurchaseBoxesPrompt);
            Dialogs.closePopup();
        }

        // localize dialog text
        PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
            Text.drawBig({
                text: Lang.menuText(MenuStringId.UPGRADE_TO_FULL),
                imgParentId: 'payMessage',
                width: resolution.uiScaledNumber(650),
                alignment: Alignment.CENTER,
                scale: 0.8 * resolution.UI_TEXT_SCALE
            });

            Text.drawBig({
                text: Lang.menuText(MenuStringId.BUY_FULL_GAME),
                imgParentId: 'payBtn',
                scale: 0.6 * resolution.UI_TEXT_SCALE
            });
        });

        $(function() {

            // trigger purchase when pay button is clicked
            $('#payImg').click(onPayClick);
            $('#payBtn').click(onPayClick);

            // close dialog buttons
            $('#payClose').click(Dialogs.closePopup);
            $("#slowComputerBtn").click(Dialogs.closePopup);
            $('#missingOkBtn').click(Dialogs.closePopup);
            $("#resetNoBtn").click(Dialogs.closePopup);


        });

        return Dialogs;
    }
);
define('ui/BoxPanel',
    [
        'ui/PanelId',
        'ui/Panel',
        'ui/Easing',
        'utils/PointerCapture',
        'resolution',
        'ZoomManager',
        'platform',
        'ui/ScoreManager',
        'utils/PubSub',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'resources/Lang',
        'visual/Text',
        'resources/MenuStringId',
        'ui/Dialogs'
    ],
    function (PanelId, Panel, Easing, PointerCapture, resolution, ZoomManager,
        platform, ScoreManager, PubSub, SoundMgr, ResourceId, Lang, Text, MenuStringId, Dialogs) {

        // BoxPanel displays the set of visible boxes (which may not include all boxes)

        var boxes = [],
            currentBoxIndex = 0,
            currentOffset = 0,
            cancelSlideFlag = false,
            isBoxCentered = true,
            isAnimationActive = false,
            spacing = resolution.uiScaledNumber(600),
            centeroffset = resolution.uiScaledNumber(312),
            bouncebox = null,
            im = null,
            canvas, ctx,
            $navBack, $navForward;


        var BoxPanel = new Panel(PanelId.BOXES, "boxPanel", "menuBackground", true);

        // dom ready events
        $(function() {
            canvas = document.getElementById("boxCanvas");
            ctx = canvas.getContext('2d');

            // size the canvas (only do this once)
            canvas.width = resolution.uiScaledNumber(1024);
            canvas.height = resolution.uiScaledNumber(576);

            // handles clicking on the prev box button
            $navBack = $('#boxNavBack').click($.proxy(function () {
                if (currentBoxIndex > 0) {
                    slideToBox(currentBoxIndex - 1);
                    SoundMgr.playSound(ResourceId.SND_TAP);
                }
            }, this));

            // handles clicking on the next box button
            $navForward = $('#boxNavForward').click($.proxy(function () {
                if (currentBoxIndex < boxes.length - 1) {
                    slideToBox(currentBoxIndex + 1);
                    SoundMgr.playSound(ResourceId.SND_TAP);
                }
            }, this));

            $('#boxUpgradePlate').click(function() {
                boxClicked(currentBoxIndex);
            });
        });

        PubSub.subscribe(PubSub.ChannelId.UpdateVisibleBoxes, function(visibleBoxes) {
            boxes = visibleBoxes;
            BoxPanel.redraw();
        });

        BoxPanel.init = function(interfaceManager) {
            im = interfaceManager;
        };

        BoxPanel.onShow = function() {
            this.activate();
        };

        BoxPanel.onHide = function() {
            this.deactivate();
        };

        BoxPanel.slideToNextBox = function() {
            slideToBox(currentBoxIndex + 1);
        };

        BoxPanel.bounceCurrentBox = function() {
            bounceCurrentBox();
        };

        // handles clicking on a box
        function boxClicked(visibleBoxIndex) {
            if (visibleBoxIndex !== currentBoxIndex) {
                // only open the selected box (otherwise navigate to different box)
                return;
            }

            // we have to translate from visible box index to edition box index
            var box = boxes[visibleBoxIndex],
                editionBoxIndex = box.index;

            // make sure the box is clickable
            if (!box.isClickable()) {
                return;
            }

            SoundMgr.playSound(ResourceId.SND_TAP);

            if (box.purchased === false) {
                PubSub.publish(PubSub.ChannelId.PurchaseBoxesPrompt);
            } else if (ScoreManager.isBoxLocked(editionBoxIndex)) {
                showLockDialog(editionBoxIndex);
            } else {
                im.openLevelMenu(editionBoxIndex);
            }
        }

        function showLockDialog(boxIndex) {
             // create localized text images
            Text.drawBig({
                text: Lang.menuText(MenuStringId.CANT_UNLOCK_TEXT1),
                imgParentId: 'missingLine1',
                scaleToUI: true
            });
            Text.drawBig({
                text: ScoreManager.requiredStars(boxIndex) -
                    ScoreManager.totalStars(),
                imgParentId: 'missingCount',
                scaleToUI: true
            });
            Text.drawBig({
                text: Lang.menuText(MenuStringId.CANT_UNLOCK_TEXT2),
                imgParentId: 'missingLine2',
                scaleToUI: true
            });
            Text.drawSmall({
                text: Lang.menuText(MenuStringId.CANT_UNLOCK_TEXT3),
                imgParentId: 'missingLine3',
                width: 260,
                scaleToUI: true
            });

            Text.drawBig({
                text: Lang.menuText(MenuStringId.OK),
                imgParentId: 'missingOkBtn',
                scaleToUI: true
            });

            SoundMgr.playSound(ResourceId.SND_TAP);
            Dialogs.showPopup("missingStars");
        }

        function bounceCurrentBox() {
            if (bouncebox != null && ctx != null) {
                bouncebox.cancelBounce();
                bouncebox.bounce(ctx);
            }
        }

        // render the boxes with the given offset
        function render(offset) {

            currentOffset = offset;

            // clear the canvas
            ctx.setTransform(1, 0, 0, 1, 0, 0);
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            var offsetX = centeroffset + offset,
                offsetY = resolution.uiScaledNumber(130);
            ctx.translate(offsetX, offsetY);

            var boxoffset = 0;

            for (var i = 0; i < boxes.length; i++) {

                var omnomoffset = null,
                    relboxoffset = offset + boxoffset,
                    box = boxes[i];

                if (box.visible) {

                    // calculate location of omnom if the box in the middle
                    if (relboxoffset > resolution.uiScaledNumber(-100) && relboxoffset < resolution.uiScaledNumber(100)) {
                        omnomoffset = (((centeroffset + offset) * -1) - boxoffset) + resolution.uiScaledNumber(452);
                    }

                    ctx.translate(boxoffset, 0);
                    box.draw(ctx, omnomoffset);
                    ctx.translate(-boxoffset, 0);

                    boxoffset += spacing;
                }
            }

            ctx.translate(-offsetX, -offsetY);
        }

        var slideInProgress = false,
            from, to, startTime;

        function slideToBox(index) {

            // clamp index
            if (index < 0) index = 0;
            if (index > boxes.length - 1) index = boxes.length - 1;

            // if we don't need to move the boxes, we still render them but only one frame
            var duration = (index == currentBoxIndex) ? 0 : 550;

            if (bouncebox && bouncebox != boxes[index] && bouncebox.onUnselected) {
                bouncebox.onUnselected();
            }

            // update the current boxindex
            currentBoxIndex = index;

            // publish new box index
            PubSub.publish(
                PubSub.ChannelId.SelectedBoxChanged,
                boxes[currentBoxIndex].index); // need to translate to edition box index

            from = currentOffset;
            to = -1.0 * spacing * index;
            startTime = Date.now();

            var renderSlide = function() {

                if (!slideInProgress) {
                    return;
                }

                var elapsed = Date.now() - startTime;
                currentOffset = Easing.easeOutExpo(elapsed, from, to - from, duration);
                render(currentOffset);

                // We need to detect whether the box animation has completed for hit testing. If we
                // wait until the animaiton is completely done, though, it feels unresponsive
                var d = Math.abs(currentOffset - to);
                if (d < 5) isBoxCentered = true;

                if (elapsed >= duration) {
                    if (bouncebox != boxes[currentBoxIndex]) {
                        bouncebox = boxes[currentBoxIndex];
                        bouncebox.bounce(ctx);
                    }
                    if (bouncebox && bouncebox.onSelected) {
                        bouncebox.onSelected();
                    }
                    slideInProgress = false;
                } else {
                    window.requestAnimationFrame(renderSlide);
                }

            };

            slideInProgress = true;
            renderSlide();

            // update the back/forward buttons
            $navBack.find('div').toggleClass('boxNavDisabled', index <= 0);
            $navForward.find('div').toggleClass('boxNavDisabled', index >= boxes.length - 1);
        }

        // cancels any current animations
        function cancelSlideToBox() {
            slideInProgress = false;

            if (bouncebox != null) {
                bouncebox.cancelBounce();
            }
        }

        function isMouseOverBox(x, y) {
            if (isBoxCentered && bouncebox != null && bouncebox.isClickable()) {
                if (x > resolution.uiScaledNumber(340) &&
                    x < resolution.uiScaledNumber(680) &&
                    y > resolution.uiScaledNumber(140) &&
                    y < resolution.uiScaledNumber(460)) {
                    return true;
                }
            }
            return false;
        }

        var ismousedown = false,
            imousedragging = false,
            upoffset = 0,
            downoffset = 0,
            delta = 0,
            downx = null,
            downy = null;

        function pointerDown(x, y) {
            if (ismousedown) { return; }
            //console.log('box canvas down: ' + x + ', ' + y);
            cancelSlideToBox();

            downx = x;
            downy = y;
            downoffset = currentOffset;
            ismousedown = true;
        }

        function pointerMove(x, y) {
            if (ismousedown) {
                cancelSlideToBox();
                delta = x - downx;
                if (Math.abs(delta) > 5) {

                    //$navBack.hide();
                    //$navForward.hide();

                    isBoxCentered = false;
                    render(downoffset + delta);
                }
            }
            else {
                $(canvas).toggleClass("ctrPointer", isMouseOverBox(x, y));
            }
        }

        function pointerUp(x, y) {
            //console.log('box canvas up: ' + x + ', ' + y);
            if (ismousedown) {
                cancelSlideToBox();
                delta = x - downx;

                if (Math.abs(delta) > spacing / 2) {
                    // if we've passed the rounding threshold then snap to the nearest box (this is for drags)
                    upoffset = currentOffset;
                    var index = Math.round(-1 * upoffset / spacing);

                    //console.log('box canvas drag to box: ' + index);
                    slideToBox(index);
                }

                else if (Math.abs(delta) > 5) {
                    // otherwise, we look for an action more like a flick and go to the next box
                    var max = resolution.uiScaledNumber(30), min = max * -1,
                        targetBoxIndex =
                            (delta > max) ? currentBoxIndex - 1 :
                                (delta < min) ? currentBoxIndex + 1 :
                                    currentBoxIndex;

                    //console.log('box canvas flick to box: ' + targetBoxIndex);
                    slideToBox(targetBoxIndex);
                }

                else {
                    //console.log('box click: ' + currentBoxIndex);
                    var currentBox = boxes[currentBoxIndex];
                    if (currentBox.isClickable()) {
                        if (!currentBox.islocked) {
                            slideToBox(currentBoxIndex);
                        }

                        if (isMouseOverBox(x, y)) {
                            boxClicked(currentBoxIndex);
                        }
                    }
                }
            }
            //$navBack.show();
            //$navForward.show();
            ismousedown = false;
        }

        function pointerOut(x, y) {
            //console.log('box canvas out: ' + x + ', ' + y);
            pointerUp(x, y);
        }

        BoxPanel.pointerCapture = null;
        BoxPanel.activate = function () {

            // ensure capture helper exists to handle mouse+touch movements
            if (!this.pointerCapture) {

                this.pointerCapture = new PointerCapture({
                    element: canvas,
                    onStart: $.proxy(pointerDown, this),
                    onMove: $.proxy(pointerMove, this),
                    onEnd: $.proxy(pointerUp, this),
                    onOut: $.proxy(pointerOut, this),
                    getZoom: function() {
                        return ZoomManager.getUIZoom();
                    }
                });
            }

            this.pointerCapture.activate();
        };

        BoxPanel.deactivate = function () {
            if (this.pointerCapture) {
                this.pointerCapture.deactivate();
            }
        };

        BoxPanel.redraw = function() {
            slideToBox(currentBoxIndex);
        };

        return BoxPanel;
    }
);
define('ui/BoxManager',
    [
        'ui/Box',
        'ui/PinnedBox',
        'ui/BoxType',
        'ui/ScoreManager',
        'utils/PubSub',
        'edition',
        'ui/QueryStrings',
        'ui/PurchaseBox',
        'ui/MoreComingBox',
        'ui/TimeBox',
        'ui/BoxPanel'
    ],
    function (Box, PinnedBox, BoxType, ScoreManager, PubSub, edition,
            QueryStrings, PurchaseBox, MoreComingBox, TimeBox, BoxPanel) {

        var BoxManager = new function () {

            var self = this,
                boxes = [];

            PubSub.subscribe(PubSub.ChannelId.SelectedBoxChanged, function(boxIndex) {
                BoxManager.currentBoxIndex = boxIndex;
                BoxManager.currentLevelIndex = 1;
            });

            var appIsReady = false;
            this.appReady = function() {
                appIsReady = true;
                loadBoxes();
            };

            self.currentBoxIndex = 0;

            // TODO: the current level index starts at 1, should be zero-based
            self.currentLevelIndex = 1;

            // listen to purchase event
            var isPaid = false;
            PubSub.subscribe(PubSub.ChannelId.SetPaidBoxes, function(paid) {
                isPaid = paid;
            });

            this.isNextLevelPlayable = function() {

                // check to make sure we aren't on the last level of the box
                if (ScoreManager.levelCount(this.currentBoxIndex) <= this.currentLevelIndex) {
                    return false;
                }

                // see if the game requires purchase of some levels
                if (isPaid || !edition.levelRequiresPurchase) {
                    return true;  // already purchased or none required
                }

                // check whether next level is free
                return !edition.levelRequiresPurchase(
                    this.currentBoxIndex,
                    this.currentLevelIndex); // NOTE: checking next level since this index is 1 based (TODO: fix!)
            };

            var loadBoxes = function() {

                // only load boxes if app is ready
                if (!appIsReady) {
                    return;
                }

                self.currentBoxIndex = 0;

                // TODO: the current level index starts at 1, should be zero-based
                self.currentLevelIndex = 1;

                createBoxes();
                updateVisibleBoxes();
            };

            // reload boxes when user signs in or out
            PubSub.subscribe(PubSub.ChannelId.SignIn, loadBoxes);
            PubSub.subscribe(PubSub.ChannelId.SignOut, loadBoxes);
            PubSub.subscribe(PubSub.ChannelId.RoamingDataChanged, loadBoxes);
            PubSub.subscribe(PubSub.ChannelId.BoxesUnlocked, loadBoxes);

            // returns the number of boxes required to win the game
            this.requiredCount = function () {
                var count = 0;
                for (var i = 0, len = boxes.length; i < len; i++) {
                    if (boxes[i].isRequired()) {
                        count++;
                    }
                }
                return count;
            };

            this.possibleStars = function () {
                var count = 0,
                    len = boxes.length;
                for (var i = 0; i < len; i++) {
                    // we'll count every box except for the hidden pinned box
                    if (boxes[i].isRequired()) {
                        count += ScoreManager.possibleStarsForBox(i);
                    }
                }
                return count;
            };

            this.visibleGameBoxes = function() {
                var count = 0,
                    i, len, box;
                for (i = 0, len = boxes.length; i < len; i++) {
                    box = boxes[i];

                    // count boxes that are required to finish the game
                    // and also purchased
                    if (box.isRequired() && box.purchased !== false) {
                        count++;
                    }
                }
                return count;
            };

            this.resetLocks = function() {
                var i, len, box;

                // don't lock the first box
                for (i = 1, len = boxes.length; i < len; i++) {
                    box = boxes[i];

                    // only lock game boxes
                    if (box.isGameBox()) {
                        box.islocked = true;
                    }
                }

                BoxPanel.redraw();
            };

            this.updateBoxLocks = function () {
                var numBoxes = boxes.length,
                    shouldRedraw = false,
                    boxIndex, box;

                // unlock new boxes if visual state has not been updated yet
                // (first box is always unlocked)
                for (boxIndex = 1; boxIndex < numBoxes; boxIndex++) {
                    box = boxes[boxIndex];
                    if (!ScoreManager.isBoxLocked(boxIndex) && box.purchased && box.islocked) {

                        box.islocked = false;
                        shouldRedraw = true;
                        ScoreManager.setStars(boxIndex, 0, 0);
                    }
                }

                if (shouldRedraw) {
                    BoxPanel.redraw();
                }
            };

            function createBoxes() {
                var images = edition.boxImages,
                    boxtypes = edition.boxTypes,
                    i, len, box, type, requiredStars, isLocked;

                // clear any existing boxes
                boxes.length = 0;

                // create each box
                for (i = 0, len = boxtypes.length; i < len; i++) {
                    type = boxtypes[i];
                    requiredStars = ScoreManager.requiredStars(i);
                    isLocked = ScoreManager.isBoxLocked(i);

                    switch (type) {
                        case BoxType.IEPINNED:
                            box = new PinnedBox(i, images[i], requiredStars, isLocked, type);
                            if (!box.initPinnedState()) {
                                box = null; // don't add if we can't init pinned state
                            }
                            break;
                        case BoxType.PURCHASE:
                            box = new PurchaseBox(i, images[i], requiredStars, isLocked, type);
                            break;
                        case BoxType.MORECOMING:
                            box = new MoreComingBox(i, images[i], requiredStars, isLocked, type);
                            break;
                        case BoxType.TIME:
                            box = new TimeBox(i, images[i], requiredStars, isLocked, type);
                            break;
                        default:
                            box = new Box(i, images[i], requiredStars, isLocked, type);
                            break;
                    }

                    if (box) {
                        boxes.push(box);
                    }
                }
            }

            function updateVisibleBoxes() {
                var visibleBoxes = [],
                    i, box, len;
                for (i = 0, len = boxes.length; i < len; i++) {
                    box = boxes[i];
                    box.index = i;
                    if (box.visible) {
                        visibleBoxes.push(box);
                    }
                }

                PubSub.publish(PubSub.ChannelId.UpdateVisibleBoxes, visibleBoxes);
            }

            function onPaidBoxesChange (paid) {

                paid = paid || (QueryStrings.unlockAllBoxes === true);

                var requiresPurchase = edition.levelRequiresPurchase || function () { return false; },
                    i, len, box;

                // first box is always unlocked
                for (i = 1, len = boxes.length; i < len; i++) {
                    box = boxes[i];

                    // hide unpurchased boxes and show upgrade prompt
                    switch(box.type) {
                        case BoxType.PURCHASE:
                            box.visible = !paid;
                            break;
                        case BoxType.MORECOMING:
                            box.visible = paid;
                            break;
                        default:
                            // if not paid, check to see if level 1 of the box requires payment
                            box.purchased = paid || !requiresPurchase(i, 0);
                            box.islocked = !box.purchased || ScoreManager.isBoxLocked(i);
                            break;
                    }
                }

                updateVisibleBoxes();
            }

            PubSub.subscribe(PubSub.ChannelId.SetPaidBoxes, onPaidBoxesChange);
        };

        return BoxManager;
    }
);
define('ui/LevelPanel',
    [
        'ui/PanelId',
        'ui/Panel',
        'resolution',
        'platform',
        'ui/ScoreManager',
        'ui/BoxManager',
        'utils/PubSub',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'resources/Lang',
        'visual/Text',
        'resources/MenuStringId',
        'edition',
        'core/Alignment',
        'ui/Dialogs'
    ],
    function (PanelId, Panel, resolution, platform, ScoreManager, BoxManager, PubSub, SoundMgr,
        ResourceId, Lang, Text, MenuStringId, edition, Alignment, Dialogs) {

        var backgroundId = edition.levelBackgroundId || 'levelBackground',
            LevelPanel = new Panel(PanelId.LEVELS, "levelPanel", backgroundId, true);

        // cache interface manager reference
        var im = null;

        LevelPanel.init = function(interfaceManager) {

            im = interfaceManager;

            // generate level elements
            var levelCount = ScoreManager.levelCount(BoxManager.currentBoxIndex);
            var $levelOptions = $("#levelOptions");

            // initialize for a 3x3 grid
            var leftOffset = 0,
                topOffset = 0,
                lineLength = resolution.uiScaledNumber(420),
                inc = resolution.uiScaledNumber(153),
                modClass = "",
                columns = 3,
                lastRowCount = levelCount % 3;


            if (levelCount > 9 && levelCount <= 12) {

                // expand to 4x3 grid
                leftOffset = -80;
                topOffset = 10;
                columns = 4;
                lineLength = resolution.uiScaledNumber(500);
                inc = resolution.uiScaledNumber(153);

            } else if (levelCount > 12) {

                // expand to a 5x5 grid
                leftOffset = -30;
                topOffset = -40;
                inc = resolution.uiScaledNumber(101);
                modClass = "option-small";
                columns = 5,
                lastRowCount = levelCount % 5;
            }

            var curTop = topOffset, curLeft = leftOffset, el;

            var adLevel = function $addLevel(i, inc, extraPad) {
                // create the level button
                $('<div/>')
                    .attr('id', 'option' + (i + 1))
                    .data('level', i)
                    .addClass('option locked ctrPointer ' + modClass)
                    .css({ 'left': curLeft + (extraPad || 0), 'top': curTop})
                    .click(onLevelClick)
                    .appendTo($levelOptions);

                curLeft += inc;
                if (curLeft > lineLength) {
                    curLeft = leftOffset;
                    curTop += inc;
                }
            }

            for (var i = 0, filledRowCount = levelCount - lastRowCount; i < filledRowCount; i++) {
                adLevel(i, inc);
            }

            if (lastRowCount > 0) {
                (function(j) {
                    var extraPad = (columns - lastRowCount) * inc / 2;
                    for (; j < levelCount; j++) {
                        adLevel(j, inc, extraPad);
                    }
                })(i);
            }
        };

        LevelPanel.onShow = function() {
            updateLevelOptions();
            $('#levelScore').delay(200).fadeIn(700);
            $('#levelBack').delay(200).fadeIn(700);
            $('#levelOptions').delay(200).fadeIn(700);
            $('#levelResults').delay(200).fadeOut(700);
        };

        // listen to purchase event
        var isPaid = false;
        PubSub.subscribe(PubSub.ChannelId.SetPaidBoxes, function(paid) {
            isPaid = paid;
            updateLevelOptions();
        });

        // update level UI when boxes are updated (paid upgrade or roaming data change)
        PubSub.subscribe(PubSub.ChannelId.UpdateVisibleBoxes, function(visibleBoxes) {
            updateLevelOptions();
        });

        function requiresPurchase(levelIndex) {
            if (isPaid) {
                return false;
            }

            if (edition.levelRequiresPurchase) {
                return edition.levelRequiresPurchase(BoxManager.currentBoxIndex, levelIndex);
            }

            return false;
        }

        function onLevelClick(event) {

            var levelIndex = parseInt($(this).data('level'), 10);
            if (ScoreManager.isLevelUnlocked(BoxManager.currentBoxIndex, levelIndex)) {
                im.openLevel(levelIndex + 1);
            } else if (requiresPurchase(levelIndex)) {
                Dialogs.showPayDialog();
            } else {
                // no action
                return;
            }

            SoundMgr.playSound(ResourceId.SND_TAP);
        }

        // draw the level options based on current scores and stars
        function updateLevelOptions() {
            var boxIndex = BoxManager.currentBoxIndex,
                levelCount = ScoreManager.levelCount(boxIndex),
                $level, stars, $levelInfo, i, levelRequiresPurchase;

            for (i = 0; i < levelCount; i++) {

                // get a reference to the level button
                $level = $("#option" + (i + 1));
                if ($level) {

                    // show and prepare the element, otherwise hide it
                    if (i < levelCount) {

                        $level.show();

                        levelRequiresPurchase = requiresPurchase(i);

                        // if the level has a score show it, otherwise make it locked
                        stars = ScoreManager.getStars(boxIndex, i);
                        if (stars != null) {

                            $levelInfo = $("<div class='txt'/>")
                                .append($(Text.drawBig({text: i + 1, scaleToUI: true })))
                                .append($('<div>').addClass('stars' + stars));

                            $level
                                .removeClass('locked purchase')
                                .addClass('open ctrPointer')
                                .empty()
                                .append($levelInfo);
                        }
                        else {
                            $level
                                .removeClass('open').addClass('locked')
                                .toggleClass('purchase ctrPointer', levelRequiresPurchase)
                                .empty();
                        }
                    }
                    else {
                        $level.hide();
                    }
                }
            }

            // update the scores
            // currently assuming each level has three stars
            var text = ScoreManager.achievedStars(BoxManager.currentBoxIndex) +
                "/" + (ScoreManager.levelCount(BoxManager.currentBoxIndex) * 3);
            Text.drawBig({text: text, imgSel: '#levelScore img', scaleToUI: true });
            BoxManager.updateBoxLocks();
            ScoreManager.updateTotalScoreText();
        }

        return LevelPanel;
    }
);
define('Doors',
    [
        'resolution',
        'edition',
        'platform',
        'ui/BoxManager',
        'ui/Easing',
        'utils/PubSub',
        'utils/Canvas'
    ],
    function (resolution, edition, platform, BoxManager, Easing, PubSub, Canvas) {

        var doorImages = [],
            tapeImgL = new Image(),
            tapeImgR = new Image();

        var BoxDoors = {};

        $(function() {
            BoxDoors.canvasLeft = document.getElementById("levelCanvasLeft");
            BoxDoors.canvasRight = document.getElementById("levelCanvasRight");

            BoxDoors.canvasLeft.width = resolution.uiScaledNumber(1024) / 2 | 0;
            BoxDoors.canvasRight.width = resolution.uiScaledNumber(1024) / 2 | 0;

            BoxDoors.canvasLeft.height = 320;
            BoxDoors.canvasRight.height = 320;

            BoxDoors.currentIndex = BoxManager.currentBoxIndex;
            BoxDoors.showTape = true;
        });

        BoxDoors.appReady = function() {

            // cache the door and tape images (which have already been preloaded)
            for (var i = 0, len = edition.boxDoors.length; i < len; i++) {
                var doorImg = new Image();
                doorImg.src = platform.uiImageBaseUrl + edition.boxDoors[i];
                doorImages[i] = doorImg;
            }

            tapeImgL.src = platform.uiImageBaseUrl + 'leveltape_left.png';
            tapeImgR.src = platform.uiImageBaseUrl + 'leveltape_right.png';

            BoxDoors.preRenderDoors();
        };

        BoxDoors.preRenderDoors = function () {
            var doorImg = doorImages[BoxManager.currentBoxIndex];
            var leftCtx = BoxDoors.canvasLeft.getContext('2d');
            var rightCtx = BoxDoors.canvasRight.getContext('2d');

            leftCtx.drawImage(doorImg, 0, 0);

            rightCtx.save();
            rightCtx.translate(doorImg.width, doorImg.height);
            rightCtx.rotate(Math.PI);
            rightCtx.drawImage(doorImg, 0, 0);
            rightCtx.restore();

            if (BoxDoors.showTape) {
                //draw the left side tape
                leftCtx.drawImage(
                    tapeImgL,
                    BoxDoors.canvasLeft.width - resolution.uiScaledNumber(26),
                    resolution.uiScaledNumber(10)
                );

                rightCtx.drawImage(
                    tapeImgR, 
                    0, 
                    resolution.uiScaledNumber(10)
                );
            }
        }

        BoxDoors.renderDoors = function (showTape, percentOpen) {
            //do another prerender
            if (BoxDoors.currentIndex !== BoxManager.currentBoxIndex ||
                BoxDoors.showTape !== showTape) {

                BoxDoors.currentIndex = BoxManager.currentBoxIndex;
                BoxDoors.showTape = showTape;
                BoxDoors.preRenderDoors(showTape);
            }

            //calculations
            var p = percentOpen || 0.0,
                dw = BoxDoors.canvasLeft.width, //door width
                offset = dw - (dw * (1 - p)); //512 - (512 * (1 - 0.1))

            //use css3 transformations
            $(BoxDoors.canvasLeft).css("transform", "translateX(" + (-1 * offset) + "px)");
            $(BoxDoors.canvasRight).css("transform", "translateX(" + (dw + offset) + "px)");
            
        };

        BoxDoors.openDoors = function (showTape, callback, runInReverse) {

            var r = runInReverse != null ? runInReverse : false;

            var begin = Date.now();
            var dur = 750;
            // Draw the door animation on the main game canvas
            var ctx = document.getElementById('c').getContext('2d');
            var easing = runInReverse ? Easing.easeOutCubic : Easing.easeInOutCubic;

            function openBoxDoors() {

                var now = Date.now(),
                    p = now - begin,
                    v = easing(p, 0, 1, dur);

                if (v < 1) {
                    BoxDoors.renderDoors(showTape, r ? 1 - v : v, ctx);
                    window.requestAnimationFrame(openBoxDoors);
                } else {
                    BoxDoors.renderDoors(showTape, r ? 0 : 1, ctx);

                    if (r) {
                        $("#levelPanel").show();
                    }
                    else {
                        $("#levelPanel").hide();
                    }

                    if (callback != null) callback();
                }
            }

            window.requestAnimationFrame(openBoxDoors);
        };

        BoxDoors.closeDoors = function (showTape, callback) {
            BoxDoors.openDoors(showTape, callback, true);
        };

        BoxDoors.closeBoxAnimation = function (callback) {

            // animating to level select
            // box already closed, just needs to be taped and then redirected
            var tapeRoll = $("#tapeRoll");
            var tapeSlice = $("#levelTape");

            $('#levelResults').fadeOut(400);
            tapeRoll.css("top", resolution.uiScaledNumber(0));
            tapeRoll.delay(400).fadeIn(200, function () {

                var offset = resolution.uiScaledNumber(650);
                var offsetH = resolution.uiScaledNumber(553);
                var b = Date.now();
                var from = parseInt(tapeRoll.css("top"), 10);
                var fromH = resolution.uiScaledNumber(63);
                var d = 1000;

                tapeSlice.css("height", fromH);
                tapeSlice.show();

                function rollTape() {
                    var now = Date.now(),
                        diff = now - b,
                        v = Easing.easeInOutCubic(diff, from, offset - from, d),
                        vH = Easing.easeInOutCubic(diff, fromH, offset - fromH, d);

                    tapeRoll.css("top", v);
                    tapeSlice.css("height", vH);

                    if (diff < d) {
                        window.requestAnimationFrame(rollTape);
                    } else {
                        // hide the tape slice and re-render the doors with tape
                        tapeSlice.hide();
                        BoxDoors.renderDoors(true);

                        //fade out tape and switch panels
                        tapeRoll.fadeOut(400, function () {
                            setTimeout(callback, 200);
                        }); //end fadeOut
                    } // end if/else
                } // end rollTape

                window.requestAnimationFrame(rollTape);

            });
        };

        BoxDoors.openBoxAnimation = function(callback) {

            // make sure the doors are rendered closed initially
            BoxDoors.renderDoors(true, 0);

            // make sure the gradient (time edition) is removed
            BoxDoors.hideGradient();

            //cut box open with boxCutter
            var boxCutter = $("#boxCutter");
            boxCutter.css("top", resolution.uiScaledNumber(371));
            boxCutter.delay(200).fadeIn(200, function () {

                var offset = resolution.uiScaledNumber(-255);
                var b = Date.now();
                var from = parseInt(boxCutter.css("top"), 10);
                var d = 1000;

                function cutBox() {
                    var now = Date.now(),
                        diff = now - b,
                        v = Easing.easeInOutCubic(diff, from, offset - from, d);

                    boxCutter.css("top", v);

                    if (diff < d) {
                        window.requestAnimationFrame(cutBox);
                    } else {
                        //fade out cutter and open doors
                        boxCutter.fadeOut(300, callback); //end fadeOut
                    } // end if/else
                } // end cutBox

                window.requestAnimationFrame(cutBox);

            });
        };

        BoxDoors.showGradient = function() {};
        BoxDoors.hideGradient = function() {};

        return BoxDoors;
    }
);

define('ui/TimePasswordPanel',
    [
        'ui/PanelId',
        'ui/Panel',
        'ui/Easing',
        'utils/PubSub',
        'game/CTRSoundMgr',
        'visual/Text',
        'ui/TimeBox',
        'Doors',
        'edition',
        'ui/QueryStrings'
    ],
    function (PanelId, Panel, Easing, PubSub, SoundMgr, Text, TimeBox, Doors, edition, QueryStrings) {

        var TimePasswordPanel = new Panel(PanelId.PASSWORD, "codePanel", "levelBackground", false),
            $message = null,
            $codeText = null,
            $okButton = null,
            $backButton = null;

        TimePasswordPanel.isGameLocked = function() {

            if (!edition.enablePasswordPanel) {
                return false;
            }

            // see if the first box is locked
            return TimeBox.isLocked(0) && !QueryStrings.unlockAllBoxes;
        };

        // dom ready events
        $(function() {

            var validating = false;

            $message = $('#codeMessage');
            $codeText = $('#codeText');
            $okButton = $('#codeOkButton');
            $backButton = $('#codeBack').toggle(!TimePasswordPanel.isGameLocked());

            function setMessageHtml(html) {

                $message.html(html);

                // for some reason, chrome isn't rendering the text when its first set
                // we need to do something to trigger layout, so its shown properly
                var width = $message.width();
                $message.width(width + 1);
                $message.width(width - 1);
            }

            var showValidatingMessage = false;
            function pulseWhileValidating() {
                if (!validating) {
                    showValidatingMessage = false;
                    return;
                }

                showValidatingMessage = !showValidatingMessage;
                setMessageHtml(showValidatingMessage ? 'Validating code . . .' : '');
                setTimeout(pulseWhileValidating, showValidatingMessage ? 600 : 250);
            }

            function validationComplete(boxIndex, isValid) {
                validating = false;
                $codeText.attr('disabled', false);

                if (!isValid) {
                    setMessageHtml('Sorry, that code is not valid or <br/> has already been redeemed.');
                } else {
                    setMessageHtml('Code Accepted!');

                    // have any boxes been unlocked?
                    var isFirstUnlock = true,
                        i, len;
                    for (i = 0, len = edition.boxes.length; i < len; i++) {
                        if (!TimeBox.isLocked(i)) {
                            isFirstUnlock = false;
                            break;
                        }
                    }

                    // unlock all the boxes
                    for (var i = boxIndex; i >= 0; i--) {
                        TimeBox.unlockBox(i);
                    }

                    // back button is initially hidden, show it once the game
                    // has been unlocked (but wait until the panel fades out)
                    $backButton.delay(3000).show(0);

                    // reload the boxes
                    PubSub.publish(PubSub.ChannelId.BoxesUnlocked, isFirstUnlock);
                }
            }

            function validateCode() {
                // make sure we are not currently validating a code
                if (validating) {
                    return;
                }

                // make sure the code is a integer within the valid range
                var numBoxes = edition.boxes.length,
                    codeString = $codeText.val() || '',
                    firstDigit = codeString.length > 0 ? (parseInt(codeString[0], 10) || 0) : 0,
                    code = parseInt(codeString, 10);
                if (isNaN(code) || code < 0 || firstDigit < 1 || firstDigit > numBoxes) {
                    setMessageHtml('Oops, that is not a valid code!');
                    return;
                }

                // find the last unlocked box
                for (var lastUnlockedIndex = numBoxes - 1; lastUnlockedIndex >= 0; lastUnlockedIndex--) {
                    if (!TimeBox.isLocked(lastUnlockedIndex)) {
                        break;
                    }
                }

                // codes start with the box number
                var codeIndex = firstDigit - 1;
                if (codeIndex <= lastUnlockedIndex) {
                    setMessageHtml('Levels for that code have already been unlocked! <br/>' +
                        'Visit Burger King each week to get a <br/> new code that will unlock additional levels.');
                    return;
                }

                // start the validation mode
                $codeText.attr('disabled', true);
                validating = true;
                pulseWhileValidating();

                $.ajax({
                    type: 'POST',
                    url: 'http://ctrbk.cloudapp.net/api/CTRBKCodes',
                    contentType: 'application/json',
                    data: '{"ctrbkcode":"'+ code + '"}',
                    dataType: 'json',
                    error: function(jqXHR, textStatus, errorThrown) {
                        //console.log('error');
                        validationComplete(codeIndex, false);
                    },
                    success: function(data, textStatus, jqXHR) {
                        //console.log('success');
                        validationComplete(codeIndex, true);
                    }
                });
            }

            // validate when user hits ENTER in textbox
            $codeText.keyup(function(e) {
                if(e.which == 13){
                    validateCode();
                } else {
                    // otherwise clear any existing messages
                    setMessageHtml('');
                }
            });

            // or when the click the ok button
            $okButton.click(function () {
                validateCode();
            });

        });

        var im;
        TimePasswordPanel.init = function(interfaceManager) {
            im = interfaceManager;
        };

        TimePasswordPanel.onShow = function() {
            $message.text('');
            $codeText.val('').focus();
            Doors.renderDoors(false, 0);
            Doors.showGradient();
        };

        TimePasswordPanel.onHide = function() {
            Doors.hideGradient();
        };

        return TimePasswordPanel;
    }
);
define('ui/PanelManager',
    [
        'ui/PanelId',
        'ui/Panel',
        'ui/BoxPanel',
        'ui/LevelPanel',
        'ui/TimePasswordPanel',
        'resolution',
        'platform',
        'ui/Easing',
        'utils/PubSub',
        'edition'
    ],
    function (PanelId, Panel, BoxPanel, LevelPanel, PasswordPanel, resolution, platform, Easing, PubSub, edition) {

        var PanelManager = new function () {

            var _this = this,
                panels = [];

            this.onShowPanel = null;

            this.domReady = function () {
                fadeToBlack = $("#fadeToBlack");

                // shadowCanvas = document.getElementById('shadowCanvas');
                // shadowCanvas.width = resolution.uiScaledNumber(1024);
                // shadowCanvas.height = resolution.uiScaledNumber(576);
            };

            this.appReady = function (onInitializePanel) {
                // we have to wait until the game is ready to run before initializing
                // panels because we need the fonts to be loaded

                shadowImage = new Image();
                shadowImage.src = platform.uiImageBaseUrl + 'shadow.png';

                // initialize each of the panels
                if (onInitializePanel) {
                    for (var i = 0, len = panels.length; i < len; i++) {
                        onInitializePanel(panels[i].id);
                    }
                }
            };

            // get a panel by id
            var getPanelById = this.getPanelById = function (panelId) {
                for (var i = 0; i < panels.length; i++) {
                    if (panels[i].id == panelId) return panels[i];
                }
                return null;
            };

            // create our panels
            panels.push(new Panel(PanelId.MENU, "menuPanel", "startBackground", true));
            panels.push(BoxPanel);
            panels.push(LevelPanel);

            // the game panel re-uses the panel doors in the levelBackground (actually in foreground)
            panels.push(new Panel(PanelId.GAME, null, "levelBackground", false));
            panels.push(new Panel(PanelId.GAMEMENU, null, null, false));
            panels.push(new Panel(PanelId.LEVELCOMPLETE, null, null, false));
            panels.push(new Panel(PanelId.GAMECOMPLETE, "gameCompletePanel", "menuBackground", true));
            panels.push(new Panel(PanelId.OPTIONS, "optionsPanel", "menuBackground", true));
            panels.push(new Panel(PanelId.CREDITS, null, null, false));
            panels.push(new Panel(PanelId.LEADERBOARDS, "leaderboardPanel", "menuBackground", true));
            panels.push(new Panel(PanelId.ACHIEVEMENTS, "achievementsPanel", "menuBackground", true));
            panels.push(PasswordPanel);

            this.currentPanelId = PanelId.MENU;

            // show a panel by id
            this.showPanel = function (panelId, skipFade) {

                _this.currentPanelId = panelId;

                var panel = getPanelById(panelId);
                var skip = skipFade == null ? false : skipFade;

                // enable / disable the shadow animation
                // if (panel.showShadow) {
                //     showShadow();
                // }
                // else {
                //     hideShadow();
                // }


                // we always use a timeout, even if we skip the animation, to keep the code clean
                var timeout = skip ? 0 : fadeInDur + fadePause;
                setTimeout(function () {

                    // show the panel
                    if (panel.bgDivId) { $("#" + panel.bgDivId).show(); }
                    if (panel.panelDivId) { $("#" + panel.panelDivId).show(); }

                    // hide other panels
                    for (var i = 0; i < panels.length; i++) {
                        var otherPanel = panels[i];

                        if (otherPanel.panelDivId != null && otherPanel.panelDivId != panel.panelDivId) {
                            $("#" + otherPanel.panelDivId).hide();
                        }

                        if (otherPanel.bgDivId != null && otherPanel.bgDivId != panel.bgDivId) {
                            $("#" + otherPanel.bgDivId).hide();
                        }
                    }

                    // run the "show" handler
                    if (_this.onShowPanel != null) {
                        _this.onShowPanel(panelId);
                    }

                    // fade back in
                    if (!skip) {
                        _this.runBlackFadeOut();
                    }

                }, timeout);

                // start the animation
                if (!skip) {
                    _this.runBlackFadeIn();
                }
            };

            // fade parameters
            var fadeInDur = 100;
            var fadePause = 50;
            var fadeOutDur = 100;
            var fadeTo = 1.0;
            var fadeToBlack;
            var isFading = false;

            this.runBlackFadeIn = function (callback) {
                isFading = true;
                var b = Date.now();

                // reset the overlay
                fadeToBlack.css('opacity', 0);
                fadeToBlack.css('display', 'block');

                // our loop
                function loop() {

                    var now = Date.now(),
                        diff = now - b,
                        v = Easing.noEase(diff, 0, fadeTo, fadeInDur);

                    fadeToBlack.css('opacity', v);

                    if (diff < fadeInDur) {
                        window.requestAnimationFrame(loop);
                    } else {
                        fadeToBlack.css('opacity', fadeTo);
                        if (callback != null) callback();
                    }
                }

                window.requestAnimationFrame(loop);
            };

            this.runBlackFadeOut = function () {
                if (!isFading) return;
                var b = Date.now();

                // our loop
                function loop() {

                    var now = Date.now(),
                        diff = now - b,
                        v = fadeTo - Easing.noEase(diff, 0, fadeTo, fadeInDur);

                    fadeToBlack.css('opacity', v);

                    if (diff < fadeInDur) {
                        window.requestAnimationFrame(loop);
                    } else {
                        fadeToBlack.css('opacity', 0);
                        fadeToBlack.css('display', 'none');
                        isFading = false;
                    }
                }

                window.requestAnimationFrame(loop);
            };

            var shadowIsRotating = false;
            var shadowAngle = 15.0;
            var shadowCanvas = null;
            var shadowImage = null;
            var shadowOpacity = 1.0;
            var shadowIsVisible = false;
            var shadowSpeedup = edition.shadowSpeedup || 1;


            var showShadow = function () {
                if (!shadowIsVisible) {
                    if (shadowCanvas != null) {
                        var ctx = shadowCanvas.getContext('2d');
                        ctx.save();
                        ctx.setTransform(1, 0, 0, 1, 0, 0);
                        ctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
                        ctx.restore();
                    }

                    shadowOpacity = 0.0;
                    shadowIsVisible = true;

                    $('#shadowPanel').show();
                    if (!shadowIsRotating) {
                        beginRotateShadow();
                    }
                }
            };

            var hideShadow = function () {
                shadowIsVisible = false;
                shadowIsRotating = false;
                $('#shadowPanel').hide();
            };

            // starts the shadow animation
            var beginRotateShadow = function () {
                var ctx = shadowCanvas.getContext('2d'),
                    requestAnimationFrame = window['requestAnimationFrame'],
                    lastRotateTime = Date.now(),
                    renderShadow = function() {

                        if (!shadowIsRotating) {
                            return;
                        }

                        // move .1 radians every 25 msec
                        var now = Date.now(),
                            delta = now - lastRotateTime;
                        shadowAngle += delta * 0.1 / 25 * shadowSpeedup;
                        lastRotateTime = now;

                        // clear the canvas
                        ctx.setTransform(1, 0, 0, 1, 0, 0);
                        ctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);

                        // update opacity
                        if (shadowOpacity < 1.0) {
                            shadowOpacity += 0.025;
                            shadowOpacity = Math.min(shadowOpacity, 1.0);
                            ctx.globalAlpha = shadowOpacity;
                        }

                        // rotate the context
                        ctx.save();
                        ctx.translate(shadowImage.width * 0.5, shadowImage.height * 0.5);
                        ctx.translate(resolution.uiScaledNumber(-300), resolution.uiScaledNumber(-510));
                        ctx.rotate(shadowAngle * Math.PI / 180);
                        ctx.translate(-shadowImage.width * 0.5, -shadowImage.height * 0.5);

                        // draw the image and update the loop
                        ctx.drawImage(shadowImage, 0, 0);
                        ctx.restore();

                        requestAnimationFrame(renderShadow);
                    };

                shadowIsRotating = true;
                renderShadow();
            };
        };

        PubSub.subscribe(PubSub.ChannelId.BoxesUnlocked, function(isFirstUnlock) {

            var nextPanelId = isFirstUnlock ? PanelId.MENU : PanelId.BOXES;

            // switch back to the boxes panel after a short delay
            setTimeout(function() {
                PanelManager.showPanel(nextPanelId);
            }, 1000);
        });

        return PanelManager;
    }
);
define('ui/SocialHelper',
    [
        'platform',
        'edition',
        'resources/Lang',
        'resources/MenuStringId',
        'utils/PubSub',
        'analytics'
    ],
    function (platform, edition, Lang, MenuStringId, PubSub, analytics) {

        var SocialHelper = new function () {

            this.siteUrl = edition.siteUrl;

            // cuttherope.ie and cuttherope.net
            this.appId = '278847552173744';

            // check for test domain (seperate FB app ids)
            var host = window.location.host || '';
            if (host.indexOf('thinkpixellab') >= 0) {
                // thinkpixellab.com
                this.appId = '239041062884795';
            } else if (host.indexOf('.dev') >= 0) {
                // ctr-net.dev
                this.appId = '261043477350153';
            }

            // listen to language changes
            var self = this;
            PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
                self.siteDescription = Lang.menuText(MenuStringId.SITE_DESC);
                self.siteName = Lang.menuText(MenuStringId.SITE_TITLE);
                self.siteActions = [
                    {
                        name: Lang.menuText(MenuStringId.SITE_ACTION),
                        link: edition.siteUrl
                    }
                ];
            });

            this.initFB = function () {

                // NOTE: must create settings this way to prevent obfuscation
                var fbInitSettings = {};
                fbInitSettings['appId'] = self.appId;
                fbInitSettings['status'] = true;
                fbInitSettings['cookie'] = true;
                fbInitSettings['xfbml'] = true;
                FB.init(fbInitSettings);

                // report facebook likes
                FB.Event.subscribe('edge.create', function (response) {
                    if (analytics.onFacebookLike) {
                        analytics.onFacebookLike();
                    }
                });
            };

            // remember to return true in the callback
            this.postToFeed = function (caption, description, imageurl, callback) {

                // see if the platform has custom sharing
                if (platform.customSharing) {
                    PubSub.publish(PubSub.ChannelId.Share, caption, description, imageurl);
                } else  {
                    // otherwise, we'll default to using facebook

                    // NOTE: must create settings this way to prevent obfuscation
                    var publish = {};
                    publish['method'] = 'feed';
                    publish['name'] = self.siteName;
                    publish['caption'] = caption;
                    publish['description'] = description;
                    publish['link'] = self.siteUrl;
                    publish['picture'] = imageurl;
                    publish['actions'] = self.siteActions;

                    FB.ui(publish, callback);
                }
            };

            this.initTwitter = function (twttr) {
                // report tweets from users
                twttr['events']['bind']('tweet', function (event) {
                    if (analytics.onTweet) {
                        analytics.onTweet();
                    }
                });
            };

        };

        return SocialHelper;
    }
);

define('ui/EasterEggManager',
    [
        'visual/Text',
        'ui/SocialHelper',
        'platform',
        'resolution',
        'analytics',
        'ui/Easing',
        'utils/PubSub',
        'game/CTRRootController',
        'resources/Lang',
        'resources/MenuStringId'
    ],
    function (Text, SocialHelper, platform, resolution, analytics, Easing, PubSub, RootController, Lang, MenuStringId) {

        var EasterEggManager = function () {

            var devCanvas, canvas,
                scaleTo = resolution.uiScaledNumber(2.2);

            this.domReady = function () {

                canvas = document.getElementById("e");
                canvas.width = resolution.uiScaledNumber(1024);
                canvas.height = 320;

                devCanvas = document.getElementById("moreCanvas");
                if (devCanvas) {
                    devCanvas.width = 51;
                    devCanvas.height = 51;
                }

                // event handlers
                $('#dshareBtn').click(function () {

                    SocialHelper.postToFeed(
                        Lang.menuText(MenuStringId.SHARE_DRAWING),
                        SocialHelper.siteDescription,
                        platform.getDrawingBaseUrl() + "drawing" + drawingNum + ".jpg",
                        function () {
                            closeDrawing();
                            return true;
                        });

                    return false; // cancel bubbling
                });

                $('#d').click(function () {
                    closeDrawing();
                });


                $('#moreLink')
                    .mouseenter(function () {
                        if (!omNomShowing) {
                            omNomShowing = true;
                            showDevLinkOmNom(function () {
                                omNomShowing = false;
                            });
                        }
                    })
                    .click(function () {
                        Analytics.atlasAction('SMG_MRTINX_CTR_SITE_BehindtheScenes');
                    });
            };

            this.appReady = function () {
                // setup (choosing not to use PanelManager for now because of the fade in animation)
                PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
                    Text.drawBig({ text: Lang.menuText(MenuStringId.FOUND_DRAWING), imgId: 'dmsg', scaleToUI: true });
                    Text.drawBig({ text: Lang.menuText(MenuStringId.SHARE), imgSel: '#dshareBtn img', scaleToUI: true});
                });

                PubSub.subscribe(PubSub.ChannelId.OmNomClicked, this.showOmNom);
                PubSub.subscribe(PubSub.ChannelId.DrawingClicked, this.showDrawing);
            };

            // ------------------------------------------------------------------------
            // Drawings
            // ------------------------------------------------------------------------

            // show a drawing
            var drawingNum = null;
            this.showDrawing = function (drawingIndex) {
                drawingNum = drawingIndex + 1;
                RootController.pauseLevel();

                $('#gameBtnTray').hide();

                $('#dpic').addClass('drawing' + drawingNum);

                // setup the scene
                $("#dframe").animate({ top: resolution.uiScaledNumber(100), scale: 0.35 }, 0);
                $("#dframe").fadeTo(0, 0);
                $("#dmsg").animate({ top: resolution.uiScaledNumber(60), scale: 0.5 }, 0);
                $("#dmsg").fadeTo(0, 0);
                $("#dshareBtn").fadeTo(0, 0);

                // fade in the background (quickly)
                $("#d").fadeIn(100, function () {
                    $("#dframe").fadeTo(0, 1);
                    $("#dmsg").fadeTo(0, 1);
                    $("#dframe").animate({ top: 0, scale: 1.0 }, 350, "easeOutBack");
                    $("#dmsg").animate({ top: 0, scale: 1.0 }, 350, "easeOutBack");
                    $("#dshareBtn").delay(600).fadeTo(200, 1.0);
                });
            };

            var closeDrawing = function () {
                $('#dpic').removeClass(); // clear the drawing
                $("#dframe").animate({ top: resolution.uiScaledNumber(50), scale: 0.2 }, 350, "easeInExpo");
                $("#dmsg").animate({ top: resolution.uiScaledNumber(50), scale: 0.2 }, 350, "easeInExpo");
                $("#dshareBtn").fadeTo(200, 0);
                $("#d").delay(200).fadeOut(200, function () {
                    RootController.resumeLevel();
                    drawingNum = null;
                    $('#gameBtnTray').show();
                });
            };

            // ------------------------------------------------------------------------
            // Om Nom
            // ------------------------------------------------------------------------

            // mouse over for dev link
            var omNomShowing = false;

            var showDevLinkOmNom = function (onComplete) {

                var ctx = devCanvas.getContext("2d");
                var begin = Date.now();
                var sx = 0.1;
                var sy = 0.1;
                var tx = 0;
                var ty = 0;
                var l = 0;
                var r = 0;
                var dur = 800;

                var sxbegin = sx;
                var sybegin = sy;

                var txbegin = tx;
                var tybegin = ty;

                var s1 = 600;
                var s2 = 400 + s1;
                var s3 = 600 + s2;
                var s4 = 700 + s3;
                var s5 = 500 + s4;
                var s6 = 800 + s5;

                var mod = 0;
                var modflag = false;
                var modt = null;

                function step() {

                    var now = Date.now(),
                        t = now - begin;

                    // zoom up OmNom
                    if (t < s1) {
                        sy = 0 - Easing.easeOutBounce(t, 0, 100, s1, 1.5);
                    }

                    // move his eyes left
                    else if (t < s2) {
                        if (t > s1 + 100) { // delay;
                            l = -1 * Easing.easeOutExpo(t - (s1 + 100), 0, 10, s2 - (s1 + 100));
                            r = l;
                        }
                    }

                    // move his eyes right
                    else if (t < s3) {
                        l = -10 + Easing.easeInOutExpo(t - s2, 0, 20, s3 - s2);
                        r = l;
                    }

                    // move his eyes back
                    else if (t < s4) {
                        if (t > s3 + 100) { // delay;
                            l = 10 - Easing.easeInOutExpo(t - (s3 + 100), 0, 10, s4 - (s3 + 100));
                            r = l;
                        }
                    }
                    else if (t < s5) {

                    }
                    // hide omnom
                    else if (t < s6) {
                        ty = Easing.easeOutExpo(t - s5, txbegin, 50, s6 - s5);
                    }

                    if (t > s1 && t < s3) {
                        mod = Easing.easeInOutBounce(t - s1, 0, 0.02, s3 - s1, 6.0);
                    }

                    if (t > s3 && t < s5) {
                        mod = 0.02 - Easing.easeInOutBounce(t - s3, 0, 0.02, s5 - s3, 2.0);
                    }

                    // position in the canvas
                    var mx = 0 + tx;
                    var my = 75 + ty + sy;

                    ctx.save();
                    ctx.rotate(30 * Math.PI / 180);
                    drawOmNom(ctx, 0.32, 0.32 + mod, mx, my, l, r);
                    ctx.restore();

                    // get the next frame
                    if (t < s6) {
                        window.requestAnimationFrame(step);
                    }
                    else {
                        onComplete();
                    }
                }

                window.requestAnimationFrame(step);
            };

            this.showOmNom = function () {

                var ctx = canvas.getContext("2d");

                RootController.pauseLevel();

                $("#e").fadeIn(function () {

                    var begin = Date.now();
                    var sx = 0.1;
                    var sy = 0.1;
                    var tx = 0;
                    var ty = 0;
                    var l = 0;
                    var r = 0;
                    var dur = 800;

                    var sxbegin = sx;
                    var sybegin = sy;

                    var txbegin = tx;
                    var tybegin = ty;

                    var s1 = 600;
                    var s2 = 400 + s1;
                    var s3 = 600 + s2;
                    var s4 = 700 + s3;
                    var s5 = 500 + s4;
                    var s6 = 800 + s5;

                    var mod = 0;
                    var modflag = false;
                    var modt = null;

                    function step() {

                        var now = Date.now(),
                            t = now - begin;

                        // zoom in OmNom
                        if (t < s1) {
                            sx = Easing.easeOutBounce(t, sxbegin, scaleTo, s1, 1.5);
                            sy = Easing.easeOutBounce(t, sybegin, scaleTo, s1, 1.5);
                        }

                        // move his eyes left
                        else if (t < s2) {
                            if (t > s1 + 100) { // delay;
                                l = -1 * Easing.easeOutExpo(t - (s1 + 100), 0, resolution.uiScaledNumber(10),
                                    s2 - (s1 + 100));
                                r = l;
                            }

                        }

                        // move his eyes right
                        else if (t < s3) {
                            l = resolution.uiScaledNumber(-10) + Easing.easeInOutExpo(t - s2, 0,
                                resolution.uiScaledNumber(20), s3 - s2);
                            r = l;
                        }

                        // move his eyes back
                        else if (t < s4) {
                            if (t > s3 + 100) { // delay;
                                l = resolution.uiScaledNumber(10) - Easing.easeInOutExpo(t - (s3 + 100), 0,
                                    resolution.uiScaledNumber(10), s4 - (s3 + 100));
                                r = l;
                            }
                        }
                        else if (t < s5) {

                        }
                        // hide omnom
                        else if (t < s6) {
                            ty = Easing.easeOutExpo(t - s5, txbegin, resolution.uiScaledNumber(300), s6 - s5);
                            sx = resolution.uiScaledNumber(scaleTo) - Easing.easeOutExpo(t - s5, 0,
                                resolution.uiScaledNumber(2.0), s1, s6 - s5);
                            sy = resolution.uiScaledNumber(scaleTo) - Easing.easeOutExpo(t - s5, 0,
                                resolution.uiScaledNumber(2.0), s1, s6 - s5);
                        }

                        if (t > s1 && t < s3) {
                            mod = Easing.easeInOutBounce(t - s1, 0, 0.1, s3 - s1, 6.0);
                        }

                        if (t > s3 && t < s5) {
                            mod = 0.1 - Easing.easeInOutBounce(t - s3, 0, 0.1, s5 - s3, 2.0);
                        }

                        // get the next frame
                        if (t < s6) {
                            window.requestAnimationFrame(step);
                        }
                        else {
                            $("#e").fadeOut();
                            RootController.resumeLevel();
                        }

                        // position in the canvas
                        var mx = tx + (resolution.uiScaledNumber(500) - (sx / scaleTo * resolution.uiScaledNumber(200)));
                        var my = ty + (resolution.uiScaledNumber(600) - (sy / scaleTo * resolution.uiScaledNumber(400)));

                        drawOmNom(ctx, sx, sy + mod, mx, my, l, r);


                    }

                    window.requestAnimationFrame(step);

                });
            };

            var drawOmNom = function (ctx, scaleX, scaleY, translateX, translateY, leftEyeOffset, rightEyeOffset) {

                // clear the canvas
                ctx.save();
                ctx.setTransform(1, 0, 0, 1, 0, 0);
                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
                ctx.restore();

                // set the scale and translate to keep OmNom in the right location on the canvas
                ctx.save();
                ctx.translate(translateX, translateY);
                ctx.scale(scaleX, scaleY);


                // omnom/dark
                ctx.save();
                ctx.beginPath();

                // omnom/dark/Path
                ctx.moveTo(116.1, 38.3);
                ctx.bezierCurveTo(117.2, 37.9, 118.2, 37.4, 119.0, 36.8);
                ctx.lineTo(119.5, 35.6);
                ctx.lineTo(123.3, 21.1);
                ctx.bezierCurveTo(124.5, 18.2, 126.8, 14.6, 130.1, 10.3);
                ctx.bezierCurveTo(129.9, 15.3, 133.6, 18.2, 141.3, 19.0);
                ctx.bezierCurveTo(138.9, 19.1, 136.7, 19.9, 134.8, 21.5);
                ctx.bezierCurveTo(132.4, 23.5, 130.7, 25.2, 129.7, 26.8);
                ctx.bezierCurveTo(128.9, 28.3, 127.9, 30.7, 126.7, 33.8);
                ctx.lineTo(126.4, 36.8);
                ctx.lineTo(126.7, 37.7);
                ctx.lineTo(128.6, 38.7);
                ctx.bezierCurveTo(124.4, 37.5, 120.2, 37.4, 116.1, 38.3);
                ctx.closePath();

                // omnom/dark/Path
                ctx.moveTo(241.8, 203.6);
                ctx.bezierCurveTo(241.6, 202.9, 241.1, 202.2, 240.5, 201.5);
                ctx.lineTo(214.2, 185.6);
                ctx.bezierCurveTo(212.8, 190.0, 210.9, 194.2, 208.4, 198.1);
                ctx.lineTo(208.3, 198.0);
                ctx.lineTo(209.4, 192.8);
                ctx.lineTo(211.0, 183.6);
                ctx.lineTo(211.2, 182.6);
                ctx.lineTo(212.8, 173.3);
                ctx.bezierCurveTo(212.3, 176.0, 211.3, 179.0, 210.0, 182.1);
                ctx.bezierCurveTo(209.9, 182.4, 209.8, 182.6, 209.7, 182.8);
                ctx.bezierCurveTo(208.6, 185.2, 207.3, 187.8, 205.8, 190.5);
                ctx.bezierCurveTo(203.4, 194.6, 200.9, 197.9, 198.1, 200.4);
                ctx.bezierCurveTo(198.7, 201.8, 199.0, 203.2, 199.2, 204.7);
                ctx.bezierCurveTo(199.2, 204.8, 199.2, 204.9, 199.2, 205.0);
                ctx.bezierCurveTo(199.5, 207.9, 199.6, 209.6, 199.7, 210.2);
                ctx.bezierCurveTo(200.0, 211.2, 200.1, 212.0, 200.2, 212.5);
                ctx.lineTo(199.6, 207.8);
                ctx.bezierCurveTo(201.8, 213.8, 203.3, 218.7, 204.0, 222.5);
                ctx.bezierCurveTo(205.3, 222.4, 206.5, 222.4, 207.7, 222.3);
                ctx.bezierCurveTo(213.4, 222.0, 218.9, 221.9, 224.3, 222.1);
                ctx.bezierCurveTo(227.5, 222.5, 230.1, 222.1, 232.3, 221.1);
                ctx.bezierCurveTo(232.8, 220.7, 233.4, 220.2, 233.9, 219.6);
                ctx.bezierCurveTo(235.2, 218.1, 236.5, 216.5, 237.8, 215.0);
                ctx.bezierCurveTo(239.1, 213.3, 240.1, 211.5, 240.9, 209.6);
                ctx.bezierCurveTo(241.8, 207.4, 242.1, 205.4, 241.8, 203.6);
                ctx.closePath();

                // omnom/dark/Path
                ctx.moveTo(148.8, 222.8);
                ctx.bezierCurveTo(139.8, 224.7, 129.5, 225.7, 117.8, 225.9);
                ctx.bezierCurveTo(109.6, 226.0, 101.7, 225.5, 94.3, 224.3);
                ctx.bezierCurveTo(94.3, 224.9, 94.4, 225.6, 94.4, 226.2);
                ctx.bezierCurveTo(94.4, 228.1, 94.3, 230.0, 94.0, 232.0);
                ctx.lineTo(93.8, 233.0);
                ctx.bezierCurveTo(103.6, 234.6, 113.4, 235.1, 123.2, 234.4);
                ctx.bezierCurveTo(132.2, 234.4, 141.0, 233.2, 149.5, 231.0);
                ctx.bezierCurveTo(149.4, 230.6, 149.4, 230.3, 149.4, 230.0);
                ctx.bezierCurveTo(149.0, 227.6, 148.9, 225.2, 148.8, 222.8);
                ctx.closePath();

                // omnom/dark/Path
                ctx.moveTo(196.6, 153.6);
                ctx.lineTo(194.5, 152.6);
                ctx.bezierCurveTo(194.1, 152.8, 193.8, 153.0, 193.4, 153.2);
                ctx.bezierCurveTo(192.6, 153.8, 191.7, 154.5, 191.0, 155.2);
                ctx.lineTo(190.2, 155.8);
                ctx.bezierCurveTo(186.6, 158.8, 183.8, 160.7, 182.0, 161.5);
                ctx.bezierCurveTo(182.0, 162.2, 181.4, 164.0, 180.2, 166.7);
                ctx.bezierCurveTo(183.2, 164.8, 186.2, 162.7, 189.3, 160.6);
                ctx.bezierCurveTo(192.6, 158.2, 195.6, 155.9, 198.4, 153.8);
                ctx.bezierCurveTo(197.6, 153.8, 197.0, 153.7, 196.6, 153.6);
                ctx.closePath();

                // omnom/dark/Path
                ctx.moveTo(121.1, 189.0);
                ctx.bezierCurveTo(128.0, 188.9, 134.8, 188.0, 141.7, 186.0);
                ctx.bezierCurveTo(141.3, 185.9, 141.0, 185.7, 140.7, 185.6);
                ctx.bezierCurveTo(136.2, 183.2, 133.0, 181.8, 131.3, 181.4);
                ctx.bezierCurveTo(128.8, 181.8, 125.7, 181.8, 121.9, 181.4);
                ctx.bezierCurveTo(118.1, 181.0, 114.0, 180.5, 109.4, 179.7);
                ctx.lineTo(109.3, 179.7);
                ctx.bezierCurveTo(108.3, 180.9, 106.5, 182.5, 103.8, 184.6);
                ctx.bezierCurveTo(103.6, 184.7, 103.4, 184.9, 103.2, 185.0);
                ctx.lineTo(102.1, 185.9);
                ctx.lineTo(102.9, 186.1);
                ctx.bezierCurveTo(110.1, 188.1, 116.2, 189.0, 121.1, 189.0);
                ctx.closePath();

                // omnom/dark/Path
                ctx.moveTo(63.1, 164.7);
                ctx.lineTo(50.7, 157.9);
                ctx.lineTo(45.8, 159.6);
                ctx.lineTo(46.2, 159.9);
                ctx.bezierCurveTo(46.3, 160.0, 46.4, 160.1, 46.5, 160.2);
                ctx.bezierCurveTo(52.0, 164.0, 57.9, 167.5, 64.4, 170.9);
                ctx.lineTo(65.5, 171.5);
                ctx.lineTo(65.2, 170.7);
                ctx.bezierCurveTo(64.0, 168.0, 63.4, 166.0, 63.1, 164.7);
                ctx.closePath();

                // omnom/dark/Path
                ctx.moveTo(36.5, 191.7);
                ctx.bezierCurveTo(35.2, 189.5, 34.0, 187.3, 33.0, 185.0);
                ctx.lineTo(33.1, 185.9);
                ctx.lineTo(34.0, 192.4);
                ctx.lineTo(31.3, 189.4);
                ctx.bezierCurveTo(30.8, 188.0, 27.8, 189.3, 22.4, 193.3);
                ctx.bezierCurveTo(16.9, 197.2, 13.7, 199.6, 12.7, 200.6);
                ctx.bezierCurveTo(11.6, 201.6, 10.2, 202.9, 8.4, 204.6);
                ctx.bezierCurveTo(6.7, 206.1, 5.4, 207.3, 4.5, 208.2);
                ctx.bezierCurveTo(3.6, 209.0, 3.2, 210.1, 3.3, 211.5);
                ctx.bezierCurveTo(3.5, 212.9, 4.0, 214.8, 4.8, 217.3);
                ctx.bezierCurveTo(5.6, 219.7, 6.5, 221.8, 7.4, 223.5);
                ctx.bezierCurveTo(8.3, 225.2, 9.8, 226.4, 11.9, 227.1);
                ctx.bezierCurveTo(13.9, 227.7, 15.9, 227.9, 17.8, 227.7);
                ctx.bezierCurveTo(19.7, 227.5, 21.3, 227.4, 22.6, 227.4);
                ctx.bezierCurveTo(24.5, 227.3, 26.4, 227.2, 28.4, 227.2);
                ctx.bezierCurveTo(30.2, 227.2, 32.0, 227.2, 33.8, 227.2);
                ctx.bezierCurveTo(35.8, 227.2, 37.7, 227.2, 39.7, 227.2);
                ctx.bezierCurveTo(41.2, 227.3, 42.9, 227.4, 44.5, 227.6);
                ctx.bezierCurveTo(44.5, 225.9, 44.5, 223.5, 44.6, 220.3);
                ctx.lineTo(44.6, 213.4);
                ctx.lineTo(44.7, 207.6);
                ctx.lineTo(45.1, 204.8);
                ctx.lineTo(45.4, 203.0);
                ctx.bezierCurveTo(45.4, 202.9, 45.4, 202.8, 45.5, 202.6);
                ctx.bezierCurveTo(43.8, 201.2, 42.3, 199.7, 40.9, 198.1);
                ctx.bezierCurveTo(39.3, 196.0, 37.9, 193.8, 36.5, 191.7);
                ctx.closePath();
                ctx.fillStyle = "rgb(100, 150, 40)";
                ctx.fill();

                // omnom/light
                ctx.beginPath();

                // omnom/light/Path
                ctx.moveTo(212.6, 151.5);
                ctx.bezierCurveTo(213.3, 158.8, 213.4, 166.1, 212.8, 173.3);
                ctx.bezierCurveTo(212.3, 176.0, 211.3, 179.0, 210.0, 182.2);
                ctx.bezierCurveTo(209.9, 182.4, 209.8, 182.6, 209.7, 182.8);
                ctx.bezierCurveTo(208.6, 185.3, 207.3, 187.8, 205.8, 190.5);
                ctx.bezierCurveTo(203.4, 194.7, 200.9, 198.0, 198.1, 200.5);
                ctx.bezierCurveTo(198.7, 201.8, 199.0, 203.3, 199.2, 204.7);
                ctx.bezierCurveTo(199.2, 204.8, 199.2, 204.9, 199.2, 205.1);
                ctx.bezierCurveTo(199.5, 207.9, 199.6, 209.7, 199.7, 210.2);
                ctx.bezierCurveTo(199.9, 214.2, 200.0, 218.2, 199.9, 222.4);
                ctx.bezierCurveTo(199.9, 222.5, 199.9, 222.7, 199.9, 222.9);
                ctx.bezierCurveTo(199.9, 225.0, 199.7, 227.0, 199.4, 228.8);
                ctx.bezierCurveTo(199.1, 230.5, 198.7, 232.0, 198.3, 233.5);
                ctx.lineTo(196.7, 235.2);
                ctx.bezierCurveTo(196.6, 235.3, 196.5, 235.4, 196.3, 235.5);
                ctx.bezierCurveTo(195.2, 236.4, 193.3, 237.6, 190.7, 239.2);
                ctx.bezierCurveTo(188.1, 240.8, 184.5, 241.7, 179.9, 242.1);
                ctx.bezierCurveTo(175.3, 242.4, 172.0, 242.5, 169.8, 242.3);
                ctx.bezierCurveTo(167.8, 242.2, 165.5, 241.7, 162.9, 240.8);
                ctx.bezierCurveTo(160.4, 240.0, 158.0, 238.0, 155.6, 234.8);
                ctx.bezierCurveTo(155.4, 234.6, 155.3, 234.4, 155.1, 234.2);
                ctx.bezierCurveTo(154.3, 233.0, 153.5, 231.6, 152.8, 230.1);
                ctx.bezierCurveTo(151.9, 227.9, 151.2, 225.3, 150.7, 222.4);
                ctx.bezierCurveTo(150.7, 222.2, 150.6, 222.1, 150.6, 221.9);
                ctx.bezierCurveTo(149.7, 216.4, 149.3, 213.1, 149.2, 212.0);
                ctx.bezierCurveTo(148.8, 215.6, 148.6, 219.2, 148.8, 222.8);
                ctx.bezierCurveTo(139.8, 224.7, 129.5, 225.7, 117.8, 225.9);
                ctx.bezierCurveTo(109.6, 226.0, 101.7, 225.5, 94.3, 224.3);
                ctx.bezierCurveTo(94.2, 220.8, 94.0, 217.1, 93.8, 213.5);
                ctx.bezierCurveTo(93.8, 214.9, 93.7, 216.0, 93.6, 216.6);
                ctx.bezierCurveTo(93.5, 217.2, 93.5, 217.7, 93.4, 218.1);
                ctx.bezierCurveTo(93.4, 218.5, 93.2, 219.4, 92.8, 220.9);
                ctx.bezierCurveTo(92.6, 222.0, 92.3, 223.0, 92.0, 224.0);
                ctx.bezierCurveTo(91.9, 224.3, 91.8, 224.6, 91.7, 224.8);
                ctx.bezierCurveTo(91.3, 226.0, 90.8, 227.2, 90.2, 228.3);
                ctx.bezierCurveTo(89.9, 229.0, 89.5, 229.7, 89.2, 230.4);
                ctx.bezierCurveTo(88.8, 230.9, 88.4, 231.4, 88.0, 231.9);
                ctx.bezierCurveTo(87.5, 232.6, 86.9, 233.2, 86.2, 233.7);
                ctx.bezierCurveTo(85.8, 234.1, 85.4, 234.5, 84.9, 234.9);
                ctx.bezierCurveTo(83.9, 235.6, 82.8, 236.2, 81.6, 236.7);
                ctx.bezierCurveTo(80.2, 237.3, 78.7, 237.8, 77.1, 238.2);
                ctx.bezierCurveTo(74.1, 238.8, 71.0, 239.1, 67.8, 239.1);
                ctx.bezierCurveTo(60.2, 239.2, 53.4, 237.3, 47.4, 233.3);
                ctx.bezierCurveTo(45.9, 232.5, 45.0, 231.7, 44.9, 230.9);
                ctx.lineTo(44.6, 229.2);
                ctx.bezierCurveTo(44.6, 228.8, 44.5, 228.3, 44.5, 227.6);
                ctx.bezierCurveTo(44.5, 226.0, 44.5, 223.5, 44.6, 220.3);
                ctx.lineTo(44.6, 213.4);
                ctx.lineTo(44.7, 207.6);
                ctx.lineTo(45.1, 204.9);
                ctx.lineTo(45.4, 203.0);
                ctx.bezierCurveTo(45.4, 202.9, 45.4, 202.8, 45.5, 202.7);
                ctx.bezierCurveTo(43.8, 201.2, 42.3, 199.7, 40.9, 198.2);
                ctx.bezierCurveTo(39.3, 196.0, 37.9, 193.9, 36.5, 191.7);
                ctx.bezierCurveTo(35.2, 189.6, 34.0, 187.4, 33.0, 185.1);
                ctx.lineTo(32.7, 183.5);
                ctx.bezierCurveTo(31.8, 176.3, 31.0, 168.9, 30.3, 161.3);
                ctx.bezierCurveTo(30.3, 161.0, 30.3, 160.7, 30.2, 160.4);
                ctx.bezierCurveTo(34.5, 162.0, 39.0, 162.1, 43.8, 160.4);
                ctx.bezierCurveTo(44.4, 160.2, 45.1, 159.9, 45.8, 159.6);
                ctx.lineTo(46.2, 159.9);
                ctx.bezierCurveTo(46.3, 160.0, 46.4, 160.1, 46.5, 160.2);
                ctx.bezierCurveTo(52.0, 164.0, 57.9, 167.6, 64.4, 170.9);
                ctx.lineTo(65.5, 171.5);
                ctx.bezierCurveTo(66.9, 174.7, 68.3, 177.8, 69.8, 180.9);
                ctx.bezierCurveTo(71.4, 184.1, 73.4, 187.0, 76.0, 189.7);
                ctx.bezierCurveTo(78.5, 192.3, 81.7, 193.4, 85.5, 193.1);
                ctx.bezierCurveTo(89.2, 192.6, 92.8, 191.5, 96.3, 189.7);
                ctx.bezierCurveTo(98.5, 188.7, 100.4, 187.4, 102.1, 185.9);
                ctx.lineTo(102.9, 186.2);
                ctx.bezierCurveTo(110.1, 188.1, 116.2, 189.1, 121.1, 189.1);
                ctx.bezierCurveTo(128.0, 189.0, 134.8, 188.0, 141.7, 186.1);
                ctx.bezierCurveTo(142.1, 186.3, 142.6, 186.5, 143.0, 186.7);
                ctx.bezierCurveTo(143.5, 186.9, 144.0, 187.1, 144.5, 187.3);
                ctx.bezierCurveTo(147.1, 188.4, 149.8, 189.4, 152.5, 190.3);
                ctx.bezierCurveTo(155.3, 191.3, 158.2, 191.8, 161.2, 191.8);
                ctx.bezierCurveTo(164.2, 191.8, 166.7, 190.7, 168.6, 188.6);
                ctx.bezierCurveTo(170.0, 187.2, 171.1, 185.7, 172.0, 184.1);
                ctx.bezierCurveTo(173.4, 181.7, 174.7, 179.2, 175.9, 176.6);
                ctx.bezierCurveTo(177.1, 174.0, 178.3, 171.4, 179.4, 168.8);
                ctx.bezierCurveTo(179.7, 168.0, 180.0, 167.4, 180.2, 166.8);
                ctx.bezierCurveTo(183.2, 164.8, 186.2, 162.8, 189.3, 160.6);
                ctx.bezierCurveTo(192.6, 158.2, 195.6, 156.0, 198.4, 153.8);
                ctx.bezierCurveTo(199.3, 153.8, 200.6, 153.8, 202.1, 153.6);
                ctx.bezierCurveTo(204.6, 153.5, 207.0, 153.1, 209.4, 152.5);
                ctx.bezierCurveTo(210.5, 152.2, 211.6, 151.8, 212.6, 151.4);
                ctx.bezierCurveTo(212.6, 151.4, 212.6, 151.4, 212.6, 151.5);
                ctx.closePath();

                // omnom/light/Path
                ctx.moveTo(124.3, 61.0);
                ctx.bezierCurveTo(124.5, 61.3, 124.8, 61.6, 125.0, 61.9);
                ctx.bezierCurveTo(133.4, 55.3, 142.8, 50.4, 153.1, 47.4);
                ctx.bezierCurveTo(149.8, 46.0, 146.3, 44.6, 142.9, 43.3);
                ctx.bezierCurveTo(139.9, 42.5, 137.6, 41.7, 135.8, 41.1);
                ctx.bezierCurveTo(134.1, 40.4, 132.7, 40.0, 131.6, 39.8);
                ctx.bezierCurveTo(130.5, 39.6, 129.5, 39.2, 128.6, 38.8);
                ctx.bezierCurveTo(124.4, 37.6, 120.2, 37.4, 116.1, 38.4);
                ctx.bezierCurveTo(115.7, 38.5, 115.2, 38.6, 114.8, 38.7);
                ctx.bezierCurveTo(114.6, 38.8, 114.2, 38.9, 113.7, 39.0);
                ctx.lineTo(106.0, 40.5);
                ctx.bezierCurveTo(102.5, 41.1, 99.2, 41.9, 95.9, 42.8);
                ctx.bezierCurveTo(106.9, 45.7, 116.3, 51.7, 124.3, 61.0);
                ctx.closePath();

                // omnom/light/Path
                ctx.moveTo(141.3, 19.0);
                ctx.lineTo(143.4, 19.0);
                ctx.lineTo(144.6, 19.1);
                ctx.bezierCurveTo(147.1, 19.4, 148.8, 19.2, 149.7, 18.6);
                ctx.bezierCurveTo(151.7, 17.6, 152.9, 16.0, 153.3, 13.7);
                ctx.bezierCurveTo(153.7, 12.1, 153.7, 10.4, 153.5, 8.6);
                ctx.bezierCurveTo(152.9, 5.3, 150.9, 3.4, 147.6, 3.1);
                ctx.bezierCurveTo(141.0, 2.4, 135.2, 4.8, 130.1, 10.3);
                ctx.bezierCurveTo(129.9, 15.3, 133.6, 18.2, 141.3, 19.0);
                ctx.closePath();

                // omnom/light/Path
                ctx.moveTo(221.0, 104.0);
                ctx.bezierCurveTo(220.8, 103.3, 220.5, 102.6, 220.2, 101.9);
                ctx.bezierCurveTo(219.3, 99.7, 218.2, 97.5, 216.9, 95.4);
                ctx.bezierCurveTo(213.3, 89.7, 209.2, 84.5, 204.4, 79.7);
                ctx.bezierCurveTo(205.4, 83.0, 205.9, 86.5, 205.9, 90.2);
                ctx.bezierCurveTo(205.8, 91.2, 205.8, 92.2, 205.7, 93.2);
                ctx.bezierCurveTo(205.5, 95.7, 205.1, 98.2, 204.6, 100.6);
                ctx.bezierCurveTo(204.9, 102.3, 205.3, 104.3, 205.7, 106.6);
                ctx.lineTo(205.7, 106.6);
                ctx.bezierCurveTo(205.2, 104.4, 204.9, 102.5, 204.6, 101.0);
                ctx.bezierCurveTo(204.2, 103.0, 203.6, 104.9, 203.0, 106.9);
                ctx.lineTo(202.9, 106.9);
                ctx.bezierCurveTo(200.6, 113.5, 197.1, 119.7, 192.3, 125.5);
                ctx.bezierCurveTo(188.4, 130.0, 183.6, 133.5, 177.8, 135.9);
                ctx.bezierCurveTo(169.7, 139.6, 161.7, 140.9, 153.7, 139.7);
                ctx.bezierCurveTo(147.7, 138.6, 142.2, 136.2, 137.2, 132.7);
                ctx.bezierCurveTo(131.7, 128.8, 126.4, 125.0, 121.3, 121.5);
                ctx.lineTo(120.9, 121.3);
                ctx.lineTo(120.6, 121.5);
                ctx.bezierCurveTo(113.9, 128.0, 106.4, 133.2, 98.0, 137.3);
                ctx.bezierCurveTo(83.7, 144.0, 70.0, 142.9, 56.8, 134.0);
                ctx.bezierCurveTo(50.6, 130.0, 45.8, 125.2, 42.5, 119.7);
                ctx.bezierCurveTo(38.6, 113.5, 36.7, 106.4, 36.6, 98.3);
                ctx.bezierCurveTo(36.6, 93.4, 37.1, 88.7, 38.2, 84.1);
                ctx.bezierCurveTo(37.7, 84.8, 37.1, 85.5, 36.6, 86.2);
                ctx.bezierCurveTo(29.8, 95.7, 24.4, 105.8, 20.3, 116.5);
                ctx.bezierCurveTo(20.4, 116.4, 20.4, 117.0, 20.3, 118.2);
                ctx.bezierCurveTo(20.2, 119.7, 20.7, 121.1, 21.6, 122.5);
                ctx.bezierCurveTo(22.2, 123.4, 23.3, 125.0, 25.0, 127.3);
                ctx.lineTo(26.2, 129.3);
                ctx.bezierCurveTo(26.3, 129.4, 26.4, 129.6, 26.5, 129.7);
                ctx.bezierCurveTo(27.9, 131.7, 29.4, 133.7, 31.1, 135.6);
                ctx.bezierCurveTo(34.0, 138.9, 37.4, 142.0, 41.1, 144.9);
                ctx.bezierCurveTo(44.1, 147.2, 47.2, 149.4, 50.5, 151.6);
                ctx.bezierCurveTo(52.5, 152.9, 54.7, 154.4, 57.0, 155.8);
                ctx.bezierCurveTo(60.8, 158.3, 65.2, 160.8, 70.1, 163.1);
                ctx.bezierCurveTo(71.0, 163.6, 72.0, 164.0, 72.9, 164.4);
                ctx.bezierCurveTo(79.8, 167.5, 86.9, 170.1, 94.1, 172.2);
                ctx.bezierCurveTo(101.5, 174.4, 109.0, 175.9, 116.7, 176.5);
                ctx.bezierCurveTo(122.5, 177.2, 128.4, 177.1, 134.5, 176.3);
                ctx.bezierCurveTo(141.6, 175.3, 147.4, 174.0, 152.0, 172.4);
                ctx.bezierCurveTo(156.6, 170.7, 159.7, 169.4, 161.4, 168.4);
                ctx.lineTo(181.4, 156.2);
                ctx.bezierCurveTo(187.7, 152.0, 192.6, 148.2, 195.9, 144.6);
                ctx.lineTo(202.9, 136.6);
                ctx.lineTo(209.8, 126.7);
                ctx.lineTo(211.7, 124.0);
                ctx.lineTo(214.9, 119.9);
                ctx.lineTo(216.7, 117.5);
                ctx.bezierCurveTo(217.8, 116.3, 218.7, 115.1, 219.4, 114.0);
                ctx.bezierCurveTo(219.8, 113.5, 220.1, 113.0, 220.3, 112.5);
                ctx.bezierCurveTo(220.8, 111.5, 221.1, 110.5, 221.3, 109.4);
                ctx.bezierCurveTo(221.5, 108.5, 221.6, 107.6, 221.6, 106.7);
                ctx.bezierCurveTo(221.6, 105.8, 221.4, 104.9, 221.0, 104.0);
                ctx.closePath();
                ctx.fillStyle = "rgb(153, 205, 0)";
                ctx.fill();

                // omnom/outline
                ctx.beginPath();

                // omnom/outline/Path
                ctx.moveTo(245.5, 203.6);
                ctx.bezierCurveTo(245.4, 202.9, 245.3, 202.4, 245.2, 202.0);
                ctx.bezierCurveTo(244.7, 200.9, 244.1, 200.1, 243.5, 199.7);
                ctx.bezierCurveTo(242.9, 199.2, 241.2, 198.1, 238.5, 196.2);
                ctx.bezierCurveTo(238.0, 195.9, 237.5, 195.5, 237.0, 195.2);
                ctx.bezierCurveTo(234.3, 193.5, 230.8, 191.6, 226.3, 189.3);
                ctx.bezierCurveTo(223.0, 187.4, 219.4, 185.7, 215.7, 184.2);
                ctx.lineTo(214.7, 183.8);
                ctx.bezierCurveTo(216.0, 179.3, 216.8, 174.4, 217.0, 169.3);
                ctx.bezierCurveTo(217.3, 162.5, 217.5, 155.7, 217.3, 148.8);
                ctx.bezierCurveTo(217.5, 148.7, 217.7, 148.6, 217.9, 148.5);
                ctx.bezierCurveTo(220.1, 147.0, 221.6, 145.1, 222.5, 142.8);
                ctx.bezierCurveTo(223.5, 140.2, 224.2, 137.4, 224.6, 134.6);
                ctx.bezierCurveTo(224.9, 132.2, 225.0, 129.8, 225.1, 127.3);
                ctx.bezierCurveTo(225.1, 124.8, 225.1, 122.4, 224.8, 119.9);
                ctx.bezierCurveTo(224.8, 119.4, 224.7, 119.0, 224.6, 118.6);
                ctx.lineTo(224.5, 115.9);
                ctx.bezierCurveTo(225.1, 114.1, 225.4, 111.8, 225.6, 108.9);
                ctx.bezierCurveTo(225.7, 107.4, 225.5, 105.8, 224.9, 104.3);
                ctx.bezierCurveTo(220.3, 91.4, 212.2, 80.3, 200.6, 71.0);
                ctx.bezierCurveTo(195.8, 62.6, 189.0, 55.8, 180.1, 50.6);
                ctx.bezierCurveTo(172.6, 46.4, 165.2, 45.0, 157.9, 46.2);
                ctx.bezierCurveTo(155.1, 45.0, 152.3, 43.9, 149.4, 42.8);
                ctx.bezierCurveTo(148.1, 42.3, 146.8, 41.9, 145.5, 41.4);
                ctx.bezierCurveTo(143.2, 40.6, 140.8, 39.9, 138.4, 39.2);
                ctx.bezierCurveTo(136.1, 38.4, 133.8, 37.6, 131.6, 36.7);
                ctx.bezierCurveTo(131.1, 36.6, 130.8, 36.3, 130.4, 36.0);
                ctx.bezierCurveTo(130.1, 35.6, 129.9, 35.3, 129.8, 35.0);
                ctx.bezierCurveTo(129.8, 34.3, 129.9, 33.8, 130.1, 33.5);
                ctx.bezierCurveTo(130.9, 31.6, 132.7, 30.0, 135.7, 29.0);
                ctx.bezierCurveTo(137.8, 27.6, 140.6, 26.5, 144.0, 25.5);
                ctx.bezierCurveTo(152.1, 23.0, 156.6, 19.2, 157.4, 13.9);
                ctx.bezierCurveTo(158.5, 7.2, 155.6, 2.8, 148.7, 0.7);
                ctx.bezierCurveTo(141.1, -1.7, 133.2, 2.0, 125.0, 11.9);
                ctx.bezierCurveTo(121.9, 15.6, 120.1, 19.0, 119.4, 22.1);
                ctx.lineTo(118.6, 25.4);
                ctx.bezierCurveTo(118.2, 30.0, 117.2, 33.0, 115.7, 34.5);
                ctx.bezierCurveTo(114.8, 35.3, 113.3, 36.0, 111.2, 36.6);
                ctx.bezierCurveTo(104.1, 37.5, 97.2, 39.2, 90.6, 41.6);
                ctx.bezierCurveTo(89.4, 41.4, 88.3, 41.3, 87.1, 41.1);
                ctx.bezierCurveTo(71.9, 41.6, 59.7, 47.8, 50.4, 59.6);
                ctx.bezierCurveTo(46.0, 64.9, 42.7, 70.6, 40.4, 76.7);
                ctx.bezierCurveTo(39.8, 77.4, 39.2, 78.1, 38.5, 78.8);
                ctx.bezierCurveTo(29.4, 89.5, 22.3, 101.0, 17.2, 113.3);
                ctx.bezierCurveTo(16.8, 114.3, 16.4, 115.3, 16.1, 116.3);
                ctx.bezierCurveTo(15.8, 117.0, 15.6, 117.6, 15.5, 118.3);
                ctx.bezierCurveTo(15.4, 118.7, 15.3, 119.1, 15.3, 119.5);
                ctx.bezierCurveTo(15.2, 120.4, 15.1, 121.2, 15.2, 122.1);
                ctx.bezierCurveTo(15.5, 124.5, 16.4, 127.1, 18.0, 129.6);
                ctx.bezierCurveTo(18.5, 132.0, 18.6, 134.2, 18.3, 136.0);
                ctx.bezierCurveTo(17.4, 144.5, 19.9, 151.6, 25.9, 157.5);
                ctx.bezierCurveTo(25.9, 159.4, 26.0, 161.4, 26.1, 163.4);
                ctx.bezierCurveTo(26.4, 169.1, 27.2, 175.1, 28.4, 181.4);
                ctx.bezierCurveTo(28.7, 182.6, 28.9, 183.7, 29.2, 184.9);
                ctx.bezierCurveTo(29.1, 184.9, 29.0, 185.0, 28.9, 185.0);
                ctx.bezierCurveTo(25.6, 186.9, 22.7, 188.6, 20.2, 190.1);
                ctx.bezierCurveTo(17.8, 191.7, 15.6, 193.1, 13.8, 194.4);
                ctx.bezierCurveTo(12.5, 195.5, 11.1, 196.7, 9.6, 197.9);
                ctx.bezierCurveTo(7.9, 199.0, 6.3, 200.4, 4.9, 201.9);
                ctx.bezierCurveTo(3.7, 203.0, 2.7, 204.1, 1.7, 205.0);
                ctx.bezierCurveTo(0.7, 206.0, 0.2, 207.4, 0.0, 209.2);
                ctx.bezierCurveTo(-0.1, 211.0, 0.0, 212.5, 0.2, 213.8);
                ctx.bezierCurveTo(0.4, 215.0, 0.9, 217.0, 1.5, 219.7);
                ctx.bezierCurveTo(2.1, 222.4, 3.0, 224.6, 4.0, 226.5);
                ctx.bezierCurveTo(5.1, 228.3, 6.7, 229.7, 9.0, 230.6);
                ctx.bezierCurveTo(9.6, 230.8, 10.3, 231.0, 11.1, 231.2);
                ctx.bezierCurveTo(14.9, 231.1, 18.6, 231.1, 22.4, 231.2);
                ctx.bezierCurveTo(24.2, 231.3, 26.0, 231.5, 27.8, 231.7);
                ctx.bezierCurveTo(29.4, 231.8, 31.0, 232.0, 32.7, 232.2);
                ctx.bezierCurveTo(34.1, 232.3, 35.6, 232.5, 37.1, 232.6);
                ctx.bezierCurveTo(38.4, 232.7, 39.8, 232.8, 41.2, 232.9);
                ctx.bezierCurveTo(41.5, 232.9, 41.8, 233.0, 42.1, 233.0);
                ctx.lineTo(42.2, 233.2);
                ctx.bezierCurveTo(42.4, 233.8, 42.8, 234.4, 43.1, 234.8);
                ctx.bezierCurveTo(48.8, 238.6, 55.0, 240.9, 61.6, 241.5);
                ctx.bezierCurveTo(68.7, 242.6, 75.6, 242.3, 82.4, 240.5);
                ctx.bezierCurveTo(82.5, 240.5, 82.5, 240.5, 82.6, 240.5);
                ctx.bezierCurveTo(84.0, 240.2, 85.4, 239.9, 86.7, 239.6);
                ctx.bezierCurveTo(88.1, 239.3, 89.3, 238.9, 90.4, 238.4);
                ctx.bezierCurveTo(90.9, 238.1, 91.4, 237.8, 91.8, 237.3);
                ctx.bezierCurveTo(104.5, 241.2, 118.7, 242.0, 134.5, 239.7);
                ctx.bezierCurveTo(139.8, 238.9, 145.2, 237.5, 150.9, 235.6);
                ctx.bezierCurveTo(151.5, 237.0, 152.2, 238.2, 153.1, 239.5);
                ctx.bezierCurveTo(153.8, 240.1, 154.6, 240.7, 155.6, 241.3);
                ctx.bezierCurveTo(156.9, 242.2, 158.4, 242.9, 160.2, 243.5);
                ctx.bezierCurveTo(161.9, 243.8, 163.7, 244.1, 165.5, 244.5);
                ctx.bezierCurveTo(171.9, 244.8, 178.0, 244.7, 183.5, 244.1);
                ctx.bezierCurveTo(188.0, 243.7, 192.3, 242.6, 196.2, 240.7);
                ctx.bezierCurveTo(197.0, 240.3, 197.6, 239.9, 198.3, 239.5);
                ctx.bezierCurveTo(200.0, 238.4, 201.4, 237.4, 202.3, 236.5);
                ctx.bezierCurveTo(203.2, 235.6, 203.9, 233.1, 204.5, 229.2);
                ctx.bezierCurveTo(204.5, 229.0, 204.6, 228.7, 204.6, 228.3);
                ctx.lineTo(223.4, 226.8);
                ctx.bezierCurveTo(225.5, 226.6, 227.6, 226.4, 229.8, 226.3);
                ctx.bezierCurveTo(232.0, 226.2, 233.8, 225.5, 235.2, 224.1);
                ctx.bezierCurveTo(237.0, 222.5, 238.5, 220.8, 239.4, 219.1);
                ctx.bezierCurveTo(240.4, 217.5, 241.3, 215.9, 242.2, 214.4);
                ctx.bezierCurveTo(243.0, 212.9, 243.6, 211.6, 244.1, 210.5);
                ctx.bezierCurveTo(244.6, 209.4, 244.9, 208.1, 245.2, 206.7);
                ctx.bezierCurveTo(245.4, 205.2, 245.5, 204.2, 245.5, 203.6);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(219.9, 140.2);
                ctx.bezierCurveTo(219.5, 141.6, 218.8, 143.0, 218.0, 144.5);
                ctx.bezierCurveTo(217.8, 144.9, 217.6, 145.2, 217.3, 145.6);
                ctx.bezierCurveTo(216.7, 146.3, 215.9, 146.9, 214.9, 147.4);
                ctx.bezierCurveTo(214.1, 147.8, 213.2, 148.2, 212.3, 148.4);
                ctx.bezierCurveTo(210.2, 149.1, 208.0, 149.5, 205.6, 149.5);
                ctx.bezierCurveTo(204.5, 149.5, 203.4, 149.4, 202.3, 149.3);
                ctx.bezierCurveTo(200.6, 148.9, 199.7, 148.0, 199.8, 146.7);
                ctx.lineTo(200.7, 145.2);
                ctx.bezierCurveTo(201.3, 144.3, 201.8, 143.4, 202.4, 142.5);
                ctx.bezierCurveTo(202.7, 141.9, 203.0, 141.3, 203.4, 140.8);
                ctx.bezierCurveTo(203.8, 140.0, 204.2, 139.2, 204.7, 138.5);
                ctx.bezierCurveTo(205.3, 137.7, 205.9, 136.8, 206.5, 136.0);
                ctx.bezierCurveTo(206.9, 135.5, 207.3, 135.0, 207.6, 134.5);
                ctx.bezierCurveTo(208.3, 133.7, 209.0, 132.8, 209.7, 132.0);
                ctx.bezierCurveTo(209.9, 131.7, 210.2, 131.4, 210.5, 131.0);
                ctx.bezierCurveTo(211.0, 130.4, 211.5, 129.9, 212.1, 129.3);
                ctx.bezierCurveTo(212.6, 128.8, 213.1, 128.3, 213.6, 127.8);
                ctx.bezierCurveTo(214.1, 127.4, 214.6, 127.0, 215.1, 126.6);
                ctx.bezierCurveTo(215.3, 126.4, 215.5, 126.3, 215.6, 126.2);
                ctx.bezierCurveTo(216.8, 125.4, 217.9, 124.8, 219.0, 124.5);
                ctx.lineTo(219.3, 125.4);
                ctx.bezierCurveTo(220.0, 127.9, 220.5, 130.6, 220.8, 133.3);
                ctx.bezierCurveTo(221.0, 135.7, 220.8, 138.0, 219.9, 140.2);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(106.0, 40.4);
                ctx.lineTo(113.7, 38.9);
                ctx.bezierCurveTo(114.2, 38.8, 114.6, 38.7, 114.8, 38.6);
                ctx.bezierCurveTo(115.2, 38.6, 115.7, 38.4, 116.1, 38.3);
                ctx.bezierCurveTo(117.2, 37.9, 118.2, 37.4, 119.0, 36.8);
                ctx.lineTo(119.5, 35.6);
                ctx.lineTo(123.3, 21.1);
                ctx.bezierCurveTo(124.5, 18.2, 126.8, 14.6, 130.1, 10.3);
                ctx.bezierCurveTo(135.2, 4.8, 141.0, 2.4, 147.6, 3.1);
                ctx.bezierCurveTo(150.9, 3.4, 152.9, 5.2, 153.5, 8.5);
                ctx.bezierCurveTo(153.7, 10.3, 153.7, 12.0, 153.3, 13.7);
                ctx.bezierCurveTo(152.9, 15.9, 151.7, 17.6, 149.7, 18.6);
                ctx.bezierCurveTo(148.8, 19.2, 147.1, 19.4, 144.6, 19.1);
                ctx.lineTo(143.4, 19.0);
                ctx.lineTo(141.3, 19.0);
                ctx.bezierCurveTo(138.9, 19.1, 136.7, 19.9, 134.8, 21.5);
                ctx.bezierCurveTo(132.4, 23.5, 130.7, 25.2, 129.7, 26.8);
                ctx.bezierCurveTo(128.9, 28.3, 127.9, 30.7, 126.7, 33.8);
                ctx.lineTo(126.4, 36.8);
                ctx.lineTo(126.7, 37.7);
                ctx.lineTo(128.6, 38.7);
                ctx.bezierCurveTo(129.5, 39.2, 130.5, 39.5, 131.6, 39.8);
                ctx.bezierCurveTo(132.7, 40.0, 134.1, 40.4, 135.8, 41.0);
                ctx.bezierCurveTo(137.6, 41.7, 139.9, 42.4, 142.9, 43.3);
                ctx.bezierCurveTo(146.3, 44.6, 149.8, 46.0, 153.1, 47.4);
                ctx.bezierCurveTo(142.8, 50.4, 133.4, 55.2, 125.0, 61.8);
                ctx.bezierCurveTo(124.8, 61.6, 124.5, 61.3, 124.3, 61.0);
                ctx.bezierCurveTo(116.3, 51.7, 106.9, 45.6, 95.9, 42.7);
                ctx.bezierCurveTo(99.2, 41.9, 102.5, 41.1, 106.0, 40.4);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(41.7, 90.1);
                ctx.bezierCurveTo(42.4, 84.8, 43.6, 79.9, 45.5, 75.3);
                ctx.bezierCurveTo(46.5, 72.7, 47.7, 70.1, 49.2, 67.7);
                ctx.bezierCurveTo(52.7, 61.9, 57.3, 56.8, 63.0, 52.3);
                ctx.bezierCurveTo(70.4, 46.7, 77.8, 43.8, 85.3, 43.7);
                ctx.bezierCurveTo(87.3, 43.6, 89.3, 43.8, 91.3, 44.1);
                ctx.bezierCurveTo(95.6, 44.9, 100.0, 46.5, 104.4, 49.1);
                ctx.bezierCurveTo(111.9, 53.4, 117.3, 59.5, 120.8, 67.4);
                ctx.lineTo(124.2, 73.8);
                ctx.lineTo(124.6, 73.8);
                ctx.bezierCurveTo(124.7, 73.5, 124.9, 73.2, 125.0, 72.9);
                ctx.bezierCurveTo(126.3, 71.1, 127.5, 69.4, 128.6, 67.7);
                ctx.bezierCurveTo(136.0, 57.6, 146.1, 51.7, 158.7, 50.1);
                ctx.bezierCurveTo(158.9, 50.1, 159.1, 50.1, 159.2, 50.1);
                ctx.bezierCurveTo(162.5, 50.1, 165.7, 50.5, 168.8, 51.2);
                ctx.bezierCurveTo(176.5, 53.0, 183.4, 56.7, 189.4, 62.4);
                ctx.bezierCurveTo(190.0, 63.0, 190.6, 63.5, 191.1, 64.1);
                ctx.bezierCurveTo(193.9, 67.0, 196.1, 70.1, 197.8, 73.5);
                ctx.bezierCurveTo(201.1, 79.7, 202.5, 86.8, 202.2, 94.7);
                ctx.bezierCurveTo(202.0, 99.0, 201.3, 103.2, 199.9, 107.1);
                ctx.bezierCurveTo(197.5, 114.2, 193.1, 120.7, 186.7, 126.4);
                ctx.bezierCurveTo(177.6, 134.4, 168.3, 137.9, 158.5, 137.0);
                ctx.bezierCurveTo(153.1, 136.4, 149.4, 135.6, 147.3, 134.6);
                ctx.bezierCurveTo(137.6, 129.8, 130.4, 122.6, 125.6, 113.0);
                ctx.bezierCurveTo(124.9, 111.7, 124.3, 110.4, 123.8, 109.0);
                ctx.bezierCurveTo(123.3, 107.9, 122.9, 106.7, 122.6, 105.5);
                ctx.bezierCurveTo(121.9, 106.9, 121.2, 108.3, 120.5, 109.6);
                ctx.bezierCurveTo(119.7, 111.0, 118.8, 112.4, 118.0, 113.7);
                ctx.bezierCurveTo(110.3, 125.2, 100.6, 132.5, 88.9, 135.6);
                ctx.bezierCurveTo(75.9, 139.1, 64.3, 136.3, 54.2, 127.3);
                ctx.bezierCurveTo(51.5, 124.9, 49.2, 122.2, 47.4, 119.3);
                ctx.bezierCurveTo(42.2, 111.4, 40.4, 101.7, 41.7, 90.1);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(20.3, 118.2);
                ctx.bezierCurveTo(20.4, 117.0, 20.4, 116.4, 20.3, 116.5);
                ctx.bezierCurveTo(24.4, 105.8, 29.8, 95.7, 36.6, 86.2);
                ctx.bezierCurveTo(37.1, 85.5, 37.7, 84.8, 38.2, 84.1);
                ctx.bezierCurveTo(37.1, 88.6, 36.6, 93.4, 36.6, 98.3);
                ctx.bezierCurveTo(36.7, 106.3, 38.6, 113.5, 42.5, 119.7);
                ctx.bezierCurveTo(45.8, 125.2, 50.6, 129.9, 56.8, 134.0);
                ctx.bezierCurveTo(70.0, 142.9, 83.7, 143.9, 98.0, 137.2);
                ctx.bezierCurveTo(106.4, 133.2, 113.9, 127.9, 120.6, 121.5);
                ctx.lineTo(120.9, 121.3);
                ctx.lineTo(121.3, 121.5);
                ctx.bezierCurveTo(126.4, 125.0, 131.7, 128.7, 137.2, 132.7);
                ctx.bezierCurveTo(142.2, 136.2, 147.7, 138.6, 153.7, 139.7);
                ctx.bezierCurveTo(161.7, 140.8, 169.7, 139.6, 177.8, 135.9);
                ctx.bezierCurveTo(183.6, 133.5, 188.4, 130.0, 192.3, 125.5);
                ctx.bezierCurveTo(197.1, 119.7, 200.6, 113.5, 202.9, 106.8);
                ctx.bezierCurveTo(203.6, 104.8, 204.2, 102.7, 204.6, 100.6);
                ctx.bezierCurveTo(205.1, 98.2, 205.5, 95.7, 205.7, 93.2);
                ctx.bezierCurveTo(205.8, 92.2, 205.8, 91.1, 205.9, 90.2);
                ctx.bezierCurveTo(205.9, 86.5, 205.4, 83.0, 204.4, 79.6);
                ctx.bezierCurveTo(209.2, 84.5, 213.3, 89.7, 216.9, 95.4);
                ctx.bezierCurveTo(218.2, 97.5, 219.3, 99.6, 220.2, 101.8);
                ctx.bezierCurveTo(220.5, 102.5, 220.8, 103.3, 221.0, 104.0);
                ctx.bezierCurveTo(221.4, 104.9, 221.6, 105.7, 221.6, 106.6);
                ctx.bezierCurveTo(221.6, 107.6, 221.5, 108.5, 221.3, 109.4);
                ctx.bezierCurveTo(221.1, 110.4, 220.8, 111.5, 220.3, 112.5);
                ctx.bezierCurveTo(220.1, 113.0, 219.8, 113.5, 219.4, 114.0);
                ctx.bezierCurveTo(218.7, 115.1, 217.8, 116.3, 216.7, 117.4);
                ctx.lineTo(214.9, 119.9);
                ctx.lineTo(211.7, 124.0);
                ctx.lineTo(209.8, 126.7);
                ctx.lineTo(202.9, 136.6);
                ctx.lineTo(195.9, 144.6);
                ctx.bezierCurveTo(192.6, 148.2, 187.7, 152.0, 181.4, 156.2);
                ctx.lineTo(161.4, 168.4);
                ctx.bezierCurveTo(159.7, 169.4, 156.6, 170.7, 152.0, 172.3);
                ctx.bezierCurveTo(147.4, 173.9, 141.6, 175.3, 134.5, 176.2);
                ctx.bezierCurveTo(128.4, 177.1, 122.5, 177.2, 116.7, 176.5);
                ctx.bezierCurveTo(109.0, 175.8, 101.5, 174.4, 94.1, 172.2);
                ctx.bezierCurveTo(86.9, 170.0, 79.8, 167.4, 72.9, 164.4);
                ctx.bezierCurveTo(72.0, 164.0, 71.0, 163.5, 70.1, 163.1);
                ctx.bezierCurveTo(65.2, 160.7, 60.8, 158.3, 57.0, 155.8);
                ctx.bezierCurveTo(54.7, 154.3, 52.5, 152.9, 50.5, 151.5);
                ctx.bezierCurveTo(47.2, 149.4, 44.1, 147.1, 41.1, 144.8);
                ctx.bezierCurveTo(37.4, 142.0, 34.0, 138.9, 31.1, 135.5);
                ctx.bezierCurveTo(29.4, 133.7, 27.9, 131.7, 26.5, 129.7);
                ctx.bezierCurveTo(26.4, 129.5, 26.3, 129.4, 26.2, 129.3);
                ctx.lineTo(25.0, 127.3);
                ctx.bezierCurveTo(23.3, 125.0, 22.2, 123.4, 21.6, 122.5);
                ctx.bezierCurveTo(20.7, 121.1, 20.2, 119.7, 20.3, 118.2);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(177.9, 163.2);
                ctx.lineTo(174.9, 169.8);
                ctx.lineTo(174.7, 170.3);
                ctx.lineTo(174.4, 170.9);
                ctx.bezierCurveTo(174.0, 172.1, 173.5, 173.3, 173.1, 174.5);
                ctx.bezierCurveTo(172.2, 177.2, 171.1, 179.7, 169.6, 182.2);
                ctx.bezierCurveTo(168.3, 184.5, 166.3, 186.1, 163.6, 187.2);
                ctx.bezierCurveTo(160.6, 188.4, 157.5, 188.5, 154.4, 187.3);
                ctx.bezierCurveTo(151.9, 186.4, 149.5, 185.5, 147.2, 184.7);
                ctx.lineTo(146.7, 184.5);
                ctx.lineTo(146.3, 184.3);
                ctx.lineTo(140.9, 182.4);
                ctx.bezierCurveTo(142.8, 182.1, 145.0, 181.4, 147.5, 180.4);
                ctx.bezierCurveTo(148.2, 180.0, 148.9, 179.7, 149.6, 179.4);
                ctx.bezierCurveTo(152.5, 178.0, 155.3, 176.5, 158.1, 174.8);
                ctx.bezierCurveTo(161.2, 172.9, 164.4, 171.1, 167.5, 169.3);
                ctx.bezierCurveTo(170.7, 167.7, 173.7, 165.9, 176.8, 163.9);
                ctx.bezierCurveTo(177.2, 163.7, 177.6, 163.5, 177.9, 163.2);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(101.1, 181.4);
                ctx.lineTo(97.1, 184.2);
                ctx.lineTo(96.8, 184.3);
                ctx.lineTo(96.3, 184.7);
                ctx.bezierCurveTo(93.2, 186.5, 90.2, 187.9, 87.1, 188.9);
                ctx.bezierCurveTo(83.9, 189.9, 81.1, 189.2, 78.6, 186.6);
                ctx.bezierCurveTo(76.0, 184.1, 74.0, 181.3, 72.5, 178.3);
                ctx.bezierCurveTo(71.3, 175.7, 70.1, 172.9, 69.0, 170.1);
                ctx.lineTo(68.0, 167.8);
                ctx.bezierCurveTo(67.8, 167.3, 67.7, 166.8, 67.5, 166.3);
                ctx.bezierCurveTo(69.1, 167.1, 70.7, 167.9, 72.4, 168.8);
                ctx.bezierCurveTo(74.1, 169.6, 75.9, 170.5, 77.9, 171.4);
                ctx.bezierCurveTo(79.9, 172.3, 83.2, 173.8, 87.9, 175.9);
                ctx.bezierCurveTo(92.5, 177.9, 95.7, 179.3, 97.5, 180.0);
                ctx.bezierCurveTo(99.2, 180.7, 100.4, 181.2, 101.1, 181.4);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(25.8, 151.1);
                ctx.bezierCurveTo(23.2, 147.7, 22.0, 143.6, 22.3, 139.0);
                ctx.bezierCurveTo(22.3, 139.0, 22.3, 139.0, 22.3, 139.0);
                ctx.lineTo(22.4, 135.1);
                ctx.bezierCurveTo(22.5, 134.4, 22.9, 134.4, 23.6, 134.9);
                ctx.bezierCurveTo(24.4, 135.4, 25.2, 136.0, 26.0, 136.6);
                ctx.bezierCurveTo(27.6, 137.8, 29.1, 139.0, 30.7, 140.2);
                ctx.bezierCurveTo(34.1, 142.9, 37.3, 145.8, 40.4, 148.8);
                ctx.bezierCurveTo(42.4, 150.6, 44.1, 152.6, 45.6, 154.7);
                ctx.bezierCurveTo(45.8, 155.3, 45.9, 155.7, 45.7, 156.0);
                ctx.lineTo(45.5, 156.1);
                ctx.bezierCurveTo(40.5, 158.4, 35.6, 158.3, 30.9, 155.9);
                ctx.bezierCurveTo(30.6, 155.7, 30.3, 155.6, 30.0, 155.4);
                ctx.bezierCurveTo(30.0, 155.3, 29.9, 155.3, 29.9, 155.3);
                ctx.bezierCurveTo(28.3, 154.0, 26.9, 152.6, 25.8, 151.1);
                ctx.closePath();

                // omnom/outline/Path
                ctx.moveTo(240.9, 209.6);
                ctx.bezierCurveTo(240.1, 211.5, 239.1, 213.3, 237.8, 215.0);
                ctx.bezierCurveTo(236.5, 216.5, 235.2, 218.1, 233.9, 219.6);
                ctx.bezierCurveTo(233.4, 220.2, 232.8, 220.7, 232.3, 221.1);
                ctx.bezierCurveTo(230.1, 222.1, 227.5, 222.5, 224.3, 222.1);
                ctx.bezierCurveTo(218.9, 221.9, 213.4, 222.0, 207.7, 222.3);
                ctx.bezierCurveTo(206.5, 222.4, 205.3, 222.4, 204.0, 222.5);
                ctx.bezierCurveTo(203.3, 218.7, 201.8, 213.8, 199.6, 207.8);
                ctx.lineTo(200.2, 212.5);
                ctx.bezierCurveTo(200.1, 212.0, 200.0, 211.2, 199.7, 210.2);
                ctx.bezierCurveTo(199.9, 214.1, 200.0, 218.2, 199.9, 222.3);
                ctx.bezierCurveTo(199.9, 222.5, 199.9, 222.7, 199.9, 222.9);
                ctx.bezierCurveTo(199.9, 225.0, 199.7, 226.9, 199.4, 228.8);
                ctx.bezierCurveTo(199.1, 230.4, 198.7, 232.0, 198.3, 233.5);
                ctx.lineTo(196.7, 235.1);
                ctx.bezierCurveTo(196.6, 235.2, 196.5, 235.4, 196.3, 235.5);
                ctx.bezierCurveTo(195.2, 236.4, 193.3, 237.6, 190.7, 239.1);
                ctx.bezierCurveTo(188.1, 240.7, 184.5, 241.7, 179.9, 242.1);
                ctx.bezierCurveTo(175.3, 242.4, 172.0, 242.5, 169.8, 242.3);
                ctx.bezierCurveTo(167.8, 242.1, 165.5, 241.6, 162.9, 240.8);
                ctx.bezierCurveTo(160.4, 239.9, 158.0, 237.9, 155.6, 234.8);
                ctx.bezierCurveTo(155.4, 234.6, 155.3, 234.4, 155.1, 234.2);
                ctx.bezierCurveTo(154.3, 233.0, 153.5, 231.6, 152.8, 230.0);
                ctx.bezierCurveTo(151.9, 227.8, 151.2, 225.3, 150.7, 222.4);
                ctx.bezierCurveTo(150.7, 222.2, 150.6, 222.1, 150.6, 221.9);
                ctx.bezierCurveTo(149.7, 216.4, 149.3, 213.1, 149.2, 212.0);
                ctx.bezierCurveTo(148.8, 215.6, 148.6, 219.2, 148.8, 222.8);
                ctx.bezierCurveTo(148.9, 225.2, 149.0, 227.6, 149.4, 230.0);
                ctx.bezierCurveTo(149.4, 230.3, 149.4, 230.6, 149.5, 231.0);
                ctx.bezierCurveTo(141.0, 233.2, 132.2, 234.4, 123.2, 234.4);
                ctx.bezierCurveTo(113.4, 235.1, 103.6, 234.6, 93.8, 233.0);
                ctx.lineTo(94.0, 232.0);
                ctx.bezierCurveTo(94.3, 230.0, 94.4, 228.1, 94.4, 226.2);
                ctx.bezierCurveTo(94.4, 225.6, 94.3, 224.9, 94.3, 224.3);
                ctx.bezierCurveTo(94.2, 220.7, 94.0, 217.1, 93.8, 213.5);
                ctx.bezierCurveTo(93.8, 214.9, 93.7, 215.9, 93.6, 216.6);
                ctx.bezierCurveTo(93.5, 217.2, 93.5, 217.7, 93.4, 218.1);
                ctx.bezierCurveTo(93.4, 218.5, 93.2, 219.4, 92.8, 220.9);
                ctx.bezierCurveTo(92.6, 222.0, 92.3, 223.0, 92.0, 223.9);
                ctx.bezierCurveTo(91.9, 224.2, 91.8, 224.5, 91.7, 224.8);
                ctx.bezierCurveTo(91.3, 226.0, 90.8, 227.1, 90.2, 228.3);
                ctx.bezierCurveTo(89.9, 229.0, 89.5, 229.7, 89.2, 230.4);
                ctx.bezierCurveTo(88.8, 230.9, 88.4, 231.4, 88.0, 231.9);
                ctx.bezierCurveTo(87.5, 232.5, 86.9, 233.1, 86.2, 233.7);
                ctx.bezierCurveTo(85.8, 234.1, 85.4, 234.5, 84.9, 234.8);
                ctx.bezierCurveTo(83.9, 235.6, 82.8, 236.2, 81.6, 236.7);
                ctx.bezierCurveTo(80.2, 237.3, 78.7, 237.7, 77.1, 238.1);
                ctx.bezierCurveTo(74.1, 238.7, 71.0, 239.0, 67.8, 239.0);
                ctx.bezierCurveTo(60.3, 239.1, 53.6, 237.3, 47.7, 233.5);
                ctx.bezierCurveTo(47.6, 233.4, 47.5, 233.4, 47.4, 233.3);
                ctx.bezierCurveTo(45.9, 232.5, 45.0, 231.7, 44.9, 230.9);
                ctx.lineTo(44.6, 229.2);
                ctx.bezierCurveTo(44.6, 228.8, 44.5, 228.2, 44.5, 227.6);
                ctx.bezierCurveTo(44.5, 225.9, 44.5, 223.5, 44.6, 220.3);
                ctx.lineTo(44.6, 213.4);
                ctx.lineTo(44.7, 207.6);
                ctx.lineTo(43.7, 213.0);
                ctx.lineTo(42.4, 220.3);
                ctx.bezierCurveTo(41.9, 223.1, 41.7, 225.4, 41.6, 227.4);
                ctx.bezierCurveTo(41.0, 227.3, 40.3, 227.3, 39.7, 227.2);
                ctx.bezierCurveTo(37.7, 227.2, 35.8, 227.2, 33.8, 227.2);
                ctx.bezierCurveTo(32.0, 227.2, 30.2, 227.2, 28.4, 227.2);
                ctx.bezierCurveTo(26.4, 227.2, 24.5, 227.3, 22.6, 227.4);
                ctx.bezierCurveTo(21.3, 227.4, 19.7, 227.5, 17.8, 227.7);
                ctx.bezierCurveTo(15.9, 227.9, 13.9, 227.7, 11.9, 227.1);
                ctx.bezierCurveTo(9.8, 226.4, 8.3, 225.2, 7.4, 223.5);
                ctx.bezierCurveTo(6.5, 221.8, 5.6, 219.7, 4.8, 217.3);
                ctx.bezierCurveTo(4.0, 214.8, 3.5, 212.9, 3.3, 211.5);
                ctx.bezierCurveTo(3.2, 210.1, 3.6, 209.0, 4.5, 208.2);
                ctx.bezierCurveTo(5.4, 207.3, 6.7, 206.1, 8.4, 204.6);
                ctx.bezierCurveTo(10.2, 202.9, 11.6, 201.6, 12.7, 200.6);
                ctx.bezierCurveTo(13.7, 199.6, 16.9, 197.2, 22.4, 193.3);
                ctx.bezierCurveTo(26.1, 190.5, 28.7, 189.1, 30.1, 188.9);
                ctx.bezierCurveTo(31.2, 193.0, 32.3, 197.0, 33.7, 201.0);
                ctx.bezierCurveTo(34.2, 202.8, 35.0, 204.4, 36.0, 205.9);
                ctx.lineTo(33.3, 187.7);
                ctx.lineTo(33.1, 185.9);
                ctx.lineTo(32.9, 184.7);
                ctx.lineTo(32.7, 183.4);
                ctx.bezierCurveTo(31.8, 176.3, 31.0, 168.9, 30.3, 161.3);
                ctx.bezierCurveTo(30.3, 161.0, 30.3, 160.7, 30.2, 160.3);
                ctx.bezierCurveTo(34.5, 162.0, 39.0, 162.0, 43.8, 160.4);
                ctx.bezierCurveTo(44.4, 160.1, 45.1, 159.9, 45.8, 159.6);
                ctx.lineTo(50.7, 157.9);
                ctx.lineTo(63.1, 164.7);
                ctx.bezierCurveTo(63.4, 166.0, 64.0, 168.0, 65.2, 170.7);
                ctx.lineTo(65.5, 171.5);
                ctx.bezierCurveTo(66.9, 174.7, 68.3, 177.8, 69.8, 180.9);
                ctx.bezierCurveTo(71.4, 184.1, 73.4, 187.0, 76.0, 189.6);
                ctx.bezierCurveTo(78.5, 192.2, 81.7, 193.4, 85.5, 193.1);
                ctx.bezierCurveTo(89.2, 192.6, 92.8, 191.5, 96.3, 189.7);
                ctx.bezierCurveTo(98.5, 188.6, 100.4, 187.4, 102.1, 185.9);
                ctx.lineTo(103.2, 185.0);
                ctx.bezierCurveTo(103.4, 184.9, 103.6, 184.7, 103.8, 184.6);
                ctx.bezierCurveTo(106.5, 182.5, 108.3, 180.9, 109.3, 179.7);
                ctx.lineTo(109.4, 179.7);
                ctx.bezierCurveTo(114.0, 180.5, 118.1, 181.0, 121.9, 181.4);
                ctx.bezierCurveTo(125.7, 181.8, 128.8, 181.8, 131.3, 181.4);
                ctx.bezierCurveTo(133.0, 181.8, 136.2, 183.2, 140.7, 185.6);
                ctx.bezierCurveTo(141.0, 185.7, 141.3, 185.9, 141.7, 186.0);
                ctx.bezierCurveTo(142.1, 186.3, 142.6, 186.5, 143.0, 186.7);
                ctx.bezierCurveTo(143.5, 186.9, 144.0, 187.1, 144.5, 187.3);
                ctx.bezierCurveTo(147.1, 188.4, 149.8, 189.4, 152.5, 190.3);
                ctx.bezierCurveTo(155.3, 191.3, 158.2, 191.7, 161.2, 191.7);
                ctx.bezierCurveTo(164.2, 191.7, 166.7, 190.7, 168.6, 188.6);
                ctx.bezierCurveTo(170.0, 187.2, 171.1, 185.6, 172.0, 184.1);
                ctx.bezierCurveTo(173.4, 181.6, 174.7, 179.1, 175.9, 176.6);
                ctx.bezierCurveTo(177.1, 174.0, 178.3, 171.4, 179.4, 168.7);
                ctx.bezierCurveTo(179.7, 168.0, 180.0, 167.3, 180.2, 166.7);
                ctx.bezierCurveTo(181.4, 164.0, 182.0, 162.2, 182.0, 161.5);
                ctx.bezierCurveTo(183.8, 160.7, 186.6, 158.8, 190.2, 155.8);
                ctx.lineTo(191.0, 155.2);
                ctx.bezierCurveTo(191.7, 154.5, 192.6, 153.8, 193.4, 153.2);
                ctx.bezierCurveTo(193.8, 153.0, 194.1, 152.8, 194.5, 152.6);
                ctx.lineTo(196.6, 153.6);
                ctx.bezierCurveTo(197.0, 153.7, 197.6, 153.8, 198.4, 153.8);
                ctx.bezierCurveTo(199.3, 153.8, 200.6, 153.7, 202.1, 153.6);
                ctx.bezierCurveTo(204.6, 153.5, 207.0, 153.1, 209.4, 152.5);
                ctx.bezierCurveTo(210.5, 152.1, 211.6, 151.8, 212.6, 151.3);
                ctx.bezierCurveTo(212.6, 151.4, 212.6, 151.4, 212.6, 151.4);
                ctx.bezierCurveTo(213.3, 158.8, 213.4, 166.1, 212.8, 173.3);
                ctx.lineTo(211.2, 182.6);
                ctx.lineTo(211.0, 183.6);
                ctx.lineTo(209.4, 192.8);
                ctx.lineTo(208.3, 198.0);
                ctx.lineTo(208.4, 198.1);
                ctx.bezierCurveTo(210.9, 194.2, 212.8, 190.0, 214.2, 185.6);
                ctx.lineTo(240.5, 201.5);
                ctx.bezierCurveTo(241.1, 202.2, 241.6, 202.9, 241.8, 203.6);
                ctx.bezierCurveTo(242.1, 205.4, 241.8, 207.4, 240.9, 209.6);
                ctx.closePath();
                ctx.fillStyle = "rgb(35, 44, 30)";
                ctx.fill();

                // omnom/white
                ctx.beginPath();

                // omnom/white/Path
                ctx.moveTo(219.9, 140.2);
                ctx.bezierCurveTo(219.5, 141.6, 218.8, 143.0, 218.0, 144.5);
                ctx.bezierCurveTo(217.8, 144.9, 217.6, 145.2, 217.3, 145.6);
                ctx.bezierCurveTo(216.7, 146.3, 215.9, 146.9, 214.9, 147.4);
                ctx.bezierCurveTo(214.1, 147.8, 213.2, 148.2, 212.3, 148.4);
                ctx.bezierCurveTo(210.2, 149.1, 208.0, 149.5, 205.6, 149.5);
                ctx.bezierCurveTo(204.5, 149.5, 203.4, 149.4, 202.3, 149.3);
                ctx.bezierCurveTo(200.6, 148.9, 199.7, 148.0, 199.8, 146.7);
                ctx.lineTo(200.7, 145.2);
                ctx.bezierCurveTo(201.3, 144.3, 201.8, 143.4, 202.4, 142.5);
                ctx.bezierCurveTo(202.7, 141.9, 203.0, 141.3, 203.4, 140.8);
                ctx.bezierCurveTo(203.8, 140.0, 204.2, 139.2, 204.7, 138.5);
                ctx.bezierCurveTo(205.3, 137.7, 205.9, 136.8, 206.5, 136.0);
                ctx.bezierCurveTo(206.9, 135.5, 207.3, 135.0, 207.6, 134.5);
                ctx.bezierCurveTo(208.3, 133.7, 209.0, 132.8, 209.7, 132.0);
                ctx.bezierCurveTo(209.9, 131.7, 210.2, 131.4, 210.5, 131.0);
                ctx.bezierCurveTo(211.0, 130.4, 211.5, 129.9, 212.1, 129.3);
                ctx.bezierCurveTo(212.6, 128.8, 213.1, 128.3, 213.6, 127.8);
                ctx.bezierCurveTo(214.1, 127.4, 214.6, 127.0, 215.1, 126.6);
                ctx.bezierCurveTo(215.3, 126.4, 215.5, 126.3, 215.6, 126.2);
                ctx.bezierCurveTo(216.8, 125.4, 217.9, 124.8, 219.0, 124.5);
                ctx.lineTo(219.3, 125.4);
                ctx.bezierCurveTo(220.0, 127.9, 220.5, 130.6, 220.8, 133.3);
                ctx.bezierCurveTo(221.0, 135.7, 220.8, 138.0, 219.9, 140.2);
                ctx.closePath();

                // omnom/white/Path
                ctx.moveTo(40.4, 148.8);
                ctx.bezierCurveTo(37.3, 145.8, 34.1, 142.9, 30.7, 140.2);
                ctx.bezierCurveTo(29.1, 139.0, 27.6, 137.8, 26.0, 136.6);
                ctx.bezierCurveTo(25.2, 136.0, 24.4, 135.4, 23.6, 134.9);
                ctx.bezierCurveTo(22.9, 134.4, 22.5, 134.4, 22.4, 135.1);
                ctx.lineTo(22.3, 139.0);
                ctx.bezierCurveTo(22.3, 139.0, 22.3, 139.0, 22.3, 139.0);
                ctx.bezierCurveTo(22.0, 143.6, 23.2, 147.7, 25.8, 151.1);
                ctx.bezierCurveTo(26.9, 152.6, 28.3, 154.0, 29.9, 155.3);
                ctx.bezierCurveTo(29.9, 155.3, 30.0, 155.3, 30.0, 155.4);
                ctx.bezierCurveTo(30.3, 155.6, 30.6, 155.7, 30.9, 155.9);
                ctx.bezierCurveTo(35.6, 158.3, 40.5, 158.4, 45.5, 156.1);
                ctx.lineTo(45.7, 156.0);
                ctx.bezierCurveTo(45.9, 155.7, 45.8, 155.3, 45.6, 154.7);
                ctx.bezierCurveTo(44.1, 152.6, 42.4, 150.6, 40.4, 148.8);
                ctx.closePath();

                // omnom/white/Path
                ctx.moveTo(87.9, 175.9);
                ctx.bezierCurveTo(83.2, 173.8, 79.9, 172.3, 77.9, 171.4);
                ctx.bezierCurveTo(75.9, 170.5, 74.1, 169.6, 72.4, 168.8);
                ctx.bezierCurveTo(70.7, 167.9, 69.1, 167.1, 67.5, 166.3);
                ctx.bezierCurveTo(67.7, 166.8, 67.8, 167.3, 68.0, 167.8);
                ctx.lineTo(69.0, 170.1);
                ctx.bezierCurveTo(70.1, 172.9, 71.3, 175.7, 72.5, 178.3);
                ctx.bezierCurveTo(74.0, 181.3, 76.0, 184.1, 78.6, 186.6);
                ctx.bezierCurveTo(81.1, 189.2, 83.9, 189.9, 87.1, 188.9);
                ctx.bezierCurveTo(90.2, 187.9, 93.2, 186.5, 96.3, 184.7);
                ctx.lineTo(96.8, 184.3);
                ctx.lineTo(97.1, 184.2);
                ctx.lineTo(101.1, 181.4);
                ctx.bezierCurveTo(100.4, 181.2, 99.2, 180.7, 97.5, 180.0);
                ctx.bezierCurveTo(95.7, 179.3, 92.5, 177.9, 87.9, 175.9);
                ctx.closePath();

                // omnom/white/Path
                ctx.moveTo(167.5, 169.3);
                ctx.bezierCurveTo(164.4, 171.1, 161.2, 172.9, 158.1, 174.8);
                ctx.bezierCurveTo(155.3, 176.5, 152.5, 178.0, 149.6, 179.4);
                ctx.bezierCurveTo(148.9, 179.7, 148.2, 180.0, 147.5, 180.4);
                ctx.bezierCurveTo(145.0, 181.4, 142.8, 182.1, 140.9, 182.4);
                ctx.lineTo(146.3, 184.3);
                ctx.lineTo(146.7, 184.5);
                ctx.lineTo(147.2, 184.7);
                ctx.bezierCurveTo(149.5, 185.5, 151.9, 186.4, 154.4, 187.3);
                ctx.bezierCurveTo(157.5, 188.5, 160.6, 188.4, 163.6, 187.2);
                ctx.bezierCurveTo(166.3, 186.1, 168.3, 184.5, 169.6, 182.2);
                ctx.bezierCurveTo(171.1, 179.7, 172.2, 177.2, 173.1, 174.5);
                ctx.bezierCurveTo(173.5, 173.3, 174.0, 172.1, 174.4, 170.9);
                ctx.lineTo(174.7, 170.3);
                ctx.lineTo(174.9, 169.8);
                ctx.lineTo(177.9, 163.2);
                ctx.bezierCurveTo(177.6, 163.5, 177.2, 163.7, 176.8, 163.9);
                ctx.bezierCurveTo(173.7, 165.9, 170.7, 167.7, 167.5, 169.3);
                ctx.closePath();

                // omnom/white/Path
                ctx.moveTo(202.2, 94.7);
                ctx.bezierCurveTo(202.5, 86.8, 201.1, 79.7, 197.8, 73.5);
                ctx.bezierCurveTo(196.1, 70.1, 193.9, 67.0, 191.1, 64.1);
                ctx.bezierCurveTo(190.6, 63.5, 190.0, 63.0, 189.4, 62.4);
                ctx.bezierCurveTo(183.4, 56.7, 176.5, 53.0, 168.8, 51.2);
                ctx.bezierCurveTo(165.7, 50.5, 162.5, 50.1, 159.2, 50.1);
                ctx.bezierCurveTo(159.1, 50.1, 158.9, 50.1, 158.7, 50.1);
                ctx.bezierCurveTo(146.1, 51.7, 136.0, 57.6, 128.6, 67.7);
                ctx.bezierCurveTo(127.5, 69.4, 126.3, 71.1, 125.0, 72.9);
                ctx.bezierCurveTo(124.9, 73.2, 124.7, 73.5, 124.6, 73.8);
                ctx.lineTo(124.4, 74.2);
                ctx.lineTo(124.2, 73.8);
                ctx.lineTo(120.8, 67.4);
                ctx.bezierCurveTo(117.3, 59.5, 111.9, 53.4, 104.4, 49.1);
                ctx.bezierCurveTo(100.0, 46.5, 95.6, 44.9, 91.3, 44.1);
                ctx.bezierCurveTo(89.3, 43.8, 87.3, 43.6, 85.3, 43.7);
                ctx.bezierCurveTo(77.8, 43.8, 70.4, 46.7, 63.0, 52.3);
                ctx.bezierCurveTo(57.3, 56.8, 52.7, 61.9, 49.2, 67.7);
                ctx.bezierCurveTo(47.7, 70.1, 46.5, 72.7, 45.5, 75.3);
                ctx.bezierCurveTo(43.6, 79.9, 42.4, 84.8, 41.7, 90.1);
                ctx.bezierCurveTo(40.4, 101.7, 42.2, 111.4, 47.4, 119.3);
                ctx.bezierCurveTo(49.2, 122.2, 51.5, 124.9, 54.2, 127.3);
                ctx.bezierCurveTo(64.3, 136.3, 75.9, 139.1, 88.9, 135.6);
                ctx.bezierCurveTo(100.6, 132.5, 110.3, 125.2, 118.0, 113.7);
                ctx.bezierCurveTo(118.8, 112.4, 119.7, 111.0, 120.5, 109.6);
                ctx.bezierCurveTo(121.2, 108.3, 121.9, 106.9, 122.6, 105.5);
                ctx.bezierCurveTo(122.9, 106.7, 123.3, 107.9, 123.8, 109.0);
                ctx.bezierCurveTo(124.3, 110.4, 124.9, 111.7, 125.6, 113.0);
                ctx.bezierCurveTo(130.4, 122.6, 137.6, 129.8, 147.3, 134.6);
                ctx.bezierCurveTo(149.4, 135.6, 153.1, 136.4, 158.5, 137.0);
                ctx.bezierCurveTo(168.3, 137.9, 177.6, 134.4, 186.7, 126.4);
                ctx.bezierCurveTo(193.1, 120.7, 197.5, 114.2, 199.9, 107.1);
                ctx.bezierCurveTo(201.3, 103.2, 202.0, 99.0, 202.2, 94.7);
                ctx.closePath();
                ctx.fillStyle = "rgb(255, 255, 255)";
                ctx.fill();

                // omnom/leftEye
                ctx.save();
                ctx.translate(leftEyeOffset, 0);
                ctx.beginPath();
                ctx.moveTo(101.3, 71.1);
                ctx.bezierCurveTo(101.0, 71.1, 100.7, 71.2, 100.5, 71.3);
                ctx.bezierCurveTo(100.5, 71.3, 100.5, 71.3, 100.5, 71.3);
                ctx.bezierCurveTo(102.7, 72.9, 103.8, 75.8, 104.0, 79.9);
                ctx.bezierCurveTo(104.1, 84.2, 100.5, 86.1, 93.0, 85.6);
                ctx.bezierCurveTo(91.0, 85.5, 89.2, 85.4, 87.8, 85.3);
                ctx.bezierCurveTo(87.7, 86.6, 87.6, 87.9, 87.6, 89.3);
                ctx.bezierCurveTo(87.8, 91.8, 88.6, 94.3, 89.9, 96.7);
                ctx.bezierCurveTo(91.9, 101.5, 95.2, 103.9, 99.8, 103.9);
                ctx.bezierCurveTo(107.4, 103.7, 112.3, 98.0, 114.7, 86.8);
                ctx.bezierCurveTo(114.7, 74.4, 110.2, 69.2, 101.3, 71.1);
                ctx.closePath();
                ctx.fillStyle = "rgb(0, 0, 0)";
                ctx.fill();
                ctx.restore();

                // omnom/rightEye
                ctx.save();
                ctx.translate(rightEyeOffset, 0);
                ctx.beginPath();
                ctx.moveTo(150.4, 74.1);
                ctx.bezierCurveTo(147.9, 71.7, 145.3, 70.5, 142.4, 70.5);
                ctx.bezierCurveTo(141.5, 70.5, 140.5, 70.7, 139.6, 71.0);
                ctx.bezierCurveTo(140.1, 71.3, 140.6, 71.6, 141.1, 72.0);
                ctx.bezierCurveTo(143.3, 73.6, 144.5, 76.5, 144.6, 80.6);
                ctx.bezierCurveTo(144.8, 84.9, 141.1, 86.8, 133.7, 86.3);
                ctx.bezierCurveTo(132.1, 86.2, 130.8, 86.2, 129.6, 86.1);
                ctx.bezierCurveTo(129.6, 86.2, 129.6, 86.2, 129.6, 86.3);
                ctx.bezierCurveTo(129.6, 89.1, 130.2, 91.7, 131.5, 94.1);
                ctx.bezierCurveTo(134.1, 99.0, 138.3, 101.5, 144.0, 101.5);
                ctx.bezierCurveTo(146.8, 101.7, 149.7, 99.7, 152.5, 95.6);
                ctx.bezierCurveTo(154.5, 91.6, 155.5, 88.9, 155.5, 87.5);
                ctx.bezierCurveTo(155.5, 81.9, 153.8, 77.5, 150.4, 74.1);
                ctx.closePath();
                ctx.fillStyle = "rgb(0, 0, 0)";
                ctx.fill();
                ctx.restore();


                ctx.restore();
                ctx.restore();

            };

        };

        return new EasterEggManager();
    }
);
// Shim to provide requestAnimationFrame if not supported by the browser

define('utils/requestAnimationFrame',[], function () {

    // see if the browser natively supports requestAnimationFrame
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for (var i = 0; i < vendors.length && !window['requestAnimationFrame']; i++) {
        window['requestAnimationFrame'] = window[vendors[i] + 'RequestAnimationFrame'];
    }

    // fallback to using setTimeout if requestAnimationFrame isn't available
    if (!window['requestAnimationFrame']) {
        var renderInterval = 1000 / 60,
            lastTime = 0;
        window['requestAnimationFrame'] = function (callback, element) {
            var currTime = Date.now();
            var timeToCall = Math.max(0, renderInterval - (currTime - lastTime));
            window.setTimeout(function () {
                callback(Date.now());
            }, timeToCall);
            lastTime = currTime + timeToCall;
        }
    }

    return window['requestAnimationFrame'];
});


define('ui/VideoManager',
    [
        'edition',
        'resolution',
        'platform',
        'ui/PanelId',
        'ui/PanelManager',
        'game/CTRSettings',
        'game/CTRSoundMgr',
        'utils/PubSub',
        'ui/ScoreManager'
    ],
    function (edition, resolution, platform, PanelId, PanelManager, settings, SoundMgr, PubSub, ScoreManager) {

        var ensureVideoElement = function () {
            var vid = document.getElementById('vid');
            if (!vid) {
                try {
                    vid = document.createElement('video');
                }
                catch (ex) {
                    // creation of the video element occasionally fails in win8
                    return null;
                }
                vid.id = 'vid';
                vid.className = 'ctrPointer';
                $('#video').append(vid);
            }
            return vid;
        };

        var closeIntroCallback = null;

        var VideoManager = {

            loadIntroVideo: function () {

                // only load the video if the first level hasn't been played
                var firstLevelStars = ScoreManager.getStars(0, 0) || 0;
                if (firstLevelStars === 0) {
                    var vid = ensureVideoElement(),
                        size = resolution.VIDEO_WIDTH,
                        extension = platform.getVideoExtension(),
                        baseUrl = platform.videoBaseUrl;
                    if (vid != null && extension != null) {
                        try {
                            vid.src = baseUrl + 'intro_' + size + extension;
                            vid.load();
                        }
                        catch (ex) {
                            // loading the video sometimes causes an exception on win8
                        }
                    }
                }
            },

            removeIntroVideo: function() {
                // we want to remove the video element to free up resources
                // as suggested by the IE team
                var firstLevelStars = ScoreManager.getStars(0, 0) || 0;
                if (firstLevelStars > 0) {
                    $('#vid').remove();
                }
            },

            playIntroVideo: function (callback) {

                // always show the intro video if the 1st level hasn't been played
                var firstLevelStars = ScoreManager.getStars(0, 0) || 0,

                    // the video might not exist if the user just reset the game
                    // (we don't want to replay it during the same app session)
                    vid = document.getElementById('vid');

                closeIntroCallback = callback;

                if (firstLevelStars === 0 && vid) {

                    // make sure we can play the video
                    var readyState = vid['readyState'];
                    if (readyState === 2 || // HAVE_CURRENT_DATA (loadeddata)
                        readyState === 3 || // HAVE_FUTURE_DATA  (canplay)
                        readyState === 4) {  // HAVE_ENOUGH_DATA  (canplaythrough)

                        SoundMgr.pauseMusic();
                        $(vid).fadeIn(300, function () {
                            vid.play();
                        });
                        vid.addEventListener('ended', VideoManager.closeIntroVideo);
                        vid.addEventListener('mousedown', VideoManager.closeIntroVideo);
                        return;
                    }
                }

                VideoManager.closeIntroVideo();
            },

            closeIntroVideo: function () {
                var vid = document.getElementById('vid');
                $(vid).fadeOut(500, function () {
                    vid.pause();
                    vid.seek = 0;
                });

                if (closeIntroCallback) {
                    closeIntroCallback();
                }
            },

            loadOutroVideo: function () {
                // we can re-use the same video element used for the intro
                // because we only show the intro video once per session.

                // get the size and supported format extension
                var vid = ensureVideoElement(),
                    size = resolution.VIDEO_WIDTH,
                    extension = platform.getVideoExtension(),
                    baseUrl = platform.videoBaseUrl;

                // start loading the video
                if (vid != null && extension != null) {
                    try {
                        vid.src = baseUrl + 'outro_' + size + extension;
                        vid.load();
                    }
                    catch (ex) {
                        // loading the video sometimes causes an exception on win8
                    }
                }
            },

            playOutroVideo: function () {
                var vid = document.getElementById('vid');
                if (vid) {
                    // make sure we can play the video
                    var readyState = vid['readyState'];
                    if (readyState === 2 || // HAVE_CURRENT_DATA (loadeddata)
                        readyState === 3 || // HAVE_FUTURE_DATA  (canplay)
                        readyState === 4) {  // HAVE_ENOUGH_DATA  (canplaythrough)

                        SoundMgr.pauseMusic();
                        if (!SoundMgr.musicEnabled) {
                            vid.volume = 0;
                        }
                        $(vid).fadeIn(300, function () {
                            vid.play();
                        });
                        vid.addEventListener('ended', VideoManager.closeOutroVideo);
                        vid.addEventListener('mousedown', VideoManager.closeOutroVideo);
                    }
                    else {
                        $(vid).remove();
                        PanelManager.showPanel(PanelId.GAMECOMPLETE, false);
                    }
                }
            },

            closeOutroVideo: function () {
                PanelManager.showPanel(PanelId.GAMECOMPLETE, true);
                var $vid = $('#vid');
                $vid.fadeOut(500, function () {
                    $vid[0].pause();
                    $vid[0].seek = 0;
                    $vid.remove();
                });
            },

            domReady: function () {
                this.loadIntroVideo();
            }
        };

        // reload the intro video when the game progress is cleared
        PubSub.subscribe(PubSub.ChannelId.LoadIntroVideo, function() {
            VideoManager.loadIntroVideo();
        });

        return VideoManager;
    }
);

define('ui/GameBorder',
    [
        'platform',
        'edition'
    ],
    function (platform, edition) {

        var $border = null,
            GAME_COMPLETE_CLASS = 'gameComplete';

        var GameBorder = {
            domReady: function () {
                $border = $('#gameBorder');
            },
            setBoxBorder: function (boxIndex) {
                var borderFile = edition.boxBorders[boxIndex],
                    backgroundUrl = borderFile
                        ? platform.uiImageBaseUrl + borderFile
                        : '';

                $border
                    .removeClass(GAME_COMPLETE_CLASS)
                    .css("background-image", 'url(' + backgroundUrl + ')');
            },
            setGameCompleteBorder: function () {
                $border
                    .css("background-image", '')
                    .addClass(GAME_COMPLETE_CLASS);
            },
            hide: function () {
                $border.hide();
            },
            show: function () {
                $border.show();
            },
            fadeIn: function (duration, delay) {
                delay = delay || 0;
                $border.delay(delay).fadeIn(duration);
            },
            fadeOut: function (duration, delay) {
                delay = delay || 0;
                $border.delay(delay).fadeOut(duration);
            }
        };

        return GameBorder;
    }
);
define('ui/InterfaceManager',
    [
        'edition',
        'resolution',
        'platform',
        'ui/ScoreManager',
        'ui/BoxManager',
        'ui/PanelId',
        'ui/PanelManager',
        'ui/EasterEggManager',
        'visual/Text',
        'utils/PointerCapture',
        'game/CTRSettings',
        'game/CTRSoundMgr',
        'resources/ResourceId',
        'utils/requestAnimationFrame',
        'ui/Easing',
        'ui/QueryStrings',
        'game/CTRRootController',
        'ui/VideoManager',
        'utils/PubSub',
        'ui/BoxType',
        'resources/Lang',
        'resources/LangId',
        'resources/MenuStringId',
        'core/Alignment',
        'ui/SocialHelper',
        'ui/GameBorder',
        'analytics',
        'Doors',
        'ui/Dialogs'
    ],
    function (edition, resolution, platform, ScoreManager, BoxManager, PanelId, PanelManager,
        EasterEggManager, Text, PointerCapture, settings, SoundMgr, ResourceId,
        requestAnimationFrame, Easing, QueryStrings, RootController, VideoManager, PubSub,
        BoxType, Lang, LangId, MenuStringId, Alignment, SocialHelper, GameBorder, analytics,
        Doors, Dialogs) {

        var menuMusicId = edition.menuMusicId || ResourceId.SND_MENU_MUSIC;

        var InterfaceManager = new function () {
            // ------------------------------------------------------------------------
            // Locals Variables
            // ------------------------------------------------------------------------

            var _this = this;
            this.useHDVersion = resolution.isHD;

            this.isInLevelSelectMode = false;
            this.isInMenuSelectMode = false;
            this.isInAdvanceBoxMode = false;
            this.isBoxOpen = false;
            this.isTransitionActive = false;

            // warn the user if the frame rate is low after the first level
            var MIN_FPS = QueryStrings.minFps || 30;

            // sets scaled menu text for the image specified by the selector query
            var setImageBigText = function (selector, menuStringId) {
                return Text.drawBig({
                    text: Lang.menuText(menuStringId),
                    imgSel: selector,
                    scaleToUI: true
                });
            };

            // ------------------------------------------------------------------------
            // Initialize Panels (called once for each panel)
            // ------------------------------------------------------------------------

            var updateMiniSoundButton = function (doToggle, buttonId, msgId) {

                var className;
                var isSoundOn = SoundMgr.soundEnabled;
                var isMusicOn = SoundMgr.musicEnabled;

                if (doToggle) {
                    if (isSoundOn && isMusicOn) {
                        isSoundOn = true;
                        isMusicOn = false;
                    }
                    else if (!isSoundOn && !isMusicOn) {
                        isSoundOn = true;
                        isMusicOn = true;
                    }
                    else {
                        isSoundOn = false;
                        isMusicOn = false;
                    }

                    // update settings
                    SoundMgr.setSoundEnabled(isSoundOn);
                    SoundMgr.setMusicEnabled(isMusicOn);
                }

                if (isSoundOn && !isMusicOn) {
                    className = "effectsOnly";
                }
                else if (!isSoundOn && !isMusicOn) {
                    className = "noSound";
                }
                else {
                    className = "allSound";
                }

                var allClassNames = 'effectsOnly noSound allSound';
                $("#optionSound").removeClass(allClassNames).addClass(className);
                $("#gameSound").removeClass(allClassNames).addClass(className);

                // option panel screen
                $("#soundBtn .options-x").css("display", !isSoundOn ? "block" : "none");
                $("#musicBtn .options-x").css("display", !isMusicOn ? "block" : "none");


                // get the localized text for the new audio setting
                var text;
                if (!isMusicOn && !isSoundOn) {
                    text = Lang.menuText(MenuStringId.EVERYTHING_OFF);
                }
                else {
                    var musicId = isMusicOn ? MenuStringId.MUSIC_ON : MenuStringId.MUSIC_OFF,
                        soundId = isSoundOn ? MenuStringId.SOUNDS_ON : MenuStringId.SOUNDS_OFF,
                        template = Lang.menuText(MenuStringId.AND_TEMPLATE);
                    text = template.replace('{0}', Lang.menuText(musicId).toLowerCase())
                        .replace('{1}', Lang.menuText(soundId).toLowerCase());
                }

                showMiniOptionMessage(msgId, text);

            };

            var showMiniOptionMessage = function (msgId, text, delay) {
                if (msgId != undefined) {

                    var showDelay = delay || 500,
                        $msg = $("#" + msgId),
                        $img = $msg.find('img');

                    // make sure the image exists
                    if ($img.length === 0) {
                        $img = $('<img/>').appendTo($msg);
                    }

                    // render the text
                    Text.drawSmall({
                        text: text,
                        img: $img[0],
                        scaleToUI: true,
                        alpha: 0.6,
                        alignment: Alignment.LEFT
                    });

                    // stop any in-progress animations and queue fade in and out
                    $msg.stop(true, true)
                        .fadeIn(500).delay(showDelay).fadeOut(750);
                }
            };

            // only enable achievements and leaderboard for signed-in users
            var signedIn = false,
                updateSignInControls = function() {
                    $('#achievementsBtn').toggleClass('disabled', !signedIn);
                    $('#leaderboardsBtn').toggleClass('disabled', !signedIn);
                };
            PubSub.subscribe(PubSub.ChannelId.SignIn, function() {
                signedIn = true;
                updateSignInControls();
            });
            PubSub.subscribe(PubSub.ChannelId.SignOut, function() {
                signedIn = false;
                updateSignInControls();
            });

            var onInitializePanel = function (panelId) {

                // initialize the MENU panel
                if (panelId == PanelId.MENU) {

                    $('#playBtn').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);

                        if (analytics.onPlayClicked) {
                            analytics.onPlayClicked();
                        }

                        VideoManager.playIntroVideo(function() {

                            var firstLevelStars = ScoreManager.getStars(0, 0) || 0;
                            if (firstLevelStars === 0) {
                                // start the first level immediately
                                _this.noMenuStartLevel(0, 0);
                            } else {
                                var panelId = edition.disableBoxMenu ? PanelId.LEVELS : PanelId.BOXES;
                                PanelManager.showPanel(panelId, true);
                            }
                        });
                    });

                    $('#optionsBtn').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        // see if there is a custom settings panel we should trigger
                        if (platform.customOptions) {
                            PubSub.publish(PubSub.ChannelId.ShowOptions);
                        }
                        else {
                            PanelManager.showPanel(PanelId.OPTIONS);
                        }
                    });

                    $('#achievementsBtn').click(function () {
                        if (signedIn) {
                            SoundMgr.playSound(ResourceId.SND_TAP);
                            PanelManager.showPanel(PanelId.ACHIEVEMENTS);
                        }
                    }).toggleClass('disabled', !signedIn);

                    $('#leaderboardsBtn').click(function () {
                        if (signedIn) {
                            SoundMgr.playSound(ResourceId.SND_TAP);
                            PanelManager.showPanel(PanelId.LEADERBOARDS);
                        }
                    }).toggleClass('disabled', !signedIn);

                    // reset popup buttons
                    var resetTimer = null;
                    $("#resetYesBtn")
                        .on(PointerCapture.startEventName, function () {
                            SoundMgr.playSound(ResourceId.SND_TAP);
                            resetTimer = setTimeout(function () {
                                Dialogs.closePopup();
                                resetTimer = null;

                                settings.clear();

                                //reset scores
                                ScoreManager.resetGame();

                                // lock all the boxes
                                BoxManager.resetLocks();

                                PubSub.publish(PubSub.ChannelId.LoadIntroVideo);

                            }, 3000);  // wait 3 seconds in case user changes their mind
                        })
                        .on(PointerCapture.endEventName, function () {
                            if (resetTimer != null) {
                                clearTimeout(resetTimer);
                            }
                        });

                    // mini options panel

                    updateMiniSoundButton(false, "optionSound");
                    $("#optionSound").click(function () {
                        updateMiniSoundButton(true, "optionSound", "optionMsg");
                    });

                    var hdtoggle;
                    if (_this.useHDVersion) {
                        $("#optionHd").addClass("activeResolution");
                        $("#optionSd").addClass("inActiveResolution");
                        $("#optionSd").addClass("ctrPointer");
                        $("#optionSd").hover(function () {
                            showMiniOptionMessage("optionMsg", Lang.menuText(MenuStringId.RELOAD_SD), 4000);
                        }, function () {
                            $("#optionMsg").stop(true, true).fadeOut(500);
                        });
                        hdtoggle = "optionSd";
                    }
                    else {
                        $("#optionSd").addClass("activeResolution");
                        $("#optionHd").addClass("inActiveResolution");
                        $("#optionHd").addClass("ctrPointer");
                        $("#optionHd").hover(function () {
                            showMiniOptionMessage("optionMsg", Lang.menuText(MenuStringId.RELOAD_HD), 4000);
                        }, function () {
                            $("#optionMsg").stop(true, true).fadeOut(500);
                        });
                        hdtoggle = "optionHd";
                    }

                    $('#' + hdtoggle).click(function (e) {
                        settings.setIsHD(!_this.useHDVersion);
                        window.location.reload(); // refresh the page
                    });

                    // handle language changes
                    PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
                        setImageBigText('#playBtn img', MenuStringId.PLAY);
                        setImageBigText('#optionsBtn img', MenuStringId.OPTIONS);
                        setImageBigText('#resetYesBtn img', MenuStringId.YES);
                        setImageBigText('#resetNoBtn img', MenuStringId.NO);

                        Text.drawBig({
                            text: Lang.menuText(MenuStringId.LEADERBOARDS),
                            imgParentId: 'leaderboardsBtn',
                            scale: 0.8 * resolution.UI_TEXT_SCALE
                        });

                        Text.drawBig({
                            text: Lang.menuText(MenuStringId.ACHIEVEMENTS),
                            imgParentId: 'achievementsBtn',
                            scale: 0.8 * resolution.UI_TEXT_SCALE
                        });

                    });
                }

                // initialize the BOXES panel
                else if (panelId == PanelId.BOXES) {

                    // handles clicking on the circular back button
                    $('#boxBack').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        PanelManager.showPanel(PanelId.MENU);
                    });

                    var panel = PanelManager.getPanelById(panelId);
                    panel.init(InterfaceManager);
                }

                else if (panelId == PanelId.PASSWORD) {

                    $('#boxEnterCodeButton').click(function() {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        PanelManager.showPanel(PanelId.PASSWORD);
                    });

                    // handles clicking on the circular back button
                    $('#codeBack').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        PanelManager.showPanel(PanelId.BOXES);
                    });

                    var panel = PanelManager.getPanelById(panelId);
                    panel.init(InterfaceManager);
                }

                // initialize the LEVELS panel
                else if (panelId == PanelId.LEVELS) {

                    $('#levelBack').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        var panelId = edition.disableBoxMenu ? PanelId.MENU : PanelId.BOXES;
                        PanelManager.showPanel(panelId);
                    });

                    // render the canvas all the way closed
                    Doors.renderDoors(true, 0.0);

                    var panel = PanelManager.getPanelById(panelId);
                    panel.init(InterfaceManager);
                }
                else if (panelId == PanelId.GAME) {
                    $('#gameRestartBtn').click(function () {
                        if (_this.isTransitionActive) return;
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        openLevel(BoxManager.currentLevelIndex, true); // is a restart
                    });

                    $('#gameMenuBtn').click(function () {
                        if (_this.isTransitionActive) return;
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        openLevelMenu();
                    });
                }
                else if (panelId == PanelId.GAMEMENU) {
                    $('#continueBtn').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        closeLevelMenu();
                        RootController.resumeLevel();
                    });

                    $('#skipBtn').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        closeLevelMenu();
                        //unlock next level
                        if (BoxManager.isNextLevelPlayable()) {
                            ScoreManager.setStars(BoxManager.currentBoxIndex, BoxManager.currentLevelIndex, 0);
                            openLevel(BoxManager.currentLevelIndex + 1, false, true);
                        } else {
                            $('#gameBtnTray').hide();
                            completeBox();
                        }
                    });

                    $('#selectBtn').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        closeLevelMenu();
                        closeLevel();
                        _this.isInLevelSelectMode = true;
                        _this.isInMenuSelectMode = false;
                        _this.closeBox();
                    });

                    $('#menuBtn').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        closeLevelMenu();
                        closeLevel();
                        _this.isInLevelSelectMode = true;
                        _this.isInMenuSelectMode = true;
                        _this.closeBox();
                    });

                    // mini options panel
                    updateMiniSoundButton(false, "gameSound");
                    $("#gameSound").click(function () {
                        updateMiniSoundButton(true, "gameSound", "gameMsg");
                    });

                    // language changes
                    PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
                        setImageBigText('#continueBtn img', MenuStringId.CONTINUE);
                        setImageBigText('#skipBtn img', MenuStringId.SKIP_LEVEL);
                        setImageBigText('#selectBtn img', MenuStringId.LEVEL_SELECT);
                        setImageBigText('#menuBtn img', MenuStringId.MAIN_MENU);
                    });
                }
                else if (panelId == PanelId.LEVELCOMPLETE) {

                    $('#nextBtn').click(function () {
                        if (_this.isTransitionActive) return;
                        notifyBeginTransition(1000, "next level");
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        //is there another level in this box?
                        if (BoxManager.isNextLevelPlayable()) {
                            openLevel(BoxManager.currentLevelIndex + 1);
                        } else {
                            completeBox();
                        }
                    });

                    $('#replayBtn').click(function () {
                        if (_this.isTransitionActive) return;
                        notifyBeginTransition(1000, "replay");
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        openLevel(BoxManager.currentLevelIndex);
                    });

                    $('#lrMenuBtn').click(function () {
                        if (_this.isTransitionActive) return;
                        notifyBeginTransition(1000, "level menu");
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        _this.isInLevelSelectMode = true;
                        _this.isInMenuSelectMode = false;
                        _this.tapeBox();
                    });

                    // handle language changes
                    PubSub.subscribe(PubSub.ChannelId.LanguageChanged, function () {
                        setImageBigText('#nextBtn img', MenuStringId.NEXT);
                        setImageBigText('#replayBtn img', MenuStringId.REPLAY);
                        setImageBigText('#lrMenuBtn img', MenuStringId.MENU);
                        Text.drawSmall({
                            text: Lang.menuText(MenuStringId.FINAL_SCORE),
                            imgId: 'resultTickerMessage',
                            scaleToUI: true,
                            canvas: true
                        });
                    });
                }
                else if (panelId == PanelId.GAMECOMPLETE) {
                    $('#gameCompleteBack').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        PanelManager.showPanel(PanelId.MENU);
                        GameBorder.hide();
                    });

                    $('#finalShareBtn').click(function () {

                        var possibleStars = BoxManager.possibleStars(),
                            totalStars = ScoreManager.totalStars();

                        SocialHelper.postToFeed(
                            platform.getGameCompleteShareText(totalStars, possibleStars),
                            SocialHelper.siteDescription,
                            platform.getScoreImageBaseUrl() + "score" + totalStars + ".png",
                            function () {
                                return true;
                            });

                    });
                }
                else if (panelId == PanelId.OPTIONS) {

                    // sound effects
                    var updateSoundOption = platform.updateSoundOption,
                        $soundBtn = $('#soundBtn'),
                        onSoundButtonChange = function () {
                            var isSoundOn = !(settings.getSoundEnabled());
                            SoundMgr.setSoundEnabled(isSoundOn);
                            SoundMgr.playSound(ResourceId.SND_TAP);
                            updateSoundOption($soundBtn, isSoundOn);
                            updateMiniSoundButton(false, "gameSound");
                            updateMiniSoundButton(false, "optionSound");
                        };
                    platform.setSoundButtonChange($soundBtn, onSoundButtonChange);

                    // game music
                    var updateMusicOption = platform.updateMusicOption,
                        $musicBtn = $('#musicBtn'),
                        onMusicButtonChange = function () {
                            SoundMgr.playSound(ResourceId.SND_TAP);
                            var isMusicOn = !(settings.getMusicEnabled());
                            SoundMgr.setMusicEnabled(isMusicOn);
                            updateMusicOption($musicBtn, isMusicOn);
                            updateMiniSoundButton(false, "gameSound");
                            updateMiniSoundButton(false, "optionSound");
                        };
                    platform.setMusicButtonChange($musicBtn, onMusicButtonChange);

                    // change language
                    var updateLangOption = platform.updateLangSetting;
                    platform.setLangOptionClick(function (newLangId) {
                        SoundMgr.playSound(ResourceId.SND_TAP);

                        // if not specified we'll assume that we should advance to
                        // the next language (so we cycle through as user clicks)
                        if (newLangId == null) {
                            var currentIndex = edition.languages.indexOf(settings.getLangId());
                            newLangId = edition.languages[(currentIndex + 1) % edition.languages.length];
                        }

                        settings.setLangId(newLangId);

                        // send the notification that language has changed
                        PubSub.publish(PubSub.ChannelId.LanguageChanged);
                    });


                    // click or drag to cut
                    var updateCutOption = platform.updateCutSetting;
                    platform.setCutOptionClick(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        var isClickToCut = !(settings.getClickToCut());
                        settings.setClickToCut(isClickToCut);
                        updateCutOption(isClickToCut);
                    });

                    // reset button
                    var $resetBtn = $('#resetBtn').click(function () {

                        // create localized text images
                        var resetTextImg = Text.drawBig({
                                text: Lang.menuText(MenuStringId.RESET_TEXT),
                                alignment: Alignment.CENTER,

                                // we use canvas scale because text is draw at game scale and
                                // scaled to UI dimensions by setting the img width & height
                                width: 1250 * resolution.CANVAS_SCALE,
                                scaleToUI: true }),
                            resetHoldYesImg = Text.drawSmall({
                                text: Lang.menuText(MenuStringId.RESET_HOLD_YES),
                                scaleToUI: true,
                                width: resolution.uiScaledNumber(550)
                            });

                        // clear existing text image and append to placeholder divs
                        $('#resetText').empty().append($(resetTextImg));
                        $('#resetHoldYes').empty().append($(resetHoldYesImg));

                        SoundMgr.playSound(ResourceId.SND_TAP);
                        Dialogs.showPopup("resetGame");
                    });

                    $('#optionsBack').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        PanelManager.showPanel(PanelId.MENU);
                    });

                    // hide the language if not supported by the edition
                    platform.toggleLangUI(!edition.disableLanguageOption);

                    // update options menu when the language changes
                    var refreshOptionsButtons = function () {
                        setImageBigText('#optionsTitle img', MenuStringId.OPTIONS);
                        updateSoundOption($soundBtn, settings.getSoundEnabled());
                        updateMusicOption($musicBtn, settings.getMusicEnabled());
                        updateLangOption();
                        updateCutOption(settings.getClickToCut());
                        platform.setResetText($resetBtn, Lang.menuText(MenuStringId.RESET));

                        // apply a lang-{code} class to a language layer for css styles
                        var langId = settings.getLangId();
                        // !LANG
                        $('#lang')
                            .removeClass('lang-system lang-en lang-de lang-ru lang-fr lang-ca lang-br lang-es lang-it lang-nl lang-ko lang-ja lang-zh')
                            .addClass('lang-' + LangId.toCountryCode(langId));

                        if (langId >= 4 && langId <= 9) {
                            $("#lang").addClass('lang-system')
                        }
                    };

                    PubSub.subscribe(PubSub.ChannelId.LanguageChanged, refreshOptionsButtons);
                    PubSub.subscribe(PubSub.ChannelId.ShowOptionsPage, refreshOptionsButtons);

                } else if (panelId === PanelId.LEADERBOARDS) {
                    $('#leaderboardBack').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        PanelManager.showPanel(PanelId.MENU);
                    });
                } else if (panelId === PanelId.ACHIEVEMENTS) {
                    $('#achievementsBack').click(function () {
                        SoundMgr.playSound(ResourceId.SND_TAP);
                        PanelManager.showPanel(PanelId.MENU);
                    });
                }
                //else if (panelId == PanelId.CREDITS) { }
            };

            // ------------------------------------------------------------------------
            // Show Panels (called for each panel when it's shown)
            // ------------------------------------------------------------------------

            var bounceTimeOut = null;
            var onShowPanel = function (panelId) {

                var panel = PanelManager.getPanelById(panelId);

                if (panelId == PanelId.MENU || panelId == PanelId.BOXES || panelId == PanelId.OPTIONS) {
                    GameBorder.fadeOut(300);
                }
                else if (panelId !== PanelId.LEVELS) {
                    GameBorder.show();
                }

                // make sure the pause level panel is closed
                if (panelId !== PanelId.GAMEMENU) {
                    closeLevelMenu();
                }

                // make sure the menu music is started on main menu and level selection
                // which are the entry points from the game.
                if (panelId === PanelId.MENU || panelId === PanelId.LEVELS) {
                    SoundMgr.playMusic(menuMusicId);
                }

                var boxPanel = PanelManager.getPanelById(PanelId.BOXES);
                if (panelId == PanelId.BOXES) {
                    BoxManager.updateBoxLocks();
                    ScoreManager.updateTotalScoreText();
                    boxPanel.onShow();

                    if (_this.isInAdvanceBoxMode) {
                        _this.isInAdvanceBoxMode = false;
                        setTimeout(function () {
                            $("#levelResults").hide();
                            boxPanel.slideToNextBox();

                            // if next level is not playable, show the purchase prompt
                            if (!BoxManager.isNextLevelPlayable()) {
                                Dialogs.showPayDialog();
                            }

                        }, 800);
                    }
                    else {
                        clearTimeout(bounceTimeOut);
                        bounceTimeOut = setTimeout(function () {
                            boxPanel.bounceCurrentBox();
                        }, 300);
                    }
                }
                else {
                    boxPanel.onHide();
                }

                var codePanel = PanelManager.getPanelById(PanelId.PASSWORD);
                if (codePanel) {
                    if (panelId === PanelId.PASSWORD) {
                        codePanel.onShow();
                    } else {
                        codePanel.onHide();
                    }
                }

                if (panelId == PanelId.LEVELS) {
                    Doors.renderDoors(true, 0);
                    panel.onShow();
                }
                else if (panelId == PanelId.GAME) {
                    updateMiniSoundButton(false, "optionSound");
                }
                //else if (panelId == PanelId.GAMEMENU) { }
                //else if (panelId == PanelId.LEVELCOMPLETE) { }
                else if (panelId === PanelId.GAMECOMPLETE) {

                    $('#levelResults').hide();

                    GameBorder.setGameCompleteBorder();

                    var gameWonText = Lang.menuText(MenuStringId.GAME_FINISHED_TEXT)
                        .replace('%d', ScoreManager.totalStars());
                    Text.drawBig({
                        text: gameWonText,
                        imgSel: '#finalScore img',
                        scale: 0.8 * resolution.UI_TEXT_SCALE,
                        alignment: 1
                    });

                    $('#congrats').empty().append(Text.drawBig({
                        text: Lang.menuText(MenuStringId.CONGRATULATIONS),
                        scale: 1.2 * resolution.UI_TEXT_SCALE
                    }));

                    Text.drawBig({
                        text: Lang.menuText(MenuStringId.SHARE_ELLIPSIS),
                        imgSel: '#finalShareBtn img',
                        scale: 0.8 * resolution.UI_TEXT_SCALE,
                        maxScaleWidth: resolution.uiScaledNumber(130)
                    });
                    Text.drawBig({
                        text: Lang.menuText(MenuStringId.MORE_CTR_FUN),
                        imgSel: '#finalFunBtn img',
                        scale: 0.8 * resolution.UI_TEXT_SCALE,
                        maxScaleWidth: resolution.uiScaledNumber(310)
                    });
                }
                else if (panelId == PanelId.OPTIONS) {
                    PubSub.publish(PubSub.ChannelId.ShowOptionsPage);
                }

                else if (panelId == PanelId.ACHIEVEMENTS) {
                    PubSub.publish(PubSub.ChannelId.UpdateCandyScroller);
                }

                else if (panelId == PanelId.LEADERBOARDS) {
                    PubSub.publish(PubSub.ChannelId.UpdateCandyScroller);
                }
                //else if (panelId == PanelId.CREDITS) { }
            };

            // ------------------------------------------------------------------------
            // UI methods
            // ------------------------------------------------------------------------

            // Sets the isTransitionActive flag to true and then back to false after the timeout. The
            // reason for using a timer here is to ensure that we always clear the flag since some UI
            // will be disabled until the flag gets cleared. This is an attempt to prevent new bugs.

            var transitionTimeout = null;
            var notifyBeginTransition = function (timeout, name) {
                _this.isTransitionActive = true;
                if (transitionTimeout != null) clearTimeout(transitionTimeout);
                transitionTimeout = setTimeout(function () {
                    _this.isTransitionActive = false;
                    transitionTimeout = null;
                }, timeout);
            };

            var runScoreTicker = function () {
                //$('#resultTicker').text(resultTopLines[currentResultLine]);
                $('#resultScore').text(resultBottomLines[currentResultLine]);
                currentResultLine++;
                if (currentResultLine < resultTopLines.length) {
                    if (currentResultLine < resultTimeShiftIndex) {
                        setTimeout(function () {
                            runScoreTicker();
                        }, 10);
                    } else {
                        setTimeout(function () {
                            runScoreTicker();
                        }, 167);
                    }
                }
            };

            // play the level
            var openLevel = this.openLevel = function (level, isRestart, isSkip) {


                GameBorder.fadeIn(650, 100);
                BoxManager.currentLevelIndex = level;

                // when we start the last level we should begin loading the outro video
                if (isLastLevel()) {
                    VideoManager.loadOutroVideo();
                }

                if (isRestart) {
                    RootController.restartLevel();
                }
                else {
                    PanelManager.showPanel(PanelId.GAME, true);
                    setTimeout(function () {
                        _this.openBox(isSkip);
                    }, 200);
                }
            };

            var closeLevel = function () {
                RootController.stopLevel();
            };

            var isLastLevel = function () {
                // see if we are on the last box
                var lastPlayableBoxIndex = BoxManager.requiredCount() - 1;
                if (BoxManager.currentBoxIndex !== lastPlayableBoxIndex) {
                    return false;
                }

                // on the last level?
                var numLevels = ScoreManager.levelCount(BoxManager.currentBoxIndex);
                // unfortunately the currentLevelIndex is not zero-based
                if (BoxManager.currentLevelIndex !== numLevels) {
                    return false;
                }

                return true;
            };

            var completeBox = function () {

                //attempt to move to the next box
                var boxIndex = BoxManager.currentBoxIndex;

                // check for game complete
                var requiredIndex = BoxManager.requiredCount() - 1,
                    isGameComplete = (boxIndex >= requiredIndex);

                if (isGameComplete) {
                    GameBorder.hide();
                    VideoManager.playOutroVideo();
                }
                else {
                    _this.isInAdvanceBoxMode = true;
                    var panelId = edition.disableBoxMenu ? PanelId.MENU : PanelId.BOXES;
                    PanelManager.showPanel(panelId, false);
                }

            };

            var openLevelMenu = function () {
                RootController.pauseLevel();
                $('#levelMenu').show();
            };

            var closeLevelMenu = function () {
                $('#levelMenu').hide();
            };

            this.tapeBox = function () {

                if (_this.isInMenuSelectMode) {
                    GameBorder.fadeOut(800, 400);
                    SoundMgr.playMusic(menuMusicId);
                }

                Doors.closeBoxAnimation(function() {
                    _this.isBoxOpen = false;
                    if (_this.isInMenuSelectMode) {
                        PanelManager.showPanel(PanelId.MENU, false);
                    } else {
                        Doors.renderDoors(true, 0);
                        PanelManager.showPanel(PanelId.LEVELS, true);
                    }
                });
            };

            this.openBox = function openboxFunc (skip) {

                var timeout = PanelManager.currentPanelId == PanelId.LEVELS ? 400 : 0;

                //fade out options elements
                $("#levelScore").fadeOut();
                $("#levelBack").fadeOut();

                RootController.startLevel(BoxManager.currentBoxIndex + 1, BoxManager.currentLevelIndex);

                $("#levelOptions").fadeOut(timeout, function () {

                    if (_this.isBoxOpen) {
                        $('#levelResults').fadeOut(800);
                        
                        setTimeout(function () {
                            if (skip) {
                                _this.showGameUI();
                            } else {
                                Doors.openDoors(false, function () {
                                    _this.showGameUI();
                                });
                            }
                        }, 400);
                    }
                    else {
                        Doors.openBoxAnimation(function () {
                            _this.isBoxOpen = true;
                            
                            Doors.openDoors(true, function () {
                                _this.showGameUI();
                            });
                        });
                    }
                }); // end fadeOut
            };

            this.closeBox = function () {
                _this.closeGameUI();

                setTimeout(function () {

                    // animating from game to results
                    if (!_this.isInLevelSelectMode) {
                        $('#levelResults').delay(750).fadeIn(250);
                    }

                    // close the doors
                    Doors.closeDoors(false, function () {
                        if (_this.isInLevelSelectMode) {
                            _this.tapeBox();
                        } else {
                            Doors.showGradient();
                            setTimeout(function () {
                                runScoreTicker();
                            }, 250);
                        }
                    });

                }, 250);
            };

            var showLevelBackground = function () {
                $("#levelBackground").show();
            };

            var hideLevelBackground = function () {
                $("#levelBackground").hide();
            };

            this.showGameUI = function () {
                hideLevelBackground();
                if (QueryStrings.showBoxBackgrounds && edition.enableBoxBackgroundEasterEgg) {
                    $("#bg").show();
                }
                $('#gameBtnTray').fadeIn();
            };

            this.closeGameUI = function () {
                Doors.renderDoors(false, 1);
                notifyBeginTransition(1000, "close game ui");
                showLevelBackground();
                if (QueryStrings.showBoxBackgrounds && edition.enableBoxBackgroundEasterEgg) {
                    $("#bg").hide();
                }
                $('#gameBtnTray').fadeOut();
            };

            var resultTopLines = [], resultBottomLines = [], currentResultLine = 0, resultTimeShiftIndex = 0;

            this.onLevelWon = function (info) {
                var stars = info.stars,
                    score = info.score,
                    levelTime = info.time;

                //show level results
                var resultStatusText;
                var currentPoints = 0;
                var index = 0;
                var totalStarPoints = stars * 1000;
                var currentTime = 1;
                var timeSlicePoints = Math.round((score - (stars * 1000)) / levelTime);

                switch (stars) {
                    case 3:
                        $('#resultStar1').removeClass('starEmpty').addClass('star');
                        $('#resultStar2').removeClass('starEmpty').addClass('star');
                        $('#resultStar3').removeClass('starEmpty').addClass('star');
                        resultStatusText = Lang.menuText(MenuStringId.LEVEL_CLEARED4);
                        break;
                    case 2:
                        $('#resultStar1').removeClass('starEmpty').addClass('star');
                        $('#resultStar2').removeClass('starEmpty').addClass('star');
                        $('#resultStar3').removeClass('star').addClass('starEmpty');
                        resultStatusText = Lang.menuText(MenuStringId.LEVEL_CLEARED3);
                        break;
                    case 1:
                        $('#resultStar1').removeClass('starEmpty').addClass('star');
                        $('#resultStar2').removeClass('star').addClass('starEmpty');
                        $('#resultStar3').removeClass('star').addClass('starEmpty');
                        resultStatusText = Lang.menuText(MenuStringId.LEVEL_CLEARED2);
                        break;
                    default:
                        $('#resultStar1').removeClass('star').addClass('starEmpty');
                        $('#resultStar2').removeClass('star').addClass('starEmpty');
                        $('#resultStar3').removeClass('star').addClass('starEmpty');
                        resultStatusText = Lang.menuText(MenuStringId.LEVEL_CLEARED1);
                        break;
                }
                Text.drawBig({text: resultStatusText, imgSel: '#resultStatus canvas', scaleToUI: true, canvas: true });

                // set stuff up
                var valdiv = $("#resultTickerValue").hide();
                var lbldiv = $("#resultTickerLabel").hide();
                var resdiv = $("#resultScore").empty().hide();
                var stamp = $("#resultImproved").hide();
                var msgdiv = $("#resultTickerMessage").hide();

                // HELPER FUNCTIONS

                var secondsToMin = function (sec) {
                    var m = sec / 60 | 0,
                        s = Math.round(sec % 60);
                    return m + ":" + (s < 10 ? "0" + s : s);
                };

                var doStarCountdown = function (from, callback) {

                    var countDownPoints = from,
                        duration = 1000,
                        lastRender = Date.now(),
                        requestAnimationFrame = window['requestAnimationFrame'];

                    var renderCount = function() {
                        var now = Date.now(),
                            timeDelta = now - lastRender,
                            pointDelta = Math.min(
                                Math.round(from * timeDelta / duration),
                                countDownPoints);

                        lastRender = now;

                        countDownPoints -= pointDelta;
                        currentPoints += pointDelta;
                        if (countDownPoints <= 0) {
                            countDownPoints = 0;
                            currentPoints = from;
                            lbldiv.fadeOut(400);
                            valdiv.fadeOut(400, callback);
                        } else {
                            requestAnimationFrame(renderCount);
                        }

                        Text.drawSmall({text: countDownPoints, img: valdiv[0], scaleToUI: true, canvas: true });
                        Text.drawBigNumbers({text: currentPoints, imgParentId: 'resultScore', scaleToUI: true, canvas: true });
                    };

                    renderCount();
                };

                var doTimeCountdown = function (fromsec, frompoints, callback) {

                    var finalPoints = currentPoints + frompoints,
                        countDownSecs = fromsec,
                        // between 1 and 2 secs depending on time
                        duration = Math.max(1000, 2000 - (fromsec * 50)),
                        lastRender = Date.now(),
                        requestAnimationFrame = window['requestAnimationFrame'];

                    var renderScore = function() {
                        var now = Date.now(),
                            percentElapsed = (now - lastRender) / duration;

                        lastRender = now;
                        currentPoints += Math.round(frompoints * percentElapsed);

                        countDownSecs -= fromsec * percentElapsed;
                        if (countDownSecs <= 0) {
                            countDownSecs = 0;
                            currentPoints = finalPoints;
                            lbldiv.fadeOut(400);
                            valdiv.fadeOut(400, callback);
                        } else {
                            requestAnimationFrame(renderScore);
                        }

                        Text.drawSmall({text: secondsToMin(countDownSecs), img: valdiv[0], scaleToUI: true, canvas: true });
                        Text.drawBigNumbers({text: currentPoints, imgParentId: 'resultScore', scaleToUI: true, canvas: true });
                    };

                    renderScore();
                };

                // ANIMATION

                // set up the star bonus countdown
                Text.drawSmall({text: Lang.menuText(MenuStringId.STAR_BONUS), img: lbldiv[0], scaleToUI: true, canvas: true });
                Text.drawSmall({text: totalStarPoints, img: valdiv[0], scaleToUI: true, canvas: true });
                $('#resultScore img').remove();
                $('#resultScore canvas').remove();

                // run the animation sequence
                setTimeout(function () {
                    lbldiv.fadeIn(300);
                    valdiv.fadeIn(300);
                    resdiv.fadeIn(300, function () {
                        doStarCountdown(totalStarPoints, function () {

                            Text.drawSmall({text: Lang.menuText(MenuStringId.TIME), img: lbldiv[0], scaleToUI: true, canvas: true });
                            lbldiv.fadeIn(300);
                            Text.drawSmall({text: secondsToMin(Math.ceil(levelTime)), img: valdiv[0], scaleToUI: true, canvas: true });
                            valdiv.fadeIn(300, function () {
                                doTimeCountdown(Math.ceil(levelTime), score - currentPoints, function () {
                                    msgdiv.fadeIn(300);
                                    // show the improved result stamp
                                    if (prevScore != null && prevScore > 0 && score > prevScore) {
                                        if ($.browser.msie) {
                                            stamp.show();
                                        } else {
                                            stamp.animate({ scale: 2.5, opacity: 0.0 }, 0, function () {
                                                stamp.css("display", "block");
                                                stamp.animate({ scale: 1.0, opacity: 1.0 }, 600, "easeInCubic");
                                            });
                                        }
                                    }
                                });
                            });

                        });
                    });
                }, 1000);


                // TODO: right now boxIndex is zero based and levelIndex starts at 1?
                var boxIndex = BoxManager.currentBoxIndex,
                    levelIndex = BoxManager.currentLevelIndex;

                // save the prev score
                var prevScore = ScoreManager.getScore(boxIndex, levelIndex - 1);

                // Update score of the current level if there is a best result
                ScoreManager.setScore(boxIndex, levelIndex - 1, score);
                ScoreManager.setStars(boxIndex, levelIndex - 1, stars);

                // unlock next level
                if (ScoreManager.levelCount(boxIndex) > levelIndex &&
                    BoxManager.isNextLevelPlayable()) {
                    ScoreManager.setStars(boxIndex, levelIndex, 0);
                }

                _this.isInLevelSelectMode = false;
                _this.closeBox();

                // events that occur after completing the first level
                if (boxIndex === 0 && levelIndex === 1) {

                    if (analytics.onFirstLevelComplete) {
                        analytics.onFirstLevelComplete(info.fps);
                    }

                    // tell the user if the fps was low on the first level
                    if (info.fps < MIN_FPS && !platform.disableSlowWarning) {

                        // delay the popup to allow the score screen to finish
                        setTimeout(function () {
                            Dialogs.showSlowComputerPopup();
                        }, 3000);
                    }

                    VideoManager.removeIntroVideo();
                }
            };

            // show hide the "behind the scenes" link and the feedback tab when the screen changes size
            var isDevLinkVisible = true;
            this.updateDevLink = function () {
                if ($(window).width() < (resolution.uiScaledNumber(1024) + 120) && isDevLinkVisible) {
                    $("#moreLink").fadeOut(function () {
                        isDevLinkVisible = false;
                    });
                    $("#zenbox_tab").fadeOut();
                }
                else if ($(window).width() > (resolution.uiScaledNumber(1024) + 120) && !isDevLinkVisible) {
                    $("#moreLink").fadeIn(function () {
                        isDevLinkVisible = true;
                    });
                    $("#zenbox_tab").fadeIn();
                }
            };

            // we'll only resume when the game is enabled
            this.gameEnabled = true;

            this.pauseGame = function () {

                // make sure the game is active and no transitions are pending
                if (PanelManager.currentPanelId === PanelId.GAME &&
                    RootController.isLevelActive() &&
                    !_this.isTransitionActive) {
                    openLevelMenu();
                }
                else {
                    SoundMgr.pauseMusic();
                }
            };

            this.resumeGame = function () {
                if (PanelManager.currentPanelId !== PanelId.GAMEMENU &&
                    _this.gameEnabled) {
                    SoundMgr.resumeMusic();
                }
            };

            // ------------------------------------------------------------------------
            // Object management stuff
            // ------------------------------------------------------------------------

            this.init = function () {
                ScoreManager.load();
                PanelManager.onShowPanel = onShowPanel;
            };

            this.domReady = function () {

                VideoManager.domReady();
                EasterEggManager.domReady();
                PanelManager.domReady();
                GameBorder.domReady();

                // pause game / music when the user switches tabs
                $(window).blur(_this.pauseGame);

                // when returning to the tab, resume music (except when on game menu - no music there)
                $(window).focus(_this.resumeGame);

                // hide behind the scenes when we update the page
                $(window).resize(function () {
                    _this.updateDevLink();
                });
            };

            this.appReady = function () {

                PubSub.subscribe(PubSub.ChannelId.LevelWon, this.onLevelWon);

                Doors.appReady();
                EasterEggManager.appReady();
                PanelManager.appReady(onInitializePanel);
                BoxManager.appReady();

                // initialize all the localized resources
                PubSub.publish(PubSub.ChannelId.LanguageChanged);

                // start a specific level?
                if (QueryStrings.box != null && QueryStrings.level != null) {
                    this.noMenuStartLevel(QueryStrings.box - 1, QueryStrings.level - 1);
                }
                else if (settings.showMenu) {

                    // make sure the game is not password locked
                    var passwordPanel = PanelManager.getPanelById(PanelId.PASSWORD);
                    if (passwordPanel && passwordPanel.isGameLocked &&
                        passwordPanel.isGameLocked()) {
                        Doors.renderDoors(true, 0);
                        PanelManager.showPanel(PanelId.PASSWORD, true);
                    } else {
                        PanelManager.showPanel(PanelId.MENU, true);
                    }
                }

                var im = this;
                PubSub.subscribe(PubSub.ChannelId.PauseGame, function() {
                    im.pauseGame();
                });
                PubSub.subscribe(PubSub.ChannelId.EnableGame, function() {
                    im.gameEnabled = true;
                    im.resumeGame();
                });
                PubSub.subscribe(PubSub.ChannelId.DisableGame, function() {
                    im.gameEnabled = false;
                    im.pauseGame();
                });
            };

            // used for debug and in level editor to start a level w/o menus
            this.noMenuStartLevel = function (boxIndex, levelIndex) {
                PanelManager.showPanel(PanelId.GAME, true);

                // unfortunate that box manager is zero index for box and 1 based for level
                BoxManager.currentBoxIndex = boxIndex;
                BoxManager.currentLevelIndex = levelIndex + 1;

                this.openBox();
            };

            this.openLevelMenu = function(boxIndex) {
                _this.isBoxOpen = false;
                Doors.renderDoors(true, 0);
                PanelManager.showPanel(PanelId.LEVELS);
                GameBorder.setBoxBorder(boxIndex);
            };

        };

        return InterfaceManager;
    }
);
define('app',
    [
        'jquery',
        'resources/PreLoader',
        'resolution',
        'ui/InterfaceManager',
        'utils/Canvas',
        'game/CTRSettings',
        'ZoomManager',
        'utils/PubSub',
        'editionUI'
    ],
    function ($, preloader, resolution, im, Canvas, settings, ZoomManager, PubSub, editionUI) {

        var App = {
            // Gives the app a chance to begin working before the DOM is ready
            init: function () {
                preloader.init();
                im.init();
                PubSub.publish(PubSub.ChannelId.AppInit);
            },

            // Called by the loader when the DOM is loaded
            domReady: function () {
                // disable text selection mode in IE9
                if (settings.disableTextSelection) {
                    if (typeof document.body['onselectstart'] != "undefined") {
                        document.body['onselectstart'] = function () {
                            return false;
                        };
                    }
                }

                // toggle the active css class when the user clicks
                $('.ctrCursor').on('mousedown mouseup', function () {
                    $(this).toggleClass('ctrCursorActive');
                });

                $('body').addClass('ui-' + resolution.UI_WIDTH);

                Canvas.domReady('c');

                // set the canvas drawing dimensions
                Canvas.element.width = resolution.CANVAS_WIDTH;
                Canvas.element.height = resolution.CANVAS_HEIGHT;

                // set the screen (css) dimensions
                $(Canvas.element)
                    .width(resolution.CANVAS_WIDTH)
                    .height(resolution.CANVAS_HEIGHT);

                if (ZoomManager.domReady) {
                    ZoomManager.domReady();
                }

                preloader.domReady();
                im.domReady();
                PubSub.publish(PubSub.ChannelId.AppDomReady);
            },

            run: function () {

                // Called by the loader when the app is ready to run
                preloader.run(function() {
                    im.appReady();
                    PubSub.publish(PubSub.ChannelId.AppRun);

                    // fade in the game
                    $(".hideAfterLoad").fadeOut(500);
                    $(".hideBeforeLoad").fadeIn(500);

                    var start = 10;
                    var inc;
                    var interval = setInterval(function () {
                        inc = Math.random() * 15 | 0;
                        start += inc;
                        
                        if (start > 100) {
                            start = 100;
                            $("#betterLoader").fadeOut(500);
                            clearInterval(interval);
                        }
                        $("#progress").animate({"width": start + "%"}, 50);
                    }, 100);

                    // show hide behind the scenes when we first load
                    im.updateDevLink();

                    // put the social links back into the footer (need to be offscreen instead of hidden during load)
                    $("#gameFooterSocial").css("top", 0);

                });
            }
        };

        return App;
    });

define('config/exports/CtrExport',
    [],
    function () {

        function ctrExport(key, value) {
            // MUST use string literals for exported properties
            var zeptoLab = window['ZeptoLab'];
            if (zeptoLab == null) {
                zeptoLab = window['ZeptoLab'] = {};
            }

            var ctr = zeptoLab['ctr'];
            if (ctr == null) {
                ctr = zeptoLab['ctr'] = {};
            }
            ctr[key] = value;
        }

        return ctrExport;
    }
);

define('ctrExports',
    [
        'config/exports/CtrExport',
        'ui/QueryStrings',
        'ui/SocialHelper',
        'game/CTRSettings',
        'utils/PubSub'
    ],
    function (ctrExport, QueryStrings, SocialHelper, settings, PubSub) {

        ctrExport('forceHTML5Audio', QueryStrings.forceHtml5Audio);

        window['showFpsCounter'] = function () {
            settings.fpsEnabled = true;
        };

        ctrExport('initFB', SocialHelper.initFB);
        ctrExport('initTwitter', SocialHelper.initTwitter);


        ctrExport('onLevelWon', function(callback) {

            PubSub.subscribe(PubSub.ChannelId.LevelWon, function(info) {

                // don't pass along the level info, just tell subscriber
                // that the level completed
                callback();
            });
        });


        ctrExport('pauseGame', function() {
            PubSub.publish(PubSub.ChannelId.PauseGame);
        });

        ctrExport('enable', function() {
            PubSub.publish(PubSub.ChannelId.EnableGame);
        });

        ctrExport('disable', function() {
            PubSub.publish(PubSub.ChannelId.DisableGame);
        });

        return window['ZeptoLab'];
    }
);


require(
    ['app', 'platform', 'ctrExports'],
    function (app, platform) {

        // verify that the client meets platform requirements
        if (!platform.meetsRequirements()) {
            return;
        }

        // initialize the application immediately
        app.init();

        // wait until the DOM is loaded before wiring up events
        $(document).ready(function () {
            app.domReady();

            // for now, we will immediately run the app
            app.run();
        });
    }
);

define("main", function(){});
}());
