webpackJsonp([4],{

/***/ 587:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(280),
	  __webpack_require__(276),
	  __webpack_require__(561)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, _, MailPoet) {

	  var Module = {};

	  Module._query = function (args) {
	    return MailPoet.Ajax.post({
	      api_version: window.mailpoet_api_version,
	      endpoint: 'automatedLatestContent',
	      action: args.action,
	      data: args.options || {}
	    });
	  };
	  Module._cachedQuery = _.memoize(Module._query, JSON.stringify);

	  Module.getNewsletter = function (options) {
	    return Module._query({
	      action: 'get',
	      options: options
	    });
	  };

	  Module.getPostTypes = function () {
	    return Module._cachedQuery({
	      action: 'getPostTypes',
	      options: {}
	    }).then(function (response) {
	      return _.values(response.data);
	    });
	  };

	  Module.getTaxonomies = function (postType) {
	    return Module._cachedQuery({
	      action: 'getTaxonomies',
	      options: {
	        postType: postType
	      }
	    }).then(function (response) {
	      return response.data;
	    });
	  };

	  Module.getTerms = function (options) {
	    return Module._cachedQuery({
	      action: 'getTerms',
	      options: options
	    }).then(function (response) {
	      return response.data;
	    });
	  };

	  Module.getPosts = function (options) {
	    return Module._cachedQuery({
	      action: 'getPosts',
	      options: options
	    }).then(function (response) {
	      return response.data;
	    });
	  };

	  Module.getTransformedPosts = function (options) {
	    return Module._cachedQuery({
	      action: 'getTransformedPosts',
	      options: options
	    }).then(function (response) {
	      return response.data;
	    });
	  };

	  Module.getBulkTransformedPosts = function (options) {
	    return Module._query({
	      action: 'getBulkTransformedPosts',
	      options: options
	    }).then(function (response) {
	      return response.data;
	    });
	  };

	  Module.saveNewsletter = function (options) {
	    return MailPoet.Ajax.post({
	      api_version: window.mailpoet_api_version,
	      endpoint: 'newsletters',
	      action: 'save',
	      data: options || {}
	    });
	  };

	  Module.previewNewsletter = function (options) {
	    return MailPoet.Ajax.post({
	      api_version: window.mailpoet_api_version,
	      endpoint: 'newsletters',
	      action: 'sendPreview',
	      data: options || {}
	    });
	  };

	  App.on('start', function () {
	    // Prefetch post types
	    Module.getPostTypes();
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 0:
/***/ function(module, exports, __webpack_require__) {

	__webpack_require__(280);
	__webpack_require__(544);
	__webpack_require__(571);
	__webpack_require__(573);
	__webpack_require__(574);
	__webpack_require__(572);
	__webpack_require__(289);
	__webpack_require__(575);
	__webpack_require__(576);
	__webpack_require__(577);
	__webpack_require__(578);
	__webpack_require__(581);
	__webpack_require__(582);
	__webpack_require__(583);
	__webpack_require__(584);
	__webpack_require__(585);
	__webpack_require__(586);
	__webpack_require__(588);
	__webpack_require__(589);
	__webpack_require__(590);
	__webpack_require__(587);
	__webpack_require__(593);
	__webpack_require__(594);
	__webpack_require__(595);
	__webpack_require__(596);
	__webpack_require__(597);
	__webpack_require__(598);
	__webpack_require__(599);
	__webpack_require__(600);
	__webpack_require__(601);
	__webpack_require__(602);
	__webpack_require__(603);
	__webpack_require__(604);
	__webpack_require__(605);
	__webpack_require__(606);
	__webpack_require__(607);
	__webpack_require__(608);
	__webpack_require__(609);
	__webpack_require__(610);
	__webpack_require__(611);
	__webpack_require__(612);
	__webpack_require__(613);
	module.exports = __webpack_require__(614);


/***/ },

/***/ 276:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function () {
	  // A placeholder for MailPoet object
	  var MailPoet = {};

	  // Expose MailPoet globally
	  window.MailPoet = MailPoet;

	  return MailPoet;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 280:
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {module.exports = global["_"] = __webpack_require__(281);
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },

/***/ 281:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;//     Underscore.js 1.8.3
	//     http://underscorejs.org
	//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	//     Underscore may be freely distributed under the MIT license.

	(function() {

	  // Baseline setup
	  // --------------

	  // Establish the root object, `window` in the browser, or `exports` on the server.
	  var root = this;

	  // Save the previous value of the `_` variable.
	  var previousUnderscore = root._;

	  // Save bytes in the minified (but not gzipped) version:
	  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

	  // Create quick reference variables for speed access to core prototypes.
	  var
	    push             = ArrayProto.push,
	    slice            = ArrayProto.slice,
	    toString         = ObjProto.toString,
	    hasOwnProperty   = ObjProto.hasOwnProperty;

	  // All **ECMAScript 5** native function implementations that we hope to use
	  // are declared here.
	  var
	    nativeIsArray      = Array.isArray,
	    nativeKeys         = Object.keys,
	    nativeBind         = FuncProto.bind,
	    nativeCreate       = Object.create;

	  // Naked function reference for surrogate-prototype-swapping.
	  var Ctor = function(){};

	  // Create a safe reference to the Underscore object for use below.
	  var _ = function(obj) {
	    if (obj instanceof _) return obj;
	    if (!(this instanceof _)) return new _(obj);
	    this._wrapped = obj;
	  };

	  // Export the Underscore object for **Node.js**, with
	  // backwards-compatibility for the old `require()` API. If we're in
	  // the browser, add `_` as a global object.
	  if (true) {
	    if (typeof module !== 'undefined' && module.exports) {
	      exports = module.exports = _;
	    }
	    exports._ = _;
	  } else {
	    root._ = _;
	  }

	  // Current version.
	  _.VERSION = '1.8.3';

	  // Internal function that returns an efficient (for current engines) version
	  // of the passed-in callback, to be repeatedly applied in other Underscore
	  // functions.
	  var optimizeCb = function(func, context, argCount) {
	    if (context === void 0) return func;
	    switch (argCount == null ? 3 : argCount) {
	      case 1: return function(value) {
	        return func.call(context, value);
	      };
	      case 2: return function(value, other) {
	        return func.call(context, value, other);
	      };
	      case 3: return function(value, index, collection) {
	        return func.call(context, value, index, collection);
	      };
	      case 4: return function(accumulator, value, index, collection) {
	        return func.call(context, accumulator, value, index, collection);
	      };
	    }
	    return function() {
	      return func.apply(context, arguments);
	    };
	  };

	  // A mostly-internal function to generate callbacks that can be applied
	  // to each element in a collection, returning the desired result — either
	  // identity, an arbitrary callback, a property matcher, or a property accessor.
	  var cb = function(value, context, argCount) {
	    if (value == null) return _.identity;
	    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
	    if (_.isObject(value)) return _.matcher(value);
	    return _.property(value);
	  };
	  _.iteratee = function(value, context) {
	    return cb(value, context, Infinity);
	  };

	  // An internal function for creating assigner functions.
	  var createAssigner = function(keysFunc, undefinedOnly) {
	    return function(obj) {
	      var length = arguments.length;
	      if (length < 2 || obj == null) return obj;
	      for (var index = 1; index < length; index++) {
	        var source = arguments[index],
	            keys = keysFunc(source),
	            l = keys.length;
	        for (var i = 0; i < l; i++) {
	          var key = keys[i];
	          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
	        }
	      }
	      return obj;
	    };
	  };

	  // An internal function for creating a new object that inherits from another.
	  var baseCreate = function(prototype) {
	    if (!_.isObject(prototype)) return {};
	    if (nativeCreate) return nativeCreate(prototype);
	    Ctor.prototype = prototype;
	    var result = new Ctor;
	    Ctor.prototype = null;
	    return result;
	  };

	  var property = function(key) {
	    return function(obj) {
	      return obj == null ? void 0 : obj[key];
	    };
	  };

	  // Helper for collection methods to determine whether a collection
	  // should be iterated as an array or as an object
	  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
	  // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
	  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
	  var getLength = property('length');
	  var isArrayLike = function(collection) {
	    var length = getLength(collection);
	    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
	  };

	  // Collection Functions
	  // --------------------

	  // The cornerstone, an `each` implementation, aka `forEach`.
	  // Handles raw objects in addition to array-likes. Treats all
	  // sparse array-likes as if they were dense.
	  _.each = _.forEach = function(obj, iteratee, context) {
	    iteratee = optimizeCb(iteratee, context);
	    var i, length;
	    if (isArrayLike(obj)) {
	      for (i = 0, length = obj.length; i < length; i++) {
	        iteratee(obj[i], i, obj);
	      }
	    } else {
	      var keys = _.keys(obj);
	      for (i = 0, length = keys.length; i < length; i++) {
	        iteratee(obj[keys[i]], keys[i], obj);
	      }
	    }
	    return obj;
	  };

	  // Return the results of applying the iteratee to each element.
	  _.map = _.collect = function(obj, iteratee, context) {
	    iteratee = cb(iteratee, context);
	    var keys = !isArrayLike(obj) && _.keys(obj),
	        length = (keys || obj).length,
	        results = Array(length);
	    for (var index = 0; index < length; index++) {
	      var currentKey = keys ? keys[index] : index;
	      results[index] = iteratee(obj[currentKey], currentKey, obj);
	    }
	    return results;
	  };

	  // Create a reducing function iterating left or right.
	  function createReduce(dir) {
	    // Optimized iterator function as using arguments.length
	    // in the main function will deoptimize the, see #1991.
	    function iterator(obj, iteratee, memo, keys, index, length) {
	      for (; index >= 0 && index < length; index += dir) {
	        var currentKey = keys ? keys[index] : index;
	        memo = iteratee(memo, obj[currentKey], currentKey, obj);
	      }
	      return memo;
	    }

	    return function(obj, iteratee, memo, context) {
	      iteratee = optimizeCb(iteratee, context, 4);
	      var keys = !isArrayLike(obj) && _.keys(obj),
	          length = (keys || obj).length,
	          index = dir > 0 ? 0 : length - 1;
	      // Determine the initial value if none is provided.
	      if (arguments.length < 3) {
	        memo = obj[keys ? keys[index] : index];
	        index += dir;
	      }
	      return iterator(obj, iteratee, memo, keys, index, length);
	    };
	  }

	  // **Reduce** builds up a single result from a list of values, aka `inject`,
	  // or `foldl`.
	  _.reduce = _.foldl = _.inject = createReduce(1);

	  // The right-associative version of reduce, also known as `foldr`.
	  _.reduceRight = _.foldr = createReduce(-1);

	  // Return the first value which passes a truth test. Aliased as `detect`.
	  _.find = _.detect = function(obj, predicate, context) {
	    var key;
	    if (isArrayLike(obj)) {
	      key = _.findIndex(obj, predicate, context);
	    } else {
	      key = _.findKey(obj, predicate, context);
	    }
	    if (key !== void 0 && key !== -1) return obj[key];
	  };

	  // Return all the elements that pass a truth test.
	  // Aliased as `select`.
	  _.filter = _.select = function(obj, predicate, context) {
	    var results = [];
	    predicate = cb(predicate, context);
	    _.each(obj, function(value, index, list) {
	      if (predicate(value, index, list)) results.push(value);
	    });
	    return results;
	  };

	  // Return all the elements for which a truth test fails.
	  _.reject = function(obj, predicate, context) {
	    return _.filter(obj, _.negate(cb(predicate)), context);
	  };

	  // Determine whether all of the elements match a truth test.
	  // Aliased as `all`.
	  _.every = _.all = function(obj, predicate, context) {
	    predicate = cb(predicate, context);
	    var keys = !isArrayLike(obj) && _.keys(obj),
	        length = (keys || obj).length;
	    for (var index = 0; index < length; index++) {
	      var currentKey = keys ? keys[index] : index;
	      if (!predicate(obj[currentKey], currentKey, obj)) return false;
	    }
	    return true;
	  };

	  // Determine if at least one element in the object matches a truth test.
	  // Aliased as `any`.
	  _.some = _.any = function(obj, predicate, context) {
	    predicate = cb(predicate, context);
	    var keys = !isArrayLike(obj) && _.keys(obj),
	        length = (keys || obj).length;
	    for (var index = 0; index < length; index++) {
	      var currentKey = keys ? keys[index] : index;
	      if (predicate(obj[currentKey], currentKey, obj)) return true;
	    }
	    return false;
	  };

	  // Determine if the array or object contains a given item (using `===`).
	  // Aliased as `includes` and `include`.
	  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
	    if (!isArrayLike(obj)) obj = _.values(obj);
	    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
	    return _.indexOf(obj, item, fromIndex) >= 0;
	  };

	  // Invoke a method (with arguments) on every item in a collection.
	  _.invoke = function(obj, method) {
	    var args = slice.call(arguments, 2);
	    var isFunc = _.isFunction(method);
	    return _.map(obj, function(value) {
	      var func = isFunc ? method : value[method];
	      return func == null ? func : func.apply(value, args);
	    });
	  };

	  // Convenience version of a common use case of `map`: fetching a property.
	  _.pluck = function(obj, key) {
	    return _.map(obj, _.property(key));
	  };

	  // Convenience version of a common use case of `filter`: selecting only objects
	  // containing specific `key:value` pairs.
	  _.where = function(obj, attrs) {
	    return _.filter(obj, _.matcher(attrs));
	  };

	  // Convenience version of a common use case of `find`: getting the first object
	  // containing specific `key:value` pairs.
	  _.findWhere = function(obj, attrs) {
	    return _.find(obj, _.matcher(attrs));
	  };

	  // Return the maximum element (or element-based computation).
	  _.max = function(obj, iteratee, context) {
	    var result = -Infinity, lastComputed = -Infinity,
	        value, computed;
	    if (iteratee == null && obj != null) {
	      obj = isArrayLike(obj) ? obj : _.values(obj);
	      for (var i = 0, length = obj.length; i < length; i++) {
	        value = obj[i];
	        if (value > result) {
	          result = value;
	        }
	      }
	    } else {
	      iteratee = cb(iteratee, context);
	      _.each(obj, function(value, index, list) {
	        computed = iteratee(value, index, list);
	        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
	          result = value;
	          lastComputed = computed;
	        }
	      });
	    }
	    return result;
	  };

	  // Return the minimum element (or element-based computation).
	  _.min = function(obj, iteratee, context) {
	    var result = Infinity, lastComputed = Infinity,
	        value, computed;
	    if (iteratee == null && obj != null) {
	      obj = isArrayLike(obj) ? obj : _.values(obj);
	      for (var i = 0, length = obj.length; i < length; i++) {
	        value = obj[i];
	        if (value < result) {
	          result = value;
	        }
	      }
	    } else {
	      iteratee = cb(iteratee, context);
	      _.each(obj, function(value, index, list) {
	        computed = iteratee(value, index, list);
	        if (computed < lastComputed || computed === Infinity && result === Infinity) {
	          result = value;
	          lastComputed = computed;
	        }
	      });
	    }
	    return result;
	  };

	  // Shuffle a collection, using the modern version of the
	  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
	  _.shuffle = function(obj) {
	    var set = isArrayLike(obj) ? obj : _.values(obj);
	    var length = set.length;
	    var shuffled = Array(length);
	    for (var index = 0, rand; index < length; index++) {
	      rand = _.random(0, index);
	      if (rand !== index) shuffled[index] = shuffled[rand];
	      shuffled[rand] = set[index];
	    }
	    return shuffled;
	  };

	  // Sample **n** random values from a collection.
	  // If **n** is not specified, returns a single random element.
	  // The internal `guard` argument allows it to work with `map`.
	  _.sample = function(obj, n, guard) {
	    if (n == null || guard) {
	      if (!isArrayLike(obj)) obj = _.values(obj);
	      return obj[_.random(obj.length - 1)];
	    }
	    return _.shuffle(obj).slice(0, Math.max(0, n));
	  };

	  // Sort the object's values by a criterion produced by an iteratee.
	  _.sortBy = function(obj, iteratee, context) {
	    iteratee = cb(iteratee, context);
	    return _.pluck(_.map(obj, function(value, index, list) {
	      return {
	        value: value,
	        index: index,
	        criteria: iteratee(value, index, list)
	      };
	    }).sort(function(left, right) {
	      var a = left.criteria;
	      var b = right.criteria;
	      if (a !== b) {
	        if (a > b || a === void 0) return 1;
	        if (a < b || b === void 0) return -1;
	      }
	      return left.index - right.index;
	    }), 'value');
	  };

	  // An internal function used for aggregate "group by" operations.
	  var group = function(behavior) {
	    return function(obj, iteratee, context) {
	      var result = {};
	      iteratee = cb(iteratee, context);
	      _.each(obj, function(value, index) {
	        var key = iteratee(value, index, obj);
	        behavior(result, value, key);
	      });
	      return result;
	    };
	  };

	  // Groups the object's values by a criterion. Pass either a string attribute
	  // to group by, or a function that returns the criterion.
	  _.groupBy = group(function(result, value, key) {
	    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
	  });

	  // Indexes the object's values by a criterion, similar to `groupBy`, but for
	  // when you know that your index values will be unique.
	  _.indexBy = group(function(result, value, key) {
	    result[key] = value;
	  });

	  // Counts instances of an object that group by a certain criterion. Pass
	  // either a string attribute to count by, or a function that returns the
	  // criterion.
	  _.countBy = group(function(result, value, key) {
	    if (_.has(result, key)) result[key]++; else result[key] = 1;
	  });

	  // Safely create a real, live array from anything iterable.
	  _.toArray = function(obj) {
	    if (!obj) return [];
	    if (_.isArray(obj)) return slice.call(obj);
	    if (isArrayLike(obj)) return _.map(obj, _.identity);
	    return _.values(obj);
	  };

	  // Return the number of elements in an object.
	  _.size = function(obj) {
	    if (obj == null) return 0;
	    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
	  };

	  // Split a collection into two arrays: one whose elements all satisfy the given
	  // predicate, and one whose elements all do not satisfy the predicate.
	  _.partition = function(obj, predicate, context) {
	    predicate = cb(predicate, context);
	    var pass = [], fail = [];
	    _.each(obj, function(value, key, obj) {
	      (predicate(value, key, obj) ? pass : fail).push(value);
	    });
	    return [pass, fail];
	  };

	  // Array Functions
	  // ---------------

	  // Get the first element of an array. Passing **n** will return the first N
	  // values in the array. Aliased as `head` and `take`. The **guard** check
	  // allows it to work with `_.map`.
	  _.first = _.head = _.take = function(array, n, guard) {
	    if (array == null) return void 0;
	    if (n == null || guard) return array[0];
	    return _.initial(array, array.length - n);
	  };

	  // Returns everything but the last entry of the array. Especially useful on
	  // the arguments object. Passing **n** will return all the values in
	  // the array, excluding the last N.
	  _.initial = function(array, n, guard) {
	    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
	  };

	  // Get the last element of an array. Passing **n** will return the last N
	  // values in the array.
	  _.last = function(array, n, guard) {
	    if (array == null) return void 0;
	    if (n == null || guard) return array[array.length - 1];
	    return _.rest(array, Math.max(0, array.length - n));
	  };

	  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
	  // Especially useful on the arguments object. Passing an **n** will return
	  // the rest N values in the array.
	  _.rest = _.tail = _.drop = function(array, n, guard) {
	    return slice.call(array, n == null || guard ? 1 : n);
	  };

	  // Trim out all falsy values from an array.
	  _.compact = function(array) {
	    return _.filter(array, _.identity);
	  };

	  // Internal implementation of a recursive `flatten` function.
	  var flatten = function(input, shallow, strict, startIndex) {
	    var output = [], idx = 0;
	    for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
	      var value = input[i];
	      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
	        //flatten current level of array or arguments object
	        if (!shallow) value = flatten(value, shallow, strict);
	        var j = 0, len = value.length;
	        output.length += len;
	        while (j < len) {
	          output[idx++] = value[j++];
	        }
	      } else if (!strict) {
	        output[idx++] = value;
	      }
	    }
	    return output;
	  };

	  // Flatten out an array, either recursively (by default), or just one level.
	  _.flatten = function(array, shallow) {
	    return flatten(array, shallow, false);
	  };

	  // Return a version of the array that does not contain the specified value(s).
	  _.without = function(array) {
	    return _.difference(array, slice.call(arguments, 1));
	  };

	  // Produce a duplicate-free version of the array. If the array has already
	  // been sorted, you have the option of using a faster algorithm.
	  // Aliased as `unique`.
	  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
	    if (!_.isBoolean(isSorted)) {
	      context = iteratee;
	      iteratee = isSorted;
	      isSorted = false;
	    }
	    if (iteratee != null) iteratee = cb(iteratee, context);
	    var result = [];
	    var seen = [];
	    for (var i = 0, length = getLength(array); i < length; i++) {
	      var value = array[i],
	          computed = iteratee ? iteratee(value, i, array) : value;
	      if (isSorted) {
	        if (!i || seen !== computed) result.push(value);
	        seen = computed;
	      } else if (iteratee) {
	        if (!_.contains(seen, computed)) {
	          seen.push(computed);
	          result.push(value);
	        }
	      } else if (!_.contains(result, value)) {
	        result.push(value);
	      }
	    }
	    return result;
	  };

	  // Produce an array that contains the union: each distinct element from all of
	  // the passed-in arrays.
	  _.union = function() {
	    return _.uniq(flatten(arguments, true, true));
	  };

	  // Produce an array that contains every item shared between all the
	  // passed-in arrays.
	  _.intersection = function(array) {
	    var result = [];
	    var argsLength = arguments.length;
	    for (var i = 0, length = getLength(array); i < length; i++) {
	      var item = array[i];
	      if (_.contains(result, item)) continue;
	      for (var j = 1; j < argsLength; j++) {
	        if (!_.contains(arguments[j], item)) break;
	      }
	      if (j === argsLength) result.push(item);
	    }
	    return result;
	  };

	  // Take the difference between one array and a number of other arrays.
	  // Only the elements present in just the first array will remain.
	  _.difference = function(array) {
	    var rest = flatten(arguments, true, true, 1);
	    return _.filter(array, function(value){
	      return !_.contains(rest, value);
	    });
	  };

	  // Zip together multiple lists into a single array -- elements that share
	  // an index go together.
	  _.zip = function() {
	    return _.unzip(arguments);
	  };

	  // Complement of _.zip. Unzip accepts an array of arrays and groups
	  // each array's elements on shared indices
	  _.unzip = function(array) {
	    var length = array && _.max(array, getLength).length || 0;
	    var result = Array(length);

	    for (var index = 0; index < length; index++) {
	      result[index] = _.pluck(array, index);
	    }
	    return result;
	  };

	  // Converts lists into objects. Pass either a single array of `[key, value]`
	  // pairs, or two parallel arrays of the same length -- one of keys, and one of
	  // the corresponding values.
	  _.object = function(list, values) {
	    var result = {};
	    for (var i = 0, length = getLength(list); i < length; i++) {
	      if (values) {
	        result[list[i]] = values[i];
	      } else {
	        result[list[i][0]] = list[i][1];
	      }
	    }
	    return result;
	  };

	  // Generator function to create the findIndex and findLastIndex functions
	  function createPredicateIndexFinder(dir) {
	    return function(array, predicate, context) {
	      predicate = cb(predicate, context);
	      var length = getLength(array);
	      var index = dir > 0 ? 0 : length - 1;
	      for (; index >= 0 && index < length; index += dir) {
	        if (predicate(array[index], index, array)) return index;
	      }
	      return -1;
	    };
	  }

	  // Returns the first index on an array-like that passes a predicate test
	  _.findIndex = createPredicateIndexFinder(1);
	  _.findLastIndex = createPredicateIndexFinder(-1);

	  // Use a comparator function to figure out the smallest index at which
	  // an object should be inserted so as to maintain order. Uses binary search.
	  _.sortedIndex = function(array, obj, iteratee, context) {
	    iteratee = cb(iteratee, context, 1);
	    var value = iteratee(obj);
	    var low = 0, high = getLength(array);
	    while (low < high) {
	      var mid = Math.floor((low + high) / 2);
	      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
	    }
	    return low;
	  };

	  // Generator function to create the indexOf and lastIndexOf functions
	  function createIndexFinder(dir, predicateFind, sortedIndex) {
	    return function(array, item, idx) {
	      var i = 0, length = getLength(array);
	      if (typeof idx == 'number') {
	        if (dir > 0) {
	            i = idx >= 0 ? idx : Math.max(idx + length, i);
	        } else {
	            length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
	        }
	      } else if (sortedIndex && idx && length) {
	        idx = sortedIndex(array, item);
	        return array[idx] === item ? idx : -1;
	      }
	      if (item !== item) {
	        idx = predicateFind(slice.call(array, i, length), _.isNaN);
	        return idx >= 0 ? idx + i : -1;
	      }
	      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
	        if (array[idx] === item) return idx;
	      }
	      return -1;
	    };
	  }

	  // Return the position of the first occurrence of an item in an array,
	  // or -1 if the item is not included in the array.
	  // If the array is large and already in sort order, pass `true`
	  // for **isSorted** to use binary search.
	  _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
	  _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

	  // Generate an integer Array containing an arithmetic progression. A port of
	  // the native Python `range()` function. See
	  // [the Python documentation](http://docs.python.org/library/functions.html#range).
	  _.range = function(start, stop, step) {
	    if (stop == null) {
	      stop = start || 0;
	      start = 0;
	    }
	    step = step || 1;

	    var length = Math.max(Math.ceil((stop - start) / step), 0);
	    var range = Array(length);

	    for (var idx = 0; idx < length; idx++, start += step) {
	      range[idx] = start;
	    }

	    return range;
	  };

	  // Function (ahem) Functions
	  // ------------------

	  // Determines whether to execute a function as a constructor
	  // or a normal function with the provided arguments
	  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
	    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
	    var self = baseCreate(sourceFunc.prototype);
	    var result = sourceFunc.apply(self, args);
	    if (_.isObject(result)) return result;
	    return self;
	  };

	  // Create a function bound to a given object (assigning `this`, and arguments,
	  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
	  // available.
	  _.bind = function(func, context) {
	    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
	    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
	    var args = slice.call(arguments, 2);
	    var bound = function() {
	      return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
	    };
	    return bound;
	  };

	  // Partially apply a function by creating a version that has had some of its
	  // arguments pre-filled, without changing its dynamic `this` context. _ acts
	  // as a placeholder, allowing any combination of arguments to be pre-filled.
	  _.partial = function(func) {
	    var boundArgs = slice.call(arguments, 1);
	    var bound = function() {
	      var position = 0, length = boundArgs.length;
	      var args = Array(length);
	      for (var i = 0; i < length; i++) {
	        args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
	      }
	      while (position < arguments.length) args.push(arguments[position++]);
	      return executeBound(func, bound, this, this, args);
	    };
	    return bound;
	  };

	  // Bind a number of an object's methods to that object. Remaining arguments
	  // are the method names to be bound. Useful for ensuring that all callbacks
	  // defined on an object belong to it.
	  _.bindAll = function(obj) {
	    var i, length = arguments.length, key;
	    if (length <= 1) throw new Error('bindAll must be passed function names');
	    for (i = 1; i < length; i++) {
	      key = arguments[i];
	      obj[key] = _.bind(obj[key], obj);
	    }
	    return obj;
	  };

	  // Memoize an expensive function by storing its results.
	  _.memoize = function(func, hasher) {
	    var memoize = function(key) {
	      var cache = memoize.cache;
	      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
	      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
	      return cache[address];
	    };
	    memoize.cache = {};
	    return memoize;
	  };

	  // Delays a function for the given number of milliseconds, and then calls
	  // it with the arguments supplied.
	  _.delay = function(func, wait) {
	    var args = slice.call(arguments, 2);
	    return setTimeout(function(){
	      return func.apply(null, args);
	    }, wait);
	  };

	  // Defers a function, scheduling it to run after the current call stack has
	  // cleared.
	  _.defer = _.partial(_.delay, _, 1);

	  // Returns a function, that, when invoked, will only be triggered at most once
	  // during a given window of time. Normally, the throttled function will run
	  // as much as it can, without ever going more than once per `wait` duration;
	  // but if you'd like to disable the execution on the leading edge, pass
	  // `{leading: false}`. To disable execution on the trailing edge, ditto.
	  _.throttle = function(func, wait, options) {
	    var context, args, result;
	    var timeout = null;
	    var previous = 0;
	    if (!options) options = {};
	    var later = function() {
	      previous = options.leading === false ? 0 : _.now();
	      timeout = null;
	      result = func.apply(context, args);
	      if (!timeout) context = args = null;
	    };
	    return function() {
	      var now = _.now();
	      if (!previous && options.leading === false) previous = now;
	      var remaining = wait - (now - previous);
	      context = this;
	      args = arguments;
	      if (remaining <= 0 || remaining > wait) {
	        if (timeout) {
	          clearTimeout(timeout);
	          timeout = null;
	        }
	        previous = now;
	        result = func.apply(context, args);
	        if (!timeout) context = args = null;
	      } else if (!timeout && options.trailing !== false) {
	        timeout = setTimeout(later, remaining);
	      }
	      return result;
	    };
	  };

	  // Returns a function, that, as long as it continues to be invoked, will not
	  // be triggered. The function will be called after it stops being called for
	  // N milliseconds. If `immediate` is passed, trigger the function on the
	  // leading edge, instead of the trailing.
	  _.debounce = function(func, wait, immediate) {
	    var timeout, args, context, timestamp, result;

	    var later = function() {
	      var last = _.now() - timestamp;

	      if (last < wait && last >= 0) {
	        timeout = setTimeout(later, wait - last);
	      } else {
	        timeout = null;
	        if (!immediate) {
	          result = func.apply(context, args);
	          if (!timeout) context = args = null;
	        }
	      }
	    };

	    return function() {
	      context = this;
	      args = arguments;
	      timestamp = _.now();
	      var callNow = immediate && !timeout;
	      if (!timeout) timeout = setTimeout(later, wait);
	      if (callNow) {
	        result = func.apply(context, args);
	        context = args = null;
	      }

	      return result;
	    };
	  };

	  // Returns the first function passed as an argument to the second,
	  // allowing you to adjust arguments, run code before and after, and
	  // conditionally execute the original function.
	  _.wrap = function(func, wrapper) {
	    return _.partial(wrapper, func);
	  };

	  // Returns a negated version of the passed-in predicate.
	  _.negate = function(predicate) {
	    return function() {
	      return !predicate.apply(this, arguments);
	    };
	  };

	  // Returns a function that is the composition of a list of functions, each
	  // consuming the return value of the function that follows.
	  _.compose = function() {
	    var args = arguments;
	    var start = args.length - 1;
	    return function() {
	      var i = start;
	      var result = args[start].apply(this, arguments);
	      while (i--) result = args[i].call(this, result);
	      return result;
	    };
	  };

	  // Returns a function that will only be executed on and after the Nth call.
	  _.after = function(times, func) {
	    return function() {
	      if (--times < 1) {
	        return func.apply(this, arguments);
	      }
	    };
	  };

	  // Returns a function that will only be executed up to (but not including) the Nth call.
	  _.before = function(times, func) {
	    var memo;
	    return function() {
	      if (--times > 0) {
	        memo = func.apply(this, arguments);
	      }
	      if (times <= 1) func = null;
	      return memo;
	    };
	  };

	  // Returns a function that will be executed at most one time, no matter how
	  // often you call it. Useful for lazy initialization.
	  _.once = _.partial(_.before, 2);

	  // Object Functions
	  // ----------------

	  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
	  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
	  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
	                      'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

	  function collectNonEnumProps(obj, keys) {
	    var nonEnumIdx = nonEnumerableProps.length;
	    var constructor = obj.constructor;
	    var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

	    // Constructor is a special case.
	    var prop = 'constructor';
	    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

	    while (nonEnumIdx--) {
	      prop = nonEnumerableProps[nonEnumIdx];
	      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
	        keys.push(prop);
	      }
	    }
	  }

	  // Retrieve the names of an object's own properties.
	  // Delegates to **ECMAScript 5**'s native `Object.keys`
	  _.keys = function(obj) {
	    if (!_.isObject(obj)) return [];
	    if (nativeKeys) return nativeKeys(obj);
	    var keys = [];
	    for (var key in obj) if (_.has(obj, key)) keys.push(key);
	    // Ahem, IE < 9.
	    if (hasEnumBug) collectNonEnumProps(obj, keys);
	    return keys;
	  };

	  // Retrieve all the property names of an object.
	  _.allKeys = function(obj) {
	    if (!_.isObject(obj)) return [];
	    var keys = [];
	    for (var key in obj) keys.push(key);
	    // Ahem, IE < 9.
	    if (hasEnumBug) collectNonEnumProps(obj, keys);
	    return keys;
	  };

	  // Retrieve the values of an object's properties.
	  _.values = function(obj) {
	    var keys = _.keys(obj);
	    var length = keys.length;
	    var values = Array(length);
	    for (var i = 0; i < length; i++) {
	      values[i] = obj[keys[i]];
	    }
	    return values;
	  };

	  // Returns the results of applying the iteratee to each element of the object
	  // In contrast to _.map it returns an object
	  _.mapObject = function(obj, iteratee, context) {
	    iteratee = cb(iteratee, context);
	    var keys =  _.keys(obj),
	          length = keys.length,
	          results = {},
	          currentKey;
	      for (var index = 0; index < length; index++) {
	        currentKey = keys[index];
	        results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
	      }
	      return results;
	  };

	  // Convert an object into a list of `[key, value]` pairs.
	  _.pairs = function(obj) {
	    var keys = _.keys(obj);
	    var length = keys.length;
	    var pairs = Array(length);
	    for (var i = 0; i < length; i++) {
	      pairs[i] = [keys[i], obj[keys[i]]];
	    }
	    return pairs;
	  };

	  // Invert the keys and values of an object. The values must be serializable.
	  _.invert = function(obj) {
	    var result = {};
	    var keys = _.keys(obj);
	    for (var i = 0, length = keys.length; i < length; i++) {
	      result[obj[keys[i]]] = keys[i];
	    }
	    return result;
	  };

	  // Return a sorted list of the function names available on the object.
	  // Aliased as `methods`
	  _.functions = _.methods = function(obj) {
	    var names = [];
	    for (var key in obj) {
	      if (_.isFunction(obj[key])) names.push(key);
	    }
	    return names.sort();
	  };

	  // Extend a given object with all the properties in passed-in object(s).
	  _.extend = createAssigner(_.allKeys);

	  // Assigns a given object with all the own properties in the passed-in object(s)
	  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
	  _.extendOwn = _.assign = createAssigner(_.keys);

	  // Returns the first key on an object that passes a predicate test
	  _.findKey = function(obj, predicate, context) {
	    predicate = cb(predicate, context);
	    var keys = _.keys(obj), key;
	    for (var i = 0, length = keys.length; i < length; i++) {
	      key = keys[i];
	      if (predicate(obj[key], key, obj)) return key;
	    }
	  };

	  // Return a copy of the object only containing the whitelisted properties.
	  _.pick = function(object, oiteratee, context) {
	    var result = {}, obj = object, iteratee, keys;
	    if (obj == null) return result;
	    if (_.isFunction(oiteratee)) {
	      keys = _.allKeys(obj);
	      iteratee = optimizeCb(oiteratee, context);
	    } else {
	      keys = flatten(arguments, false, false, 1);
	      iteratee = function(value, key, obj) { return key in obj; };
	      obj = Object(obj);
	    }
	    for (var i = 0, length = keys.length; i < length; i++) {
	      var key = keys[i];
	      var value = obj[key];
	      if (iteratee(value, key, obj)) result[key] = value;
	    }
	    return result;
	  };

	   // Return a copy of the object without the blacklisted properties.
	  _.omit = function(obj, iteratee, context) {
	    if (_.isFunction(iteratee)) {
	      iteratee = _.negate(iteratee);
	    } else {
	      var keys = _.map(flatten(arguments, false, false, 1), String);
	      iteratee = function(value, key) {
	        return !_.contains(keys, key);
	      };
	    }
	    return _.pick(obj, iteratee, context);
	  };

	  // Fill in a given object with default properties.
	  _.defaults = createAssigner(_.allKeys, true);

	  // Creates an object that inherits from the given prototype object.
	  // If additional properties are provided then they will be added to the
	  // created object.
	  _.create = function(prototype, props) {
	    var result = baseCreate(prototype);
	    if (props) _.extendOwn(result, props);
	    return result;
	  };

	  // Create a (shallow-cloned) duplicate of an object.
	  _.clone = function(obj) {
	    if (!_.isObject(obj)) return obj;
	    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
	  };

	  // Invokes interceptor with the obj, and then returns obj.
	  // The primary purpose of this method is to "tap into" a method chain, in
	  // order to perform operations on intermediate results within the chain.
	  _.tap = function(obj, interceptor) {
	    interceptor(obj);
	    return obj;
	  };

	  // Returns whether an object has a given set of `key:value` pairs.
	  _.isMatch = function(object, attrs) {
	    var keys = _.keys(attrs), length = keys.length;
	    if (object == null) return !length;
	    var obj = Object(object);
	    for (var i = 0; i < length; i++) {
	      var key = keys[i];
	      if (attrs[key] !== obj[key] || !(key in obj)) return false;
	    }
	    return true;
	  };


	  // Internal recursive comparison function for `isEqual`.
	  var eq = function(a, b, aStack, bStack) {
	    // Identical objects are equal. `0 === -0`, but they aren't identical.
	    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
	    if (a === b) return a !== 0 || 1 / a === 1 / b;
	    // A strict comparison is necessary because `null == undefined`.
	    if (a == null || b == null) return a === b;
	    // Unwrap any wrapped objects.
	    if (a instanceof _) a = a._wrapped;
	    if (b instanceof _) b = b._wrapped;
	    // Compare `[[Class]]` names.
	    var className = toString.call(a);
	    if (className !== toString.call(b)) return false;
	    switch (className) {
	      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
	      case '[object RegExp]':
	      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
	      case '[object String]':
	        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
	        // equivalent to `new String("5")`.
	        return '' + a === '' + b;
	      case '[object Number]':
	        // `NaN`s are equivalent, but non-reflexive.
	        // Object(NaN) is equivalent to NaN
	        if (+a !== +a) return +b !== +b;
	        // An `egal` comparison is performed for other numeric values.
	        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
	      case '[object Date]':
	      case '[object Boolean]':
	        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
	        // millisecond representations. Note that invalid dates with millisecond representations
	        // of `NaN` are not equivalent.
	        return +a === +b;
	    }

	    var areArrays = className === '[object Array]';
	    if (!areArrays) {
	      if (typeof a != 'object' || typeof b != 'object') return false;

	      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
	      // from different frames are.
	      var aCtor = a.constructor, bCtor = b.constructor;
	      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
	                               _.isFunction(bCtor) && bCtor instanceof bCtor)
	                          && ('constructor' in a && 'constructor' in b)) {
	        return false;
	      }
	    }
	    // Assume equality for cyclic structures. The algorithm for detecting cyclic
	    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.

	    // Initializing stack of traversed objects.
	    // It's done here since we only need them for objects and arrays comparison.
	    aStack = aStack || [];
	    bStack = bStack || [];
	    var length = aStack.length;
	    while (length--) {
	      // Linear search. Performance is inversely proportional to the number of
	      // unique nested structures.
	      if (aStack[length] === a) return bStack[length] === b;
	    }

	    // Add the first object to the stack of traversed objects.
	    aStack.push(a);
	    bStack.push(b);

	    // Recursively compare objects and arrays.
	    if (areArrays) {
	      // Compare array lengths to determine if a deep comparison is necessary.
	      length = a.length;
	      if (length !== b.length) return false;
	      // Deep compare the contents, ignoring non-numeric properties.
	      while (length--) {
	        if (!eq(a[length], b[length], aStack, bStack)) return false;
	      }
	    } else {
	      // Deep compare objects.
	      var keys = _.keys(a), key;
	      length = keys.length;
	      // Ensure that both objects contain the same number of properties before comparing deep equality.
	      if (_.keys(b).length !== length) return false;
	      while (length--) {
	        // Deep compare each member
	        key = keys[length];
	        if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
	      }
	    }
	    // Remove the first object from the stack of traversed objects.
	    aStack.pop();
	    bStack.pop();
	    return true;
	  };

	  // Perform a deep comparison to check if two objects are equal.
	  _.isEqual = function(a, b) {
	    return eq(a, b);
	  };

	  // Is a given array, string, or object empty?
	  // An "empty" object has no enumerable own-properties.
	  _.isEmpty = function(obj) {
	    if (obj == null) return true;
	    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
	    return _.keys(obj).length === 0;
	  };

	  // Is a given value a DOM element?
	  _.isElement = function(obj) {
	    return !!(obj && obj.nodeType === 1);
	  };

	  // Is a given value an array?
	  // Delegates to ECMA5's native Array.isArray
	  _.isArray = nativeIsArray || function(obj) {
	    return toString.call(obj) === '[object Array]';
	  };

	  // Is a given variable an object?
	  _.isObject = function(obj) {
	    var type = typeof obj;
	    return type === 'function' || type === 'object' && !!obj;
	  };

	  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
	  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
	    _['is' + name] = function(obj) {
	      return toString.call(obj) === '[object ' + name + ']';
	    };
	  });

	  // Define a fallback version of the method in browsers (ahem, IE < 9), where
	  // there isn't any inspectable "Arguments" type.
	  if (!_.isArguments(arguments)) {
	    _.isArguments = function(obj) {
	      return _.has(obj, 'callee');
	    };
	  }

	  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
	  // IE 11 (#1621), and in Safari 8 (#1929).
	  if (typeof /./ != 'function' && typeof Int8Array != 'object') {
	    _.isFunction = function(obj) {
	      return typeof obj == 'function' || false;
	    };
	  }

	  // Is a given object a finite number?
	  _.isFinite = function(obj) {
	    return isFinite(obj) && !isNaN(parseFloat(obj));
	  };

	  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
	  _.isNaN = function(obj) {
	    return _.isNumber(obj) && obj !== +obj;
	  };

	  // Is a given value a boolean?
	  _.isBoolean = function(obj) {
	    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
	  };

	  // Is a given value equal to null?
	  _.isNull = function(obj) {
	    return obj === null;
	  };

	  // Is a given variable undefined?
	  _.isUndefined = function(obj) {
	    return obj === void 0;
	  };

	  // Shortcut function for checking if an object has a given property directly
	  // on itself (in other words, not on a prototype).
	  _.has = function(obj, key) {
	    return obj != null && hasOwnProperty.call(obj, key);
	  };

	  // Utility Functions
	  // -----------------

	  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
	  // previous owner. Returns a reference to the Underscore object.
	  _.noConflict = function() {
	    root._ = previousUnderscore;
	    return this;
	  };

	  // Keep the identity function around for default iteratees.
	  _.identity = function(value) {
	    return value;
	  };

	  // Predicate-generating functions. Often useful outside of Underscore.
	  _.constant = function(value) {
	    return function() {
	      return value;
	    };
	  };

	  _.noop = function(){};

	  _.property = property;

	  // Generates a function for a given object that returns a given property.
	  _.propertyOf = function(obj) {
	    return obj == null ? function(){} : function(key) {
	      return obj[key];
	    };
	  };

	  // Returns a predicate for checking whether an object has a given set of
	  // `key:value` pairs.
	  _.matcher = _.matches = function(attrs) {
	    attrs = _.extendOwn({}, attrs);
	    return function(obj) {
	      return _.isMatch(obj, attrs);
	    };
	  };

	  // Run a function **n** times.
	  _.times = function(n, iteratee, context) {
	    var accum = Array(Math.max(0, n));
	    iteratee = optimizeCb(iteratee, context, 1);
	    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
	    return accum;
	  };

	  // Return a random integer between min and max (inclusive).
	  _.random = function(min, max) {
	    if (max == null) {
	      max = min;
	      min = 0;
	    }
	    return min + Math.floor(Math.random() * (max - min + 1));
	  };

	  // A (possibly faster) way to get the current timestamp as an integer.
	  _.now = Date.now || function() {
	    return new Date().getTime();
	  };

	   // List of HTML entities for escaping.
	  var escapeMap = {
	    '&': '&amp;',
	    '<': '&lt;',
	    '>': '&gt;',
	    '"': '&quot;',
	    "'": '&#x27;',
	    '`': '&#x60;'
	  };
	  var unescapeMap = _.invert(escapeMap);

	  // Functions for escaping and unescaping strings to/from HTML interpolation.
	  var createEscaper = function(map) {
	    var escaper = function(match) {
	      return map[match];
	    };
	    // Regexes for identifying a key that needs to be escaped
	    var source = '(?:' + _.keys(map).join('|') + ')';
	    var testRegexp = RegExp(source);
	    var replaceRegexp = RegExp(source, 'g');
	    return function(string) {
	      string = string == null ? '' : '' + string;
	      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
	    };
	  };
	  _.escape = createEscaper(escapeMap);
	  _.unescape = createEscaper(unescapeMap);

	  // If the value of the named `property` is a function then invoke it with the
	  // `object` as context; otherwise, return it.
	  _.result = function(object, property, fallback) {
	    var value = object == null ? void 0 : object[property];
	    if (value === void 0) {
	      value = fallback;
	    }
	    return _.isFunction(value) ? value.call(object) : value;
	  };

	  // Generate a unique integer id (unique within the entire client session).
	  // Useful for temporary DOM ids.
	  var idCounter = 0;
	  _.uniqueId = function(prefix) {
	    var id = ++idCounter + '';
	    return prefix ? prefix + id : id;
	  };

	  // By default, Underscore uses ERB-style template delimiters, change the
	  // following template settings to use alternative delimiters.
	  _.templateSettings = {
	    evaluate    : /<%([\s\S]+?)%>/g,
	    interpolate : /<%=([\s\S]+?)%>/g,
	    escape      : /<%-([\s\S]+?)%>/g
	  };

	  // When customizing `templateSettings`, if you don't want to define an
	  // interpolation, evaluation or escaping regex, we need one that is
	  // guaranteed not to match.
	  var noMatch = /(.)^/;

	  // Certain characters need to be escaped so that they can be put into a
	  // string literal.
	  var escapes = {
	    "'":      "'",
	    '\\':     '\\',
	    '\r':     'r',
	    '\n':     'n',
	    '\u2028': 'u2028',
	    '\u2029': 'u2029'
	  };

	  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;

	  var escapeChar = function(match) {
	    return '\\' + escapes[match];
	  };

	  // JavaScript micro-templating, similar to John Resig's implementation.
	  // Underscore templating handles arbitrary delimiters, preserves whitespace,
	  // and correctly escapes quotes within interpolated code.
	  // NB: `oldSettings` only exists for backwards compatibility.
	  _.template = function(text, settings, oldSettings) {
	    if (!settings && oldSettings) settings = oldSettings;
	    settings = _.defaults({}, settings, _.templateSettings);

	    // Combine delimiters into one regular expression via alternation.
	    var matcher = RegExp([
	      (settings.escape || noMatch).source,
	      (settings.interpolate || noMatch).source,
	      (settings.evaluate || noMatch).source
	    ].join('|') + '|$', 'g');

	    // Compile the template source, escaping string literals appropriately.
	    var index = 0;
	    var source = "__p+='";
	    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
	      source += text.slice(index, offset).replace(escaper, escapeChar);
	      index = offset + match.length;

	      if (escape) {
	        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
	      } else if (interpolate) {
	        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
	      } else if (evaluate) {
	        source += "';\n" + evaluate + "\n__p+='";
	      }

	      // Adobe VMs need the match returned to produce the correct offest.
	      return match;
	    });
	    source += "';\n";

	    // If a variable is not specified, place data values in local scope.
	    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

	    source = "var __t,__p='',__j=Array.prototype.join," +
	      "print=function(){__p+=__j.call(arguments,'');};\n" +
	      source + 'return __p;\n';

	    try {
	      var render = new Function(settings.variable || 'obj', '_', source);
	    } catch (e) {
	      e.source = source;
	      throw e;
	    }

	    var template = function(data) {
	      return render.call(this, data, _);
	    };

	    // Provide the compiled source as a convenience for precompilation.
	    var argument = settings.variable || 'obj';
	    template.source = 'function(' + argument + '){\n' + source + '}';

	    return template;
	  };

	  // Add a "chain" function. Start chaining a wrapped Underscore object.
	  _.chain = function(obj) {
	    var instance = _(obj);
	    instance._chain = true;
	    return instance;
	  };

	  // OOP
	  // ---------------
	  // If Underscore is called as a function, it returns a wrapped object that
	  // can be used OO-style. This wrapper holds altered versions of all the
	  // underscore functions. Wrapped objects may be chained.

	  // Helper function to continue chaining intermediate results.
	  var result = function(instance, obj) {
	    return instance._chain ? _(obj).chain() : obj;
	  };

	  // Add your own custom functions to the Underscore object.
	  _.mixin = function(obj) {
	    _.each(_.functions(obj), function(name) {
	      var func = _[name] = obj[name];
	      _.prototype[name] = function() {
	        var args = [this._wrapped];
	        push.apply(args, arguments);
	        return result(this, func.apply(_, args));
	      };
	    });
	  };

	  // Add all of the Underscore functions to the wrapper object.
	  _.mixin(_);

	  // Add all mutator Array functions to the wrapper.
	  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
	    var method = ArrayProto[name];
	    _.prototype[name] = function() {
	      var obj = this._wrapped;
	      method.apply(obj, arguments);
	      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
	      return result(this, obj);
	    };
	  });

	  // Add all accessor Array functions to the wrapper.
	  _.each(['concat', 'join', 'slice'], function(name) {
	    var method = ArrayProto[name];
	    _.prototype[name] = function() {
	      return result(this, method.apply(this._wrapped, arguments));
	    };
	  });

	  // Extracts the result from a wrapped and chained object.
	  _.prototype.value = function() {
	    return this._wrapped;
	  };

	  // Provide unwrapping proxy for some methods used in engine operations
	  // such as arithmetic and JSON stringification.
	  _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

	  _.prototype.toString = function() {
	    return '' + this._wrapped;
	  };

	  // AMD registration happens at the end for compatibility with AMD loaders
	  // that may not enforce next-turn semantics on modules. Even though general
	  // practice for AMD registration is to be anonymous, underscore registers
	  // as a named module because, like jQuery, it is a base library that is
	  // popular enough to be bundled in a third party lib, but not be part of
	  // an AMD load request. Those cases could generate an error when an
	  // anonymous define() is called outside of a loader request.
	  if (true) {
	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {
	      return _;
	    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	  }
	}.call(this));


/***/ },

/***/ 289:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;var require;var require;/*!
	 * Select2 4.0.5
	 * https://select2.github.io
	 *
	 * Released under the MIT license
	 * https://github.com/select2/select2/blob/master/LICENSE.md
	 */
	(function (factory) {
	  if (true) {
	    // AMD. Register as an anonymous module.
	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(275)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	  } else if (typeof module === 'object' && module.exports) {
	    // Node/CommonJS
	    module.exports = function (root, jQuery) {
	      if (jQuery === undefined) {
	        // require('jQuery') returns a factory that requires window to
	        // build a jQuery instance, we normalize how we use modules
	        // that require this pattern but the window provided is a noop
	        // if it's defined (how jquery works)
	        if (typeof window !== 'undefined') {
	          jQuery = require('jquery');
	        }
	        else {
	          jQuery = require('jquery')(root);
	        }
	      }
	      factory(jQuery);
	      return jQuery;
	    };
	  } else {
	    // Browser globals
	    factory(jQuery);
	  }
	} (function (jQuery) {
	  // This is needed so we can catch the AMD loader configuration and use it
	  // The inner file should be wrapped (by `banner.start.js`) in a function that
	  // returns the AMD loader references.
	  var S2 =(function () {
	  // Restore the Select2 AMD loader so it can be used
	  // Needed mostly in the language files, where the loader is not inserted
	  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
	    var S2 = jQuery.fn.select2.amd;
	  }
	var S2;(function () { if (!S2 || !S2.requirejs) {
	if (!S2) { S2 = {}; } else { require = S2; }
	/**
	 * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
	 * Released under MIT license, http://github.com/requirejs/almond/LICENSE
	 */
	//Going sloppy to avoid 'use strict' string cost, but strict practices should
	//be followed.
	/*global setTimeout: false */

	var requirejs, require, define;
	(function (undef) {
	    var main, req, makeMap, handlers,
	        defined = {},
	        waiting = {},
	        config = {},
	        defining = {},
	        hasOwn = Object.prototype.hasOwnProperty,
	        aps = [].slice,
	        jsSuffixRegExp = /\.js$/;

	    function hasProp(obj, prop) {
	        return hasOwn.call(obj, prop);
	    }

	    /**
	     * Given a relative module name, like ./something, normalize it to
	     * a real name that can be mapped to a path.
	     * @param {String} name the relative name
	     * @param {String} baseName a real name that the name arg is relative
	     * to.
	     * @returns {String} normalized name
	     */
	    function normalize(name, baseName) {
	        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
	            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
	            baseParts = baseName && baseName.split("/"),
	            map = config.map,
	            starMap = (map && map['*']) || {};

	        //Adjust any relative paths.
	        if (name) {
	            name = name.split('/');
	            lastIndex = name.length - 1;

	            // If wanting node ID compatibility, strip .js from end
	            // of IDs. Have to do this here, and not in nameToUrl
	            // because node allows either .js or non .js to map
	            // to same file.
	            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
	                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
	            }

	            // Starts with a '.' so need the baseName
	            if (name[0].charAt(0) === '.' && baseParts) {
	                //Convert baseName to array, and lop off the last part,
	                //so that . matches that 'directory' and not name of the baseName's
	                //module. For instance, baseName of 'one/two/three', maps to
	                //'one/two/three.js', but we want the directory, 'one/two' for
	                //this normalization.
	                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
	                name = normalizedBaseParts.concat(name);
	            }

	            //start trimDots
	            for (i = 0; i < name.length; i++) {
	                part = name[i];
	                if (part === '.') {
	                    name.splice(i, 1);
	                    i -= 1;
	                } else if (part === '..') {
	                    // If at the start, or previous value is still ..,
	                    // keep them so that when converted to a path it may
	                    // still work when converted to a path, even though
	                    // as an ID it is less than ideal. In larger point
	                    // releases, may be better to just kick out an error.
	                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
	                        continue;
	                    } else if (i > 0) {
	                        name.splice(i - 1, 2);
	                        i -= 2;
	                    }
	                }
	            }
	            //end trimDots

	            name = name.join('/');
	        }

	        //Apply map config if available.
	        if ((baseParts || starMap) && map) {
	            nameParts = name.split('/');

	            for (i = nameParts.length; i > 0; i -= 1) {
	                nameSegment = nameParts.slice(0, i).join("/");

	                if (baseParts) {
	                    //Find the longest baseName segment match in the config.
	                    //So, do joins on the biggest to smallest lengths of baseParts.
	                    for (j = baseParts.length; j > 0; j -= 1) {
	                        mapValue = map[baseParts.slice(0, j).join('/')];

	                        //baseName segment has  config, find if it has one for
	                        //this name.
	                        if (mapValue) {
	                            mapValue = mapValue[nameSegment];
	                            if (mapValue) {
	                                //Match, update name to the new value.
	                                foundMap = mapValue;
	                                foundI = i;
	                                break;
	                            }
	                        }
	                    }
	                }

	                if (foundMap) {
	                    break;
	                }

	                //Check for a star map match, but just hold on to it,
	                //if there is a shorter segment match later in a matching
	                //config, then favor over this star map.
	                if (!foundStarMap && starMap && starMap[nameSegment]) {
	                    foundStarMap = starMap[nameSegment];
	                    starI = i;
	                }
	            }

	            if (!foundMap && foundStarMap) {
	                foundMap = foundStarMap;
	                foundI = starI;
	            }

	            if (foundMap) {
	                nameParts.splice(0, foundI, foundMap);
	                name = nameParts.join('/');
	            }
	        }

	        return name;
	    }

	    function makeRequire(relName, forceSync) {
	        return function () {
	            //A version of a require function that passes a moduleName
	            //value for items that may need to
	            //look up paths relative to the moduleName
	            var args = aps.call(arguments, 0);

	            //If first arg is not require('string'), and there is only
	            //one arg, it is the array form without a callback. Insert
	            //a null so that the following concat is correct.
	            if (typeof args[0] !== 'string' && args.length === 1) {
	                args.push(null);
	            }
	            return req.apply(undef, args.concat([relName, forceSync]));
	        };
	    }

	    function makeNormalize(relName) {
	        return function (name) {
	            return normalize(name, relName);
	        };
	    }

	    function makeLoad(depName) {
	        return function (value) {
	            defined[depName] = value;
	        };
	    }

	    function callDep(name) {
	        if (hasProp(waiting, name)) {
	            var args = waiting[name];
	            delete waiting[name];
	            defining[name] = true;
	            main.apply(undef, args);
	        }

	        if (!hasProp(defined, name) && !hasProp(defining, name)) {
	            throw new Error('No ' + name);
	        }
	        return defined[name];
	    }

	    //Turns a plugin!resource to [plugin, resource]
	    //with the plugin being undefined if the name
	    //did not have a plugin prefix.
	    function splitPrefix(name) {
	        var prefix,
	            index = name ? name.indexOf('!') : -1;
	        if (index > -1) {
	            prefix = name.substring(0, index);
	            name = name.substring(index + 1, name.length);
	        }
	        return [prefix, name];
	    }

	    //Creates a parts array for a relName where first part is plugin ID,
	    //second part is resource ID. Assumes relName has already been normalized.
	    function makeRelParts(relName) {
	        return relName ? splitPrefix(relName) : [];
	    }

	    /**
	     * Makes a name map, normalizing the name, and using a plugin
	     * for normalization if necessary. Grabs a ref to plugin
	     * too, as an optimization.
	     */
	    makeMap = function (name, relParts) {
	        var plugin,
	            parts = splitPrefix(name),
	            prefix = parts[0],
	            relResourceName = relParts[1];

	        name = parts[1];

	        if (prefix) {
	            prefix = normalize(prefix, relResourceName);
	            plugin = callDep(prefix);
	        }

	        //Normalize according
	        if (prefix) {
	            if (plugin && plugin.normalize) {
	                name = plugin.normalize(name, makeNormalize(relResourceName));
	            } else {
	                name = normalize(name, relResourceName);
	            }
	        } else {
	            name = normalize(name, relResourceName);
	            parts = splitPrefix(name);
	            prefix = parts[0];
	            name = parts[1];
	            if (prefix) {
	                plugin = callDep(prefix);
	            }
	        }

	        //Using ridiculous property names for space reasons
	        return {
	            f: prefix ? prefix + '!' + name : name, //fullName
	            n: name,
	            pr: prefix,
	            p: plugin
	        };
	    };

	    function makeConfig(name) {
	        return function () {
	            return (config && config.config && config.config[name]) || {};
	        };
	    }

	    handlers = {
	        require: function (name) {
	            return makeRequire(name);
	        },
	        exports: function (name) {
	            var e = defined[name];
	            if (typeof e !== 'undefined') {
	                return e;
	            } else {
	                return (defined[name] = {});
	            }
	        },
	        module: function (name) {
	            return {
	                id: name,
	                uri: '',
	                exports: defined[name],
	                config: makeConfig(name)
	            };
	        }
	    };

	    main = function (name, deps, callback, relName) {
	        var cjsModule, depName, ret, map, i, relParts,
	            args = [],
	            callbackType = typeof callback,
	            usingExports;

	        //Use name if no relName
	        relName = relName || name;
	        relParts = makeRelParts(relName);

	        //Call the callback to define the module, if necessary.
	        if (callbackType === 'undefined' || callbackType === 'function') {
	            //Pull out the defined dependencies and pass the ordered
	            //values to the callback.
	            //Default to [require, exports, module] if no deps
	            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
	            for (i = 0; i < deps.length; i += 1) {
	                map = makeMap(deps[i], relParts);
	                depName = map.f;

	                //Fast path CommonJS standard dependencies.
	                if (depName === "require") {
	                    args[i] = handlers.require(name);
	                } else if (depName === "exports") {
	                    //CommonJS module spec 1.1
	                    args[i] = handlers.exports(name);
	                    usingExports = true;
	                } else if (depName === "module") {
	                    //CommonJS module spec 1.1
	                    cjsModule = args[i] = handlers.module(name);
	                } else if (hasProp(defined, depName) ||
	                           hasProp(waiting, depName) ||
	                           hasProp(defining, depName)) {
	                    args[i] = callDep(depName);
	                } else if (map.p) {
	                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
	                    args[i] = defined[depName];
	                } else {
	                    throw new Error(name + ' missing ' + depName);
	                }
	            }

	            ret = callback ? callback.apply(defined[name], args) : undefined;

	            if (name) {
	                //If setting exports via "module" is in play,
	                //favor that over return value and exports. After that,
	                //favor a non-undefined return value over exports use.
	                if (cjsModule && cjsModule.exports !== undef &&
	                        cjsModule.exports !== defined[name]) {
	                    defined[name] = cjsModule.exports;
	                } else if (ret !== undef || !usingExports) {
	                    //Use the return value from the function.
	                    defined[name] = ret;
	                }
	            }
	        } else if (name) {
	            //May just be an object definition for the module. Only
	            //worry about defining if have a module name.
	            defined[name] = callback;
	        }
	    };

	    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
	        if (typeof deps === "string") {
	            if (handlers[deps]) {
	                //callback in this case is really relName
	                return handlers[deps](callback);
	            }
	            //Just return the module wanted. In this scenario, the
	            //deps arg is the module name, and second arg (if passed)
	            //is just the relName.
	            //Normalize module name, if it contains . or ..
	            return callDep(makeMap(deps, makeRelParts(callback)).f);
	        } else if (!deps.splice) {
	            //deps is a config object, not an array.
	            config = deps;
	            if (config.deps) {
	                req(config.deps, config.callback);
	            }
	            if (!callback) {
	                return;
	            }

	            if (callback.splice) {
	                //callback is an array, which means it is a dependency list.
	                //Adjust args if there are dependencies
	                deps = callback;
	                callback = relName;
	                relName = null;
	            } else {
	                deps = undef;
	            }
	        }

	        //Support require(['a'])
	        callback = callback || function () {};

	        //If relName is a function, it is an errback handler,
	        //so remove it.
	        if (typeof relName === 'function') {
	            relName = forceSync;
	            forceSync = alt;
	        }

	        //Simulate async callback;
	        if (forceSync) {
	            main(undef, deps, callback, relName);
	        } else {
	            //Using a non-zero value because of concern for what old browsers
	            //do, and latest browsers "upgrade" to 4 if lower value is used:
	            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
	            //If want a value immediately, use require('id') instead -- something
	            //that works in almond on the global level, but not guaranteed and
	            //unlikely to work in other AMD implementations.
	            setTimeout(function () {
	                main(undef, deps, callback, relName);
	            }, 4);
	        }

	        return req;
	    };

	    /**
	     * Just drops the config on the floor, but returns req in case
	     * the config return value is used.
	     */
	    req.config = function (cfg) {
	        return req(cfg);
	    };

	    /**
	     * Expose module registry for debugging and tooling
	     */
	    requirejs._defined = defined;

	    define = function (name, deps, callback) {
	        if (typeof name !== 'string') {
	            throw new Error('See almond README: incorrect module build, no module name');
	        }

	        //This module may not have dependencies
	        if (!deps.splice) {
	            //deps is not an array, so probably means
	            //an object literal or factory function for
	            //the value. Adjust args.
	            callback = deps;
	            deps = [];
	        }

	        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
	            waiting[name] = [name, deps, callback];
	        }
	    };

	    define.amd = {
	        jQuery: true
	    };
	}());

	S2.requirejs = requirejs;S2.require = require;S2.define = define;
	}
	}());
	S2.define("almond", function(){});

	/* global jQuery:false, $:false */
	S2.define('jquery',[],function () {
	  var _$ = jQuery || $;

	  if (_$ == null && console && console.error) {
	    console.error(
	      'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
	      'found. Make sure that you are including jQuery before Select2 on your ' +
	      'web page.'
	    );
	  }

	  return _$;
	});

	S2.define('select2/utils',[
	  'jquery'
	], function ($) {
	  var Utils = {};

	  Utils.Extend = function (ChildClass, SuperClass) {
	    var __hasProp = {}.hasOwnProperty;

	    function BaseConstructor () {
	      this.constructor = ChildClass;
	    }

	    for (var key in SuperClass) {
	      if (__hasProp.call(SuperClass, key)) {
	        ChildClass[key] = SuperClass[key];
	      }
	    }

	    BaseConstructor.prototype = SuperClass.prototype;
	    ChildClass.prototype = new BaseConstructor();
	    ChildClass.__super__ = SuperClass.prototype;

	    return ChildClass;
	  };

	  function getMethods (theClass) {
	    var proto = theClass.prototype;

	    var methods = [];

	    for (var methodName in proto) {
	      var m = proto[methodName];

	      if (typeof m !== 'function') {
	        continue;
	      }

	      if (methodName === 'constructor') {
	        continue;
	      }

	      methods.push(methodName);
	    }

	    return methods;
	  }

	  Utils.Decorate = function (SuperClass, DecoratorClass) {
	    var decoratedMethods = getMethods(DecoratorClass);
	    var superMethods = getMethods(SuperClass);

	    function DecoratedClass () {
	      var unshift = Array.prototype.unshift;

	      var argCount = DecoratorClass.prototype.constructor.length;

	      var calledConstructor = SuperClass.prototype.constructor;

	      if (argCount > 0) {
	        unshift.call(arguments, SuperClass.prototype.constructor);

	        calledConstructor = DecoratorClass.prototype.constructor;
	      }

	      calledConstructor.apply(this, arguments);
	    }

	    DecoratorClass.displayName = SuperClass.displayName;

	    function ctr () {
	      this.constructor = DecoratedClass;
	    }

	    DecoratedClass.prototype = new ctr();

	    for (var m = 0; m < superMethods.length; m++) {
	        var superMethod = superMethods[m];

	        DecoratedClass.prototype[superMethod] =
	          SuperClass.prototype[superMethod];
	    }

	    var calledMethod = function (methodName) {
	      // Stub out the original method if it's not decorating an actual method
	      var originalMethod = function () {};

	      if (methodName in DecoratedClass.prototype) {
	        originalMethod = DecoratedClass.prototype[methodName];
	      }

	      var decoratedMethod = DecoratorClass.prototype[methodName];

	      return function () {
	        var unshift = Array.prototype.unshift;

	        unshift.call(arguments, originalMethod);

	        return decoratedMethod.apply(this, arguments);
	      };
	    };

	    for (var d = 0; d < decoratedMethods.length; d++) {
	      var decoratedMethod = decoratedMethods[d];

	      DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
	    }

	    return DecoratedClass;
	  };

	  var Observable = function () {
	    this.listeners = {};
	  };

	  Observable.prototype.on = function (event, callback) {
	    this.listeners = this.listeners || {};

	    if (event in this.listeners) {
	      this.listeners[event].push(callback);
	    } else {
	      this.listeners[event] = [callback];
	    }
	  };

	  Observable.prototype.trigger = function (event) {
	    var slice = Array.prototype.slice;
	    var params = slice.call(arguments, 1);

	    this.listeners = this.listeners || {};

	    // Params should always come in as an array
	    if (params == null) {
	      params = [];
	    }

	    // If there are no arguments to the event, use a temporary object
	    if (params.length === 0) {
	      params.push({});
	    }

	    // Set the `_type` of the first object to the event
	    params[0]._type = event;

	    if (event in this.listeners) {
	      this.invoke(this.listeners[event], slice.call(arguments, 1));
	    }

	    if ('*' in this.listeners) {
	      this.invoke(this.listeners['*'], arguments);
	    }
	  };

	  Observable.prototype.invoke = function (listeners, params) {
	    for (var i = 0, len = listeners.length; i < len; i++) {
	      listeners[i].apply(this, params);
	    }
	  };

	  Utils.Observable = Observable;

	  Utils.generateChars = function (length) {
	    var chars = '';

	    for (var i = 0; i < length; i++) {
	      var randomChar = Math.floor(Math.random() * 36);
	      chars += randomChar.toString(36);
	    }

	    return chars;
	  };

	  Utils.bind = function (func, context) {
	    return function () {
	      func.apply(context, arguments);
	    };
	  };

	  Utils._convertData = function (data) {
	    for (var originalKey in data) {
	      var keys = originalKey.split('-');

	      var dataLevel = data;

	      if (keys.length === 1) {
	        continue;
	      }

	      for (var k = 0; k < keys.length; k++) {
	        var key = keys[k];

	        // Lowercase the first letter
	        // By default, dash-separated becomes camelCase
	        key = key.substring(0, 1).toLowerCase() + key.substring(1);

	        if (!(key in dataLevel)) {
	          dataLevel[key] = {};
	        }

	        if (k == keys.length - 1) {
	          dataLevel[key] = data[originalKey];
	        }

	        dataLevel = dataLevel[key];
	      }

	      delete data[originalKey];
	    }

	    return data;
	  };

	  Utils.hasScroll = function (index, el) {
	    // Adapted from the function created by @ShadowScripter
	    // and adapted by @BillBarry on the Stack Exchange Code Review website.
	    // The original code can be found at
	    // http://codereview.stackexchange.com/q/13338
	    // and was designed to be used with the Sizzle selector engine.

	    var $el = $(el);
	    var overflowX = el.style.overflowX;
	    var overflowY = el.style.overflowY;

	    //Check both x and y declarations
	    if (overflowX === overflowY &&
	        (overflowY === 'hidden' || overflowY === 'visible')) {
	      return false;
	    }

	    if (overflowX === 'scroll' || overflowY === 'scroll') {
	      return true;
	    }

	    return ($el.innerHeight() < el.scrollHeight ||
	      $el.innerWidth() < el.scrollWidth);
	  };

	  Utils.escapeMarkup = function (markup) {
	    var replaceMap = {
	      '\\': '&#92;',
	      '&': '&amp;',
	      '<': '&lt;',
	      '>': '&gt;',
	      '"': '&quot;',
	      '\'': '&#39;',
	      '/': '&#47;'
	    };

	    // Do not try to escape the markup if it's not a string
	    if (typeof markup !== 'string') {
	      return markup;
	    }

	    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
	      return replaceMap[match];
	    });
	  };

	  // Append an array of jQuery nodes to a given element.
	  Utils.appendMany = function ($element, $nodes) {
	    // jQuery 1.7.x does not support $.fn.append() with an array
	    // Fall back to a jQuery object collection using $.fn.add()
	    if ($.fn.jquery.substr(0, 3) === '1.7') {
	      var $jqNodes = $();

	      $.map($nodes, function (node) {
	        $jqNodes = $jqNodes.add(node);
	      });

	      $nodes = $jqNodes;
	    }

	    $element.append($nodes);
	  };

	  return Utils;
	});

	S2.define('select2/results',[
	  'jquery',
	  './utils'
	], function ($, Utils) {
	  function Results ($element, options, dataAdapter) {
	    this.$element = $element;
	    this.data = dataAdapter;
	    this.options = options;

	    Results.__super__.constructor.call(this);
	  }

	  Utils.Extend(Results, Utils.Observable);

	  Results.prototype.render = function () {
	    var $results = $(
	      '<ul class="select2-results__options" role="tree"></ul>'
	    );

	    if (this.options.get('multiple')) {
	      $results.attr('aria-multiselectable', 'true');
	    }

	    this.$results = $results;

	    return $results;
	  };

	  Results.prototype.clear = function () {
	    this.$results.empty();
	  };

	  Results.prototype.displayMessage = function (params) {
	    var escapeMarkup = this.options.get('escapeMarkup');

	    this.clear();
	    this.hideLoading();

	    var $message = $(
	      '<li role="treeitem" aria-live="assertive"' +
	      ' class="select2-results__option"></li>'
	    );

	    var message = this.options.get('translations').get(params.message);

	    $message.append(
	      escapeMarkup(
	        message(params.args)
	      )
	    );

	    $message[0].className += ' select2-results__message';

	    this.$results.append($message);
	  };

	  Results.prototype.hideMessages = function () {
	    this.$results.find('.select2-results__message').remove();
	  };

	  Results.prototype.append = function (data) {
	    this.hideLoading();

	    var $options = [];

	    if (data.results == null || data.results.length === 0) {
	      if (this.$results.children().length === 0) {
	        this.trigger('results:message', {
	          message: 'noResults'
	        });
	      }

	      return;
	    }

	    data.results = this.sort(data.results);

	    for (var d = 0; d < data.results.length; d++) {
	      var item = data.results[d];

	      var $option = this.option(item);

	      $options.push($option);
	    }

	    this.$results.append($options);
	  };

	  Results.prototype.position = function ($results, $dropdown) {
	    var $resultsContainer = $dropdown.find('.select2-results');
	    $resultsContainer.append($results);
	  };

	  Results.prototype.sort = function (data) {
	    var sorter = this.options.get('sorter');

	    return sorter(data);
	  };

	  Results.prototype.highlightFirstItem = function () {
	    var $options = this.$results
	      .find('.select2-results__option[aria-selected]');

	    var $selected = $options.filter('[aria-selected=true]');

	    // Check if there are any selected options
	    if ($selected.length > 0) {
	      // If there are selected options, highlight the first
	      $selected.first().trigger('mouseenter');
	    } else {
	      // If there are no selected options, highlight the first option
	      // in the dropdown
	      $options.first().trigger('mouseenter');
	    }

	    this.ensureHighlightVisible();
	  };

	  Results.prototype.setClasses = function () {
	    var self = this;

	    this.data.current(function (selected) {
	      var selectedIds = $.map(selected, function (s) {
	        return s.id.toString();
	      });

	      var $options = self.$results
	        .find('.select2-results__option[aria-selected]');

	      $options.each(function () {
	        var $option = $(this);

	        var item = $.data(this, 'data');

	        // id needs to be converted to a string when comparing
	        var id = '' + item.id;

	        if ((item.element != null && item.element.selected) ||
	            (item.element == null && $.inArray(id, selectedIds) > -1)) {
	          $option.attr('aria-selected', 'true');
	        } else {
	          $option.attr('aria-selected', 'false');
	        }
	      });

	    });
	  };

	  Results.prototype.showLoading = function (params) {
	    this.hideLoading();

	    var loadingMore = this.options.get('translations').get('searching');

	    var loading = {
	      disabled: true,
	      loading: true,
	      text: loadingMore(params)
	    };
	    var $loading = this.option(loading);
	    $loading.className += ' loading-results';

	    this.$results.prepend($loading);
	  };

	  Results.prototype.hideLoading = function () {
	    this.$results.find('.loading-results').remove();
	  };

	  Results.prototype.option = function (data) {
	    var option = document.createElement('li');
	    option.className = 'select2-results__option';

	    var attrs = {
	      'role': 'treeitem',
	      'aria-selected': 'false'
	    };

	    if (data.disabled) {
	      delete attrs['aria-selected'];
	      attrs['aria-disabled'] = 'true';
	    }

	    if (data.id == null) {
	      delete attrs['aria-selected'];
	    }

	    if (data._resultId != null) {
	      option.id = data._resultId;
	    }

	    if (data.title) {
	      option.title = data.title;
	    }

	    if (data.children) {
	      attrs.role = 'group';
	      attrs['aria-label'] = data.text;
	      delete attrs['aria-selected'];
	    }

	    for (var attr in attrs) {
	      var val = attrs[attr];

	      option.setAttribute(attr, val);
	    }

	    if (data.children) {
	      var $option = $(option);

	      var label = document.createElement('strong');
	      label.className = 'select2-results__group';

	      var $label = $(label);
	      this.template(data, label);

	      var $children = [];

	      for (var c = 0; c < data.children.length; c++) {
	        var child = data.children[c];

	        var $child = this.option(child);

	        $children.push($child);
	      }

	      var $childrenContainer = $('<ul></ul>', {
	        'class': 'select2-results__options select2-results__options--nested'
	      });

	      $childrenContainer.append($children);

	      $option.append(label);
	      $option.append($childrenContainer);
	    } else {
	      this.template(data, option);
	    }

	    $.data(option, 'data', data);

	    return option;
	  };

	  Results.prototype.bind = function (container, $container) {
	    var self = this;

	    var id = container.id + '-results';

	    this.$results.attr('id', id);

	    container.on('results:all', function (params) {
	      self.clear();
	      self.append(params.data);

	      if (container.isOpen()) {
	        self.setClasses();
	        self.highlightFirstItem();
	      }
	    });

	    container.on('results:append', function (params) {
	      self.append(params.data);

	      if (container.isOpen()) {
	        self.setClasses();
	      }
	    });

	    container.on('query', function (params) {
	      self.hideMessages();
	      self.showLoading(params);
	    });

	    container.on('select', function () {
	      if (!container.isOpen()) {
	        return;
	      }

	      self.setClasses();
	      self.highlightFirstItem();
	    });

	    container.on('unselect', function () {
	      if (!container.isOpen()) {
	        return;
	      }

	      self.setClasses();
	      self.highlightFirstItem();
	    });

	    container.on('open', function () {
	      // When the dropdown is open, aria-expended="true"
	      self.$results.attr('aria-expanded', 'true');
	      self.$results.attr('aria-hidden', 'false');

	      self.setClasses();
	      self.ensureHighlightVisible();
	    });

	    container.on('close', function () {
	      // When the dropdown is closed, aria-expended="false"
	      self.$results.attr('aria-expanded', 'false');
	      self.$results.attr('aria-hidden', 'true');
	      self.$results.removeAttr('aria-activedescendant');
	    });

	    container.on('results:toggle', function () {
	      var $highlighted = self.getHighlightedResults();

	      if ($highlighted.length === 0) {
	        return;
	      }

	      $highlighted.trigger('mouseup');
	    });

	    container.on('results:select', function () {
	      var $highlighted = self.getHighlightedResults();

	      if ($highlighted.length === 0) {
	        return;
	      }

	      var data = $highlighted.data('data');

	      if ($highlighted.attr('aria-selected') == 'true') {
	        self.trigger('close', {});
	      } else {
	        self.trigger('select', {
	          data: data
	        });
	      }
	    });

	    container.on('results:previous', function () {
	      var $highlighted = self.getHighlightedResults();

	      var $options = self.$results.find('[aria-selected]');

	      var currentIndex = $options.index($highlighted);

	      // If we are already at te top, don't move further
	      if (currentIndex === 0) {
	        return;
	      }

	      var nextIndex = currentIndex - 1;

	      // If none are highlighted, highlight the first
	      if ($highlighted.length === 0) {
	        nextIndex = 0;
	      }

	      var $next = $options.eq(nextIndex);

	      $next.trigger('mouseenter');

	      var currentOffset = self.$results.offset().top;
	      var nextTop = $next.offset().top;
	      var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);

	      if (nextIndex === 0) {
	        self.$results.scrollTop(0);
	      } else if (nextTop - currentOffset < 0) {
	        self.$results.scrollTop(nextOffset);
	      }
	    });

	    container.on('results:next', function () {
	      var $highlighted = self.getHighlightedResults();

	      var $options = self.$results.find('[aria-selected]');

	      var currentIndex = $options.index($highlighted);

	      var nextIndex = currentIndex + 1;

	      // If we are at the last option, stay there
	      if (nextIndex >= $options.length) {
	        return;
	      }

	      var $next = $options.eq(nextIndex);

	      $next.trigger('mouseenter');

	      var currentOffset = self.$results.offset().top +
	        self.$results.outerHeight(false);
	      var nextBottom = $next.offset().top + $next.outerHeight(false);
	      var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;

	      if (nextIndex === 0) {
	        self.$results.scrollTop(0);
	      } else if (nextBottom > currentOffset) {
	        self.$results.scrollTop(nextOffset);
	      }
	    });

	    container.on('results:focus', function (params) {
	      params.element.addClass('select2-results__option--highlighted');
	    });

	    container.on('results:message', function (params) {
	      self.displayMessage(params);
	    });

	    if ($.fn.mousewheel) {
	      this.$results.on('mousewheel', function (e) {
	        var top = self.$results.scrollTop();

	        var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;

	        var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
	        var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();

	        if (isAtTop) {
	          self.$results.scrollTop(0);

	          e.preventDefault();
	          e.stopPropagation();
	        } else if (isAtBottom) {
	          self.$results.scrollTop(
	            self.$results.get(0).scrollHeight - self.$results.height()
	          );

	          e.preventDefault();
	          e.stopPropagation();
	        }
	      });
	    }

	    this.$results.on('mouseup', '.select2-results__option[aria-selected]',
	      function (evt) {
	      var $this = $(this);

	      var data = $this.data('data');

	      if ($this.attr('aria-selected') === 'true') {
	        if (self.options.get('multiple')) {
	          self.trigger('unselect', {
	            originalEvent: evt,
	            data: data
	          });
	        } else {
	          self.trigger('close', {});
	        }

	        return;
	      }

	      self.trigger('select', {
	        originalEvent: evt,
	        data: data
	      });
	    });

	    this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
	      function (evt) {
	      var data = $(this).data('data');

	      self.getHighlightedResults()
	          .removeClass('select2-results__option--highlighted');

	      self.trigger('results:focus', {
	        data: data,
	        element: $(this)
	      });
	    });
	  };

	  Results.prototype.getHighlightedResults = function () {
	    var $highlighted = this.$results
	    .find('.select2-results__option--highlighted');

	    return $highlighted;
	  };

	  Results.prototype.destroy = function () {
	    this.$results.remove();
	  };

	  Results.prototype.ensureHighlightVisible = function () {
	    var $highlighted = this.getHighlightedResults();

	    if ($highlighted.length === 0) {
	      return;
	    }

	    var $options = this.$results.find('[aria-selected]');

	    var currentIndex = $options.index($highlighted);

	    var currentOffset = this.$results.offset().top;
	    var nextTop = $highlighted.offset().top;
	    var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);

	    var offsetDelta = nextTop - currentOffset;
	    nextOffset -= $highlighted.outerHeight(false) * 2;

	    if (currentIndex <= 2) {
	      this.$results.scrollTop(0);
	    } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
	      this.$results.scrollTop(nextOffset);
	    }
	  };

	  Results.prototype.template = function (result, container) {
	    var template = this.options.get('templateResult');
	    var escapeMarkup = this.options.get('escapeMarkup');

	    var content = template(result, container);

	    if (content == null) {
	      container.style.display = 'none';
	    } else if (typeof content === 'string') {
	      container.innerHTML = escapeMarkup(content);
	    } else {
	      $(container).append(content);
	    }
	  };

	  return Results;
	});

	S2.define('select2/keys',[

	], function () {
	  var KEYS = {
	    BACKSPACE: 8,
	    TAB: 9,
	    ENTER: 13,
	    SHIFT: 16,
	    CTRL: 17,
	    ALT: 18,
	    ESC: 27,
	    SPACE: 32,
	    PAGE_UP: 33,
	    PAGE_DOWN: 34,
	    END: 35,
	    HOME: 36,
	    LEFT: 37,
	    UP: 38,
	    RIGHT: 39,
	    DOWN: 40,
	    DELETE: 46
	  };

	  return KEYS;
	});

	S2.define('select2/selection/base',[
	  'jquery',
	  '../utils',
	  '../keys'
	], function ($, Utils, KEYS) {
	  function BaseSelection ($element, options) {
	    this.$element = $element;
	    this.options = options;

	    BaseSelection.__super__.constructor.call(this);
	  }

	  Utils.Extend(BaseSelection, Utils.Observable);

	  BaseSelection.prototype.render = function () {
	    var $selection = $(
	      '<span class="select2-selection" role="combobox" ' +
	      ' aria-haspopup="true" aria-expanded="false">' +
	      '</span>'
	    );

	    this._tabindex = 0;

	    if (this.$element.data('old-tabindex') != null) {
	      this._tabindex = this.$element.data('old-tabindex');
	    } else if (this.$element.attr('tabindex') != null) {
	      this._tabindex = this.$element.attr('tabindex');
	    }

	    $selection.attr('title', this.$element.attr('title'));
	    $selection.attr('tabindex', this._tabindex);

	    this.$selection = $selection;

	    return $selection;
	  };

	  BaseSelection.prototype.bind = function (container, $container) {
	    var self = this;

	    var id = container.id + '-container';
	    var resultsId = container.id + '-results';

	    this.container = container;

	    this.$selection.on('focus', function (evt) {
	      self.trigger('focus', evt);
	    });

	    this.$selection.on('blur', function (evt) {
	      self._handleBlur(evt);
	    });

	    this.$selection.on('keydown', function (evt) {
	      self.trigger('keypress', evt);

	      if (evt.which === KEYS.SPACE) {
	        evt.preventDefault();
	      }
	    });

	    container.on('results:focus', function (params) {
	      self.$selection.attr('aria-activedescendant', params.data._resultId);
	    });

	    container.on('selection:update', function (params) {
	      self.update(params.data);
	    });

	    container.on('open', function () {
	      // When the dropdown is open, aria-expanded="true"
	      self.$selection.attr('aria-expanded', 'true');
	      self.$selection.attr('aria-owns', resultsId);

	      self._attachCloseHandler(container);
	    });

	    container.on('close', function () {
	      // When the dropdown is closed, aria-expanded="false"
	      self.$selection.attr('aria-expanded', 'false');
	      self.$selection.removeAttr('aria-activedescendant');
	      self.$selection.removeAttr('aria-owns');

	      self.$selection.focus();

	      self._detachCloseHandler(container);
	    });

	    container.on('enable', function () {
	      self.$selection.attr('tabindex', self._tabindex);
	    });

	    container.on('disable', function () {
	      self.$selection.attr('tabindex', '-1');
	    });
	  };

	  BaseSelection.prototype._handleBlur = function (evt) {
	    var self = this;

	    // This needs to be delayed as the active element is the body when the tab
	    // key is pressed, possibly along with others.
	    window.setTimeout(function () {
	      // Don't trigger `blur` if the focus is still in the selection
	      if (
	        (document.activeElement == self.$selection[0]) ||
	        ($.contains(self.$selection[0], document.activeElement))
	      ) {
	        return;
	      }

	      self.trigger('blur', evt);
	    }, 1);
	  };

	  BaseSelection.prototype._attachCloseHandler = function (container) {
	    var self = this;

	    $(document.body).on('mousedown.select2.' + container.id, function (e) {
	      var $target = $(e.target);

	      var $select = $target.closest('.select2');

	      var $all = $('.select2.select2-container--open');

	      $all.each(function () {
	        var $this = $(this);

	        if (this == $select[0]) {
	          return;
	        }

	        var $element = $this.data('element');

	        $element.select2('close');
	      });
	    });
	  };

	  BaseSelection.prototype._detachCloseHandler = function (container) {
	    $(document.body).off('mousedown.select2.' + container.id);
	  };

	  BaseSelection.prototype.position = function ($selection, $container) {
	    var $selectionContainer = $container.find('.selection');
	    $selectionContainer.append($selection);
	  };

	  BaseSelection.prototype.destroy = function () {
	    this._detachCloseHandler(this.container);
	  };

	  BaseSelection.prototype.update = function (data) {
	    throw new Error('The `update` method must be defined in child classes.');
	  };

	  return BaseSelection;
	});

	S2.define('select2/selection/single',[
	  'jquery',
	  './base',
	  '../utils',
	  '../keys'
	], function ($, BaseSelection, Utils, KEYS) {
	  function SingleSelection () {
	    SingleSelection.__super__.constructor.apply(this, arguments);
	  }

	  Utils.Extend(SingleSelection, BaseSelection);

	  SingleSelection.prototype.render = function () {
	    var $selection = SingleSelection.__super__.render.call(this);

	    $selection.addClass('select2-selection--single');

	    $selection.html(
	      '<span class="select2-selection__rendered"></span>' +
	      '<span class="select2-selection__arrow" role="presentation">' +
	        '<b role="presentation"></b>' +
	      '</span>'
	    );

	    return $selection;
	  };

	  SingleSelection.prototype.bind = function (container, $container) {
	    var self = this;

	    SingleSelection.__super__.bind.apply(this, arguments);

	    var id = container.id + '-container';

	    this.$selection.find('.select2-selection__rendered').attr('id', id);
	    this.$selection.attr('aria-labelledby', id);

	    this.$selection.on('mousedown', function (evt) {
	      // Only respond to left clicks
	      if (evt.which !== 1) {
	        return;
	      }

	      self.trigger('toggle', {
	        originalEvent: evt
	      });
	    });

	    this.$selection.on('focus', function (evt) {
	      // User focuses on the container
	    });

	    this.$selection.on('blur', function (evt) {
	      // User exits the container
	    });

	    container.on('focus', function (evt) {
	      if (!container.isOpen()) {
	        self.$selection.focus();
	      }
	    });

	    container.on('selection:update', function (params) {
	      self.update(params.data);
	    });
	  };

	  SingleSelection.prototype.clear = function () {
	    this.$selection.find('.select2-selection__rendered').empty();
	  };

	  SingleSelection.prototype.display = function (data, container) {
	    var template = this.options.get('templateSelection');
	    var escapeMarkup = this.options.get('escapeMarkup');

	    return escapeMarkup(template(data, container));
	  };

	  SingleSelection.prototype.selectionContainer = function () {
	    return $('<span></span>');
	  };

	  SingleSelection.prototype.update = function (data) {
	    if (data.length === 0) {
	      this.clear();
	      return;
	    }

	    var selection = data[0];

	    var $rendered = this.$selection.find('.select2-selection__rendered');
	    var formatted = this.display(selection, $rendered);

	    $rendered.empty().append(formatted);
	    $rendered.prop('title', selection.title || selection.text);
	  };

	  return SingleSelection;
	});

	S2.define('select2/selection/multiple',[
	  'jquery',
	  './base',
	  '../utils'
	], function ($, BaseSelection, Utils) {
	  function MultipleSelection ($element, options) {
	    MultipleSelection.__super__.constructor.apply(this, arguments);
	  }

	  Utils.Extend(MultipleSelection, BaseSelection);

	  MultipleSelection.prototype.render = function () {
	    var $selection = MultipleSelection.__super__.render.call(this);

	    $selection.addClass('select2-selection--multiple');

	    $selection.html(
	      '<ul class="select2-selection__rendered"></ul>'
	    );

	    return $selection;
	  };

	  MultipleSelection.prototype.bind = function (container, $container) {
	    var self = this;

	    MultipleSelection.__super__.bind.apply(this, arguments);

	    this.$selection.on('click', function (evt) {
	      self.trigger('toggle', {
	        originalEvent: evt
	      });
	    });

	    this.$selection.on(
	      'click',
	      '.select2-selection__choice__remove',
	      function (evt) {
	        // Ignore the event if it is disabled
	        if (self.options.get('disabled')) {
	          return;
	        }

	        var $remove = $(this);
	        var $selection = $remove.parent();

	        var data = $selection.data('data');

	        self.trigger('unselect', {
	          originalEvent: evt,
	          data: data
	        });
	      }
	    );
	  };

	  MultipleSelection.prototype.clear = function () {
	    this.$selection.find('.select2-selection__rendered').empty();
	  };

	  MultipleSelection.prototype.display = function (data, container) {
	    var template = this.options.get('templateSelection');
	    var escapeMarkup = this.options.get('escapeMarkup');

	    return escapeMarkup(template(data, container));
	  };

	  MultipleSelection.prototype.selectionContainer = function () {
	    var $container = $(
	      '<li class="select2-selection__choice">' +
	        '<span class="select2-selection__choice__remove" role="presentation">' +
	          '&times;' +
	        '</span>' +
	      '</li>'
	    );

	    return $container;
	  };

	  MultipleSelection.prototype.update = function (data) {
	    this.clear();

	    if (data.length === 0) {
	      return;
	    }

	    var $selections = [];

	    for (var d = 0; d < data.length; d++) {
	      var selection = data[d];

	      var $selection = this.selectionContainer();
	      var formatted = this.display(selection, $selection);

	      $selection.append(formatted);
	      $selection.prop('title', selection.title || selection.text);

	      $selection.data('data', selection);

	      $selections.push($selection);
	    }

	    var $rendered = this.$selection.find('.select2-selection__rendered');

	    Utils.appendMany($rendered, $selections);
	  };

	  return MultipleSelection;
	});

	S2.define('select2/selection/placeholder',[
	  '../utils'
	], function (Utils) {
	  function Placeholder (decorated, $element, options) {
	    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));

	    decorated.call(this, $element, options);
	  }

	  Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
	    if (typeof placeholder === 'string') {
	      placeholder = {
	        id: '',
	        text: placeholder
	      };
	    }

	    return placeholder;
	  };

	  Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
	    var $placeholder = this.selectionContainer();

	    $placeholder.html(this.display(placeholder));
	    $placeholder.addClass('select2-selection__placeholder')
	                .removeClass('select2-selection__choice');

	    return $placeholder;
	  };

	  Placeholder.prototype.update = function (decorated, data) {
	    var singlePlaceholder = (
	      data.length == 1 && data[0].id != this.placeholder.id
	    );
	    var multipleSelections = data.length > 1;

	    if (multipleSelections || singlePlaceholder) {
	      return decorated.call(this, data);
	    }

	    this.clear();

	    var $placeholder = this.createPlaceholder(this.placeholder);

	    this.$selection.find('.select2-selection__rendered').append($placeholder);
	  };

	  return Placeholder;
	});

	S2.define('select2/selection/allowClear',[
	  'jquery',
	  '../keys'
	], function ($, KEYS) {
	  function AllowClear () { }

	  AllowClear.prototype.bind = function (decorated, container, $container) {
	    var self = this;

	    decorated.call(this, container, $container);

	    if (this.placeholder == null) {
	      if (this.options.get('debug') && window.console && console.error) {
	        console.error(
	          'Select2: The `allowClear` option should be used in combination ' +
	          'with the `placeholder` option.'
	        );
	      }
	    }

	    this.$selection.on('mousedown', '.select2-selection__clear',
	      function (evt) {
	        self._handleClear(evt);
	    });

	    container.on('keypress', function (evt) {
	      self._handleKeyboardClear(evt, container);
	    });
	  };

	  AllowClear.prototype._handleClear = function (_, evt) {
	    // Ignore the event if it is disabled
	    if (this.options.get('disabled')) {
	      return;
	    }

	    var $clear = this.$selection.find('.select2-selection__clear');

	    // Ignore the event if nothing has been selected
	    if ($clear.length === 0) {
	      return;
	    }

	    evt.stopPropagation();

	    var data = $clear.data('data');

	    for (var d = 0; d < data.length; d++) {
	      var unselectData = {
	        data: data[d]
	      };

	      // Trigger the `unselect` event, so people can prevent it from being
	      // cleared.
	      this.trigger('unselect', unselectData);

	      // If the event was prevented, don't clear it out.
	      if (unselectData.prevented) {
	        return;
	      }
	    }

	    this.$element.val(this.placeholder.id).trigger('change');

	    this.trigger('toggle', {});
	  };

	  AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
	    if (container.isOpen()) {
	      return;
	    }

	    if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
	      this._handleClear(evt);
	    }
	  };

	  AllowClear.prototype.update = function (decorated, data) {
	    decorated.call(this, data);

	    if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
	        data.length === 0) {
	      return;
	    }

	    var $remove = $(
	      '<span class="select2-selection__clear">' +
	        '&times;' +
	      '</span>'
	    );
	    $remove.data('data', data);

	    this.$selection.find('.select2-selection__rendered').prepend($remove);
	  };

	  return AllowClear;
	});

	S2.define('select2/selection/search',[
	  'jquery',
	  '../utils',
	  '../keys'
	], function ($, Utils, KEYS) {
	  function Search (decorated, $element, options) {
	    decorated.call(this, $element, options);
	  }

	  Search.prototype.render = function (decorated) {
	    var $search = $(
	      '<li class="select2-search select2-search--inline">' +
	        '<input class="select2-search__field" type="search" tabindex="-1"' +
	        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
	        ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
	      '</li>'
	    );

	    this.$searchContainer = $search;
	    this.$search = $search.find('input');

	    var $rendered = decorated.call(this);

	    this._transferTabIndex();

	    return $rendered;
	  };

	  Search.prototype.bind = function (decorated, container, $container) {
	    var self = this;

	    decorated.call(this, container, $container);

	    container.on('open', function () {
	      self.$search.trigger('focus');
	    });

	    container.on('close', function () {
	      self.$search.val('');
	      self.$search.removeAttr('aria-activedescendant');
	      self.$search.trigger('focus');
	    });

	    container.on('enable', function () {
	      self.$search.prop('disabled', false);

	      self._transferTabIndex();
	    });

	    container.on('disable', function () {
	      self.$search.prop('disabled', true);
	    });

	    container.on('focus', function (evt) {
	      self.$search.trigger('focus');
	    });

	    container.on('results:focus', function (params) {
	      self.$search.attr('aria-activedescendant', params.id);
	    });

	    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
	      self.trigger('focus', evt);
	    });

	    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
	      self._handleBlur(evt);
	    });

	    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
	      evt.stopPropagation();

	      self.trigger('keypress', evt);

	      self._keyUpPrevented = evt.isDefaultPrevented();

	      var key = evt.which;

	      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
	        var $previousChoice = self.$searchContainer
	          .prev('.select2-selection__choice');

	        if ($previousChoice.length > 0) {
	          var item = $previousChoice.data('data');

	          self.searchRemoveChoice(item);

	          evt.preventDefault();
	        }
	      }
	    });

	    // Try to detect the IE version should the `documentMode` property that
	    // is stored on the document. This is only implemented in IE and is
	    // slightly cleaner than doing a user agent check.
	    // This property is not available in Edge, but Edge also doesn't have
	    // this bug.
	    var msie = document.documentMode;
	    var disableInputEvents = msie && msie <= 11;

	    // Workaround for browsers which do not support the `input` event
	    // This will prevent double-triggering of events for browsers which support
	    // both the `keyup` and `input` events.
	    this.$selection.on(
	      'input.searchcheck',
	      '.select2-search--inline',
	      function (evt) {
	        // IE will trigger the `input` event when a placeholder is used on a
	        // search box. To get around this issue, we are forced to ignore all
	        // `input` events in IE and keep using `keyup`.
	        if (disableInputEvents) {
	          self.$selection.off('input.search input.searchcheck');
	          return;
	        }

	        // Unbind the duplicated `keyup` event
	        self.$selection.off('keyup.search');
	      }
	    );

	    this.$selection.on(
	      'keyup.search input.search',
	      '.select2-search--inline',
	      function (evt) {
	        // IE will trigger the `input` event when a placeholder is used on a
	        // search box. To get around this issue, we are forced to ignore all
	        // `input` events in IE and keep using `keyup`.
	        if (disableInputEvents && evt.type === 'input') {
	          self.$selection.off('input.search input.searchcheck');
	          return;
	        }

	        var key = evt.which;

	        // We can freely ignore events from modifier keys
	        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
	          return;
	        }

	        // Tabbing will be handled during the `keydown` phase
	        if (key == KEYS.TAB) {
	          return;
	        }

	        self.handleSearch(evt);
	      }
	    );
	  };

	  /**
	   * This method will transfer the tabindex attribute from the rendered
	   * selection to the search box. This allows for the search box to be used as
	   * the primary focus instead of the selection container.
	   *
	   * @private
	   */
	  Search.prototype._transferTabIndex = function (decorated) {
	    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
	    this.$selection.attr('tabindex', '-1');
	  };

	  Search.prototype.createPlaceholder = function (decorated, placeholder) {
	    this.$search.attr('placeholder', placeholder.text);
	  };

	  Search.prototype.update = function (decorated, data) {
	    var searchHadFocus = this.$search[0] == document.activeElement;

	    this.$search.attr('placeholder', '');

	    decorated.call(this, data);

	    this.$selection.find('.select2-selection__rendered')
	                   .append(this.$searchContainer);

	    this.resizeSearch();
	    if (searchHadFocus) {
	      this.$search.focus();
	    }
	  };

	  Search.prototype.handleSearch = function () {
	    this.resizeSearch();

	    if (!this._keyUpPrevented) {
	      var input = this.$search.val();

	      this.trigger('query', {
	        term: input
	      });
	    }

	    this._keyUpPrevented = false;
	  };

	  Search.prototype.searchRemoveChoice = function (decorated, item) {
	    this.trigger('unselect', {
	      data: item
	    });

	    this.$search.val(item.text);
	    this.handleSearch();
	  };

	  Search.prototype.resizeSearch = function () {
	    this.$search.css('width', '25px');

	    var width = '';

	    if (this.$search.attr('placeholder') !== '') {
	      width = this.$selection.find('.select2-selection__rendered').innerWidth();
	    } else {
	      var minimumWidth = this.$search.val().length + 1;

	      width = (minimumWidth * 0.75) + 'em';
	    }

	    this.$search.css('width', width);
	  };

	  return Search;
	});

	S2.define('select2/selection/eventRelay',[
	  'jquery'
	], function ($) {
	  function EventRelay () { }

	  EventRelay.prototype.bind = function (decorated, container, $container) {
	    var self = this;
	    var relayEvents = [
	      'open', 'opening',
	      'close', 'closing',
	      'select', 'selecting',
	      'unselect', 'unselecting'
	    ];

	    var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];

	    decorated.call(this, container, $container);

	    container.on('*', function (name, params) {
	      // Ignore events that should not be relayed
	      if ($.inArray(name, relayEvents) === -1) {
	        return;
	      }

	      // The parameters should always be an object
	      params = params || {};

	      // Generate the jQuery event for the Select2 event
	      var evt = $.Event('select2:' + name, {
	        params: params
	      });

	      self.$element.trigger(evt);

	      // Only handle preventable events if it was one
	      if ($.inArray(name, preventableEvents) === -1) {
	        return;
	      }

	      params.prevented = evt.isDefaultPrevented();
	    });
	  };

	  return EventRelay;
	});

	S2.define('select2/translation',[
	  'jquery',
	  'require'
	], function ($, require) {
	  function Translation (dict) {
	    this.dict = dict || {};
	  }

	  Translation.prototype.all = function () {
	    return this.dict;
	  };

	  Translation.prototype.get = function (key) {
	    return this.dict[key];
	  };

	  Translation.prototype.extend = function (translation) {
	    this.dict = $.extend({}, translation.all(), this.dict);
	  };

	  // Static functions

	  Translation._cache = {};

	  Translation.loadPath = function (path) {
	    if (!(path in Translation._cache)) {
	      var translations = require(path);

	      Translation._cache[path] = translations;
	    }

	    return new Translation(Translation._cache[path]);
	  };

	  return Translation;
	});

	S2.define('select2/diacritics',[

	], function () {
	  var diacritics = {
	    '\u24B6': 'A',
	    '\uFF21': 'A',
	    '\u00C0': 'A',
	    '\u00C1': 'A',
	    '\u00C2': 'A',
	    '\u1EA6': 'A',
	    '\u1EA4': 'A',
	    '\u1EAA': 'A',
	    '\u1EA8': 'A',
	    '\u00C3': 'A',
	    '\u0100': 'A',
	    '\u0102': 'A',
	    '\u1EB0': 'A',
	    '\u1EAE': 'A',
	    '\u1EB4': 'A',
	    '\u1EB2': 'A',
	    '\u0226': 'A',
	    '\u01E0': 'A',
	    '\u00C4': 'A',
	    '\u01DE': 'A',
	    '\u1EA2': 'A',
	    '\u00C5': 'A',
	    '\u01FA': 'A',
	    '\u01CD': 'A',
	    '\u0200': 'A',
	    '\u0202': 'A',
	    '\u1EA0': 'A',
	    '\u1EAC': 'A',
	    '\u1EB6': 'A',
	    '\u1E00': 'A',
	    '\u0104': 'A',
	    '\u023A': 'A',
	    '\u2C6F': 'A',
	    '\uA732': 'AA',
	    '\u00C6': 'AE',
	    '\u01FC': 'AE',
	    '\u01E2': 'AE',
	    '\uA734': 'AO',
	    '\uA736': 'AU',
	    '\uA738': 'AV',
	    '\uA73A': 'AV',
	    '\uA73C': 'AY',
	    '\u24B7': 'B',
	    '\uFF22': 'B',
	    '\u1E02': 'B',
	    '\u1E04': 'B',
	    '\u1E06': 'B',
	    '\u0243': 'B',
	    '\u0182': 'B',
	    '\u0181': 'B',
	    '\u24B8': 'C',
	    '\uFF23': 'C',
	    '\u0106': 'C',
	    '\u0108': 'C',
	    '\u010A': 'C',
	    '\u010C': 'C',
	    '\u00C7': 'C',
	    '\u1E08': 'C',
	    '\u0187': 'C',
	    '\u023B': 'C',
	    '\uA73E': 'C',
	    '\u24B9': 'D',
	    '\uFF24': 'D',
	    '\u1E0A': 'D',
	    '\u010E': 'D',
	    '\u1E0C': 'D',
	    '\u1E10': 'D',
	    '\u1E12': 'D',
	    '\u1E0E': 'D',
	    '\u0110': 'D',
	    '\u018B': 'D',
	    '\u018A': 'D',
	    '\u0189': 'D',
	    '\uA779': 'D',
	    '\u01F1': 'DZ',
	    '\u01C4': 'DZ',
	    '\u01F2': 'Dz',
	    '\u01C5': 'Dz',
	    '\u24BA': 'E',
	    '\uFF25': 'E',
	    '\u00C8': 'E',
	    '\u00C9': 'E',
	    '\u00CA': 'E',
	    '\u1EC0': 'E',
	    '\u1EBE': 'E',
	    '\u1EC4': 'E',
	    '\u1EC2': 'E',
	    '\u1EBC': 'E',
	    '\u0112': 'E',
	    '\u1E14': 'E',
	    '\u1E16': 'E',
	    '\u0114': 'E',
	    '\u0116': 'E',
	    '\u00CB': 'E',
	    '\u1EBA': 'E',
	    '\u011A': 'E',
	    '\u0204': 'E',
	    '\u0206': 'E',
	    '\u1EB8': 'E',
	    '\u1EC6': 'E',
	    '\u0228': 'E',
	    '\u1E1C': 'E',
	    '\u0118': 'E',
	    '\u1E18': 'E',
	    '\u1E1A': 'E',
	    '\u0190': 'E',
	    '\u018E': 'E',
	    '\u24BB': 'F',
	    '\uFF26': 'F',
	    '\u1E1E': 'F',
	    '\u0191': 'F',
	    '\uA77B': 'F',
	    '\u24BC': 'G',
	    '\uFF27': 'G',
	    '\u01F4': 'G',
	    '\u011C': 'G',
	    '\u1E20': 'G',
	    '\u011E': 'G',
	    '\u0120': 'G',
	    '\u01E6': 'G',
	    '\u0122': 'G',
	    '\u01E4': 'G',
	    '\u0193': 'G',
	    '\uA7A0': 'G',
	    '\uA77D': 'G',
	    '\uA77E': 'G',
	    '\u24BD': 'H',
	    '\uFF28': 'H',
	    '\u0124': 'H',
	    '\u1E22': 'H',
	    '\u1E26': 'H',
	    '\u021E': 'H',
	    '\u1E24': 'H',
	    '\u1E28': 'H',
	    '\u1E2A': 'H',
	    '\u0126': 'H',
	    '\u2C67': 'H',
	    '\u2C75': 'H',
	    '\uA78D': 'H',
	    '\u24BE': 'I',
	    '\uFF29': 'I',
	    '\u00CC': 'I',
	    '\u00CD': 'I',
	    '\u00CE': 'I',
	    '\u0128': 'I',
	    '\u012A': 'I',
	    '\u012C': 'I',
	    '\u0130': 'I',
	    '\u00CF': 'I',
	    '\u1E2E': 'I',
	    '\u1EC8': 'I',
	    '\u01CF': 'I',
	    '\u0208': 'I',
	    '\u020A': 'I',
	    '\u1ECA': 'I',
	    '\u012E': 'I',
	    '\u1E2C': 'I',
	    '\u0197': 'I',
	    '\u24BF': 'J',
	    '\uFF2A': 'J',
	    '\u0134': 'J',
	    '\u0248': 'J',
	    '\u24C0': 'K',
	    '\uFF2B': 'K',
	    '\u1E30': 'K',
	    '\u01E8': 'K',
	    '\u1E32': 'K',
	    '\u0136': 'K',
	    '\u1E34': 'K',
	    '\u0198': 'K',
	    '\u2C69': 'K',
	    '\uA740': 'K',
	    '\uA742': 'K',
	    '\uA744': 'K',
	    '\uA7A2': 'K',
	    '\u24C1': 'L',
	    '\uFF2C': 'L',
	    '\u013F': 'L',
	    '\u0139': 'L',
	    '\u013D': 'L',
	    '\u1E36': 'L',
	    '\u1E38': 'L',
	    '\u013B': 'L',
	    '\u1E3C': 'L',
	    '\u1E3A': 'L',
	    '\u0141': 'L',
	    '\u023D': 'L',
	    '\u2C62': 'L',
	    '\u2C60': 'L',
	    '\uA748': 'L',
	    '\uA746': 'L',
	    '\uA780': 'L',
	    '\u01C7': 'LJ',
	    '\u01C8': 'Lj',
	    '\u24C2': 'M',
	    '\uFF2D': 'M',
	    '\u1E3E': 'M',
	    '\u1E40': 'M',
	    '\u1E42': 'M',
	    '\u2C6E': 'M',
	    '\u019C': 'M',
	    '\u24C3': 'N',
	    '\uFF2E': 'N',
	    '\u01F8': 'N',
	    '\u0143': 'N',
	    '\u00D1': 'N',
	    '\u1E44': 'N',
	    '\u0147': 'N',
	    '\u1E46': 'N',
	    '\u0145': 'N',
	    '\u1E4A': 'N',
	    '\u1E48': 'N',
	    '\u0220': 'N',
	    '\u019D': 'N',
	    '\uA790': 'N',
	    '\uA7A4': 'N',
	    '\u01CA': 'NJ',
	    '\u01CB': 'Nj',
	    '\u24C4': 'O',
	    '\uFF2F': 'O',
	    '\u00D2': 'O',
	    '\u00D3': 'O',
	    '\u00D4': 'O',
	    '\u1ED2': 'O',
	    '\u1ED0': 'O',
	    '\u1ED6': 'O',
	    '\u1ED4': 'O',
	    '\u00D5': 'O',
	    '\u1E4C': 'O',
	    '\u022C': 'O',
	    '\u1E4E': 'O',
	    '\u014C': 'O',
	    '\u1E50': 'O',
	    '\u1E52': 'O',
	    '\u014E': 'O',
	    '\u022E': 'O',
	    '\u0230': 'O',
	    '\u00D6': 'O',
	    '\u022A': 'O',
	    '\u1ECE': 'O',
	    '\u0150': 'O',
	    '\u01D1': 'O',
	    '\u020C': 'O',
	    '\u020E': 'O',
	    '\u01A0': 'O',
	    '\u1EDC': 'O',
	    '\u1EDA': 'O',
	    '\u1EE0': 'O',
	    '\u1EDE': 'O',
	    '\u1EE2': 'O',
	    '\u1ECC': 'O',
	    '\u1ED8': 'O',
	    '\u01EA': 'O',
	    '\u01EC': 'O',
	    '\u00D8': 'O',
	    '\u01FE': 'O',
	    '\u0186': 'O',
	    '\u019F': 'O',
	    '\uA74A': 'O',
	    '\uA74C': 'O',
	    '\u01A2': 'OI',
	    '\uA74E': 'OO',
	    '\u0222': 'OU',
	    '\u24C5': 'P',
	    '\uFF30': 'P',
	    '\u1E54': 'P',
	    '\u1E56': 'P',
	    '\u01A4': 'P',
	    '\u2C63': 'P',
	    '\uA750': 'P',
	    '\uA752': 'P',
	    '\uA754': 'P',
	    '\u24C6': 'Q',
	    '\uFF31': 'Q',
	    '\uA756': 'Q',
	    '\uA758': 'Q',
	    '\u024A': 'Q',
	    '\u24C7': 'R',
	    '\uFF32': 'R',
	    '\u0154': 'R',
	    '\u1E58': 'R',
	    '\u0158': 'R',
	    '\u0210': 'R',
	    '\u0212': 'R',
	    '\u1E5A': 'R',
	    '\u1E5C': 'R',
	    '\u0156': 'R',
	    '\u1E5E': 'R',
	    '\u024C': 'R',
	    '\u2C64': 'R',
	    '\uA75A': 'R',
	    '\uA7A6': 'R',
	    '\uA782': 'R',
	    '\u24C8': 'S',
	    '\uFF33': 'S',
	    '\u1E9E': 'S',
	    '\u015A': 'S',
	    '\u1E64': 'S',
	    '\u015C': 'S',
	    '\u1E60': 'S',
	    '\u0160': 'S',
	    '\u1E66': 'S',
	    '\u1E62': 'S',
	    '\u1E68': 'S',
	    '\u0218': 'S',
	    '\u015E': 'S',
	    '\u2C7E': 'S',
	    '\uA7A8': 'S',
	    '\uA784': 'S',
	    '\u24C9': 'T',
	    '\uFF34': 'T',
	    '\u1E6A': 'T',
	    '\u0164': 'T',
	    '\u1E6C': 'T',
	    '\u021A': 'T',
	    '\u0162': 'T',
	    '\u1E70': 'T',
	    '\u1E6E': 'T',
	    '\u0166': 'T',
	    '\u01AC': 'T',
	    '\u01AE': 'T',
	    '\u023E': 'T',
	    '\uA786': 'T',
	    '\uA728': 'TZ',
	    '\u24CA': 'U',
	    '\uFF35': 'U',
	    '\u00D9': 'U',
	    '\u00DA': 'U',
	    '\u00DB': 'U',
	    '\u0168': 'U',
	    '\u1E78': 'U',
	    '\u016A': 'U',
	    '\u1E7A': 'U',
	    '\u016C': 'U',
	    '\u00DC': 'U',
	    '\u01DB': 'U',
	    '\u01D7': 'U',
	    '\u01D5': 'U',
	    '\u01D9': 'U',
	    '\u1EE6': 'U',
	    '\u016E': 'U',
	    '\u0170': 'U',
	    '\u01D3': 'U',
	    '\u0214': 'U',
	    '\u0216': 'U',
	    '\u01AF': 'U',
	    '\u1EEA': 'U',
	    '\u1EE8': 'U',
	    '\u1EEE': 'U',
	    '\u1EEC': 'U',
	    '\u1EF0': 'U',
	    '\u1EE4': 'U',
	    '\u1E72': 'U',
	    '\u0172': 'U',
	    '\u1E76': 'U',
	    '\u1E74': 'U',
	    '\u0244': 'U',
	    '\u24CB': 'V',
	    '\uFF36': 'V',
	    '\u1E7C': 'V',
	    '\u1E7E': 'V',
	    '\u01B2': 'V',
	    '\uA75E': 'V',
	    '\u0245': 'V',
	    '\uA760': 'VY',
	    '\u24CC': 'W',
	    '\uFF37': 'W',
	    '\u1E80': 'W',
	    '\u1E82': 'W',
	    '\u0174': 'W',
	    '\u1E86': 'W',
	    '\u1E84': 'W',
	    '\u1E88': 'W',
	    '\u2C72': 'W',
	    '\u24CD': 'X',
	    '\uFF38': 'X',
	    '\u1E8A': 'X',
	    '\u1E8C': 'X',
	    '\u24CE': 'Y',
	    '\uFF39': 'Y',
	    '\u1EF2': 'Y',
	    '\u00DD': 'Y',
	    '\u0176': 'Y',
	    '\u1EF8': 'Y',
	    '\u0232': 'Y',
	    '\u1E8E': 'Y',
	    '\u0178': 'Y',
	    '\u1EF6': 'Y',
	    '\u1EF4': 'Y',
	    '\u01B3': 'Y',
	    '\u024E': 'Y',
	    '\u1EFE': 'Y',
	    '\u24CF': 'Z',
	    '\uFF3A': 'Z',
	    '\u0179': 'Z',
	    '\u1E90': 'Z',
	    '\u017B': 'Z',
	    '\u017D': 'Z',
	    '\u1E92': 'Z',
	    '\u1E94': 'Z',
	    '\u01B5': 'Z',
	    '\u0224': 'Z',
	    '\u2C7F': 'Z',
	    '\u2C6B': 'Z',
	    '\uA762': 'Z',
	    '\u24D0': 'a',
	    '\uFF41': 'a',
	    '\u1E9A': 'a',
	    '\u00E0': 'a',
	    '\u00E1': 'a',
	    '\u00E2': 'a',
	    '\u1EA7': 'a',
	    '\u1EA5': 'a',
	    '\u1EAB': 'a',
	    '\u1EA9': 'a',
	    '\u00E3': 'a',
	    '\u0101': 'a',
	    '\u0103': 'a',
	    '\u1EB1': 'a',
	    '\u1EAF': 'a',
	    '\u1EB5': 'a',
	    '\u1EB3': 'a',
	    '\u0227': 'a',
	    '\u01E1': 'a',
	    '\u00E4': 'a',
	    '\u01DF': 'a',
	    '\u1EA3': 'a',
	    '\u00E5': 'a',
	    '\u01FB': 'a',
	    '\u01CE': 'a',
	    '\u0201': 'a',
	    '\u0203': 'a',
	    '\u1EA1': 'a',
	    '\u1EAD': 'a',
	    '\u1EB7': 'a',
	    '\u1E01': 'a',
	    '\u0105': 'a',
	    '\u2C65': 'a',
	    '\u0250': 'a',
	    '\uA733': 'aa',
	    '\u00E6': 'ae',
	    '\u01FD': 'ae',
	    '\u01E3': 'ae',
	    '\uA735': 'ao',
	    '\uA737': 'au',
	    '\uA739': 'av',
	    '\uA73B': 'av',
	    '\uA73D': 'ay',
	    '\u24D1': 'b',
	    '\uFF42': 'b',
	    '\u1E03': 'b',
	    '\u1E05': 'b',
	    '\u1E07': 'b',
	    '\u0180': 'b',
	    '\u0183': 'b',
	    '\u0253': 'b',
	    '\u24D2': 'c',
	    '\uFF43': 'c',
	    '\u0107': 'c',
	    '\u0109': 'c',
	    '\u010B': 'c',
	    '\u010D': 'c',
	    '\u00E7': 'c',
	    '\u1E09': 'c',
	    '\u0188': 'c',
	    '\u023C': 'c',
	    '\uA73F': 'c',
	    '\u2184': 'c',
	    '\u24D3': 'd',
	    '\uFF44': 'd',
	    '\u1E0B': 'd',
	    '\u010F': 'd',
	    '\u1E0D': 'd',
	    '\u1E11': 'd',
	    '\u1E13': 'd',
	    '\u1E0F': 'd',
	    '\u0111': 'd',
	    '\u018C': 'd',
	    '\u0256': 'd',
	    '\u0257': 'd',
	    '\uA77A': 'd',
	    '\u01F3': 'dz',
	    '\u01C6': 'dz',
	    '\u24D4': 'e',
	    '\uFF45': 'e',
	    '\u00E8': 'e',
	    '\u00E9': 'e',
	    '\u00EA': 'e',
	    '\u1EC1': 'e',
	    '\u1EBF': 'e',
	    '\u1EC5': 'e',
	    '\u1EC3': 'e',
	    '\u1EBD': 'e',
	    '\u0113': 'e',
	    '\u1E15': 'e',
	    '\u1E17': 'e',
	    '\u0115': 'e',
	    '\u0117': 'e',
	    '\u00EB': 'e',
	    '\u1EBB': 'e',
	    '\u011B': 'e',
	    '\u0205': 'e',
	    '\u0207': 'e',
	    '\u1EB9': 'e',
	    '\u1EC7': 'e',
	    '\u0229': 'e',
	    '\u1E1D': 'e',
	    '\u0119': 'e',
	    '\u1E19': 'e',
	    '\u1E1B': 'e',
	    '\u0247': 'e',
	    '\u025B': 'e',
	    '\u01DD': 'e',
	    '\u24D5': 'f',
	    '\uFF46': 'f',
	    '\u1E1F': 'f',
	    '\u0192': 'f',
	    '\uA77C': 'f',
	    '\u24D6': 'g',
	    '\uFF47': 'g',
	    '\u01F5': 'g',
	    '\u011D': 'g',
	    '\u1E21': 'g',
	    '\u011F': 'g',
	    '\u0121': 'g',
	    '\u01E7': 'g',
	    '\u0123': 'g',
	    '\u01E5': 'g',
	    '\u0260': 'g',
	    '\uA7A1': 'g',
	    '\u1D79': 'g',
	    '\uA77F': 'g',
	    '\u24D7': 'h',
	    '\uFF48': 'h',
	    '\u0125': 'h',
	    '\u1E23': 'h',
	    '\u1E27': 'h',
	    '\u021F': 'h',
	    '\u1E25': 'h',
	    '\u1E29': 'h',
	    '\u1E2B': 'h',
	    '\u1E96': 'h',
	    '\u0127': 'h',
	    '\u2C68': 'h',
	    '\u2C76': 'h',
	    '\u0265': 'h',
	    '\u0195': 'hv',
	    '\u24D8': 'i',
	    '\uFF49': 'i',
	    '\u00EC': 'i',
	    '\u00ED': 'i',
	    '\u00EE': 'i',
	    '\u0129': 'i',
	    '\u012B': 'i',
	    '\u012D': 'i',
	    '\u00EF': 'i',
	    '\u1E2F': 'i',
	    '\u1EC9': 'i',
	    '\u01D0': 'i',
	    '\u0209': 'i',
	    '\u020B': 'i',
	    '\u1ECB': 'i',
	    '\u012F': 'i',
	    '\u1E2D': 'i',
	    '\u0268': 'i',
	    '\u0131': 'i',
	    '\u24D9': 'j',
	    '\uFF4A': 'j',
	    '\u0135': 'j',
	    '\u01F0': 'j',
	    '\u0249': 'j',
	    '\u24DA': 'k',
	    '\uFF4B': 'k',
	    '\u1E31': 'k',
	    '\u01E9': 'k',
	    '\u1E33': 'k',
	    '\u0137': 'k',
	    '\u1E35': 'k',
	    '\u0199': 'k',
	    '\u2C6A': 'k',
	    '\uA741': 'k',
	    '\uA743': 'k',
	    '\uA745': 'k',
	    '\uA7A3': 'k',
	    '\u24DB': 'l',
	    '\uFF4C': 'l',
	    '\u0140': 'l',
	    '\u013A': 'l',
	    '\u013E': 'l',
	    '\u1E37': 'l',
	    '\u1E39': 'l',
	    '\u013C': 'l',
	    '\u1E3D': 'l',
	    '\u1E3B': 'l',
	    '\u017F': 'l',
	    '\u0142': 'l',
	    '\u019A': 'l',
	    '\u026B': 'l',
	    '\u2C61': 'l',
	    '\uA749': 'l',
	    '\uA781': 'l',
	    '\uA747': 'l',
	    '\u01C9': 'lj',
	    '\u24DC': 'm',
	    '\uFF4D': 'm',
	    '\u1E3F': 'm',
	    '\u1E41': 'm',
	    '\u1E43': 'm',
	    '\u0271': 'm',
	    '\u026F': 'm',
	    '\u24DD': 'n',
	    '\uFF4E': 'n',
	    '\u01F9': 'n',
	    '\u0144': 'n',
	    '\u00F1': 'n',
	    '\u1E45': 'n',
	    '\u0148': 'n',
	    '\u1E47': 'n',
	    '\u0146': 'n',
	    '\u1E4B': 'n',
	    '\u1E49': 'n',
	    '\u019E': 'n',
	    '\u0272': 'n',
	    '\u0149': 'n',
	    '\uA791': 'n',
	    '\uA7A5': 'n',
	    '\u01CC': 'nj',
	    '\u24DE': 'o',
	    '\uFF4F': 'o',
	    '\u00F2': 'o',
	    '\u00F3': 'o',
	    '\u00F4': 'o',
	    '\u1ED3': 'o',
	    '\u1ED1': 'o',
	    '\u1ED7': 'o',
	    '\u1ED5': 'o',
	    '\u00F5': 'o',
	    '\u1E4D': 'o',
	    '\u022D': 'o',
	    '\u1E4F': 'o',
	    '\u014D': 'o',
	    '\u1E51': 'o',
	    '\u1E53': 'o',
	    '\u014F': 'o',
	    '\u022F': 'o',
	    '\u0231': 'o',
	    '\u00F6': 'o',
	    '\u022B': 'o',
	    '\u1ECF': 'o',
	    '\u0151': 'o',
	    '\u01D2': 'o',
	    '\u020D': 'o',
	    '\u020F': 'o',
	    '\u01A1': 'o',
	    '\u1EDD': 'o',
	    '\u1EDB': 'o',
	    '\u1EE1': 'o',
	    '\u1EDF': 'o',
	    '\u1EE3': 'o',
	    '\u1ECD': 'o',
	    '\u1ED9': 'o',
	    '\u01EB': 'o',
	    '\u01ED': 'o',
	    '\u00F8': 'o',
	    '\u01FF': 'o',
	    '\u0254': 'o',
	    '\uA74B': 'o',
	    '\uA74D': 'o',
	    '\u0275': 'o',
	    '\u01A3': 'oi',
	    '\u0223': 'ou',
	    '\uA74F': 'oo',
	    '\u24DF': 'p',
	    '\uFF50': 'p',
	    '\u1E55': 'p',
	    '\u1E57': 'p',
	    '\u01A5': 'p',
	    '\u1D7D': 'p',
	    '\uA751': 'p',
	    '\uA753': 'p',
	    '\uA755': 'p',
	    '\u24E0': 'q',
	    '\uFF51': 'q',
	    '\u024B': 'q',
	    '\uA757': 'q',
	    '\uA759': 'q',
	    '\u24E1': 'r',
	    '\uFF52': 'r',
	    '\u0155': 'r',
	    '\u1E59': 'r',
	    '\u0159': 'r',
	    '\u0211': 'r',
	    '\u0213': 'r',
	    '\u1E5B': 'r',
	    '\u1E5D': 'r',
	    '\u0157': 'r',
	    '\u1E5F': 'r',
	    '\u024D': 'r',
	    '\u027D': 'r',
	    '\uA75B': 'r',
	    '\uA7A7': 'r',
	    '\uA783': 'r',
	    '\u24E2': 's',
	    '\uFF53': 's',
	    '\u00DF': 's',
	    '\u015B': 's',
	    '\u1E65': 's',
	    '\u015D': 's',
	    '\u1E61': 's',
	    '\u0161': 's',
	    '\u1E67': 's',
	    '\u1E63': 's',
	    '\u1E69': 's',
	    '\u0219': 's',
	    '\u015F': 's',
	    '\u023F': 's',
	    '\uA7A9': 's',
	    '\uA785': 's',
	    '\u1E9B': 's',
	    '\u24E3': 't',
	    '\uFF54': 't',
	    '\u1E6B': 't',
	    '\u1E97': 't',
	    '\u0165': 't',
	    '\u1E6D': 't',
	    '\u021B': 't',
	    '\u0163': 't',
	    '\u1E71': 't',
	    '\u1E6F': 't',
	    '\u0167': 't',
	    '\u01AD': 't',
	    '\u0288': 't',
	    '\u2C66': 't',
	    '\uA787': 't',
	    '\uA729': 'tz',
	    '\u24E4': 'u',
	    '\uFF55': 'u',
	    '\u00F9': 'u',
	    '\u00FA': 'u',
	    '\u00FB': 'u',
	    '\u0169': 'u',
	    '\u1E79': 'u',
	    '\u016B': 'u',
	    '\u1E7B': 'u',
	    '\u016D': 'u',
	    '\u00FC': 'u',
	    '\u01DC': 'u',
	    '\u01D8': 'u',
	    '\u01D6': 'u',
	    '\u01DA': 'u',
	    '\u1EE7': 'u',
	    '\u016F': 'u',
	    '\u0171': 'u',
	    '\u01D4': 'u',
	    '\u0215': 'u',
	    '\u0217': 'u',
	    '\u01B0': 'u',
	    '\u1EEB': 'u',
	    '\u1EE9': 'u',
	    '\u1EEF': 'u',
	    '\u1EED': 'u',
	    '\u1EF1': 'u',
	    '\u1EE5': 'u',
	    '\u1E73': 'u',
	    '\u0173': 'u',
	    '\u1E77': 'u',
	    '\u1E75': 'u',
	    '\u0289': 'u',
	    '\u24E5': 'v',
	    '\uFF56': 'v',
	    '\u1E7D': 'v',
	    '\u1E7F': 'v',
	    '\u028B': 'v',
	    '\uA75F': 'v',
	    '\u028C': 'v',
	    '\uA761': 'vy',
	    '\u24E6': 'w',
	    '\uFF57': 'w',
	    '\u1E81': 'w',
	    '\u1E83': 'w',
	    '\u0175': 'w',
	    '\u1E87': 'w',
	    '\u1E85': 'w',
	    '\u1E98': 'w',
	    '\u1E89': 'w',
	    '\u2C73': 'w',
	    '\u24E7': 'x',
	    '\uFF58': 'x',
	    '\u1E8B': 'x',
	    '\u1E8D': 'x',
	    '\u24E8': 'y',
	    '\uFF59': 'y',
	    '\u1EF3': 'y',
	    '\u00FD': 'y',
	    '\u0177': 'y',
	    '\u1EF9': 'y',
	    '\u0233': 'y',
	    '\u1E8F': 'y',
	    '\u00FF': 'y',
	    '\u1EF7': 'y',
	    '\u1E99': 'y',
	    '\u1EF5': 'y',
	    '\u01B4': 'y',
	    '\u024F': 'y',
	    '\u1EFF': 'y',
	    '\u24E9': 'z',
	    '\uFF5A': 'z',
	    '\u017A': 'z',
	    '\u1E91': 'z',
	    '\u017C': 'z',
	    '\u017E': 'z',
	    '\u1E93': 'z',
	    '\u1E95': 'z',
	    '\u01B6': 'z',
	    '\u0225': 'z',
	    '\u0240': 'z',
	    '\u2C6C': 'z',
	    '\uA763': 'z',
	    '\u0386': '\u0391',
	    '\u0388': '\u0395',
	    '\u0389': '\u0397',
	    '\u038A': '\u0399',
	    '\u03AA': '\u0399',
	    '\u038C': '\u039F',
	    '\u038E': '\u03A5',
	    '\u03AB': '\u03A5',
	    '\u038F': '\u03A9',
	    '\u03AC': '\u03B1',
	    '\u03AD': '\u03B5',
	    '\u03AE': '\u03B7',
	    '\u03AF': '\u03B9',
	    '\u03CA': '\u03B9',
	    '\u0390': '\u03B9',
	    '\u03CC': '\u03BF',
	    '\u03CD': '\u03C5',
	    '\u03CB': '\u03C5',
	    '\u03B0': '\u03C5',
	    '\u03C9': '\u03C9',
	    '\u03C2': '\u03C3'
	  };

	  return diacritics;
	});

	S2.define('select2/data/base',[
	  '../utils'
	], function (Utils) {
	  function BaseAdapter ($element, options) {
	    BaseAdapter.__super__.constructor.call(this);
	  }

	  Utils.Extend(BaseAdapter, Utils.Observable);

	  BaseAdapter.prototype.current = function (callback) {
	    throw new Error('The `current` method must be defined in child classes.');
	  };

	  BaseAdapter.prototype.query = function (params, callback) {
	    throw new Error('The `query` method must be defined in child classes.');
	  };

	  BaseAdapter.prototype.bind = function (container, $container) {
	    // Can be implemented in subclasses
	  };

	  BaseAdapter.prototype.destroy = function () {
	    // Can be implemented in subclasses
	  };

	  BaseAdapter.prototype.generateResultId = function (container, data) {
	    var id = container.id + '-result-';

	    id += Utils.generateChars(4);

	    if (data.id != null) {
	      id += '-' + data.id.toString();
	    } else {
	      id += '-' + Utils.generateChars(4);
	    }
	    return id;
	  };

	  return BaseAdapter;
	});

	S2.define('select2/data/select',[
	  './base',
	  '../utils',
	  'jquery'
	], function (BaseAdapter, Utils, $) {
	  function SelectAdapter ($element, options) {
	    this.$element = $element;
	    this.options = options;

	    SelectAdapter.__super__.constructor.call(this);
	  }

	  Utils.Extend(SelectAdapter, BaseAdapter);

	  SelectAdapter.prototype.current = function (callback) {
	    var data = [];
	    var self = this;

	    this.$element.find(':selected').each(function () {
	      var $option = $(this);

	      var option = self.item($option);

	      data.push(option);
	    });

	    callback(data);
	  };

	  SelectAdapter.prototype.select = function (data) {
	    var self = this;

	    data.selected = true;

	    // If data.element is a DOM node, use it instead
	    if ($(data.element).is('option')) {
	      data.element.selected = true;

	      this.$element.trigger('change');

	      return;
	    }

	    if (this.$element.prop('multiple')) {
	      this.current(function (currentData) {
	        var val = [];

	        data = [data];
	        data.push.apply(data, currentData);

	        for (var d = 0; d < data.length; d++) {
	          var id = data[d].id;

	          if ($.inArray(id, val) === -1) {
	            val.push(id);
	          }
	        }

	        self.$element.val(val);
	        self.$element.trigger('change');
	      });
	    } else {
	      var val = data.id;

	      this.$element.val(val);
	      this.$element.trigger('change');
	    }
	  };

	  SelectAdapter.prototype.unselect = function (data) {
	    var self = this;

	    if (!this.$element.prop('multiple')) {
	      return;
	    }

	    data.selected = false;

	    if ($(data.element).is('option')) {
	      data.element.selected = false;

	      this.$element.trigger('change');

	      return;
	    }

	    this.current(function (currentData) {
	      var val = [];

	      for (var d = 0; d < currentData.length; d++) {
	        var id = currentData[d].id;

	        if (id !== data.id && $.inArray(id, val) === -1) {
	          val.push(id);
	        }
	      }

	      self.$element.val(val);

	      self.$element.trigger('change');
	    });
	  };

	  SelectAdapter.prototype.bind = function (container, $container) {
	    var self = this;

	    this.container = container;

	    container.on('select', function (params) {
	      self.select(params.data);
	    });

	    container.on('unselect', function (params) {
	      self.unselect(params.data);
	    });
	  };

	  SelectAdapter.prototype.destroy = function () {
	    // Remove anything added to child elements
	    this.$element.find('*').each(function () {
	      // Remove any custom data set by Select2
	      $.removeData(this, 'data');
	    });
	  };

	  SelectAdapter.prototype.query = function (params, callback) {
	    var data = [];
	    var self = this;

	    var $options = this.$element.children();

	    $options.each(function () {
	      var $option = $(this);

	      if (!$option.is('option') && !$option.is('optgroup')) {
	        return;
	      }

	      var option = self.item($option);

	      var matches = self.matches(params, option);

	      if (matches !== null) {
	        data.push(matches);
	      }
	    });

	    callback({
	      results: data
	    });
	  };

	  SelectAdapter.prototype.addOptions = function ($options) {
	    Utils.appendMany(this.$element, $options);
	  };

	  SelectAdapter.prototype.option = function (data) {
	    var option;

	    if (data.children) {
	      option = document.createElement('optgroup');
	      option.label = data.text;
	    } else {
	      option = document.createElement('option');

	      if (option.textContent !== undefined) {
	        option.textContent = data.text;
	      } else {
	        option.innerText = data.text;
	      }
	    }

	    if (data.id !== undefined) {
	      option.value = data.id;
	    }

	    if (data.disabled) {
	      option.disabled = true;
	    }

	    if (data.selected) {
	      option.selected = true;
	    }

	    if (data.title) {
	      option.title = data.title;
	    }

	    var $option = $(option);

	    var normalizedData = this._normalizeItem(data);
	    normalizedData.element = option;

	    // Override the option's data with the combined data
	    $.data(option, 'data', normalizedData);

	    return $option;
	  };

	  SelectAdapter.prototype.item = function ($option) {
	    var data = {};

	    data = $.data($option[0], 'data');

	    if (data != null) {
	      return data;
	    }

	    if ($option.is('option')) {
	      data = {
	        id: $option.val(),
	        text: $option.text(),
	        disabled: $option.prop('disabled'),
	        selected: $option.prop('selected'),
	        title: $option.prop('title')
	      };
	    } else if ($option.is('optgroup')) {
	      data = {
	        text: $option.prop('label'),
	        children: [],
	        title: $option.prop('title')
	      };

	      var $children = $option.children('option');
	      var children = [];

	      for (var c = 0; c < $children.length; c++) {
	        var $child = $($children[c]);

	        var child = this.item($child);

	        children.push(child);
	      }

	      data.children = children;
	    }

	    data = this._normalizeItem(data);
	    data.element = $option[0];

	    $.data($option[0], 'data', data);

	    return data;
	  };

	  SelectAdapter.prototype._normalizeItem = function (item) {
	    if (!$.isPlainObject(item)) {
	      item = {
	        id: item,
	        text: item
	      };
	    }

	    item = $.extend({}, {
	      text: ''
	    }, item);

	    var defaults = {
	      selected: false,
	      disabled: false
	    };

	    if (item.id != null) {
	      item.id = item.id.toString();
	    }

	    if (item.text != null) {
	      item.text = item.text.toString();
	    }

	    if (item._resultId == null && item.id && this.container != null) {
	      item._resultId = this.generateResultId(this.container, item);
	    }

	    return $.extend({}, defaults, item);
	  };

	  SelectAdapter.prototype.matches = function (params, data) {
	    var matcher = this.options.get('matcher');

	    return matcher(params, data);
	  };

	  return SelectAdapter;
	});

	S2.define('select2/data/array',[
	  './select',
	  '../utils',
	  'jquery'
	], function (SelectAdapter, Utils, $) {
	  function ArrayAdapter ($element, options) {
	    var data = options.get('data') || [];

	    ArrayAdapter.__super__.constructor.call(this, $element, options);

	    this.addOptions(this.convertToOptions(data));
	  }

	  Utils.Extend(ArrayAdapter, SelectAdapter);

	  ArrayAdapter.prototype.select = function (data) {
	    var $option = this.$element.find('option').filter(function (i, elm) {
	      return elm.value == data.id.toString();
	    });

	    if ($option.length === 0) {
	      $option = this.option(data);

	      this.addOptions($option);
	    }

	    ArrayAdapter.__super__.select.call(this, data);
	  };

	  ArrayAdapter.prototype.convertToOptions = function (data) {
	    var self = this;

	    var $existing = this.$element.find('option');
	    var existingIds = $existing.map(function () {
	      return self.item($(this)).id;
	    }).get();

	    var $options = [];

	    // Filter out all items except for the one passed in the argument
	    function onlyItem (item) {
	      return function () {
	        return $(this).val() == item.id;
	      };
	    }

	    for (var d = 0; d < data.length; d++) {
	      var item = this._normalizeItem(data[d]);

	      // Skip items which were pre-loaded, only merge the data
	      if ($.inArray(item.id, existingIds) >= 0) {
	        var $existingOption = $existing.filter(onlyItem(item));

	        var existingData = this.item($existingOption);
	        var newData = $.extend(true, {}, item, existingData);

	        var $newOption = this.option(newData);

	        $existingOption.replaceWith($newOption);

	        continue;
	      }

	      var $option = this.option(item);

	      if (item.children) {
	        var $children = this.convertToOptions(item.children);

	        Utils.appendMany($option, $children);
	      }

	      $options.push($option);
	    }

	    return $options;
	  };

	  return ArrayAdapter;
	});

	S2.define('select2/data/ajax',[
	  './array',
	  '../utils',
	  'jquery'
	], function (ArrayAdapter, Utils, $) {
	  function AjaxAdapter ($element, options) {
	    this.ajaxOptions = this._applyDefaults(options.get('ajax'));

	    if (this.ajaxOptions.processResults != null) {
	      this.processResults = this.ajaxOptions.processResults;
	    }

	    AjaxAdapter.__super__.constructor.call(this, $element, options);
	  }

	  Utils.Extend(AjaxAdapter, ArrayAdapter);

	  AjaxAdapter.prototype._applyDefaults = function (options) {
	    var defaults = {
	      data: function (params) {
	        return $.extend({}, params, {
	          q: params.term
	        });
	      },
	      transport: function (params, success, failure) {
	        var $request = $.ajax(params);

	        $request.then(success);
	        $request.fail(failure);

	        return $request;
	      }
	    };

	    return $.extend({}, defaults, options, true);
	  };

	  AjaxAdapter.prototype.processResults = function (results) {
	    return results;
	  };

	  AjaxAdapter.prototype.query = function (params, callback) {
	    var matches = [];
	    var self = this;

	    if (this._request != null) {
	      // JSONP requests cannot always be aborted
	      if ($.isFunction(this._request.abort)) {
	        this._request.abort();
	      }

	      this._request = null;
	    }

	    var options = $.extend({
	      type: 'GET'
	    }, this.ajaxOptions);

	    if (typeof options.url === 'function') {
	      options.url = options.url.call(this.$element, params);
	    }

	    if (typeof options.data === 'function') {
	      options.data = options.data.call(this.$element, params);
	    }

	    function request () {
	      var $request = options.transport(options, function (data) {
	        var results = self.processResults(data, params);

	        if (self.options.get('debug') && window.console && console.error) {
	          // Check to make sure that the response included a `results` key.
	          if (!results || !results.results || !$.isArray(results.results)) {
	            console.error(
	              'Select2: The AJAX results did not return an array in the ' +
	              '`results` key of the response.'
	            );
	          }
	        }

	        callback(results);
	      }, function () {
	        // Attempt to detect if a request was aborted
	        // Only works if the transport exposes a status property
	        if ($request.status && $request.status === '0') {
	          return;
	        }

	        self.trigger('results:message', {
	          message: 'errorLoading'
	        });
	      });

	      self._request = $request;
	    }

	    if (this.ajaxOptions.delay && params.term != null) {
	      if (this._queryTimeout) {
	        window.clearTimeout(this._queryTimeout);
	      }

	      this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
	    } else {
	      request();
	    }
	  };

	  return AjaxAdapter;
	});

	S2.define('select2/data/tags',[
	  'jquery'
	], function ($) {
	  function Tags (decorated, $element, options) {
	    var tags = options.get('tags');

	    var createTag = options.get('createTag');

	    if (createTag !== undefined) {
	      this.createTag = createTag;
	    }

	    var insertTag = options.get('insertTag');

	    if (insertTag !== undefined) {
	        this.insertTag = insertTag;
	    }

	    decorated.call(this, $element, options);

	    if ($.isArray(tags)) {
	      for (var t = 0; t < tags.length; t++) {
	        var tag = tags[t];
	        var item = this._normalizeItem(tag);

	        var $option = this.option(item);

	        this.$element.append($option);
	      }
	    }
	  }

	  Tags.prototype.query = function (decorated, params, callback) {
	    var self = this;

	    this._removeOldTags();

	    if (params.term == null || params.page != null) {
	      decorated.call(this, params, callback);
	      return;
	    }

	    function wrapper (obj, child) {
	      var data = obj.results;

	      for (var i = 0; i < data.length; i++) {
	        var option = data[i];

	        var checkChildren = (
	          option.children != null &&
	          !wrapper({
	            results: option.children
	          }, true)
	        );

	        var optionText = (option.text || '').toUpperCase();
	        var paramsTerm = (params.term || '').toUpperCase();

	        var checkText = optionText === paramsTerm;

	        if (checkText || checkChildren) {
	          if (child) {
	            return false;
	          }

	          obj.data = data;
	          callback(obj);

	          return;
	        }
	      }

	      if (child) {
	        return true;
	      }

	      var tag = self.createTag(params);

	      if (tag != null) {
	        var $option = self.option(tag);
	        $option.attr('data-select2-tag', true);

	        self.addOptions([$option]);

	        self.insertTag(data, tag);
	      }

	      obj.results = data;

	      callback(obj);
	    }

	    decorated.call(this, params, wrapper);
	  };

	  Tags.prototype.createTag = function (decorated, params) {
	    var term = $.trim(params.term);

	    if (term === '') {
	      return null;
	    }

	    return {
	      id: term,
	      text: term
	    };
	  };

	  Tags.prototype.insertTag = function (_, data, tag) {
	    data.unshift(tag);
	  };

	  Tags.prototype._removeOldTags = function (_) {
	    var tag = this._lastTag;

	    var $options = this.$element.find('option[data-select2-tag]');

	    $options.each(function () {
	      if (this.selected) {
	        return;
	      }

	      $(this).remove();
	    });
	  };

	  return Tags;
	});

	S2.define('select2/data/tokenizer',[
	  'jquery'
	], function ($) {
	  function Tokenizer (decorated, $element, options) {
	    var tokenizer = options.get('tokenizer');

	    if (tokenizer !== undefined) {
	      this.tokenizer = tokenizer;
	    }

	    decorated.call(this, $element, options);
	  }

	  Tokenizer.prototype.bind = function (decorated, container, $container) {
	    decorated.call(this, container, $container);

	    this.$search =  container.dropdown.$search || container.selection.$search ||
	      $container.find('.select2-search__field');
	  };

	  Tokenizer.prototype.query = function (decorated, params, callback) {
	    var self = this;

	    function createAndSelect (data) {
	      // Normalize the data object so we can use it for checks
	      var item = self._normalizeItem(data);

	      // Check if the data object already exists as a tag
	      // Select it if it doesn't
	      var $existingOptions = self.$element.find('option').filter(function () {
	        return $(this).val() === item.id;
	      });

	      // If an existing option wasn't found for it, create the option
	      if (!$existingOptions.length) {
	        var $option = self.option(item);
	        $option.attr('data-select2-tag', true);

	        self._removeOldTags();
	        self.addOptions([$option]);
	      }

	      // Select the item, now that we know there is an option for it
	      select(item);
	    }

	    function select (data) {
	      self.trigger('select', {
	        data: data
	      });
	    }

	    params.term = params.term || '';

	    var tokenData = this.tokenizer(params, this.options, createAndSelect);

	    if (tokenData.term !== params.term) {
	      // Replace the search term if we have the search box
	      if (this.$search.length) {
	        this.$search.val(tokenData.term);
	        this.$search.focus();
	      }

	      params.term = tokenData.term;
	    }

	    decorated.call(this, params, callback);
	  };

	  Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
	    var separators = options.get('tokenSeparators') || [];
	    var term = params.term;
	    var i = 0;

	    var createTag = this.createTag || function (params) {
	      return {
	        id: params.term,
	        text: params.term
	      };
	    };

	    while (i < term.length) {
	      var termChar = term[i];

	      if ($.inArray(termChar, separators) === -1) {
	        i++;

	        continue;
	      }

	      var part = term.substr(0, i);
	      var partParams = $.extend({}, params, {
	        term: part
	      });

	      var data = createTag(partParams);

	      if (data == null) {
	        i++;
	        continue;
	      }

	      callback(data);

	      // Reset the term to not include the tokenized portion
	      term = term.substr(i + 1) || '';
	      i = 0;
	    }

	    return {
	      term: term
	    };
	  };

	  return Tokenizer;
	});

	S2.define('select2/data/minimumInputLength',[

	], function () {
	  function MinimumInputLength (decorated, $e, options) {
	    this.minimumInputLength = options.get('minimumInputLength');

	    decorated.call(this, $e, options);
	  }

	  MinimumInputLength.prototype.query = function (decorated, params, callback) {
	    params.term = params.term || '';

	    if (params.term.length < this.minimumInputLength) {
	      this.trigger('results:message', {
	        message: 'inputTooShort',
	        args: {
	          minimum: this.minimumInputLength,
	          input: params.term,
	          params: params
	        }
	      });

	      return;
	    }

	    decorated.call(this, params, callback);
	  };

	  return MinimumInputLength;
	});

	S2.define('select2/data/maximumInputLength',[

	], function () {
	  function MaximumInputLength (decorated, $e, options) {
	    this.maximumInputLength = options.get('maximumInputLength');

	    decorated.call(this, $e, options);
	  }

	  MaximumInputLength.prototype.query = function (decorated, params, callback) {
	    params.term = params.term || '';

	    if (this.maximumInputLength > 0 &&
	        params.term.length > this.maximumInputLength) {
	      this.trigger('results:message', {
	        message: 'inputTooLong',
	        args: {
	          maximum: this.maximumInputLength,
	          input: params.term,
	          params: params
	        }
	      });

	      return;
	    }

	    decorated.call(this, params, callback);
	  };

	  return MaximumInputLength;
	});

	S2.define('select2/data/maximumSelectionLength',[

	], function (){
	  function MaximumSelectionLength (decorated, $e, options) {
	    this.maximumSelectionLength = options.get('maximumSelectionLength');

	    decorated.call(this, $e, options);
	  }

	  MaximumSelectionLength.prototype.query =
	    function (decorated, params, callback) {
	      var self = this;

	      this.current(function (currentData) {
	        var count = currentData != null ? currentData.length : 0;
	        if (self.maximumSelectionLength > 0 &&
	          count >= self.maximumSelectionLength) {
	          self.trigger('results:message', {
	            message: 'maximumSelected',
	            args: {
	              maximum: self.maximumSelectionLength
	            }
	          });
	          return;
	        }
	        decorated.call(self, params, callback);
	      });
	  };

	  return MaximumSelectionLength;
	});

	S2.define('select2/dropdown',[
	  'jquery',
	  './utils'
	], function ($, Utils) {
	  function Dropdown ($element, options) {
	    this.$element = $element;
	    this.options = options;

	    Dropdown.__super__.constructor.call(this);
	  }

	  Utils.Extend(Dropdown, Utils.Observable);

	  Dropdown.prototype.render = function () {
	    var $dropdown = $(
	      '<span class="select2-dropdown">' +
	        '<span class="select2-results"></span>' +
	      '</span>'
	    );

	    $dropdown.attr('dir', this.options.get('dir'));

	    this.$dropdown = $dropdown;

	    return $dropdown;
	  };

	  Dropdown.prototype.bind = function () {
	    // Should be implemented in subclasses
	  };

	  Dropdown.prototype.position = function ($dropdown, $container) {
	    // Should be implmented in subclasses
	  };

	  Dropdown.prototype.destroy = function () {
	    // Remove the dropdown from the DOM
	    this.$dropdown.remove();
	  };

	  return Dropdown;
	});

	S2.define('select2/dropdown/search',[
	  'jquery',
	  '../utils'
	], function ($, Utils) {
	  function Search () { }

	  Search.prototype.render = function (decorated) {
	    var $rendered = decorated.call(this);

	    var $search = $(
	      '<span class="select2-search select2-search--dropdown">' +
	        '<input class="select2-search__field" type="search" tabindex="-1"' +
	        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
	        ' spellcheck="false" role="textbox" />' +
	      '</span>'
	    );

	    this.$searchContainer = $search;
	    this.$search = $search.find('input');

	    $rendered.prepend($search);

	    return $rendered;
	  };

	  Search.prototype.bind = function (decorated, container, $container) {
	    var self = this;

	    decorated.call(this, container, $container);

	    this.$search.on('keydown', function (evt) {
	      self.trigger('keypress', evt);

	      self._keyUpPrevented = evt.isDefaultPrevented();
	    });

	    // Workaround for browsers which do not support the `input` event
	    // This will prevent double-triggering of events for browsers which support
	    // both the `keyup` and `input` events.
	    this.$search.on('input', function (evt) {
	      // Unbind the duplicated `keyup` event
	      $(this).off('keyup');
	    });

	    this.$search.on('keyup input', function (evt) {
	      self.handleSearch(evt);
	    });

	    container.on('open', function () {
	      self.$search.attr('tabindex', 0);

	      self.$search.focus();

	      window.setTimeout(function () {
	        self.$search.focus();
	      }, 0);
	    });

	    container.on('close', function () {
	      self.$search.attr('tabindex', -1);

	      self.$search.val('');
	    });

	    container.on('focus', function () {
	      if (!container.isOpen()) {
	        self.$search.focus();
	      }
	    });

	    container.on('results:all', function (params) {
	      if (params.query.term == null || params.query.term === '') {
	        var showSearch = self.showSearch(params);

	        if (showSearch) {
	          self.$searchContainer.removeClass('select2-search--hide');
	        } else {
	          self.$searchContainer.addClass('select2-search--hide');
	        }
	      }
	    });
	  };

	  Search.prototype.handleSearch = function (evt) {
	    if (!this._keyUpPrevented) {
	      var input = this.$search.val();

	      this.trigger('query', {
	        term: input
	      });
	    }

	    this._keyUpPrevented = false;
	  };

	  Search.prototype.showSearch = function (_, params) {
	    return true;
	  };

	  return Search;
	});

	S2.define('select2/dropdown/hidePlaceholder',[

	], function () {
	  function HidePlaceholder (decorated, $element, options, dataAdapter) {
	    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));

	    decorated.call(this, $element, options, dataAdapter);
	  }

	  HidePlaceholder.prototype.append = function (decorated, data) {
	    data.results = this.removePlaceholder(data.results);

	    decorated.call(this, data);
	  };

	  HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
	    if (typeof placeholder === 'string') {
	      placeholder = {
	        id: '',
	        text: placeholder
	      };
	    }

	    return placeholder;
	  };

	  HidePlaceholder.prototype.removePlaceholder = function (_, data) {
	    var modifiedData = data.slice(0);

	    for (var d = data.length - 1; d >= 0; d--) {
	      var item = data[d];

	      if (this.placeholder.id === item.id) {
	        modifiedData.splice(d, 1);
	      }
	    }

	    return modifiedData;
	  };

	  return HidePlaceholder;
	});

	S2.define('select2/dropdown/infiniteScroll',[
	  'jquery'
	], function ($) {
	  function InfiniteScroll (decorated, $element, options, dataAdapter) {
	    this.lastParams = {};

	    decorated.call(this, $element, options, dataAdapter);

	    this.$loadingMore = this.createLoadingMore();
	    this.loading = false;
	  }

	  InfiniteScroll.prototype.append = function (decorated, data) {
	    this.$loadingMore.remove();
	    this.loading = false;

	    decorated.call(this, data);

	    if (this.showLoadingMore(data)) {
	      this.$results.append(this.$loadingMore);
	    }
	  };

	  InfiniteScroll.prototype.bind = function (decorated, container, $container) {
	    var self = this;

	    decorated.call(this, container, $container);

	    container.on('query', function (params) {
	      self.lastParams = params;
	      self.loading = true;
	    });

	    container.on('query:append', function (params) {
	      self.lastParams = params;
	      self.loading = true;
	    });

	    this.$results.on('scroll', function () {
	      var isLoadMoreVisible = $.contains(
	        document.documentElement,
	        self.$loadingMore[0]
	      );

	      if (self.loading || !isLoadMoreVisible) {
	        return;
	      }

	      var currentOffset = self.$results.offset().top +
	        self.$results.outerHeight(false);
	      var loadingMoreOffset = self.$loadingMore.offset().top +
	        self.$loadingMore.outerHeight(false);

	      if (currentOffset + 50 >= loadingMoreOffset) {
	        self.loadMore();
	      }
	    });
	  };

	  InfiniteScroll.prototype.loadMore = function () {
	    this.loading = true;

	    var params = $.extend({}, {page: 1}, this.lastParams);

	    params.page++;

	    this.trigger('query:append', params);
	  };

	  InfiniteScroll.prototype.showLoadingMore = function (_, data) {
	    return data.pagination && data.pagination.more;
	  };

	  InfiniteScroll.prototype.createLoadingMore = function () {
	    var $option = $(
	      '<li ' +
	      'class="select2-results__option select2-results__option--load-more"' +
	      'role="treeitem" aria-disabled="true"></li>'
	    );

	    var message = this.options.get('translations').get('loadingMore');

	    $option.html(message(this.lastParams));

	    return $option;
	  };

	  return InfiniteScroll;
	});

	S2.define('select2/dropdown/attachBody',[
	  'jquery',
	  '../utils'
	], function ($, Utils) {
	  function AttachBody (decorated, $element, options) {
	    this.$dropdownParent = options.get('dropdownParent') || $(document.body);

	    decorated.call(this, $element, options);
	  }

	  AttachBody.prototype.bind = function (decorated, container, $container) {
	    var self = this;

	    var setupResultsEvents = false;

	    decorated.call(this, container, $container);

	    container.on('open', function () {
	      self._showDropdown();
	      self._attachPositioningHandler(container);

	      if (!setupResultsEvents) {
	        setupResultsEvents = true;

	        container.on('results:all', function () {
	          self._positionDropdown();
	          self._resizeDropdown();
	        });

	        container.on('results:append', function () {
	          self._positionDropdown();
	          self._resizeDropdown();
	        });
	      }
	    });

	    container.on('close', function () {
	      self._hideDropdown();
	      self._detachPositioningHandler(container);
	    });

	    this.$dropdownContainer.on('mousedown', function (evt) {
	      evt.stopPropagation();
	    });
	  };

	  AttachBody.prototype.destroy = function (decorated) {
	    decorated.call(this);

	    this.$dropdownContainer.remove();
	  };

	  AttachBody.prototype.position = function (decorated, $dropdown, $container) {
	    // Clone all of the container classes
	    $dropdown.attr('class', $container.attr('class'));

	    $dropdown.removeClass('select2');
	    $dropdown.addClass('select2-container--open');

	    $dropdown.css({
	      position: 'absolute',
	      top: -999999
	    });

	    this.$container = $container;
	  };

	  AttachBody.prototype.render = function (decorated) {
	    var $container = $('<span></span>');

	    var $dropdown = decorated.call(this);
	    $container.append($dropdown);

	    this.$dropdownContainer = $container;

	    return $container;
	  };

	  AttachBody.prototype._hideDropdown = function (decorated) {
	    this.$dropdownContainer.detach();
	  };

	  AttachBody.prototype._attachPositioningHandler =
	      function (decorated, container) {
	    var self = this;

	    var scrollEvent = 'scroll.select2.' + container.id;
	    var resizeEvent = 'resize.select2.' + container.id;
	    var orientationEvent = 'orientationchange.select2.' + container.id;

	    var $watchers = this.$container.parents().filter(Utils.hasScroll);
	    $watchers.each(function () {
	      $(this).data('select2-scroll-position', {
	        x: $(this).scrollLeft(),
	        y: $(this).scrollTop()
	      });
	    });

	    $watchers.on(scrollEvent, function (ev) {
	      var position = $(this).data('select2-scroll-position');
	      $(this).scrollTop(position.y);
	    });

	    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
	      function (e) {
	      self._positionDropdown();
	      self._resizeDropdown();
	    });
	  };

	  AttachBody.prototype._detachPositioningHandler =
	      function (decorated, container) {
	    var scrollEvent = 'scroll.select2.' + container.id;
	    var resizeEvent = 'resize.select2.' + container.id;
	    var orientationEvent = 'orientationchange.select2.' + container.id;

	    var $watchers = this.$container.parents().filter(Utils.hasScroll);
	    $watchers.off(scrollEvent);

	    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
	  };

	  AttachBody.prototype._positionDropdown = function () {
	    var $window = $(window);

	    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
	    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');

	    var newDirection = null;

	    var offset = this.$container.offset();

	    offset.bottom = offset.top + this.$container.outerHeight(false);

	    var container = {
	      height: this.$container.outerHeight(false)
	    };

	    container.top = offset.top;
	    container.bottom = offset.top + container.height;

	    var dropdown = {
	      height: this.$dropdown.outerHeight(false)
	    };

	    var viewport = {
	      top: $window.scrollTop(),
	      bottom: $window.scrollTop() + $window.height()
	    };

	    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
	    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);

	    var css = {
	      left: offset.left,
	      top: container.bottom
	    };

	    // Determine what the parent element is to use for calciulating the offset
	    var $offsetParent = this.$dropdownParent;

	    // For statically positoned elements, we need to get the element
	    // that is determining the offset
	    if ($offsetParent.css('position') === 'static') {
	      $offsetParent = $offsetParent.offsetParent();
	    }

	    var parentOffset = $offsetParent.offset();

	    css.top -= parentOffset.top;
	    css.left -= parentOffset.left;

	    if (!isCurrentlyAbove && !isCurrentlyBelow) {
	      newDirection = 'below';
	    }

	    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
	      newDirection = 'above';
	    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
	      newDirection = 'below';
	    }

	    if (newDirection == 'above' ||
	      (isCurrentlyAbove && newDirection !== 'below')) {
	      css.top = container.top - parentOffset.top - dropdown.height;
	    }

	    if (newDirection != null) {
	      this.$dropdown
	        .removeClass('select2-dropdown--below select2-dropdown--above')
	        .addClass('select2-dropdown--' + newDirection);
	      this.$container
	        .removeClass('select2-container--below select2-container--above')
	        .addClass('select2-container--' + newDirection);
	    }

	    this.$dropdownContainer.css(css);
	  };

	  AttachBody.prototype._resizeDropdown = function () {
	    var css = {
	      width: this.$container.outerWidth(false) + 'px'
	    };

	    if (this.options.get('dropdownAutoWidth')) {
	      css.minWidth = css.width;
	      css.position = 'relative';
	      css.width = 'auto';
	    }

	    this.$dropdown.css(css);
	  };

	  AttachBody.prototype._showDropdown = function (decorated) {
	    this.$dropdownContainer.appendTo(this.$dropdownParent);

	    this._positionDropdown();
	    this._resizeDropdown();
	  };

	  return AttachBody;
	});

	S2.define('select2/dropdown/minimumResultsForSearch',[

	], function () {
	  function countResults (data) {
	    var count = 0;

	    for (var d = 0; d < data.length; d++) {
	      var item = data[d];

	      if (item.children) {
	        count += countResults(item.children);
	      } else {
	        count++;
	      }
	    }

	    return count;
	  }

	  function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
	    this.minimumResultsForSearch = options.get('minimumResultsForSearch');

	    if (this.minimumResultsForSearch < 0) {
	      this.minimumResultsForSearch = Infinity;
	    }

	    decorated.call(this, $element, options, dataAdapter);
	  }

	  MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
	    if (countResults(params.data.results) < this.minimumResultsForSearch) {
	      return false;
	    }

	    return decorated.call(this, params);
	  };

	  return MinimumResultsForSearch;
	});

	S2.define('select2/dropdown/selectOnClose',[

	], function () {
	  function SelectOnClose () { }

	  SelectOnClose.prototype.bind = function (decorated, container, $container) {
	    var self = this;

	    decorated.call(this, container, $container);

	    container.on('close', function (params) {
	      self._handleSelectOnClose(params);
	    });
	  };

	  SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
	    if (params && params.originalSelect2Event != null) {
	      var event = params.originalSelect2Event;

	      // Don't select an item if the close event was triggered from a select or
	      // unselect event
	      if (event._type === 'select' || event._type === 'unselect') {
	        return;
	      }
	    }

	    var $highlightedResults = this.getHighlightedResults();

	    // Only select highlighted results
	    if ($highlightedResults.length < 1) {
	      return;
	    }

	    var data = $highlightedResults.data('data');

	    // Don't re-select already selected resulte
	    if (
	      (data.element != null && data.element.selected) ||
	      (data.element == null && data.selected)
	    ) {
	      return;
	    }

	    this.trigger('select', {
	        data: data
	    });
	  };

	  return SelectOnClose;
	});

	S2.define('select2/dropdown/closeOnSelect',[

	], function () {
	  function CloseOnSelect () { }

	  CloseOnSelect.prototype.bind = function (decorated, container, $container) {
	    var self = this;

	    decorated.call(this, container, $container);

	    container.on('select', function (evt) {
	      self._selectTriggered(evt);
	    });

	    container.on('unselect', function (evt) {
	      self._selectTriggered(evt);
	    });
	  };

	  CloseOnSelect.prototype._selectTriggered = function (_, evt) {
	    var originalEvent = evt.originalEvent;

	    // Don't close if the control key is being held
	    if (originalEvent && originalEvent.ctrlKey) {
	      return;
	    }

	    this.trigger('close', {
	      originalEvent: originalEvent,
	      originalSelect2Event: evt
	    });
	  };

	  return CloseOnSelect;
	});

	S2.define('select2/i18n/en',[],function () {
	  // English
	  return {
	    errorLoading: function () {
	      return 'The results could not be loaded.';
	    },
	    inputTooLong: function (args) {
	      var overChars = args.input.length - args.maximum;

	      var message = 'Please delete ' + overChars + ' character';

	      if (overChars != 1) {
	        message += 's';
	      }

	      return message;
	    },
	    inputTooShort: function (args) {
	      var remainingChars = args.minimum - args.input.length;

	      var message = 'Please enter ' + remainingChars + ' or more characters';

	      return message;
	    },
	    loadingMore: function () {
	      return 'Loading more results…';
	    },
	    maximumSelected: function (args) {
	      var message = 'You can only select ' + args.maximum + ' item';

	      if (args.maximum != 1) {
	        message += 's';
	      }

	      return message;
	    },
	    noResults: function () {
	      return 'No results found';
	    },
	    searching: function () {
	      return 'Searching…';
	    }
	  };
	});

	S2.define('select2/defaults',[
	  'jquery',
	  'require',

	  './results',

	  './selection/single',
	  './selection/multiple',
	  './selection/placeholder',
	  './selection/allowClear',
	  './selection/search',
	  './selection/eventRelay',

	  './utils',
	  './translation',
	  './diacritics',

	  './data/select',
	  './data/array',
	  './data/ajax',
	  './data/tags',
	  './data/tokenizer',
	  './data/minimumInputLength',
	  './data/maximumInputLength',
	  './data/maximumSelectionLength',

	  './dropdown',
	  './dropdown/search',
	  './dropdown/hidePlaceholder',
	  './dropdown/infiniteScroll',
	  './dropdown/attachBody',
	  './dropdown/minimumResultsForSearch',
	  './dropdown/selectOnClose',
	  './dropdown/closeOnSelect',

	  './i18n/en'
	], function ($, require,

	             ResultsList,

	             SingleSelection, MultipleSelection, Placeholder, AllowClear,
	             SelectionSearch, EventRelay,

	             Utils, Translation, DIACRITICS,

	             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
	             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,

	             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
	             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,

	             EnglishTranslation) {
	  function Defaults () {
	    this.reset();
	  }

	  Defaults.prototype.apply = function (options) {
	    options = $.extend(true, {}, this.defaults, options);

	    if (options.dataAdapter == null) {
	      if (options.ajax != null) {
	        options.dataAdapter = AjaxData;
	      } else if (options.data != null) {
	        options.dataAdapter = ArrayData;
	      } else {
	        options.dataAdapter = SelectData;
	      }

	      if (options.minimumInputLength > 0) {
	        options.dataAdapter = Utils.Decorate(
	          options.dataAdapter,
	          MinimumInputLength
	        );
	      }

	      if (options.maximumInputLength > 0) {
	        options.dataAdapter = Utils.Decorate(
	          options.dataAdapter,
	          MaximumInputLength
	        );
	      }

	      if (options.maximumSelectionLength > 0) {
	        options.dataAdapter = Utils.Decorate(
	          options.dataAdapter,
	          MaximumSelectionLength
	        );
	      }

	      if (options.tags) {
	        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
	      }

	      if (options.tokenSeparators != null || options.tokenizer != null) {
	        options.dataAdapter = Utils.Decorate(
	          options.dataAdapter,
	          Tokenizer
	        );
	      }

	      if (options.query != null) {
	        var Query = require(options.amdBase + 'compat/query');

	        options.dataAdapter = Utils.Decorate(
	          options.dataAdapter,
	          Query
	        );
	      }

	      if (options.initSelection != null) {
	        var InitSelection = require(options.amdBase + 'compat/initSelection');

	        options.dataAdapter = Utils.Decorate(
	          options.dataAdapter,
	          InitSelection
	        );
	      }
	    }

	    if (options.resultsAdapter == null) {
	      options.resultsAdapter = ResultsList;

	      if (options.ajax != null) {
	        options.resultsAdapter = Utils.Decorate(
	          options.resultsAdapter,
	          InfiniteScroll
	        );
	      }

	      if (options.placeholder != null) {
	        options.resultsAdapter = Utils.Decorate(
	          options.resultsAdapter,
	          HidePlaceholder
	        );
	      }

	      if (options.selectOnClose) {
	        options.resultsAdapter = Utils.Decorate(
	          options.resultsAdapter,
	          SelectOnClose
	        );
	      }
	    }

	    if (options.dropdownAdapter == null) {
	      if (options.multiple) {
	        options.dropdownAdapter = Dropdown;
	      } else {
	        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);

	        options.dropdownAdapter = SearchableDropdown;
	      }

	      if (options.minimumResultsForSearch !== 0) {
	        options.dropdownAdapter = Utils.Decorate(
	          options.dropdownAdapter,
	          MinimumResultsForSearch
	        );
	      }

	      if (options.closeOnSelect) {
	        options.dropdownAdapter = Utils.Decorate(
	          options.dropdownAdapter,
	          CloseOnSelect
	        );
	      }

	      if (
	        options.dropdownCssClass != null ||
	        options.dropdownCss != null ||
	        options.adaptDropdownCssClass != null
	      ) {
	        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');

	        options.dropdownAdapter = Utils.Decorate(
	          options.dropdownAdapter,
	          DropdownCSS
	        );
	      }

	      options.dropdownAdapter = Utils.Decorate(
	        options.dropdownAdapter,
	        AttachBody
	      );
	    }

	    if (options.selectionAdapter == null) {
	      if (options.multiple) {
	        options.selectionAdapter = MultipleSelection;
	      } else {
	        options.selectionAdapter = SingleSelection;
	      }

	      // Add the placeholder mixin if a placeholder was specified
	      if (options.placeholder != null) {
	        options.selectionAdapter = Utils.Decorate(
	          options.selectionAdapter,
	          Placeholder
	        );
	      }

	      if (options.allowClear) {
	        options.selectionAdapter = Utils.Decorate(
	          options.selectionAdapter,
	          AllowClear
	        );
	      }

	      if (options.multiple) {
	        options.selectionAdapter = Utils.Decorate(
	          options.selectionAdapter,
	          SelectionSearch
	        );
	      }

	      if (
	        options.containerCssClass != null ||
	        options.containerCss != null ||
	        options.adaptContainerCssClass != null
	      ) {
	        var ContainerCSS = require(options.amdBase + 'compat/containerCss');

	        options.selectionAdapter = Utils.Decorate(
	          options.selectionAdapter,
	          ContainerCSS
	        );
	      }

	      options.selectionAdapter = Utils.Decorate(
	        options.selectionAdapter,
	        EventRelay
	      );
	    }

	    if (typeof options.language === 'string') {
	      // Check if the language is specified with a region
	      if (options.language.indexOf('-') > 0) {
	        // Extract the region information if it is included
	        var languageParts = options.language.split('-');
	        var baseLanguage = languageParts[0];

	        options.language = [options.language, baseLanguage];
	      } else {
	        options.language = [options.language];
	      }
	    }

	    if ($.isArray(options.language)) {
	      var languages = new Translation();
	      options.language.push('en');

	      var languageNames = options.language;

	      for (var l = 0; l < languageNames.length; l++) {
	        var name = languageNames[l];
	        var language = {};

	        try {
	          // Try to load it with the original name
	          language = Translation.loadPath(name);
	        } catch (e) {
	          try {
	            // If we couldn't load it, check if it wasn't the full path
	            name = this.defaults.amdLanguageBase + name;
	            language = Translation.loadPath(name);
	          } catch (ex) {
	            // The translation could not be loaded at all. Sometimes this is
	            // because of a configuration problem, other times this can be
	            // because of how Select2 helps load all possible translation files.
	            if (options.debug && window.console && console.warn) {
	              console.warn(
	                'Select2: The language file for "' + name + '" could not be ' +
	                'automatically loaded. A fallback will be used instead.'
	              );
	            }

	            continue;
	          }
	        }

	        languages.extend(language);
	      }

	      options.translations = languages;
	    } else {
	      var baseTranslation = Translation.loadPath(
	        this.defaults.amdLanguageBase + 'en'
	      );
	      var customTranslation = new Translation(options.language);

	      customTranslation.extend(baseTranslation);

	      options.translations = customTranslation;
	    }

	    return options;
	  };

	  Defaults.prototype.reset = function () {
	    function stripDiacritics (text) {
	      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
	      function match(a) {
	        return DIACRITICS[a] || a;
	      }

	      return text.replace(/[^\u0000-\u007E]/g, match);
	    }

	    function matcher (params, data) {
	      // Always return the object if there is nothing to compare
	      if ($.trim(params.term) === '') {
	        return data;
	      }

	      // Do a recursive check for options with children
	      if (data.children && data.children.length > 0) {
	        // Clone the data object if there are children
	        // This is required as we modify the object to remove any non-matches
	        var match = $.extend(true, {}, data);

	        // Check each child of the option
	        for (var c = data.children.length - 1; c >= 0; c--) {
	          var child = data.children[c];

	          var matches = matcher(params, child);

	          // If there wasn't a match, remove the object in the array
	          if (matches == null) {
	            match.children.splice(c, 1);
	          }
	        }

	        // If any children matched, return the new object
	        if (match.children.length > 0) {
	          return match;
	        }

	        // If there were no matching children, check just the plain object
	        return matcher(params, match);
	      }

	      var original = stripDiacritics(data.text).toUpperCase();
	      var term = stripDiacritics(params.term).toUpperCase();

	      // Check if the text contains the term
	      if (original.indexOf(term) > -1) {
	        return data;
	      }

	      // If it doesn't contain the term, don't return anything
	      return null;
	    }

	    this.defaults = {
	      amdBase: './',
	      amdLanguageBase: './i18n/',
	      closeOnSelect: true,
	      debug: false,
	      dropdownAutoWidth: false,
	      escapeMarkup: Utils.escapeMarkup,
	      language: EnglishTranslation,
	      matcher: matcher,
	      minimumInputLength: 0,
	      maximumInputLength: 0,
	      maximumSelectionLength: 0,
	      minimumResultsForSearch: 0,
	      selectOnClose: false,
	      sorter: function (data) {
	        return data;
	      },
	      templateResult: function (result) {
	        return result.text;
	      },
	      templateSelection: function (selection) {
	        return selection.text;
	      },
	      theme: 'default',
	      width: 'resolve'
	    };
	  };

	  Defaults.prototype.set = function (key, value) {
	    var camelKey = $.camelCase(key);

	    var data = {};
	    data[camelKey] = value;

	    var convertedData = Utils._convertData(data);

	    $.extend(this.defaults, convertedData);
	  };

	  var defaults = new Defaults();

	  return defaults;
	});

	S2.define('select2/options',[
	  'require',
	  'jquery',
	  './defaults',
	  './utils'
	], function (require, $, Defaults, Utils) {
	  function Options (options, $element) {
	    this.options = options;

	    if ($element != null) {
	      this.fromElement($element);
	    }

	    this.options = Defaults.apply(this.options);

	    if ($element && $element.is('input')) {
	      var InputCompat = require(this.get('amdBase') + 'compat/inputData');

	      this.options.dataAdapter = Utils.Decorate(
	        this.options.dataAdapter,
	        InputCompat
	      );
	    }
	  }

	  Options.prototype.fromElement = function ($e) {
	    var excludedData = ['select2'];

	    if (this.options.multiple == null) {
	      this.options.multiple = $e.prop('multiple');
	    }

	    if (this.options.disabled == null) {
	      this.options.disabled = $e.prop('disabled');
	    }

	    if (this.options.language == null) {
	      if ($e.prop('lang')) {
	        this.options.language = $e.prop('lang').toLowerCase();
	      } else if ($e.closest('[lang]').prop('lang')) {
	        this.options.language = $e.closest('[lang]').prop('lang');
	      }
	    }

	    if (this.options.dir == null) {
	      if ($e.prop('dir')) {
	        this.options.dir = $e.prop('dir');
	      } else if ($e.closest('[dir]').prop('dir')) {
	        this.options.dir = $e.closest('[dir]').prop('dir');
	      } else {
	        this.options.dir = 'ltr';
	      }
	    }

	    $e.prop('disabled', this.options.disabled);
	    $e.prop('multiple', this.options.multiple);

	    if ($e.data('select2Tags')) {
	      if (this.options.debug && window.console && console.warn) {
	        console.warn(
	          'Select2: The `data-select2-tags` attribute has been changed to ' +
	          'use the `data-data` and `data-tags="true"` attributes and will be ' +
	          'removed in future versions of Select2.'
	        );
	      }

	      $e.data('data', $e.data('select2Tags'));
	      $e.data('tags', true);
	    }

	    if ($e.data('ajaxUrl')) {
	      if (this.options.debug && window.console && console.warn) {
	        console.warn(
	          'Select2: The `data-ajax-url` attribute has been changed to ' +
	          '`data-ajax--url` and support for the old attribute will be removed' +
	          ' in future versions of Select2.'
	        );
	      }

	      $e.attr('ajax--url', $e.data('ajaxUrl'));
	      $e.data('ajax--url', $e.data('ajaxUrl'));
	    }

	    var dataset = {};

	    // Prefer the element's `dataset` attribute if it exists
	    // jQuery 1.x does not correctly handle data attributes with multiple dashes
	    if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
	      dataset = $.extend(true, {}, $e[0].dataset, $e.data());
	    } else {
	      dataset = $e.data();
	    }

	    var data = $.extend(true, {}, dataset);

	    data = Utils._convertData(data);

	    for (var key in data) {
	      if ($.inArray(key, excludedData) > -1) {
	        continue;
	      }

	      if ($.isPlainObject(this.options[key])) {
	        $.extend(this.options[key], data[key]);
	      } else {
	        this.options[key] = data[key];
	      }
	    }

	    return this;
	  };

	  Options.prototype.get = function (key) {
	    return this.options[key];
	  };

	  Options.prototype.set = function (key, val) {
	    this.options[key] = val;
	  };

	  return Options;
	});

	S2.define('select2/core',[
	  'jquery',
	  './options',
	  './utils',
	  './keys'
	], function ($, Options, Utils, KEYS) {
	  var Select2 = function ($element, options) {
	    if ($element.data('select2') != null) {
	      $element.data('select2').destroy();
	    }

	    this.$element = $element;

	    this.id = this._generateId($element);

	    options = options || {};

	    this.options = new Options(options, $element);

	    Select2.__super__.constructor.call(this);

	    // Set up the tabindex

	    var tabindex = $element.attr('tabindex') || 0;
	    $element.data('old-tabindex', tabindex);
	    $element.attr('tabindex', '-1');

	    // Set up containers and adapters

	    var DataAdapter = this.options.get('dataAdapter');
	    this.dataAdapter = new DataAdapter($element, this.options);

	    var $container = this.render();

	    this._placeContainer($container);

	    var SelectionAdapter = this.options.get('selectionAdapter');
	    this.selection = new SelectionAdapter($element, this.options);
	    this.$selection = this.selection.render();

	    this.selection.position(this.$selection, $container);

	    var DropdownAdapter = this.options.get('dropdownAdapter');
	    this.dropdown = new DropdownAdapter($element, this.options);
	    this.$dropdown = this.dropdown.render();

	    this.dropdown.position(this.$dropdown, $container);

	    var ResultsAdapter = this.options.get('resultsAdapter');
	    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
	    this.$results = this.results.render();

	    this.results.position(this.$results, this.$dropdown);

	    // Bind events

	    var self = this;

	    // Bind the container to all of the adapters
	    this._bindAdapters();

	    // Register any DOM event handlers
	    this._registerDomEvents();

	    // Register any internal event handlers
	    this._registerDataEvents();
	    this._registerSelectionEvents();
	    this._registerDropdownEvents();
	    this._registerResultsEvents();
	    this._registerEvents();

	    // Set the initial state
	    this.dataAdapter.current(function (initialData) {
	      self.trigger('selection:update', {
	        data: initialData
	      });
	    });

	    // Hide the original select
	    $element.addClass('select2-hidden-accessible');
	    $element.attr('aria-hidden', 'true');

	    // Synchronize any monitored attributes
	    this._syncAttributes();

	    $element.data('select2', this);
	  };

	  Utils.Extend(Select2, Utils.Observable);

	  Select2.prototype._generateId = function ($element) {
	    var id = '';

	    if ($element.attr('id') != null) {
	      id = $element.attr('id');
	    } else if ($element.attr('name') != null) {
	      id = $element.attr('name') + '-' + Utils.generateChars(2);
	    } else {
	      id = Utils.generateChars(4);
	    }

	    id = id.replace(/(:|\.|\[|\]|,)/g, '');
	    id = 'select2-' + id;

	    return id;
	  };

	  Select2.prototype._placeContainer = function ($container) {
	    $container.insertAfter(this.$element);

	    var width = this._resolveWidth(this.$element, this.options.get('width'));

	    if (width != null) {
	      $container.css('width', width);
	    }
	  };

	  Select2.prototype._resolveWidth = function ($element, method) {
	    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;

	    if (method == 'resolve') {
	      var styleWidth = this._resolveWidth($element, 'style');

	      if (styleWidth != null) {
	        return styleWidth;
	      }

	      return this._resolveWidth($element, 'element');
	    }

	    if (method == 'element') {
	      var elementWidth = $element.outerWidth(false);

	      if (elementWidth <= 0) {
	        return 'auto';
	      }

	      return elementWidth + 'px';
	    }

	    if (method == 'style') {
	      var style = $element.attr('style');

	      if (typeof(style) !== 'string') {
	        return null;
	      }

	      var attrs = style.split(';');

	      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
	        var attr = attrs[i].replace(/\s/g, '');
	        var matches = attr.match(WIDTH);

	        if (matches !== null && matches.length >= 1) {
	          return matches[1];
	        }
	      }

	      return null;
	    }

	    return method;
	  };

	  Select2.prototype._bindAdapters = function () {
	    this.dataAdapter.bind(this, this.$container);
	    this.selection.bind(this, this.$container);

	    this.dropdown.bind(this, this.$container);
	    this.results.bind(this, this.$container);
	  };

	  Select2.prototype._registerDomEvents = function () {
	    var self = this;

	    this.$element.on('change.select2', function () {
	      self.dataAdapter.current(function (data) {
	        self.trigger('selection:update', {
	          data: data
	        });
	      });
	    });

	    this.$element.on('focus.select2', function (evt) {
	      self.trigger('focus', evt);
	    });

	    this._syncA = Utils.bind(this._syncAttributes, this);
	    this._syncS = Utils.bind(this._syncSubtree, this);

	    if (this.$element[0].attachEvent) {
	      this.$element[0].attachEvent('onpropertychange', this._syncA);
	    }

	    var observer = window.MutationObserver ||
	      window.WebKitMutationObserver ||
	      window.MozMutationObserver
	    ;

	    if (observer != null) {
	      this._observer = new observer(function (mutations) {
	        $.each(mutations, self._syncA);
	        $.each(mutations, self._syncS);
	      });
	      this._observer.observe(this.$element[0], {
	        attributes: true,
	        childList: true,
	        subtree: false
	      });
	    } else if (this.$element[0].addEventListener) {
	      this.$element[0].addEventListener(
	        'DOMAttrModified',
	        self._syncA,
	        false
	      );
	      this.$element[0].addEventListener(
	        'DOMNodeInserted',
	        self._syncS,
	        false
	      );
	      this.$element[0].addEventListener(
	        'DOMNodeRemoved',
	        self._syncS,
	        false
	      );
	    }
	  };

	  Select2.prototype._registerDataEvents = function () {
	    var self = this;

	    this.dataAdapter.on('*', function (name, params) {
	      self.trigger(name, params);
	    });
	  };

	  Select2.prototype._registerSelectionEvents = function () {
	    var self = this;
	    var nonRelayEvents = ['toggle', 'focus'];

	    this.selection.on('toggle', function () {
	      self.toggleDropdown();
	    });

	    this.selection.on('focus', function (params) {
	      self.focus(params);
	    });

	    this.selection.on('*', function (name, params) {
	      if ($.inArray(name, nonRelayEvents) !== -1) {
	        return;
	      }

	      self.trigger(name, params);
	    });
	  };

	  Select2.prototype._registerDropdownEvents = function () {
	    var self = this;

	    this.dropdown.on('*', function (name, params) {
	      self.trigger(name, params);
	    });
	  };

	  Select2.prototype._registerResultsEvents = function () {
	    var self = this;

	    this.results.on('*', function (name, params) {
	      self.trigger(name, params);
	    });
	  };

	  Select2.prototype._registerEvents = function () {
	    var self = this;

	    this.on('open', function () {
	      self.$container.addClass('select2-container--open');
	    });

	    this.on('close', function () {
	      self.$container.removeClass('select2-container--open');
	    });

	    this.on('enable', function () {
	      self.$container.removeClass('select2-container--disabled');
	    });

	    this.on('disable', function () {
	      self.$container.addClass('select2-container--disabled');
	    });

	    this.on('blur', function () {
	      self.$container.removeClass('select2-container--focus');
	    });

	    this.on('query', function (params) {
	      if (!self.isOpen()) {
	        self.trigger('open', {});
	      }

	      this.dataAdapter.query(params, function (data) {
	        self.trigger('results:all', {
	          data: data,
	          query: params
	        });
	      });
	    });

	    this.on('query:append', function (params) {
	      this.dataAdapter.query(params, function (data) {
	        self.trigger('results:append', {
	          data: data,
	          query: params
	        });
	      });
	    });

	    this.on('keypress', function (evt) {
	      var key = evt.which;

	      if (self.isOpen()) {
	        if (key === KEYS.ESC || key === KEYS.TAB ||
	            (key === KEYS.UP && evt.altKey)) {
	          self.close();

	          evt.preventDefault();
	        } else if (key === KEYS.ENTER) {
	          self.trigger('results:select', {});

	          evt.preventDefault();
	        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
	          self.trigger('results:toggle', {});

	          evt.preventDefault();
	        } else if (key === KEYS.UP) {
	          self.trigger('results:previous', {});

	          evt.preventDefault();
	        } else if (key === KEYS.DOWN) {
	          self.trigger('results:next', {});

	          evt.preventDefault();
	        }
	      } else {
	        if (key === KEYS.ENTER || key === KEYS.SPACE ||
	            (key === KEYS.DOWN && evt.altKey)) {
	          self.open();

	          evt.preventDefault();
	        }
	      }
	    });
	  };

	  Select2.prototype._syncAttributes = function () {
	    this.options.set('disabled', this.$element.prop('disabled'));

	    if (this.options.get('disabled')) {
	      if (this.isOpen()) {
	        this.close();
	      }

	      this.trigger('disable', {});
	    } else {
	      this.trigger('enable', {});
	    }
	  };

	  Select2.prototype._syncSubtree = function (evt, mutations) {
	    var changed = false;
	    var self = this;

	    // Ignore any mutation events raised for elements that aren't options or
	    // optgroups. This handles the case when the select element is destroyed
	    if (
	      evt && evt.target && (
	        evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
	      )
	    ) {
	      return;
	    }

	    if (!mutations) {
	      // If mutation events aren't supported, then we can only assume that the
	      // change affected the selections
	      changed = true;
	    } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
	      for (var n = 0; n < mutations.addedNodes.length; n++) {
	        var node = mutations.addedNodes[n];

	        if (node.selected) {
	          changed = true;
	        }
	      }
	    } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
	      changed = true;
	    }

	    // Only re-pull the data if we think there is a change
	    if (changed) {
	      this.dataAdapter.current(function (currentData) {
	        self.trigger('selection:update', {
	          data: currentData
	        });
	      });
	    }
	  };

	  /**
	   * Override the trigger method to automatically trigger pre-events when
	   * there are events that can be prevented.
	   */
	  Select2.prototype.trigger = function (name, args) {
	    var actualTrigger = Select2.__super__.trigger;
	    var preTriggerMap = {
	      'open': 'opening',
	      'close': 'closing',
	      'select': 'selecting',
	      'unselect': 'unselecting'
	    };

	    if (args === undefined) {
	      args = {};
	    }

	    if (name in preTriggerMap) {
	      var preTriggerName = preTriggerMap[name];
	      var preTriggerArgs = {
	        prevented: false,
	        name: name,
	        args: args
	      };

	      actualTrigger.call(this, preTriggerName, preTriggerArgs);

	      if (preTriggerArgs.prevented) {
	        args.prevented = true;

	        return;
	      }
	    }

	    actualTrigger.call(this, name, args);
	  };

	  Select2.prototype.toggleDropdown = function () {
	    if (this.options.get('disabled')) {
	      return;
	    }

	    if (this.isOpen()) {
	      this.close();
	    } else {
	      this.open();
	    }
	  };

	  Select2.prototype.open = function () {
	    if (this.isOpen()) {
	      return;
	    }

	    this.trigger('query', {});
	  };

	  Select2.prototype.close = function () {
	    if (!this.isOpen()) {
	      return;
	    }

	    this.trigger('close', {});
	  };

	  Select2.prototype.isOpen = function () {
	    return this.$container.hasClass('select2-container--open');
	  };

	  Select2.prototype.hasFocus = function () {
	    return this.$container.hasClass('select2-container--focus');
	  };

	  Select2.prototype.focus = function (data) {
	    // No need to re-trigger focus events if we are already focused
	    if (this.hasFocus()) {
	      return;
	    }

	    this.$container.addClass('select2-container--focus');
	    this.trigger('focus', {});
	  };

	  Select2.prototype.enable = function (args) {
	    if (this.options.get('debug') && window.console && console.warn) {
	      console.warn(
	        'Select2: The `select2("enable")` method has been deprecated and will' +
	        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
	        ' instead.'
	      );
	    }

	    if (args == null || args.length === 0) {
	      args = [true];
	    }

	    var disabled = !args[0];

	    this.$element.prop('disabled', disabled);
	  };

	  Select2.prototype.data = function () {
	    if (this.options.get('debug') &&
	        arguments.length > 0 && window.console && console.warn) {
	      console.warn(
	        'Select2: Data can no longer be set using `select2("data")`. You ' +
	        'should consider setting the value instead using `$element.val()`.'
	      );
	    }

	    var data = [];

	    this.dataAdapter.current(function (currentData) {
	      data = currentData;
	    });

	    return data;
	  };

	  Select2.prototype.val = function (args) {
	    if (this.options.get('debug') && window.console && console.warn) {
	      console.warn(
	        'Select2: The `select2("val")` method has been deprecated and will be' +
	        ' removed in later Select2 versions. Use $element.val() instead.'
	      );
	    }

	    if (args == null || args.length === 0) {
	      return this.$element.val();
	    }

	    var newVal = args[0];

	    if ($.isArray(newVal)) {
	      newVal = $.map(newVal, function (obj) {
	        return obj.toString();
	      });
	    }

	    this.$element.val(newVal).trigger('change');
	  };

	  Select2.prototype.destroy = function () {
	    this.$container.remove();

	    if (this.$element[0].detachEvent) {
	      this.$element[0].detachEvent('onpropertychange', this._syncA);
	    }

	    if (this._observer != null) {
	      this._observer.disconnect();
	      this._observer = null;
	    } else if (this.$element[0].removeEventListener) {
	      this.$element[0]
	        .removeEventListener('DOMAttrModified', this._syncA, false);
	      this.$element[0]
	        .removeEventListener('DOMNodeInserted', this._syncS, false);
	      this.$element[0]
	        .removeEventListener('DOMNodeRemoved', this._syncS, false);
	    }

	    this._syncA = null;
	    this._syncS = null;

	    this.$element.off('.select2');
	    this.$element.attr('tabindex', this.$element.data('old-tabindex'));

	    this.$element.removeClass('select2-hidden-accessible');
	    this.$element.attr('aria-hidden', 'false');
	    this.$element.removeData('select2');

	    this.dataAdapter.destroy();
	    this.selection.destroy();
	    this.dropdown.destroy();
	    this.results.destroy();

	    this.dataAdapter = null;
	    this.selection = null;
	    this.dropdown = null;
	    this.results = null;
	  };

	  Select2.prototype.render = function () {
	    var $container = $(
	      '<span class="select2 select2-container">' +
	        '<span class="selection"></span>' +
	        '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
	      '</span>'
	    );

	    $container.attr('dir', this.options.get('dir'));

	    this.$container = $container;

	    this.$container.addClass('select2-container--' + this.options.get('theme'));

	    $container.data('element', this.$element);

	    return $container;
	  };

	  return Select2;
	});

	S2.define('jquery-mousewheel',[
	  'jquery'
	], function ($) {
	  // Used to shim jQuery.mousewheel for non-full builds.
	  return $;
	});

	S2.define('jquery.select2',[
	  'jquery',
	  'jquery-mousewheel',

	  './select2/core',
	  './select2/defaults'
	], function ($, _, Select2, Defaults) {
	  if ($.fn.select2 == null) {
	    // All methods that should return the element
	    var thisMethods = ['open', 'close', 'destroy'];

	    $.fn.select2 = function (options) {
	      options = options || {};

	      if (typeof options === 'object') {
	        this.each(function () {
	          var instanceOptions = $.extend(true, {}, options);

	          var instance = new Select2($(this), instanceOptions);
	        });

	        return this;
	      } else if (typeof options === 'string') {
	        var ret;
	        var args = Array.prototype.slice.call(arguments, 1);

	        this.each(function () {
	          var instance = $(this).data('select2');

	          if (instance == null && window.console && console.error) {
	            console.error(
	              'The select2(\'' + options + '\') method was called on an ' +
	              'element that is not using Select2.'
	            );
	          }

	          ret = instance[options].apply(instance, args);
	        });

	        // Check if we should be returning `this`
	        if ($.inArray(options, thisMethods) > -1) {
	          return this;
	        }

	        return ret;
	      } else {
	        throw new Error('Invalid arguments for Select2: ' + options);
	      }
	    };
	  }

	  if ($.fn.select2.defaults == null) {
	    $.fn.select2.defaults = Defaults;
	  }

	  return Select2;
	});

	  // Return the AMD loader configuration so it can be used outside of this file
	  return {
	    define: S2.define,
	    require: S2.require
	  };
	}());

	  // Autoload the jQuery bindings
	  // We know that all of the modules exist above this, so we're safe
	  var select2 = S2.require('jquery.select2');

	  // Hold the AMD module references on the jQuery function that was just loaded
	  // This allows Select2 to use the internal loader outside of this file, such
	  // as in the language files.
	  jQuery.fn.select2.amd = S2;

	  // Return the Select2 instance for anyone who is importing it.
	  return select2;
	}));


/***/ },

/***/ 544:
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {module.exports = global["Backbone"] = __webpack_require__(545);
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },

/***/ 545:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global) {//     Backbone.js 1.3.3

	//     (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
	//     Backbone may be freely distributed under the MIT license.
	//     For all details and documentation:
	//     http://backbonejs.org

	(function(factory) {

	  // Establish the root object, `window` (`self`) in the browser, or `global` on the server.
	  // We use `self` instead of `window` for `WebWorker` support.
	  var root = (typeof self == 'object' && self.self === self && self) ||
	            (typeof global == 'object' && global.global === global && global);

	  // Set up Backbone appropriately for the environment. Start with AMD.
	  if (true) {
	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(280), __webpack_require__(275), exports], __WEBPACK_AMD_DEFINE_RESULT__ = function(_, $, exports) {
	      // Export global even in AMD case in case this script is loaded with
	      // others that may still expect a global Backbone.
	      root.Backbone = factory(root, exports, _, $);
	    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));

	  // Next for Node.js or CommonJS. jQuery may not be needed as a module.
	  } else if (typeof exports !== 'undefined') {
	    var _ = require('underscore'), $;
	    try { $ = require('jquery'); } catch (e) {}
	    factory(root, exports, _, $);

	  // Finally, as a browser global.
	  } else {
	    root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
	  }

	})(function(root, Backbone, _, $) {

	  // Initial Setup
	  // -------------

	  // Save the previous value of the `Backbone` variable, so that it can be
	  // restored later on, if `noConflict` is used.
	  var previousBackbone = root.Backbone;

	  // Create a local reference to a common array method we'll want to use later.
	  var slice = Array.prototype.slice;

	  // Current version of the library. Keep in sync with `package.json`.
	  Backbone.VERSION = '1.3.3';

	  // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
	  // the `$` variable.
	  Backbone.$ = $;

	  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
	  // to its previous owner. Returns a reference to this Backbone object.
	  Backbone.noConflict = function() {
	    root.Backbone = previousBackbone;
	    return this;
	  };

	  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
	  // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
	  // set a `X-Http-Method-Override` header.
	  Backbone.emulateHTTP = false;

	  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
	  // `application/json` requests ... this will encode the body as
	  // `application/x-www-form-urlencoded` instead and will send the model in a
	  // form param named `model`.
	  Backbone.emulateJSON = false;

	  // Proxy Backbone class methods to Underscore functions, wrapping the model's
	  // `attributes` object or collection's `models` array behind the scenes.
	  //
	  // collection.filter(function(model) { return model.get('age') > 10 });
	  // collection.each(this.addView);
	  //
	  // `Function#apply` can be slow so we use the method's arg count, if we know it.
	  var addMethod = function(length, method, attribute) {
	    switch (length) {
	      case 1: return function() {
	        return _[method](this[attribute]);
	      };
	      case 2: return function(value) {
	        return _[method](this[attribute], value);
	      };
	      case 3: return function(iteratee, context) {
	        return _[method](this[attribute], cb(iteratee, this), context);
	      };
	      case 4: return function(iteratee, defaultVal, context) {
	        return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
	      };
	      default: return function() {
	        var args = slice.call(arguments);
	        args.unshift(this[attribute]);
	        return _[method].apply(_, args);
	      };
	    }
	  };
	  var addUnderscoreMethods = function(Class, methods, attribute) {
	    _.each(methods, function(length, method) {
	      if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
	    });
	  };

	  // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
	  var cb = function(iteratee, instance) {
	    if (_.isFunction(iteratee)) return iteratee;
	    if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
	    if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
	    return iteratee;
	  };
	  var modelMatcher = function(attrs) {
	    var matcher = _.matches(attrs);
	    return function(model) {
	      return matcher(model.attributes);
	    };
	  };

	  // Backbone.Events
	  // ---------------

	  // A module that can be mixed in to *any object* in order to provide it with
	  // a custom event channel. You may bind a callback to an event with `on` or
	  // remove with `off`; `trigger`-ing an event fires all callbacks in
	  // succession.
	  //
	  //     var object = {};
	  //     _.extend(object, Backbone.Events);
	  //     object.on('expand', function(){ alert('expanded'); });
	  //     object.trigger('expand');
	  //
	  var Events = Backbone.Events = {};

	  // Regular expression used to split event strings.
	  var eventSplitter = /\s+/;

	  // Iterates over the standard `event, callback` (as well as the fancy multiple
	  // space-separated events `"change blur", callback` and jQuery-style event
	  // maps `{event: callback}`).
	  var eventsApi = function(iteratee, events, name, callback, opts) {
	    var i = 0, names;
	    if (name && typeof name === 'object') {
	      // Handle event maps.
	      if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
	      for (names = _.keys(name); i < names.length ; i++) {
	        events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
	      }
	    } else if (name && eventSplitter.test(name)) {
	      // Handle space-separated event names by delegating them individually.
	      for (names = name.split(eventSplitter); i < names.length; i++) {
	        events = iteratee(events, names[i], callback, opts);
	      }
	    } else {
	      // Finally, standard events.
	      events = iteratee(events, name, callback, opts);
	    }
	    return events;
	  };

	  // Bind an event to a `callback` function. Passing `"all"` will bind
	  // the callback to all events fired.
	  Events.on = function(name, callback, context) {
	    return internalOn(this, name, callback, context);
	  };

	  // Guard the `listening` argument from the public API.
	  var internalOn = function(obj, name, callback, context, listening) {
	    obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
	      context: context,
	      ctx: obj,
	      listening: listening
	    });

	    if (listening) {
	      var listeners = obj._listeners || (obj._listeners = {});
	      listeners[listening.id] = listening;
	    }

	    return obj;
	  };

	  // Inversion-of-control versions of `on`. Tell *this* object to listen to
	  // an event in another object... keeping track of what it's listening to
	  // for easier unbinding later.
	  Events.listenTo = function(obj, name, callback) {
	    if (!obj) return this;
	    var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
	    var listeningTo = this._listeningTo || (this._listeningTo = {});
	    var listening = listeningTo[id];

	    // This object is not listening to any other events on `obj` yet.
	    // Setup the necessary references to track the listening callbacks.
	    if (!listening) {
	      var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
	      listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
	    }

	    // Bind callbacks on obj, and keep track of them on listening.
	    internalOn(obj, name, callback, this, listening);
	    return this;
	  };

	  // The reducing API that adds a callback to the `events` object.
	  var onApi = function(events, name, callback, options) {
	    if (callback) {
	      var handlers = events[name] || (events[name] = []);
	      var context = options.context, ctx = options.ctx, listening = options.listening;
	      if (listening) listening.count++;

	      handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
	    }
	    return events;
	  };

	  // Remove one or many callbacks. If `context` is null, removes all
	  // callbacks with that function. If `callback` is null, removes all
	  // callbacks for the event. If `name` is null, removes all bound
	  // callbacks for all events.
	  Events.off = function(name, callback, context) {
	    if (!this._events) return this;
	    this._events = eventsApi(offApi, this._events, name, callback, {
	      context: context,
	      listeners: this._listeners
	    });
	    return this;
	  };

	  // Tell this object to stop listening to either specific events ... or
	  // to every object it's currently listening to.
	  Events.stopListening = function(obj, name, callback) {
	    var listeningTo = this._listeningTo;
	    if (!listeningTo) return this;

	    var ids = obj ? [obj._listenId] : _.keys(listeningTo);

	    for (var i = 0; i < ids.length; i++) {
	      var listening = listeningTo[ids[i]];

	      // If listening doesn't exist, this object is not currently
	      // listening to obj. Break out early.
	      if (!listening) break;

	      listening.obj.off(name, callback, this);
	    }

	    return this;
	  };

	  // The reducing API that removes a callback from the `events` object.
	  var offApi = function(events, name, callback, options) {
	    if (!events) return;

	    var i = 0, listening;
	    var context = options.context, listeners = options.listeners;

	    // Delete all events listeners and "drop" events.
	    if (!name && !callback && !context) {
	      var ids = _.keys(listeners);
	      for (; i < ids.length; i++) {
	        listening = listeners[ids[i]];
	        delete listeners[listening.id];
	        delete listening.listeningTo[listening.objId];
	      }
	      return;
	    }

	    var names = name ? [name] : _.keys(events);
	    for (; i < names.length; i++) {
	      name = names[i];
	      var handlers = events[name];

	      // Bail out if there are no events stored.
	      if (!handlers) break;

	      // Replace events if there are any remaining.  Otherwise, clean up.
	      var remaining = [];
	      for (var j = 0; j < handlers.length; j++) {
	        var handler = handlers[j];
	        if (
	          callback && callback !== handler.callback &&
	            callback !== handler.callback._callback ||
	              context && context !== handler.context
	        ) {
	          remaining.push(handler);
	        } else {
	          listening = handler.listening;
	          if (listening && --listening.count === 0) {
	            delete listeners[listening.id];
	            delete listening.listeningTo[listening.objId];
	          }
	        }
	      }

	      // Update tail event if the list has any events.  Otherwise, clean up.
	      if (remaining.length) {
	        events[name] = remaining;
	      } else {
	        delete events[name];
	      }
	    }
	    return events;
	  };

	  // Bind an event to only be triggered a single time. After the first time
	  // the callback is invoked, its listener will be removed. If multiple events
	  // are passed in using the space-separated syntax, the handler will fire
	  // once for each event, not once for a combination of all events.
	  Events.once = function(name, callback, context) {
	    // Map the event into a `{event: once}` object.
	    var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
	    if (typeof name === 'string' && context == null) callback = void 0;
	    return this.on(events, callback, context);
	  };

	  // Inversion-of-control versions of `once`.
	  Events.listenToOnce = function(obj, name, callback) {
	    // Map the event into a `{event: once}` object.
	    var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
	    return this.listenTo(obj, events);
	  };

	  // Reduces the event callbacks into a map of `{event: onceWrapper}`.
	  // `offer` unbinds the `onceWrapper` after it has been called.
	  var onceMap = function(map, name, callback, offer) {
	    if (callback) {
	      var once = map[name] = _.once(function() {
	        offer(name, once);
	        callback.apply(this, arguments);
	      });
	      once._callback = callback;
	    }
	    return map;
	  };

	  // Trigger one or many events, firing all bound callbacks. Callbacks are
	  // passed the same arguments as `trigger` is, apart from the event name
	  // (unless you're listening on `"all"`, which will cause your callback to
	  // receive the true name of the event as the first argument).
	  Events.trigger = function(name) {
	    if (!this._events) return this;

	    var length = Math.max(0, arguments.length - 1);
	    var args = Array(length);
	    for (var i = 0; i < length; i++) args[i] = arguments[i + 1];

	    eventsApi(triggerApi, this._events, name, void 0, args);
	    return this;
	  };

	  // Handles triggering the appropriate event callbacks.
	  var triggerApi = function(objEvents, name, callback, args) {
	    if (objEvents) {
	      var events = objEvents[name];
	      var allEvents = objEvents.all;
	      if (events && allEvents) allEvents = allEvents.slice();
	      if (events) triggerEvents(events, args);
	      if (allEvents) triggerEvents(allEvents, [name].concat(args));
	    }
	    return objEvents;
	  };

	  // A difficult-to-believe, but optimized internal dispatch function for
	  // triggering events. Tries to keep the usual cases speedy (most internal
	  // Backbone events have 3 arguments).
	  var triggerEvents = function(events, args) {
	    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
	    switch (args.length) {
	      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
	      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
	      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
	      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
	      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
	    }
	  };

	  // Aliases for backwards compatibility.
	  Events.bind   = Events.on;
	  Events.unbind = Events.off;

	  // Allow the `Backbone` object to serve as a global event bus, for folks who
	  // want global "pubsub" in a convenient place.
	  _.extend(Backbone, Events);

	  // Backbone.Model
	  // --------------

	  // Backbone **Models** are the basic data object in the framework --
	  // frequently representing a row in a table in a database on your server.
	  // A discrete chunk of data and a bunch of useful, related methods for
	  // performing computations and transformations on that data.

	  // Create a new model with the specified attributes. A client id (`cid`)
	  // is automatically generated and assigned for you.
	  var Model = Backbone.Model = function(attributes, options) {
	    var attrs = attributes || {};
	    options || (options = {});
	    this.cid = _.uniqueId(this.cidPrefix);
	    this.attributes = {};
	    if (options.collection) this.collection = options.collection;
	    if (options.parse) attrs = this.parse(attrs, options) || {};
	    var defaults = _.result(this, 'defaults');
	    attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
	    this.set(attrs, options);
	    this.changed = {};
	    this.initialize.apply(this, arguments);
	  };

	  // Attach all inheritable methods to the Model prototype.
	  _.extend(Model.prototype, Events, {

	    // A hash of attributes whose current and previous value differ.
	    changed: null,

	    // The value returned during the last failed validation.
	    validationError: null,

	    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
	    // CouchDB users may want to set this to `"_id"`.
	    idAttribute: 'id',

	    // The prefix is used to create the client id which is used to identify models locally.
	    // You may want to override this if you're experiencing name clashes with model ids.
	    cidPrefix: 'c',

	    // Initialize is an empty function by default. Override it with your own
	    // initialization logic.
	    initialize: function(){},

	    // Return a copy of the model's `attributes` object.
	    toJSON: function(options) {
	      return _.clone(this.attributes);
	    },

	    // Proxy `Backbone.sync` by default -- but override this if you need
	    // custom syncing semantics for *this* particular model.
	    sync: function() {
	      return Backbone.sync.apply(this, arguments);
	    },

	    // Get the value of an attribute.
	    get: function(attr) {
	      return this.attributes[attr];
	    },

	    // Get the HTML-escaped value of an attribute.
	    escape: function(attr) {
	      return _.escape(this.get(attr));
	    },

	    // Returns `true` if the attribute contains a value that is not null
	    // or undefined.
	    has: function(attr) {
	      return this.get(attr) != null;
	    },

	    // Special-cased proxy to underscore's `_.matches` method.
	    matches: function(attrs) {
	      return !!_.iteratee(attrs, this)(this.attributes);
	    },

	    // Set a hash of model attributes on the object, firing `"change"`. This is
	    // the core primitive operation of a model, updating the data and notifying
	    // anyone who needs to know about the change in state. The heart of the beast.
	    set: function(key, val, options) {
	      if (key == null) return this;

	      // Handle both `"key", value` and `{key: value}` -style arguments.
	      var attrs;
	      if (typeof key === 'object') {
	        attrs = key;
	        options = val;
	      } else {
	        (attrs = {})[key] = val;
	      }

	      options || (options = {});

	      // Run validation.
	      if (!this._validate(attrs, options)) return false;

	      // Extract attributes and options.
	      var unset      = options.unset;
	      var silent     = options.silent;
	      var changes    = [];
	      var changing   = this._changing;
	      this._changing = true;

	      if (!changing) {
	        this._previousAttributes = _.clone(this.attributes);
	        this.changed = {};
	      }

	      var current = this.attributes;
	      var changed = this.changed;
	      var prev    = this._previousAttributes;

	      // For each `set` attribute, update or delete the current value.
	      for (var attr in attrs) {
	        val = attrs[attr];
	        if (!_.isEqual(current[attr], val)) changes.push(attr);
	        if (!_.isEqual(prev[attr], val)) {
	          changed[attr] = val;
	        } else {
	          delete changed[attr];
	        }
	        unset ? delete current[attr] : current[attr] = val;
	      }

	      // Update the `id`.
	      if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);

	      // Trigger all relevant attribute changes.
	      if (!silent) {
	        if (changes.length) this._pending = options;
	        for (var i = 0; i < changes.length; i++) {
	          this.trigger('change:' + changes[i], this, current[changes[i]], options);
	        }
	      }

	      // You might be wondering why there's a `while` loop here. Changes can
	      // be recursively nested within `"change"` events.
	      if (changing) return this;
	      if (!silent) {
	        while (this._pending) {
	          options = this._pending;
	          this._pending = false;
	          this.trigger('change', this, options);
	        }
	      }
	      this._pending = false;
	      this._changing = false;
	      return this;
	    },

	    // Remove an attribute from the model, firing `"change"`. `unset` is a noop
	    // if the attribute doesn't exist.
	    unset: function(attr, options) {
	      return this.set(attr, void 0, _.extend({}, options, {unset: true}));
	    },

	    // Clear all attributes on the model, firing `"change"`.
	    clear: function(options) {
	      var attrs = {};
	      for (var key in this.attributes) attrs[key] = void 0;
	      return this.set(attrs, _.extend({}, options, {unset: true}));
	    },

	    // Determine if the model has changed since the last `"change"` event.
	    // If you specify an attribute name, determine if that attribute has changed.
	    hasChanged: function(attr) {
	      if (attr == null) return !_.isEmpty(this.changed);
	      return _.has(this.changed, attr);
	    },

	    // Return an object containing all the attributes that have changed, or
	    // false if there are no changed attributes. Useful for determining what
	    // parts of a view need to be updated and/or what attributes need to be
	    // persisted to the server. Unset attributes will be set to undefined.
	    // You can also pass an attributes object to diff against the model,
	    // determining if there *would be* a change.
	    changedAttributes: function(diff) {
	      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
	      var old = this._changing ? this._previousAttributes : this.attributes;
	      var changed = {};
	      for (var attr in diff) {
	        var val = diff[attr];
	        if (_.isEqual(old[attr], val)) continue;
	        changed[attr] = val;
	      }
	      return _.size(changed) ? changed : false;
	    },

	    // Get the previous value of an attribute, recorded at the time the last
	    // `"change"` event was fired.
	    previous: function(attr) {
	      if (attr == null || !this._previousAttributes) return null;
	      return this._previousAttributes[attr];
	    },

	    // Get all of the attributes of the model at the time of the previous
	    // `"change"` event.
	    previousAttributes: function() {
	      return _.clone(this._previousAttributes);
	    },

	    // Fetch the model from the server, merging the response with the model's
	    // local attributes. Any changed attributes will trigger a "change" event.
	    fetch: function(options) {
	      options = _.extend({parse: true}, options);
	      var model = this;
	      var success = options.success;
	      options.success = function(resp) {
	        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
	        if (!model.set(serverAttrs, options)) return false;
	        if (success) success.call(options.context, model, resp, options);
	        model.trigger('sync', model, resp, options);
	      };
	      wrapError(this, options);
	      return this.sync('read', this, options);
	    },

	    // Set a hash of model attributes, and sync the model to the server.
	    // If the server returns an attributes hash that differs, the model's
	    // state will be `set` again.
	    save: function(key, val, options) {
	      // Handle both `"key", value` and `{key: value}` -style arguments.
	      var attrs;
	      if (key == null || typeof key === 'object') {
	        attrs = key;
	        options = val;
	      } else {
	        (attrs = {})[key] = val;
	      }

	      options = _.extend({validate: true, parse: true}, options);
	      var wait = options.wait;

	      // If we're not waiting and attributes exist, save acts as
	      // `set(attr).save(null, opts)` with validation. Otherwise, check if
	      // the model will be valid when the attributes, if any, are set.
	      if (attrs && !wait) {
	        if (!this.set(attrs, options)) return false;
	      } else if (!this._validate(attrs, options)) {
	        return false;
	      }

	      // After a successful server-side save, the client is (optionally)
	      // updated with the server-side state.
	      var model = this;
	      var success = options.success;
	      var attributes = this.attributes;
	      options.success = function(resp) {
	        // Ensure attributes are restored during synchronous saves.
	        model.attributes = attributes;
	        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
	        if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
	        if (serverAttrs && !model.set(serverAttrs, options)) return false;
	        if (success) success.call(options.context, model, resp, options);
	        model.trigger('sync', model, resp, options);
	      };
	      wrapError(this, options);

	      // Set temporary attributes if `{wait: true}` to properly find new ids.
	      if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);

	      var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
	      if (method === 'patch' && !options.attrs) options.attrs = attrs;
	      var xhr = this.sync(method, this, options);

	      // Restore attributes.
	      this.attributes = attributes;

	      return xhr;
	    },

	    // Destroy this model on the server if it was already persisted.
	    // Optimistically removes the model from its collection, if it has one.
	    // If `wait: true` is passed, waits for the server to respond before removal.
	    destroy: function(options) {
	      options = options ? _.clone(options) : {};
	      var model = this;
	      var success = options.success;
	      var wait = options.wait;

	      var destroy = function() {
	        model.stopListening();
	        model.trigger('destroy', model, model.collection, options);
	      };

	      options.success = function(resp) {
	        if (wait) destroy();
	        if (success) success.call(options.context, model, resp, options);
	        if (!model.isNew()) model.trigger('sync', model, resp, options);
	      };

	      var xhr = false;
	      if (this.isNew()) {
	        _.defer(options.success);
	      } else {
	        wrapError(this, options);
	        xhr = this.sync('delete', this, options);
	      }
	      if (!wait) destroy();
	      return xhr;
	    },

	    // Default URL for the model's representation on the server -- if you're
	    // using Backbone's restful methods, override this to change the endpoint
	    // that will be called.
	    url: function() {
	      var base =
	        _.result(this, 'urlRoot') ||
	        _.result(this.collection, 'url') ||
	        urlError();
	      if (this.isNew()) return base;
	      var id = this.get(this.idAttribute);
	      return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
	    },

	    // **parse** converts a response into the hash of attributes to be `set` on
	    // the model. The default implementation is just to pass the response along.
	    parse: function(resp, options) {
	      return resp;
	    },

	    // Create a new model with identical attributes to this one.
	    clone: function() {
	      return new this.constructor(this.attributes);
	    },

	    // A model is new if it has never been saved to the server, and lacks an id.
	    isNew: function() {
	      return !this.has(this.idAttribute);
	    },

	    // Check if the model is currently in a valid state.
	    isValid: function(options) {
	      return this._validate({}, _.extend({}, options, {validate: true}));
	    },

	    // Run validation against the next complete set of model attributes,
	    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
	    _validate: function(attrs, options) {
	      if (!options.validate || !this.validate) return true;
	      attrs = _.extend({}, this.attributes, attrs);
	      var error = this.validationError = this.validate(attrs, options) || null;
	      if (!error) return true;
	      this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
	      return false;
	    }

	  });

	  // Underscore methods that we want to implement on the Model, mapped to the
	  // number of arguments they take.
	  var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
	      omit: 0, chain: 1, isEmpty: 1};

	  // Mix in each Underscore method as a proxy to `Model#attributes`.
	  addUnderscoreMethods(Model, modelMethods, 'attributes');

	  // Backbone.Collection
	  // -------------------

	  // If models tend to represent a single row of data, a Backbone Collection is
	  // more analogous to a table full of data ... or a small slice or page of that
	  // table, or a collection of rows that belong together for a particular reason
	  // -- all of the messages in this particular folder, all of the documents
	  // belonging to this particular author, and so on. Collections maintain
	  // indexes of their models, both in order, and for lookup by `id`.

	  // Create a new **Collection**, perhaps to contain a specific type of `model`.
	  // If a `comparator` is specified, the Collection will maintain
	  // its models in sort order, as they're added and removed.
	  var Collection = Backbone.Collection = function(models, options) {
	    options || (options = {});
	    if (options.model) this.model = options.model;
	    if (options.comparator !== void 0) this.comparator = options.comparator;
	    this._reset();
	    this.initialize.apply(this, arguments);
	    if (models) this.reset(models, _.extend({silent: true}, options));
	  };

	  // Default options for `Collection#set`.
	  var setOptions = {add: true, remove: true, merge: true};
	  var addOptions = {add: true, remove: false};

	  // Splices `insert` into `array` at index `at`.
	  var splice = function(array, insert, at) {
	    at = Math.min(Math.max(at, 0), array.length);
	    var tail = Array(array.length - at);
	    var length = insert.length;
	    var i;
	    for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
	    for (i = 0; i < length; i++) array[i + at] = insert[i];
	    for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
	  };

	  // Define the Collection's inheritable methods.
	  _.extend(Collection.prototype, Events, {

	    // The default model for a collection is just a **Backbone.Model**.
	    // This should be overridden in most cases.
	    model: Model,

	    // Initialize is an empty function by default. Override it with your own
	    // initialization logic.
	    initialize: function(){},

	    // The JSON representation of a Collection is an array of the
	    // models' attributes.
	    toJSON: function(options) {
	      return this.map(function(model) { return model.toJSON(options); });
	    },

	    // Proxy `Backbone.sync` by default.
	    sync: function() {
	      return Backbone.sync.apply(this, arguments);
	    },

	    // Add a model, or list of models to the set. `models` may be Backbone
	    // Models or raw JavaScript objects to be converted to Models, or any
	    // combination of the two.
	    add: function(models, options) {
	      return this.set(models, _.extend({merge: false}, options, addOptions));
	    },

	    // Remove a model, or a list of models from the set.
	    remove: function(models, options) {
	      options = _.extend({}, options);
	      var singular = !_.isArray(models);
	      models = singular ? [models] : models.slice();
	      var removed = this._removeModels(models, options);
	      if (!options.silent && removed.length) {
	        options.changes = {added: [], merged: [], removed: removed};
	        this.trigger('update', this, options);
	      }
	      return singular ? removed[0] : removed;
	    },

	    // Update a collection by `set`-ing a new list of models, adding new ones,
	    // removing models that are no longer present, and merging models that
	    // already exist in the collection, as necessary. Similar to **Model#set**,
	    // the core operation for updating the data contained by the collection.
	    set: function(models, options) {
	      if (models == null) return;

	      options = _.extend({}, setOptions, options);
	      if (options.parse && !this._isModel(models)) {
	        models = this.parse(models, options) || [];
	      }

	      var singular = !_.isArray(models);
	      models = singular ? [models] : models.slice();

	      var at = options.at;
	      if (at != null) at = +at;
	      if (at > this.length) at = this.length;
	      if (at < 0) at += this.length + 1;

	      var set = [];
	      var toAdd = [];
	      var toMerge = [];
	      var toRemove = [];
	      var modelMap = {};

	      var add = options.add;
	      var merge = options.merge;
	      var remove = options.remove;

	      var sort = false;
	      var sortable = this.comparator && at == null && options.sort !== false;
	      var sortAttr = _.isString(this.comparator) ? this.comparator : null;

	      // Turn bare objects into model references, and prevent invalid models
	      // from being added.
	      var model, i;
	      for (i = 0; i < models.length; i++) {
	        model = models[i];

	        // If a duplicate is found, prevent it from being added and
	        // optionally merge it into the existing model.
	        var existing = this.get(model);
	        if (existing) {
	          if (merge && model !== existing) {
	            var attrs = this._isModel(model) ? model.attributes : model;
	            if (options.parse) attrs = existing.parse(attrs, options);
	            existing.set(attrs, options);
	            toMerge.push(existing);
	            if (sortable && !sort) sort = existing.hasChanged(sortAttr);
	          }
	          if (!modelMap[existing.cid]) {
	            modelMap[existing.cid] = true;
	            set.push(existing);
	          }
	          models[i] = existing;

	        // If this is a new, valid model, push it to the `toAdd` list.
	        } else if (add) {
	          model = models[i] = this._prepareModel(model, options);
	          if (model) {
	            toAdd.push(model);
	            this._addReference(model, options);
	            modelMap[model.cid] = true;
	            set.push(model);
	          }
	        }
	      }

	      // Remove stale models.
	      if (remove) {
	        for (i = 0; i < this.length; i++) {
	          model = this.models[i];
	          if (!modelMap[model.cid]) toRemove.push(model);
	        }
	        if (toRemove.length) this._removeModels(toRemove, options);
	      }

	      // See if sorting is needed, update `length` and splice in new models.
	      var orderChanged = false;
	      var replace = !sortable && add && remove;
	      if (set.length && replace) {
	        orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
	          return m !== set[index];
	        });
	        this.models.length = 0;
	        splice(this.models, set, 0);
	        this.length = this.models.length;
	      } else if (toAdd.length) {
	        if (sortable) sort = true;
	        splice(this.models, toAdd, at == null ? this.length : at);
	        this.length = this.models.length;
	      }

	      // Silently sort the collection if appropriate.
	      if (sort) this.sort({silent: true});

	      // Unless silenced, it's time to fire all appropriate add/sort/update events.
	      if (!options.silent) {
	        for (i = 0; i < toAdd.length; i++) {
	          if (at != null) options.index = at + i;
	          model = toAdd[i];
	          model.trigger('add', model, this, options);
	        }
	        if (sort || orderChanged) this.trigger('sort', this, options);
	        if (toAdd.length || toRemove.length || toMerge.length) {
	          options.changes = {
	            added: toAdd,
	            removed: toRemove,
	            merged: toMerge
	          };
	          this.trigger('update', this, options);
	        }
	      }

	      // Return the added (or merged) model (or models).
	      return singular ? models[0] : models;
	    },

	    // When you have more items than you want to add or remove individually,
	    // you can reset the entire set with a new list of models, without firing
	    // any granular `add` or `remove` events. Fires `reset` when finished.
	    // Useful for bulk operations and optimizations.
	    reset: function(models, options) {
	      options = options ? _.clone(options) : {};
	      for (var i = 0; i < this.models.length; i++) {
	        this._removeReference(this.models[i], options);
	      }
	      options.previousModels = this.models;
	      this._reset();
	      models = this.add(models, _.extend({silent: true}, options));
	      if (!options.silent) this.trigger('reset', this, options);
	      return models;
	    },

	    // Add a model to the end of the collection.
	    push: function(model, options) {
	      return this.add(model, _.extend({at: this.length}, options));
	    },

	    // Remove a model from the end of the collection.
	    pop: function(options) {
	      var model = this.at(this.length - 1);
	      return this.remove(model, options);
	    },

	    // Add a model to the beginning of the collection.
	    unshift: function(model, options) {
	      return this.add(model, _.extend({at: 0}, options));
	    },

	    // Remove a model from the beginning of the collection.
	    shift: function(options) {
	      var model = this.at(0);
	      return this.remove(model, options);
	    },

	    // Slice out a sub-array of models from the collection.
	    slice: function() {
	      return slice.apply(this.models, arguments);
	    },

	    // Get a model from the set by id, cid, model object with id or cid
	    // properties, or an attributes object that is transformed through modelId.
	    get: function(obj) {
	      if (obj == null) return void 0;
	      return this._byId[obj] ||
	        this._byId[this.modelId(obj.attributes || obj)] ||
	        obj.cid && this._byId[obj.cid];
	    },

	    // Returns `true` if the model is in the collection.
	    has: function(obj) {
	      return this.get(obj) != null;
	    },

	    // Get the model at the given index.
	    at: function(index) {
	      if (index < 0) index += this.length;
	      return this.models[index];
	    },

	    // Return models with matching attributes. Useful for simple cases of
	    // `filter`.
	    where: function(attrs, first) {
	      return this[first ? 'find' : 'filter'](attrs);
	    },

	    // Return the first model with matching attributes. Useful for simple cases
	    // of `find`.
	    findWhere: function(attrs) {
	      return this.where(attrs, true);
	    },

	    // Force the collection to re-sort itself. You don't need to call this under
	    // normal circumstances, as the set will maintain sort order as each item
	    // is added.
	    sort: function(options) {
	      var comparator = this.comparator;
	      if (!comparator) throw new Error('Cannot sort a set without a comparator');
	      options || (options = {});

	      var length = comparator.length;
	      if (_.isFunction(comparator)) comparator = _.bind(comparator, this);

	      // Run sort based on type of `comparator`.
	      if (length === 1 || _.isString(comparator)) {
	        this.models = this.sortBy(comparator);
	      } else {
	        this.models.sort(comparator);
	      }
	      if (!options.silent) this.trigger('sort', this, options);
	      return this;
	    },

	    // Pluck an attribute from each model in the collection.
	    pluck: function(attr) {
	      return this.map(attr + '');
	    },

	    // Fetch the default set of models for this collection, resetting the
	    // collection when they arrive. If `reset: true` is passed, the response
	    // data will be passed through the `reset` method instead of `set`.
	    fetch: function(options) {
	      options = _.extend({parse: true}, options);
	      var success = options.success;
	      var collection = this;
	      options.success = function(resp) {
	        var method = options.reset ? 'reset' : 'set';
	        collection[method](resp, options);
	        if (success) success.call(options.context, collection, resp, options);
	        collection.trigger('sync', collection, resp, options);
	      };
	      wrapError(this, options);
	      return this.sync('read', this, options);
	    },

	    // Create a new instance of a model in this collection. Add the model to the
	    // collection immediately, unless `wait: true` is passed, in which case we
	    // wait for the server to agree.
	    create: function(model, options) {
	      options = options ? _.clone(options) : {};
	      var wait = options.wait;
	      model = this._prepareModel(model, options);
	      if (!model) return false;
	      if (!wait) this.add(model, options);
	      var collection = this;
	      var success = options.success;
	      options.success = function(m, resp, callbackOpts) {
	        if (wait) collection.add(m, callbackOpts);
	        if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
	      };
	      model.save(null, options);
	      return model;
	    },

	    // **parse** converts a response into a list of models to be added to the
	    // collection. The default implementation is just to pass it through.
	    parse: function(resp, options) {
	      return resp;
	    },

	    // Create a new collection with an identical list of models as this one.
	    clone: function() {
	      return new this.constructor(this.models, {
	        model: this.model,
	        comparator: this.comparator
	      });
	    },

	    // Define how to uniquely identify models in the collection.
	    modelId: function(attrs) {
	      return attrs[this.model.prototype.idAttribute || 'id'];
	    },

	    // Private method to reset all internal state. Called when the collection
	    // is first initialized or reset.
	    _reset: function() {
	      this.length = 0;
	      this.models = [];
	      this._byId  = {};
	    },

	    // Prepare a hash of attributes (or other model) to be added to this
	    // collection.
	    _prepareModel: function(attrs, options) {
	      if (this._isModel(attrs)) {
	        if (!attrs.collection) attrs.collection = this;
	        return attrs;
	      }
	      options = options ? _.clone(options) : {};
	      options.collection = this;
	      var model = new this.model(attrs, options);
	      if (!model.validationError) return model;
	      this.trigger('invalid', this, model.validationError, options);
	      return false;
	    },

	    // Internal method called by both remove and set.
	    _removeModels: function(models, options) {
	      var removed = [];
	      for (var i = 0; i < models.length; i++) {
	        var model = this.get(models[i]);
	        if (!model) continue;

	        var index = this.indexOf(model);
	        this.models.splice(index, 1);
	        this.length--;

	        // Remove references before triggering 'remove' event to prevent an
	        // infinite loop. #3693
	        delete this._byId[model.cid];
	        var id = this.modelId(model.attributes);
	        if (id != null) delete this._byId[id];

	        if (!options.silent) {
	          options.index = index;
	          model.trigger('remove', model, this, options);
	        }

	        removed.push(model);
	        this._removeReference(model, options);
	      }
	      return removed;
	    },

	    // Method for checking whether an object should be considered a model for
	    // the purposes of adding to the collection.
	    _isModel: function(model) {
	      return model instanceof Model;
	    },

	    // Internal method to create a model's ties to a collection.
	    _addReference: function(model, options) {
	      this._byId[model.cid] = model;
	      var id = this.modelId(model.attributes);
	      if (id != null) this._byId[id] = model;
	      model.on('all', this._onModelEvent, this);
	    },

	    // Internal method to sever a model's ties to a collection.
	    _removeReference: function(model, options) {
	      delete this._byId[model.cid];
	      var id = this.modelId(model.attributes);
	      if (id != null) delete this._byId[id];
	      if (this === model.collection) delete model.collection;
	      model.off('all', this._onModelEvent, this);
	    },

	    // Internal method called every time a model in the set fires an event.
	    // Sets need to update their indexes when models change ids. All other
	    // events simply proxy through. "add" and "remove" events that originate
	    // in other collections are ignored.
	    _onModelEvent: function(event, model, collection, options) {
	      if (model) {
	        if ((event === 'add' || event === 'remove') && collection !== this) return;
	        if (event === 'destroy') this.remove(model, options);
	        if (event === 'change') {
	          var prevId = this.modelId(model.previousAttributes());
	          var id = this.modelId(model.attributes);
	          if (prevId !== id) {
	            if (prevId != null) delete this._byId[prevId];
	            if (id != null) this._byId[id] = model;
	          }
	        }
	      }
	      this.trigger.apply(this, arguments);
	    }

	  });

	  // Underscore methods that we want to implement on the Collection.
	  // 90% of the core usefulness of Backbone Collections is actually implemented
	  // right here:
	  var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
	      foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
	      select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
	      contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
	      head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
	      without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
	      isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
	      sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};

	  // Mix in each Underscore method as a proxy to `Collection#models`.
	  addUnderscoreMethods(Collection, collectionMethods, 'models');

	  // Backbone.View
	  // -------------

	  // Backbone Views are almost more convention than they are actual code. A View
	  // is simply a JavaScript object that represents a logical chunk of UI in the
	  // DOM. This might be a single item, an entire list, a sidebar or panel, or
	  // even the surrounding frame which wraps your whole app. Defining a chunk of
	  // UI as a **View** allows you to define your DOM events declaratively, without
	  // having to worry about render order ... and makes it easy for the view to
	  // react to specific changes in the state of your models.

	  // Creating a Backbone.View creates its initial element outside of the DOM,
	  // if an existing element is not provided...
	  var View = Backbone.View = function(options) {
	    this.cid = _.uniqueId('view');
	    _.extend(this, _.pick(options, viewOptions));
	    this._ensureElement();
	    this.initialize.apply(this, arguments);
	  };

	  // Cached regex to split keys for `delegate`.
	  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

	  // List of view options to be set as properties.
	  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

	  // Set up all inheritable **Backbone.View** properties and methods.
	  _.extend(View.prototype, Events, {

	    // The default `tagName` of a View's element is `"div"`.
	    tagName: 'div',

	    // jQuery delegate for element lookup, scoped to DOM elements within the
	    // current view. This should be preferred to global lookups where possible.
	    $: function(selector) {
	      return this.$el.find(selector);
	    },

	    // Initialize is an empty function by default. Override it with your own
	    // initialization logic.
	    initialize: function(){},

	    // **render** is the core function that your view should override, in order
	    // to populate its element (`this.el`), with the appropriate HTML. The
	    // convention is for **render** to always return `this`.
	    render: function() {
	      return this;
	    },

	    // Remove this view by taking the element out of the DOM, and removing any
	    // applicable Backbone.Events listeners.
	    remove: function() {
	      this._removeElement();
	      this.stopListening();
	      return this;
	    },

	    // Remove this view's element from the document and all event listeners
	    // attached to it. Exposed for subclasses using an alternative DOM
	    // manipulation API.
	    _removeElement: function() {
	      this.$el.remove();
	    },

	    // Change the view's element (`this.el` property) and re-delegate the
	    // view's events on the new element.
	    setElement: function(element) {
	      this.undelegateEvents();
	      this._setElement(element);
	      this.delegateEvents();
	      return this;
	    },

	    // Creates the `this.el` and `this.$el` references for this view using the
	    // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
	    // context or an element. Subclasses can override this to utilize an
	    // alternative DOM manipulation API and are only required to set the
	    // `this.el` property.
	    _setElement: function(el) {
	      this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
	      this.el = this.$el[0];
	    },

	    // Set callbacks, where `this.events` is a hash of
	    //
	    // *{"event selector": "callback"}*
	    //
	    //     {
	    //       'mousedown .title':  'edit',
	    //       'click .button':     'save',
	    //       'click .open':       function(e) { ... }
	    //     }
	    //
	    // pairs. Callbacks will be bound to the view, with `this` set properly.
	    // Uses event delegation for efficiency.
	    // Omitting the selector binds the event to `this.el`.
	    delegateEvents: function(events) {
	      events || (events = _.result(this, 'events'));
	      if (!events) return this;
	      this.undelegateEvents();
	      for (var key in events) {
	        var method = events[key];
	        if (!_.isFunction(method)) method = this[method];
	        if (!method) continue;
	        var match = key.match(delegateEventSplitter);
	        this.delegate(match[1], match[2], _.bind(method, this));
	      }
	      return this;
	    },

	    // Add a single event listener to the view's element (or a child element
	    // using `selector`). This only works for delegate-able events: not `focus`,
	    // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
	    delegate: function(eventName, selector, listener) {
	      this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
	      return this;
	    },

	    // Clears all callbacks previously bound to the view by `delegateEvents`.
	    // You usually don't need to use this, but may wish to if you have multiple
	    // Backbone views attached to the same DOM element.
	    undelegateEvents: function() {
	      if (this.$el) this.$el.off('.delegateEvents' + this.cid);
	      return this;
	    },

	    // A finer-grained `undelegateEvents` for removing a single delegated event.
	    // `selector` and `listener` are both optional.
	    undelegate: function(eventName, selector, listener) {
	      this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
	      return this;
	    },

	    // Produces a DOM element to be assigned to your view. Exposed for
	    // subclasses using an alternative DOM manipulation API.
	    _createElement: function(tagName) {
	      return document.createElement(tagName);
	    },

	    // Ensure that the View has a DOM element to render into.
	    // If `this.el` is a string, pass it through `$()`, take the first
	    // matching element, and re-assign it to `el`. Otherwise, create
	    // an element from the `id`, `className` and `tagName` properties.
	    _ensureElement: function() {
	      if (!this.el) {
	        var attrs = _.extend({}, _.result(this, 'attributes'));
	        if (this.id) attrs.id = _.result(this, 'id');
	        if (this.className) attrs['class'] = _.result(this, 'className');
	        this.setElement(this._createElement(_.result(this, 'tagName')));
	        this._setAttributes(attrs);
	      } else {
	        this.setElement(_.result(this, 'el'));
	      }
	    },

	    // Set attributes from a hash on this view's element.  Exposed for
	    // subclasses using an alternative DOM manipulation API.
	    _setAttributes: function(attributes) {
	      this.$el.attr(attributes);
	    }

	  });

	  // Backbone.sync
	  // -------------

	  // Override this function to change the manner in which Backbone persists
	  // models to the server. You will be passed the type of request, and the
	  // model in question. By default, makes a RESTful Ajax request
	  // to the model's `url()`. Some possible customizations could be:
	  //
	  // * Use `setTimeout` to batch rapid-fire updates into a single request.
	  // * Send up the models as XML instead of JSON.
	  // * Persist models via WebSockets instead of Ajax.
	  //
	  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
	  // as `POST`, with a `_method` parameter containing the true HTTP method,
	  // as well as all requests with the body as `application/x-www-form-urlencoded`
	  // instead of `application/json` with the model in a param named `model`.
	  // Useful when interfacing with server-side languages like **PHP** that make
	  // it difficult to read the body of `PUT` requests.
	  Backbone.sync = function(method, model, options) {
	    var type = methodMap[method];

	    // Default options, unless specified.
	    _.defaults(options || (options = {}), {
	      emulateHTTP: Backbone.emulateHTTP,
	      emulateJSON: Backbone.emulateJSON
	    });

	    // Default JSON-request options.
	    var params = {type: type, dataType: 'json'};

	    // Ensure that we have a URL.
	    if (!options.url) {
	      params.url = _.result(model, 'url') || urlError();
	    }

	    // Ensure that we have the appropriate request data.
	    if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
	      params.contentType = 'application/json';
	      params.data = JSON.stringify(options.attrs || model.toJSON(options));
	    }

	    // For older servers, emulate JSON by encoding the request into an HTML-form.
	    if (options.emulateJSON) {
	      params.contentType = 'application/x-www-form-urlencoded';
	      params.data = params.data ? {model: params.data} : {};
	    }

	    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
	    // And an `X-HTTP-Method-Override` header.
	    if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
	      params.type = 'POST';
	      if (options.emulateJSON) params.data._method = type;
	      var beforeSend = options.beforeSend;
	      options.beforeSend = function(xhr) {
	        xhr.setRequestHeader('X-HTTP-Method-Override', type);
	        if (beforeSend) return beforeSend.apply(this, arguments);
	      };
	    }

	    // Don't process data on a non-GET request.
	    if (params.type !== 'GET' && !options.emulateJSON) {
	      params.processData = false;
	    }

	    // Pass along `textStatus` and `errorThrown` from jQuery.
	    var error = options.error;
	    options.error = function(xhr, textStatus, errorThrown) {
	      options.textStatus = textStatus;
	      options.errorThrown = errorThrown;
	      if (error) error.call(options.context, xhr, textStatus, errorThrown);
	    };

	    // Make the request, allowing the user to override any Ajax options.
	    var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
	    model.trigger('request', model, xhr, options);
	    return xhr;
	  };

	  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
	  var methodMap = {
	    'create': 'POST',
	    'update': 'PUT',
	    'patch': 'PATCH',
	    'delete': 'DELETE',
	    'read': 'GET'
	  };

	  // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
	  // Override this if you'd like to use a different library.
	  Backbone.ajax = function() {
	    return Backbone.$.ajax.apply(Backbone.$, arguments);
	  };

	  // Backbone.Router
	  // ---------------

	  // Routers map faux-URLs to actions, and fire events when routes are
	  // matched. Creating a new one sets its `routes` hash, if not set statically.
	  var Router = Backbone.Router = function(options) {
	    options || (options = {});
	    if (options.routes) this.routes = options.routes;
	    this._bindRoutes();
	    this.initialize.apply(this, arguments);
	  };

	  // Cached regular expressions for matching named param parts and splatted
	  // parts of route strings.
	  var optionalParam = /\((.*?)\)/g;
	  var namedParam    = /(\(\?)?:\w+/g;
	  var splatParam    = /\*\w+/g;
	  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;

	  // Set up all inheritable **Backbone.Router** properties and methods.
	  _.extend(Router.prototype, Events, {

	    // Initialize is an empty function by default. Override it with your own
	    // initialization logic.
	    initialize: function(){},

	    // Manually bind a single named route to a callback. For example:
	    //
	    //     this.route('search/:query/p:num', 'search', function(query, num) {
	    //       ...
	    //     });
	    //
	    route: function(route, name, callback) {
	      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
	      if (_.isFunction(name)) {
	        callback = name;
	        name = '';
	      }
	      if (!callback) callback = this[name];
	      var router = this;
	      Backbone.history.route(route, function(fragment) {
	        var args = router._extractParameters(route, fragment);
	        if (router.execute(callback, args, name) !== false) {
	          router.trigger.apply(router, ['route:' + name].concat(args));
	          router.trigger('route', name, args);
	          Backbone.history.trigger('route', router, name, args);
	        }
	      });
	      return this;
	    },

	    // Execute a route handler with the provided parameters.  This is an
	    // excellent place to do pre-route setup or post-route cleanup.
	    execute: function(callback, args, name) {
	      if (callback) callback.apply(this, args);
	    },

	    // Simple proxy to `Backbone.history` to save a fragment into the history.
	    navigate: function(fragment, options) {
	      Backbone.history.navigate(fragment, options);
	      return this;
	    },

	    // Bind all defined routes to `Backbone.history`. We have to reverse the
	    // order of the routes here to support behavior where the most general
	    // routes can be defined at the bottom of the route map.
	    _bindRoutes: function() {
	      if (!this.routes) return;
	      this.routes = _.result(this, 'routes');
	      var route, routes = _.keys(this.routes);
	      while ((route = routes.pop()) != null) {
	        this.route(route, this.routes[route]);
	      }
	    },

	    // Convert a route string into a regular expression, suitable for matching
	    // against the current location hash.
	    _routeToRegExp: function(route) {
	      route = route.replace(escapeRegExp, '\\$&')
	                   .replace(optionalParam, '(?:$1)?')
	                   .replace(namedParam, function(match, optional) {
	                     return optional ? match : '([^/?]+)';
	                   })
	                   .replace(splatParam, '([^?]*?)');
	      return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
	    },

	    // Given a route, and a URL fragment that it matches, return the array of
	    // extracted decoded parameters. Empty or unmatched parameters will be
	    // treated as `null` to normalize cross-browser behavior.
	    _extractParameters: function(route, fragment) {
	      var params = route.exec(fragment).slice(1);
	      return _.map(params, function(param, i) {
	        // Don't decode the search params.
	        if (i === params.length - 1) return param || null;
	        return param ? decodeURIComponent(param) : null;
	      });
	    }

	  });

	  // Backbone.History
	  // ----------------

	  // Handles cross-browser history management, based on either
	  // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
	  // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
	  // and URL fragments. If the browser supports neither (old IE, natch),
	  // falls back to polling.
	  var History = Backbone.History = function() {
	    this.handlers = [];
	    this.checkUrl = _.bind(this.checkUrl, this);

	    // Ensure that `History` can be used outside of the browser.
	    if (typeof window !== 'undefined') {
	      this.location = window.location;
	      this.history = window.history;
	    }
	  };

	  // Cached regex for stripping a leading hash/slash and trailing space.
	  var routeStripper = /^[#\/]|\s+$/g;

	  // Cached regex for stripping leading and trailing slashes.
	  var rootStripper = /^\/+|\/+$/g;

	  // Cached regex for stripping urls of hash.
	  var pathStripper = /#.*$/;

	  // Has the history handling already been started?
	  History.started = false;

	  // Set up all inheritable **Backbone.History** properties and methods.
	  _.extend(History.prototype, Events, {

	    // The default interval to poll for hash changes, if necessary, is
	    // twenty times a second.
	    interval: 50,

	    // Are we at the app root?
	    atRoot: function() {
	      var path = this.location.pathname.replace(/[^\/]$/, '$&/');
	      return path === this.root && !this.getSearch();
	    },

	    // Does the pathname match the root?
	    matchRoot: function() {
	      var path = this.decodeFragment(this.location.pathname);
	      var rootPath = path.slice(0, this.root.length - 1) + '/';
	      return rootPath === this.root;
	    },

	    // Unicode characters in `location.pathname` are percent encoded so they're
	    // decoded for comparison. `%25` should not be decoded since it may be part
	    // of an encoded parameter.
	    decodeFragment: function(fragment) {
	      return decodeURI(fragment.replace(/%25/g, '%2525'));
	    },

	    // In IE6, the hash fragment and search params are incorrect if the
	    // fragment contains `?`.
	    getSearch: function() {
	      var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
	      return match ? match[0] : '';
	    },

	    // Gets the true hash value. Cannot use location.hash directly due to bug
	    // in Firefox where location.hash will always be decoded.
	    getHash: function(window) {
	      var match = (window || this).location.href.match(/#(.*)$/);
	      return match ? match[1] : '';
	    },

	    // Get the pathname and search params, without the root.
	    getPath: function() {
	      var path = this.decodeFragment(
	        this.location.pathname + this.getSearch()
	      ).slice(this.root.length - 1);
	      return path.charAt(0) === '/' ? path.slice(1) : path;
	    },

	    // Get the cross-browser normalized URL fragment from the path or hash.
	    getFragment: function(fragment) {
	      if (fragment == null) {
	        if (this._usePushState || !this._wantsHashChange) {
	          fragment = this.getPath();
	        } else {
	          fragment = this.getHash();
	        }
	      }
	      return fragment.replace(routeStripper, '');
	    },

	    // Start the hash change handling, returning `true` if the current URL matches
	    // an existing route, and `false` otherwise.
	    start: function(options) {
	      if (History.started) throw new Error('Backbone.history has already been started');
	      History.started = true;

	      // Figure out the initial configuration. Do we need an iframe?
	      // Is pushState desired ... is it available?
	      this.options          = _.extend({root: '/'}, this.options, options);
	      this.root             = this.options.root;
	      this._wantsHashChange = this.options.hashChange !== false;
	      this._hasHashChange   = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
	      this._useHashChange   = this._wantsHashChange && this._hasHashChange;
	      this._wantsPushState  = !!this.options.pushState;
	      this._hasPushState    = !!(this.history && this.history.pushState);
	      this._usePushState    = this._wantsPushState && this._hasPushState;
	      this.fragment         = this.getFragment();

	      // Normalize root to always include a leading and trailing slash.
	      this.root = ('/' + this.root + '/').replace(rootStripper, '/');

	      // Transition from hashChange to pushState or vice versa if both are
	      // requested.
	      if (this._wantsHashChange && this._wantsPushState) {

	        // If we've started off with a route from a `pushState`-enabled
	        // browser, but we're currently in a browser that doesn't support it...
	        if (!this._hasPushState && !this.atRoot()) {
	          var rootPath = this.root.slice(0, -1) || '/';
	          this.location.replace(rootPath + '#' + this.getPath());
	          // Return immediately as browser will do redirect to new url
	          return true;

	        // Or if we've started out with a hash-based route, but we're currently
	        // in a browser where it could be `pushState`-based instead...
	        } else if (this._hasPushState && this.atRoot()) {
	          this.navigate(this.getHash(), {replace: true});
	        }

	      }

	      // Proxy an iframe to handle location events if the browser doesn't
	      // support the `hashchange` event, HTML5 history, or the user wants
	      // `hashChange` but not `pushState`.
	      if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
	        this.iframe = document.createElement('iframe');
	        this.iframe.src = 'javascript:0';
	        this.iframe.style.display = 'none';
	        this.iframe.tabIndex = -1;
	        var body = document.body;
	        // Using `appendChild` will throw on IE < 9 if the document is not ready.
	        var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
	        iWindow.document.open();
	        iWindow.document.close();
	        iWindow.location.hash = '#' + this.fragment;
	      }

	      // Add a cross-platform `addEventListener` shim for older browsers.
	      var addEventListener = window.addEventListener || function(eventName, listener) {
	        return attachEvent('on' + eventName, listener);
	      };

	      // Depending on whether we're using pushState or hashes, and whether
	      // 'onhashchange' is supported, determine how we check the URL state.
	      if (this._usePushState) {
	        addEventListener('popstate', this.checkUrl, false);
	      } else if (this._useHashChange && !this.iframe) {
	        addEventListener('hashchange', this.checkUrl, false);
	      } else if (this._wantsHashChange) {
	        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
	      }

	      if (!this.options.silent) return this.loadUrl();
	    },

	    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
	    // but possibly useful for unit testing Routers.
	    stop: function() {
	      // Add a cross-platform `removeEventListener` shim for older browsers.
	      var removeEventListener = window.removeEventListener || function(eventName, listener) {
	        return detachEvent('on' + eventName, listener);
	      };

	      // Remove window listeners.
	      if (this._usePushState) {
	        removeEventListener('popstate', this.checkUrl, false);
	      } else if (this._useHashChange && !this.iframe) {
	        removeEventListener('hashchange', this.checkUrl, false);
	      }

	      // Clean up the iframe if necessary.
	      if (this.iframe) {
	        document.body.removeChild(this.iframe);
	        this.iframe = null;
	      }

	      // Some environments will throw when clearing an undefined interval.
	      if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
	      History.started = false;
	    },

	    // Add a route to be tested when the fragment changes. Routes added later
	    // may override previous routes.
	    route: function(route, callback) {
	      this.handlers.unshift({route: route, callback: callback});
	    },

	    // Checks the current URL to see if it has changed, and if it has,
	    // calls `loadUrl`, normalizing across the hidden iframe.
	    checkUrl: function(e) {
	      var current = this.getFragment();

	      // If the user pressed the back button, the iframe's hash will have
	      // changed and we should use that for comparison.
	      if (current === this.fragment && this.iframe) {
	        current = this.getHash(this.iframe.contentWindow);
	      }

	      if (current === this.fragment) return false;
	      if (this.iframe) this.navigate(current);
	      this.loadUrl();
	    },

	    // Attempt to load the current URL fragment. If a route succeeds with a
	    // match, returns `true`. If no defined routes matches the fragment,
	    // returns `false`.
	    loadUrl: function(fragment) {
	      // If the root doesn't match, no routes can match either.
	      if (!this.matchRoot()) return false;
	      fragment = this.fragment = this.getFragment(fragment);
	      return _.some(this.handlers, function(handler) {
	        if (handler.route.test(fragment)) {
	          handler.callback(fragment);
	          return true;
	        }
	      });
	    },

	    // Save a fragment into the hash history, or replace the URL state if the
	    // 'replace' option is passed. You are responsible for properly URL-encoding
	    // the fragment in advance.
	    //
	    // The options object can contain `trigger: true` if you wish to have the
	    // route callback be fired (not usually desirable), or `replace: true`, if
	    // you wish to modify the current URL without adding an entry to the history.
	    navigate: function(fragment, options) {
	      if (!History.started) return false;
	      if (!options || options === true) options = {trigger: !!options};

	      // Normalize the fragment.
	      fragment = this.getFragment(fragment || '');

	      // Don't include a trailing slash on the root.
	      var rootPath = this.root;
	      if (fragment === '' || fragment.charAt(0) === '?') {
	        rootPath = rootPath.slice(0, -1) || '/';
	      }
	      var url = rootPath + fragment;

	      // Strip the hash and decode for matching.
	      fragment = this.decodeFragment(fragment.replace(pathStripper, ''));

	      if (this.fragment === fragment) return;
	      this.fragment = fragment;

	      // If pushState is available, we use it to set the fragment as a real URL.
	      if (this._usePushState) {
	        this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);

	      // If hash changes haven't been explicitly disabled, update the hash
	      // fragment to store history.
	      } else if (this._wantsHashChange) {
	        this._updateHash(this.location, fragment, options.replace);
	        if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
	          var iWindow = this.iframe.contentWindow;

	          // Opening and closing the iframe tricks IE7 and earlier to push a
	          // history entry on hash-tag change.  When replace is true, we don't
	          // want this.
	          if (!options.replace) {
	            iWindow.document.open();
	            iWindow.document.close();
	          }

	          this._updateHash(iWindow.location, fragment, options.replace);
	        }

	      // If you've told us that you explicitly don't want fallback hashchange-
	      // based history, then `navigate` becomes a page refresh.
	      } else {
	        return this.location.assign(url);
	      }
	      if (options.trigger) return this.loadUrl(fragment);
	    },

	    // Update the hash location, either replacing the current entry, or adding
	    // a new one to the browser history.
	    _updateHash: function(location, fragment, replace) {
	      if (replace) {
	        var href = location.href.replace(/(javascript:|#).*$/, '');
	        location.replace(href + '#' + fragment);
	      } else {
	        // Some browsers require that `hash` contains a leading #.
	        location.hash = '#' + fragment;
	      }
	    }

	  });

	  // Create the default Backbone.history.
	  Backbone.history = new History;

	  // Helpers
	  // -------

	  // Helper function to correctly set up the prototype chain for subclasses.
	  // Similar to `goog.inherits`, but uses a hash of prototype properties and
	  // class properties to be extended.
	  var extend = function(protoProps, staticProps) {
	    var parent = this;
	    var child;

	    // The constructor function for the new subclass is either defined by you
	    // (the "constructor" property in your `extend` definition), or defaulted
	    // by us to simply call the parent constructor.
	    if (protoProps && _.has(protoProps, 'constructor')) {
	      child = protoProps.constructor;
	    } else {
	      child = function(){ return parent.apply(this, arguments); };
	    }

	    // Add static properties to the constructor function, if supplied.
	    _.extend(child, parent, staticProps);

	    // Set the prototype chain to inherit from `parent`, without calling
	    // `parent`'s constructor function and add the prototype properties.
	    child.prototype = _.create(parent.prototype, protoProps);
	    child.prototype.constructor = child;

	    // Set a convenience property in case the parent's prototype is needed
	    // later.
	    child.__super__ = parent.prototype;

	    return child;
	  };

	  // Set up inheritance for the model, collection, router, view and history.
	  Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;

	  // Throw an error when a URL is needed, and none is supplied.
	  var urlError = function() {
	    throw new Error('A "url" property or function must be specified');
	  };

	  // Wrap an optional error callback with a fallback error event.
	  var wrapError = function(model, options) {
	    var error = options.error;
	    options.error = function(resp) {
	      if (error) error.call(options.context, model, resp, options);
	      model.trigger('error', model, resp, options);
	    };
	  };

	  return Backbone;
	});

	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },

/***/ 561:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;function requestFailed(errorMessage, xhr) {
	  if (xhr.responseJSON) {
	    return xhr.responseJSON;
	  }
	  return {
	    errors: [
	      {
	        message: errorMessage.replace('%d', xhr.status)
	      }
	    ]
	  };
	}

	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(276), __webpack_require__(275), __webpack_require__(280)], __WEBPACK_AMD_DEFINE_RESULT__ = function (mp, jQuery, _) {
	  var MailPoet = mp;

	  MailPoet.Ajax = {
	    version: 0.5,
	    options: {},
	    defaults: {
	      url: null,
	      api_version: null,
	      endpoint: null,
	      action: null,
	      token: null,
	      data: {}
	    },
	    post: function (options) {
	      return this.request('post', options);
	    },
	    init: function (options) {
	      // merge options
	      this.options = jQuery.extend({}, this.defaults, options);

	      // set default url
	      if (this.options.url === null) {
	        this.options.url = window.ajaxurl;
	      }

	      // set default token
	      if (this.options.token === null) {
	        this.options.token = window.mailpoet_token;
	      }
	    },
	    getParams: function () {
	      return {
	        action: 'mailpoet',
	        api_version: this.options.api_version,
	        token: this.options.token,
	        endpoint: this.options.endpoint,
	        method: this.options.action,
	        data: this.options.data || {}
	      };
	    },
	    request: function (method, options) {
	      var params;
	      var deferred;
	      // set options
	      this.init(options);

	      // set request params
	      params = this.getParams();

	      // remove null values from the data object
	      if (_.isObject(params.data)) {
	        params.data = _.pick(params.data, function (value) {
	          return (value !== null);
	        });
	      }

	      // ajax request
	      deferred = jQuery.post(
	          this.options.url,
	          params,
	          null,
	          'json'
	        ).then(function (data) {
	          return data;
	        }, _.partial(requestFailed, MailPoet.I18n.t('ajaxFailedErrorMessage')));

	        // clear options
	      this.options = {};

	      return deferred;
	    }
	  };
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 564:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(276), __webpack_require__(275)], __WEBPACK_AMD_DEFINE_RESULT__ = function (mp, jQuery) {
	    'use strict';

	    var MailPoet = mp;
	    /** *************************************************************************
	    MailPoet Modal:

	      version: 0.9
	      author: Jonathan Labreuille
	      company: Wysija
	      dependencies: jQuery

	    Usage:
	      // popup mode
	      MailPoet.Modal.popup(options);

	      // panel mode
	      MailPoet.Modal.panel(options);

	      // loading mode
	      MailPoet.Modal.loading(bool);
	    ************************************************************************** */

	    MailPoet.Modal = {
	      version: 0.9,

	      // flags
	      initialized: false,
	      opened: false,
	      locked: false,

	      // previously focused element
	      prevFocus: null,

	      // sub panels
	      subpanels: [],

	      // default values
	      defaults: {
	        // title
	        title: null,

	        // type
	        type: null,

	        // positionning
	        position: 'right',

	        // data sources
	        data: {},
	        url: null,
	        method: 'get',
	        params: {},

	        // template
	        template: null,
	        body_template: null,

	        // dimensions
	        width: 'auto',
	        height: 'auto',

	        // display overlay
	        overlay: false,

	        // focus upon displaying
	        focus: true,

	        // highlighted elements
	        highlight: null,

	        // callbacks
	        onInit: null,
	        onSuccess: null,
	        onCancel: null
	      },
	      renderer: 'html',
	      options: {},
	      templates: {
	        overlay: '<div id="mailpoet_modal_overlay" style="display:none;"></div>',
	        popup: '<div id="mailpoet_popup" tabindex="-1">' +
	        '<div class="mailpoet_popup_wrapper">' +
	        '<a href="javascript:;" id="mailpoet_modal_close"></a>' +
	        '<div id="mailpoet_popup_title"><h2></h2></div>' +
	        '<div class="mailpoet_popup_body clearfix"></div>' +
	        '</div>' +
	        '</div>',
	        loading: '<div id="mailpoet_loading" style="display:none;">' +
	        '<div id="mailpoet_modal_loading_1" class="mailpoet_modal_loading"></div>' +
	        '<div id="mailpoet_modal_loading_2" class="mailpoet_modal_loading"></div>' +
	        '<div id="mailpoet_modal_loading_3" class="mailpoet_modal_loading"></div>' +
	        '</div>',
	        panel: '<div id="mailpoet_panel">' +
	        '<a href="javascript:;" id="mailpoet_modal_close"></a>' +
	        '<div class="mailpoet_panel_wrapper" tabindex="-1">' +
	        '<div class="mailpoet_panel_body clearfix"></div>' +
	        '</div>' +
	        '</div>',
	        subpanel: '<div class="mailpoet_panel_wrapper" tabindex="-1">' +
	        '<div class="mailpoet_panel_body clearfix"></div>' +
	        '</div>'
	      },
	      getContentContainer: function () {
	        return jQuery('.mailpoet_' + this.options.type + '_body');
	      },
	      setRenderer: function (renderer) {
	        this.renderer = renderer;
	        return this;
	      },
	      compileTemplate: function (template) {
	        if (this.renderer === 'html') {
	          return function () { return template; };
	        } else {
	          return window.Handlebars.compile(template);
	        }
	      },
	      init: function (options) {
	        var modal;
	        if (this.initialized === true) {
	          this.close();
	        }
	        // merge options
	        this.options = jQuery.extend({}, this.defaults, options);

	        // set renderer
	        this.setRenderer(this.options.renderer);

	        // init overlay
	        this.initOverlay();

	        // toggle overlay
	        this.toggleOverlay(this.options.overlay);

	        if (this.options.type !== null) {
	          // insert modal depending on its type
	          if (this.options.type === 'popup') {
	            modal = this.compileTemplate(
	              this.templates[this.options.type]
	            );
	            // create modal
	            jQuery('#mailpoet_modal_overlay')
	              .append(modal(this.options));
	            // set title
	            jQuery('#mailpoet_popup_title h2')
	              .html(this.options.title);
	          } else if (this.options.type === 'panel') {
	            // create panel
	            jQuery('#mailpoet_modal_overlay')
	              .after(this.templates[this.options.type]);
	          }

	          // add proper overlay class
	          jQuery('#mailpoet_modal_overlay')
	            .removeClass('mailpoet_popup_overlay mailpoet_panel_overlay')
	            .addClass('mailpoet_' + this.options.type + '_overlay');
	        }

	        // set "success" callback if specified
	        if (options.onSuccess !== undefined) {
	          this.options.onSuccess = options.onSuccess;
	        }

	        // set "cancel" callback if specified
	        if (options.onCancel !== undefined) {
	          this.options.onCancel = options.onCancel;
	        }

	        // compile template
	        this.options.body_template = this.compileTemplate(
	          this.options.template
	        );

	        // setup events
	        this.setupEvents();

	        // set popup as initialized
	        this.initialized = true;

	        return this;
	      },
	      initOverlay: function () {
	        if (jQuery('#mailpoet_modal_overlay').length === 0) {
	            // insert overlay into the DOM
	          jQuery('body').append(this.templates.overlay);
	            // insert loading indicator into overlay
	          jQuery('#mailpoet_modal_overlay').append(this.templates.loading);
	        }
	        return this;
	      },
	      toggleOverlay: function (toggle) {
	        if (toggle === true) {
	          jQuery('#mailpoet_modal_overlay')
	              .removeClass('mailpoet_overlay_hidden');
	        } else {
	          jQuery('#mailpoet_modal_overlay')
	              .addClass('mailpoet_overlay_hidden');
	        }

	        return this;
	      },
	      setupEvents: function () {
	        // close popup when user clicks on close button
	        jQuery('#mailpoet_modal_close').on('click', this.cancel.bind(this));

	        // close popup when user clicks on overlay
	        jQuery('#mailpoet_modal_overlay').on('click', function (e) {
	          // we need to make sure that we are actually clicking on the overlay
	          // because when clicking on the popup content, it will trigger
	          // the click event on the overlay
	          if (e.target.id === 'mailpoet_modal_overlay') { this.cancel(); }
	        }.bind(this));

	        // close popup when user presses ESC key
	        jQuery(document).on('keyup.mailpoet_modal', function (e) {
	          if (this.opened === false) { return false; }
	          if (e.keyCode === 27) { this.cancel(); }
	        }.bind(this));

	        // make sure the popup is repositioned when the window is resized
	        jQuery(window).on('resize.mailpoet_modal', function () {
	          this.setPosition();
	        }.bind(this));

	        return this;
	      },
	      removeEvents: function () {
	        jQuery(document).unbind('keyup.mailpoet_modal');
	        jQuery(window).unbind('resize.mailpoet_modal');
	        jQuery('#mailpoet_modal_close').off('click');
	        if (this.options.overlay === true) {
	          jQuery('#mailpoet_modal_overlay').off('click');
	        }

	        return this;
	      },
	      lock: function () {
	        this.locked = true;

	        return this;
	      },
	      unlock: function () {
	        this.locked = false;

	        return this;
	      },
	      isLocked: function () {
	        return this.locked;
	      },
	      loadTemplate: function () {
	        if (this.subpanels.length > 0) {
	            // hide panel
	          jQuery('.mailpoet_' + this.options.type + '_wrapper').hide();

	            // add sub panel wrapper
	          jQuery('#mailpoet_' + this.options.type)
	              .append(this.templates['subpanel']);

	            // add sub panel content
	          jQuery('.mailpoet_' + this.options.type + '_body').last()
	              .html(this.subpanels[(this.subpanels.length - 1)].element);

	            // focus on sub panel
	          if (this.options.focus) {
	            this.focus();
	          }
	        } else if (this.options.element) {
	          jQuery('.mailpoet_' + this.options.type + '_body').empty();
	          jQuery('.mailpoet_' + this.options.type + '_body')
	              .append(this.options.element);
	        } else {
	          jQuery('.mailpoet_' + this.options.type + '_body')
	              .html(
	                this.options.body_template(
	                  this.options.data
	                )
	              );
	        }

	        return this;
	      },
	      loadUrl: function () {
	        if (this.options.method === 'get') {
	            // make ajax request
	          jQuery.getJSON(this.options.url,
	              function (data) {
	                this.options.data = jQuery.extend({}, this.options.data, data);
	                // load template using fetched data
	                this.loadTemplate();
	                // show modal window
	                this.showModal();
	              }.bind(this)
	            );
	        } else if (this.options.method === 'post') {
	            // make ajax request
	          jQuery.post(this.options.url, JSON.stringify(this.options.params),
	              function (data) {
	                this.options.data = jQuery.extend({}, this.options.data, data);
	                // load template using fetched data
	                this.loadTemplate();
	                // show modal window
	                this.showModal();
	              }.bind(this),
	              'json'
	            );
	        }

	        return this;
	      },
	      setDimensions: function () {
	        switch (this.options.type) {
	          case 'popup':
	              // set popup dimensions
	            jQuery('#mailpoet_popup').css({
	              width: this.options.width,
	              height: this.options.height
	            });
	              // set popup wrapper height
	            jQuery('#mailpoet_popup_wrapper').css({
	              height: this.options.height
	            });
	            break;
	          case 'panel':
	              // set dimensions
	            if (this.options.position === 'right') {
	              jQuery('#mailpoet_panel').css({
	                width: this.options.width,
	                right: 0,
	                marginRight: '-' + this.options.width,
	                left: 'auto'
	              });
	            } else if (this.options.position === 'left') {
	              jQuery('#mailpoet_panel').css({
	                width: this.options.width,
	                left: 0,
	                marginLeft: '-' + this.options.width,
	                right: 'auto'
	              });
	            }
	            jQuery('#mailpoet_panel').css({ minHeight: 'auto' });
	            break;
	        }

	        return this;
	      },
	      setPosition: function () {
	        var screenWidth;
	        var screenHeight;
	        var modalWidth;
	        var modalHeight;
	        switch (this.options.type) {
	          case 'popup':
	            screenWidth = jQuery(window).width();
	            screenHeight = jQuery(window).height();
	            modalWidth = jQuery('.mailpoet_'+ this.options.type +'_wrapper').width();
	            modalHeight = jQuery('.mailpoet_'+ this.options.type +'_wrapper').height();

	            // set position of popup depending on screen dimensions.
	            jQuery('#mailpoet_popup').css({
	              top: Math.max(48, parseInt((screenHeight / 2) - (modalHeight / 2))),
	              left: Math.max(0, parseInt((screenWidth / 2) - (modalWidth / 2)))
	            });
	            break;
	          case 'panel':
	            setTimeout(function () {
	                // set position of popup depending on screen dimensions.
	              if (this.options.position === 'right') {
	                jQuery('#mailpoet_panel').css({
	                  marginRight: 0
	                });
	              } else if (this.options.position === 'left') {
	                jQuery('#mailpoet_panel').css({
	                  marginLeft: 0
	                });
	              }
	            }.bind(this), 0);
	            break;
	        }

	        return this;
	      },
	      showModal: function () {
	          // set modal dimensions
	        this.setDimensions();

	          // remember the previously focused element
	        this.prevFocus = jQuery(':focus');

	          // show popup
	        jQuery('#mailpoet_' + this.options.type).show();

	          // display overlay
	        this.showOverlay();

	          // set modal position
	        this.setPosition();

	          // add class on highlighted elements
	        if (this.options.highlight !== null) {
	          if (this.options.highlight.length > 0) {
	            this.highlightOn(this.options.highlight);
	          }
	        }

	        if (this.options.focus) {
	          this.focus();
	        }

	          // set popup as opened
	        this.opened = true;

	          // trigger init event if specified
	        if (this.options.onInit !== null) {
	          this.options.onInit(this);
	        }

	        return this;
	      },
	      focus: function () {
	        if (this.options.type == 'popup') {
	          jQuery('#mailpoet_' + this.options.type).focus();
	        } else {
	            // panel and subpanel
	          jQuery('#mailpoet_' + this.options.type + ' .mailpoet_panel_wrapper')
	              .filter(':visible').focus();
	        }
	        return this;
	      },
	      highlightOn: function (element) {
	        jQuery(element).addClass('mailpoet_modal_highlight');
	        return this;
	      },
	      highlightOff: function () {
	        jQuery('.mailpoet_modal_highlight')
	            .removeClass('mailpoet_modal_highlight');
	        return this;
	      },
	      hideModal: function () {
	          // set modal as closed
	        this.opened = false;

	          // hide modal
	        jQuery('#mailpoet_' + this.options.type).hide();

	           // remove class on highlighted elements
	        this.highlightOff();

	        return this;
	      },
	      showOverlay: function () {
	        jQuery('#mailpoet_modal_overlay').show();
	        return this;
	      },
	      hideOverlay: function () {
	        jQuery('#mailpoet_modal_overlay').hide();
	        return this;
	      },
	      popup: function (opts) {
	          // get options
	        var options = opts || {};
	          // set modal type
	        options.type = 'popup';
	          // set overlay state
	        options.overlay = options.overlay || true;
	          // initialize modal
	        this.init(options);
	          // open modal
	        this.open();

	        return this;
	      },
	      panel: function (opts) {
	          // get options
	        var options = opts || {};
	          // reset subpanels
	        this.subpanels = [];
	          // set modal type
	        options.type = 'panel';
	          // set overlay state
	        options.overlay = options.overlay || false;
	          // set highlighted element
	        options.highlight = options.highlight || null;
	          // set modal dimensions
	        options.width = options.width || '40%';
	        options.height = options.height || 'auto';
	          // initialize modal
	        this.init(options);
	          // open modal
	        this.open();

	        return this;
	      },
	      subpanel: function (options) {
	        if (this.opened === false) {
	            // if no panel is already opened, let's create one instead
	          this.panel(options);
	        } else {
	            // if a panel is already opened, add a sub panel to it
	          this.subpanels.push(options);
	          this.loadTemplate();
	        }

	        return this;
	      },
	      loading: function (toggle) {
	          // make sure the overlay is initialized and that it's visible
	        this.initOverlay(true);

	        if (toggle === true) {
	          this.showLoading();
	        } else {
	          this.hideLoading();
	        }

	        return this;
	      },
	      showLoading: function () {
	        jQuery('#mailpoet_loading').show();

	          // add loading class to overlay
	        jQuery('#mailpoet_modal_overlay')
	            .addClass('mailpoet_overlay_loading');

	        return this;
	      },
	      hideLoading: function () {
	        jQuery('#mailpoet_loading').hide();

	          // remove loading class from overlay
	        jQuery('#mailpoet_modal_overlay')
	            .removeClass('mailpoet_overlay_loading');

	        return this;
	      },
	      open: function () {
	          // load template if specified
	        if (this.options.template !== null) {
	            // check if a url was specified to get extra data
	          if (this.options.url !== null) {
	            this.loadUrl();
	          } else {
	              // load template
	            this.loadTemplate();

	              // show modal window
	            this.showModal();
	          }
	        } else {
	          this.cancel();
	        }

	        return this;
	      },
	      success: function () {
	        if (this.subpanels.length > 0) {
	          if (this.subpanels[(this.subpanels.length - 1)].onSuccess !== undefined) {
	            this.subpanels[(this.subpanels.length - 1)].onSuccess(this.subpanels[(this.subpanels.length - 1)].data);
	          }
	        } else {
	          if (this.options.onSuccess !== null) {
	            this.options.onSuccess(this.options.data);
	          }
	        }
	        this.close();

	        return this;
	      },
	      cancel: function () {
	        if (this.subpanels.length > 0) {
	          if (this.subpanels[(this.subpanels.length - 1)].onCancel !== undefined) {
	            this.subpanels[(this.subpanels.length - 1)].onCancel(this.subpanels[(this.subpanels.length - 1)].data);
	          }
	        } else {
	          if (this.options.onCancel !== null) {
	            this.options.onCancel(this.options.data);
	          }
	        }
	        this.close();

	        return this;
	      },
	      destroy: function () {
	        this.hideOverlay();

	          // remove extra modal
	        if (jQuery('#mailpoet_' + this.options.type).length > 0) {
	          jQuery('#mailpoet_' + this.options.type).remove();
	        }

	        this.initialized = false;

	        return this;
	      },
	      close: function () {
	        if (this.isLocked() === true) { return this; }

	        if (this.subpanels.length > 0) {
	            // close subpanel
	          jQuery('.mailpoet_' + this.options.type + '_wrapper').last().remove();

	            // show previous panel
	          jQuery('.mailpoet_' + this.options.type + '_wrapper').last().show();

	            // remove last subpanels
	          this.subpanels.pop();

	            // focus on previous panel
	          if (this.options.focus) {
	            this.focus();
	          }

	          return this;
	        }

	          // remove event handlers
	        this.removeEvents();

	          // hide modal window
	        this.hideModal();

	          // destroy modal element
	        this.destroy();

	          // restore the previously focused element
	        if (this.prevFocus !== undefined) {
	          this.prevFocus.focus();
	        }

	          // reset options
	        this.options = {
	          onSuccess: null,
	          onCancel: null
	        };

	        return this;
	      }
	    };
	  }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));



/***/ },

/***/ 565:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(276), __webpack_require__(275)], __WEBPACK_AMD_DEFINE_RESULT__ = function (mp, jQuery) {
	  'use strict';

	  /*= =================================================================================================

	      MailPoet Notice:

	          description: Handles notices
	          version: 1.0
	          author: Jonathan Labreuille
	          company: Wysija
	          dependencies: jQuery

	      Usage:

	          // success message (static: false)
	          MailPoet.Notice.success('Yatta!');

	          // error message (static: false)
	          MailPoet.Notice.error('Boo!');

	          // system message (static: true)
	          MailPoet.Notice.system('You need to updated ASAP!');

	  ================================================================================================== */
	  var MailPoet = mp;
	  MailPoet.Notice = {
	    version: 1.0,
	    // default options
	    defaults: {
	      type: 'success',
	      message: '',
	      static: false,
	      hideClose: false,
	      id: null,
	      positionAfter: false,
	      scroll: false,
	      timeout: 5000,
	      onOpen: null,
	      onClose: null
	    },
	    options: {},
	    init: function (options) {
	      // set options
	      this.options = jQuery.extend({}, this.defaults, options);

	      return this;
	    },
	    createNotice: function () {
	      var onClose;
	      var positionAfter;
	      // clone element
	      this.element = jQuery('#mailpoet_notice_' + this.options.type).clone();

	      // add data-id to the element
	      if (this.options.id) {
	        this.element.attr(
	          'data-id',
	          this.options.id
	        );
	      }

	      // remove id from clone
	      this.element.removeAttr('id');

	      // insert notice after its parent
	      if (typeof this.options.positionAfter === 'object') {
	        positionAfter = this.options.positionAfter;
	      } else if (typeof this.options.positionAfter === 'string') {
	        positionAfter = jQuery(this.options.positionAfter);
	      } else {
	        positionAfter = jQuery('#mailpoet_notice_' + this.options.type);
	      }
	      positionAfter.after(this.element);

	      // setup onClose callback
	      onClose = null;
	      if (this.options.onClose !== null) {
	        onClose = this.options.onClose;
	      }

	      // listen to remove event
	      jQuery(this.element).on('close', function () {
	        jQuery(this).fadeOut(200, function () {
	          // on close callback
	          if (onClose !== null) {
	            onClose();
	          }
	          // remove notice
	          jQuery(this).remove();
	        });
	      }.bind(this.element));

	      // listen to message event
	      jQuery(this.element).on('setMessage', function (e, message) {
	        MailPoet.Notice.setMessage(message);
	      }.bind(this.element));

	      return this;
	    },
	    updateNotice: function () {
	      // update notice's message
	      jQuery('[data-id="' + this.options.id + '"').first().trigger(
	        'setMessage', this.options.message
	      );
	    },
	    setMessage: function (message) {
	      var formattedMessage = this.formatMessage(message);

	      // let's sugar coat the message with a fancy <p>
	      formattedMessage = '<p>' + formattedMessage + '</p>';
	      // set message
	      return this.element.html(formattedMessage);
	    },
	    formatMessage: function (message) {
	      if (Array.isArray(message)) {
	        return message.join('<br />');
	      } else {
	        return message;
	      }
	    },
	    show: function (options) {
	      // initialize
	      this.init(options);

	      if (
	        this.options.id !== null
	        &&
	        jQuery('[data-id="' + this.options.id + '"]').length > 0
	      ) {
	        this.updateNotice();
	      } else {
	        this.createNotice();
	      }
	      this.showNotice();
	    },
	    showNotice: function () {
	      // set message
	      this.setMessage(this.options.message);

	      // position notice
	      this.element.insertAfter(jQuery('h2.title'));

	      // set class name
	      switch (this.options.type) {
	        case 'success':
	          this.element.addClass('notice notice-success');
	          break;
	        case 'system':
	          this.element.addClass('notice notice-warning');
	          break;
	        case 'error':
	          this.element.addClass('notice notice-error');
	          break;
	      }

	      // make the notice appear
	      this.element.fadeIn(200);

	      // if scroll option is enabled, scroll to the notice
	      if (this.options.scroll === true) {
	        this.element.get(0).scrollIntoView(false);
	      }

	      // if the notice is not static, it has to disappear after a timeout
	      if (this.options.static === false) {
	        this.element.delay(this.options.timeout).trigger('close');
	      } else if (this.options.hideClose === false) {
	        this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
	        this.element.find('.mailpoet_notice_close').on('click', function () {
	          jQuery(this).trigger('close');
	        });
	      }

	      // call onOpen callback
	      if (this.options.onOpen !== null) {
	        this.options.onOpen(this.element);
	      }
	    },
	    hide: function (all) {
	      var id;
	      if (all !== undefined && all === true) {
	        // all notices
	        jQuery('.mailpoet_notice:not([id])').trigger('close');
	      } else if (all !== undefined && jQuery.isArray(all)) {
	        // array of ids
	        for (id in all) {
	          jQuery('[data-id="' + all[id] + '"]').trigger('close');
	        }
	      } if (all !== undefined) {
	        // single id
	        jQuery('[data-id="' + all + '"]').trigger('close');
	      } else {
	        jQuery('.mailpoet_notice.notice-success:not([id]), .mailpoet_notice.notice-error:not([id])')
	          .trigger('close');
	      }
	    },
	    error: function (message, options) {
	      this.show(jQuery.extend({}, {
	        type: 'error',
	        message: message
	      }, options));
	    },
	    success: function (message, options) {
	      this.show(jQuery.extend({}, {
	        type: 'success',
	        message: message
	      }, options));
	    },
	    system: function (message, options) {
	      this.show(jQuery.extend({}, {
	        type: 'system',
	        static: true,
	        message: message
	      }, options));
	    }
	  };
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 571:
/***/ function(module, exports, __webpack_require__) {

	// MarionetteJS (Backbone.Marionette)
	// ----------------------------------
	// v3.2.0
	//
	// Copyright (c)2017 Derick Bailey, Muted Solutions, LLC.
	// Distributed under MIT license
	//
	// http://marionettejs.com


	(function (global, factory) {
		 true ? module.exports = factory(__webpack_require__(544), __webpack_require__(280), __webpack_require__(572)) :
		typeof define === 'function' && define.amd ? define(['backbone', 'underscore', 'backbone.radio'], factory) :
		(global.Marionette = global['Mn'] = factory(global.Backbone,global._,global.Backbone.Radio));
	}(this, (function (Backbone,_,Radio) { 'use strict';

	Backbone = 'default' in Backbone ? Backbone['default'] : Backbone;
	_ = 'default' in _ ? _['default'] : _;
	Radio = 'default' in Radio ? Radio['default'] : Radio;

	var version = "3.2.0";

	//Internal utility for creating context style global utils
	var proxy = function proxy(method) {
	  return function (context) {
	    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
	      args[_key - 1] = arguments[_key];
	    }

	    return method.apply(context, args);
	  };
	};

	// Marionette.extend
	// -----------------

	// Borrow the Backbone `extend` method so we can use it as needed
	var extend = Backbone.Model.extend;

	/* global console */

	var deprecate = function deprecate(message, test) {
	  if (_.isObject(message)) {
	    message = message.prev + ' is going to be removed in the future. ' + 'Please use ' + message.next + ' instead.' + (message.url ? ' See: ' + message.url : '');
	  }

	  if (!Marionette.DEV_MODE) {
	    return;
	  }

	  if ((test === undefined || !test) && !deprecate._cache[message]) {
	    deprecate._warn('Deprecation warning: ' + message);
	    deprecate._cache[message] = true;
	  }
	};

	deprecate._console = typeof console !== 'undefined' ? console : {};
	deprecate._warn = function () {
	  var warn = deprecate._console.warn || deprecate._console.log || _.noop;
	  return warn.apply(deprecate._console, arguments);
	};
	deprecate._cache = {};

	// Marionette.isNodeAttached
	// -------------------------

	// Determine if `el` is a child of the document
	var isNodeAttached = function isNodeAttached(el) {
	  return document.documentElement.contains(el && el.parentNode);
	};

	// Merge `keys` from `options` onto `this`
	var mergeOptions = function mergeOptions(options, keys) {
	  var _this = this;

	  if (!options) {
	    return;
	  }

	  _.each(keys, function (key) {
	    var option = options[key];
	    if (option !== undefined) {
	      _this[key] = option;
	    }
	  });
	};

	// Marionette.getOption
	// --------------------

	// Retrieve an object, function or other value from the
	// object or its `options`, with `options` taking precedence.
	var getOption = function getOption(optionName) {
	  if (!optionName) {
	    return;
	  }
	  if (this.options && this.options[optionName] !== undefined) {
	    return this.options[optionName];
	  } else {
	    return this[optionName];
	  }
	};

	// Marionette.normalizeMethods
	// ----------------------

	// Pass in a mapping of events => functions or function names
	// and return a mapping of events => functions
	var normalizeMethods = function normalizeMethods(hash) {
	  var _this = this;

	  return _.reduce(hash, function (normalizedHash, method, name) {
	    if (!_.isFunction(method)) {
	      method = _this[method];
	    }
	    if (method) {
	      normalizedHash[name] = method;
	    }
	    return normalizedHash;
	  }, {});
	};

	// Trigger Method
	// --------------

	// split the event name on the ":"
	var splitter = /(^|:)(\w)/gi;

	// take the event section ("section1:section2:section3")
	// and turn it in to uppercase name onSection1Section2Section3
	function getEventName(match, prefix, eventName) {
	  return eventName.toUpperCase();
	}

	var getOnMethodName = _.memoize(function (event) {
	  return 'on' + event.replace(splitter, getEventName);
	});

	// Trigger an event and/or a corresponding method name. Examples:
	//
	// `this.triggerMethod("foo")` will trigger the "foo" event and
	// call the "onFoo" method.
	//
	// `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
	// call the "onFooBar" method.
	function triggerMethod$1(event) {
	  for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
	    args[_key - 1] = arguments[_key];
	  }

	  // get the method name from the event name
	  var methodName = getOnMethodName(event);
	  var method = getOption.call(this, methodName);
	  var result = void 0;

	  // call the onMethodName if it exists
	  if (_.isFunction(method)) {
	    // pass all args, except the event name
	    result = method.apply(this, args);
	  }

	  // trigger the event
	  this.trigger.apply(this, arguments);

	  return result;
	}

	// triggerMethodOn invokes triggerMethod on a specific context
	//
	// e.g. `Marionette.triggerMethodOn(view, 'show')`
	// will trigger a "show" event or invoke onShow the view.
	function triggerMethodOn(context) {
	  for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
	    args[_key2 - 1] = arguments[_key2];
	  }

	  if (_.isFunction(context.triggerMethod)) {
	    return context.triggerMethod.apply(context, args);
	  }

	  return triggerMethod$1.apply(context, args);
	}

	// DOM Refresh
	// -----------

	// Trigger method on children unless a pure Backbone.View
	function triggerMethodChildren(view, event, shouldTrigger) {
	  if (!view._getImmediateChildren) {
	    return;
	  }
	  _.each(view._getImmediateChildren(), function (child) {
	    if (!shouldTrigger(child)) {
	      return;
	    }
	    triggerMethodOn(child, event, child);
	  });
	}

	function shouldTriggerAttach(view) {
	  return !view._isAttached;
	}

	function shouldAttach(view) {
	  if (!shouldTriggerAttach(view)) {
	    return false;
	  }
	  view._isAttached = true;
	  return true;
	}

	function shouldTriggerDetach(view) {
	  return view._isAttached;
	}

	function shouldDetach(view) {
	  if (!shouldTriggerDetach(view)) {
	    return false;
	  }
	  view._isAttached = false;
	  return true;
	}

	function triggerDOMRefresh(view) {
	  if (view._isAttached && view._isRendered) {
	    triggerMethodOn(view, 'dom:refresh', view);
	  }
	}

	function handleBeforeAttach() {
	  triggerMethodChildren(this, 'before:attach', shouldTriggerAttach);
	}

	function handleAttach() {
	  triggerMethodChildren(this, 'attach', shouldAttach);
	  triggerDOMRefresh(this);
	}

	function handleBeforeDetach() {
	  triggerMethodChildren(this, 'before:detach', shouldTriggerDetach);
	}

	function handleDetach() {
	  triggerMethodChildren(this, 'detach', shouldDetach);
	}

	function handleRender() {
	  triggerDOMRefresh(this);
	}

	// Monitor a view's state, propagating attach/detach events to children and firing dom:refresh
	// whenever a rendered view is attached or an attached view is rendered.
	function monitorViewEvents(view) {
	  if (view._areViewEventsMonitored) {
	    return;
	  }

	  view._areViewEventsMonitored = true;

	  view.on({
	    'before:attach': handleBeforeAttach,
	    'attach': handleAttach,
	    'before:detach': handleBeforeDetach,
	    'detach': handleDetach,
	    'render': handleRender
	  });
	}

	// Error
	// -----

	var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];

	var MarionetteError = extend.call(Error, {
	  urlRoot: 'http://marionettejs.com/docs/v' + version + '/',

	  constructor: function constructor(message, options) {
	    if (_.isObject(message)) {
	      options = message;
	      message = options.message;
	    } else if (!options) {
	      options = {};
	    }

	    var error = Error.call(this, message);
	    _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));

	    this.captureStackTrace();

	    if (options.url) {
	      this.url = this.urlRoot + options.url;
	    }
	  },
	  captureStackTrace: function captureStackTrace() {
	    if (Error.captureStackTrace) {
	      Error.captureStackTrace(this, MarionetteError);
	    }
	  },
	  toString: function toString() {
	    return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
	  }
	});

	MarionetteError.extend = extend;

	// Bind Entity Events & Unbind Entity Events
	// -----------------------------------------
	//
	// These methods are used to bind/unbind a backbone "entity" (e.g. collection/model)
	// to methods on a target object.
	//
	// The first parameter, `target`, must have the Backbone.Events module mixed in.
	//
	// The second parameter is the `entity` (Backbone.Model, Backbone.Collection or
	// any object that has Backbone.Events mixed in) to bind the events from.
	//
	// The third parameter is a hash of { "event:name": "eventHandler" }
	// configuration. Multiple handlers can be separated by a space. A
	// function can be supplied instead of a string handler name.

	// Bind/unbind the event to handlers specified as a string of
	// handler names on the target object
	function bindFromStrings(target, entity, evt, methods, actionName) {
	  var methodNames = methods.split(/\s+/);

	  _.each(methodNames, function (methodName) {
	    var method = target[methodName];
	    if (!method) {
	      throw new MarionetteError('Method "' + methodName + '" was configured as an event handler, but does not exist.');
	    }

	    target[actionName](entity, evt, method);
	  });
	}

	// generic looping function
	function iterateEvents(target, entity, bindings, actionName) {
	  if (!entity || !bindings) {
	    return;
	  }

	  // type-check bindings
	  if (!_.isObject(bindings)) {
	    throw new MarionetteError({
	      message: 'Bindings must be an object.',
	      url: 'marionette.functions.html#marionettebindevents'
	    });
	  }

	  // iterate the bindings and bind/unbind them
	  _.each(bindings, function (method, evt) {

	    // allow for a list of method names as a string
	    if (_.isString(method)) {
	      bindFromStrings(target, entity, evt, method, actionName);
	      return;
	    }

	    target[actionName](entity, evt, method);
	  });
	}

	function bindEvents(entity, bindings) {
	  iterateEvents(this, entity, bindings, 'listenTo');
	  return this;
	}

	function unbindEvents(entity, bindings) {
	  iterateEvents(this, entity, bindings, 'stopListening');
	  return this;
	}

	// Bind/Unbind Radio Requests
	// -----------------------------------------
	//
	// These methods are used to bind/unbind a backbone.radio request
	// to methods on a target object.
	//
	// The first parameter, `target`, will set the context of the reply method
	//
	// The second parameter is the `Radio.channel` to bind the reply to.
	//
	// The third parameter is a hash of { "request:name": "replyHandler" }
	// configuration. A function can be supplied instead of a string handler name.

	function iterateReplies(target, channel, bindings, actionName) {
	  if (!channel || !bindings) {
	    return;
	  }

	  // type-check bindings
	  if (!_.isObject(bindings)) {
	    throw new MarionetteError({
	      message: 'Bindings must be an object.',
	      url: 'marionette.functions.html#marionettebindrequests'
	    });
	  }

	  var normalizedRadioRequests = normalizeMethods.call(target, bindings);

	  channel[actionName](normalizedRadioRequests, target);
	}

	function bindRequests(channel, bindings) {
	  iterateReplies(this, channel, bindings, 'reply');
	  return this;
	}

	function unbindRequests(channel, bindings) {
	  iterateReplies(this, channel, bindings, 'stopReplying');
	  return this;
	}

	// Internal utility for setting options consistently across Mn
	var setOptions = function setOptions() {
	  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
	    args[_key] = arguments[_key];
	  }

	  this.options = _.extend.apply(_, [{}, _.result(this, 'options')].concat(args));
	};

	var CommonMixin = {

	  // Imports the "normalizeMethods" to transform hashes of
	  // events=>function references/names to a hash of events=>function references
	  normalizeMethods: normalizeMethods,

	  _setOptions: setOptions,

	  // A handy way to merge passed-in options onto the instance
	  mergeOptions: mergeOptions,

	  // Enable getting options from this or this.options by name.
	  getOption: getOption,

	  // Enable binding view's events from another entity.
	  bindEvents: bindEvents,

	  // Enable unbinding view's events from another entity.
	  unbindEvents: unbindEvents
	};

	// MixinOptions
	// - channelName
	// - radioEvents
	// - radioRequests

	var RadioMixin = {
	  _initRadio: function _initRadio() {
	    var channelName = _.result(this, 'channelName');

	    if (!channelName) {
	      return;
	    }

	    /* istanbul ignore next */
	    if (!Radio) {
	      throw new MarionetteError({
	        name: 'BackboneRadioMissing',
	        message: 'The dependency "backbone.radio" is missing.'
	      });
	    }

	    var channel = this._channel = Radio.channel(channelName);

	    var radioEvents = _.result(this, 'radioEvents');
	    this.bindEvents(channel, radioEvents);

	    var radioRequests = _.result(this, 'radioRequests');
	    this.bindRequests(channel, radioRequests);

	    this.on('destroy', this._destroyRadio);
	  },
	  _destroyRadio: function _destroyRadio() {
	    this._channel.stopReplying(null, null, this);
	  },
	  getChannel: function getChannel() {
	    return this._channel;
	  },


	  // Proxy `bindEvents`
	  bindEvents: bindEvents,

	  // Proxy `unbindEvents`
	  unbindEvents: unbindEvents,

	  // Proxy `bindRequests`
	  bindRequests: bindRequests,

	  // Proxy `unbindRequests`
	  unbindRequests: unbindRequests

	};

	// Object
	// ------

	var ClassOptions = ['channelName', 'radioEvents', 'radioRequests'];

	// A Base Class that other Classes should descend from.
	// Object borrows many conventions and utilities from Backbone.
	var MarionetteObject = function MarionetteObject(options) {
	  this._setOptions(options);
	  this.mergeOptions(options, ClassOptions);
	  this.cid = _.uniqueId(this.cidPrefix);
	  this._initRadio();
	  this.initialize.apply(this, arguments);
	};

	MarionetteObject.extend = extend;

	// Object Methods
	// --------------

	// Ensure it can trigger events with Backbone.Events
	_.extend(MarionetteObject.prototype, Backbone.Events, CommonMixin, RadioMixin, {
	  cidPrefix: 'mno',

	  // for parity with Marionette.AbstractView lifecyle
	  _isDestroyed: false,

	  isDestroyed: function isDestroyed() {
	    return this._isDestroyed;
	  },


	  //this is a noop method intended to be overridden by classes that extend from this base
	  initialize: function initialize() {},
	  destroy: function destroy() {
	    if (this._isDestroyed) {
	      return this;
	    }

	    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
	      args[_key] = arguments[_key];
	    }

	    this.triggerMethod.apply(this, ['before:destroy', this].concat(args));

	    this._isDestroyed = true;
	    this.triggerMethod.apply(this, ['destroy', this].concat(args));
	    this.stopListening();

	    return this;
	  },


	  triggerMethod: triggerMethod$1
	});

	// DomMixin
	//  ---------

	var DomMixin = {
	  createBuffer: function createBuffer() {
	    return document.createDocumentFragment();
	  },
	  appendChildren: function appendChildren(el, children) {
	    Backbone.$(el).append(children);
	  },
	  beforeEl: function beforeEl(el, sibling) {
	    Backbone.$(el).before(sibling);
	  },
	  replaceEl: function replaceEl(newEl, oldEl) {
	    if (newEl === oldEl) {
	      return;
	    }

	    var parent = oldEl.parentNode;

	    if (!parent) {
	      return;
	    }

	    parent.replaceChild(newEl, oldEl);
	  },
	  detachContents: function detachContents(el) {
	    Backbone.$(el).contents().detach();
	  },
	  setInnerContent: function setInnerContent(el, html) {
	    Backbone.$(el).html(html);
	  },
	  removeEl: function removeEl(el) {
	    Backbone.$(el).remove();
	  },
	  findEls: function findEls(selector, context) {
	    return Backbone.$(selector, context);
	  }
	};

	// Template Cache
	// --------------

	// Manage templates stored in `<script>` blocks,
	// caching them for faster access.
	var TemplateCache = function TemplateCache(templateId) {
	  this.templateId = templateId;
	};

	// TemplateCache object-level methods. Manage the template
	// caches from these method calls instead of creating
	// your own TemplateCache instances
	_.extend(TemplateCache, {
	  templateCaches: {},

	  // Get the specified template by id. Either
	  // retrieves the cached version, or loads it
	  // from the DOM.
	  get: function get(templateId, options) {
	    var cachedTemplate = this.templateCaches[templateId];

	    if (!cachedTemplate) {
	      cachedTemplate = new TemplateCache(templateId);
	      this.templateCaches[templateId] = cachedTemplate;
	    }

	    return cachedTemplate.load(options);
	  },


	  // Clear templates from the cache. If no arguments
	  // are specified, clears all templates:
	  // `clear()`
	  //
	  // If arguments are specified, clears each of the
	  // specified templates from the cache:
	  // `clear("#t1", "#t2", "...")`
	  clear: function clear() {
	    var i = void 0;

	    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
	      args[_key] = arguments[_key];
	    }

	    var length = args.length;

	    if (length > 0) {
	      for (i = 0; i < length; i++) {
	        delete this.templateCaches[args[i]];
	      }
	    } else {
	      this.templateCaches = {};
	    }
	  }
	});

	// TemplateCache instance methods, allowing each
	// template cache object to manage its own state
	// and know whether or not it has been loaded
	_.extend(TemplateCache.prototype, DomMixin, {

	  // Internal method to load the template
	  load: function load(options) {
	    // Guard clause to prevent loading this template more than once
	    if (this.compiledTemplate) {
	      return this.compiledTemplate;
	    }

	    // Load the template and compile it
	    var template = this.loadTemplate(this.templateId, options);
	    this.compiledTemplate = this.compileTemplate(template, options);

	    return this.compiledTemplate;
	  },


	  // Load a template from the DOM, by default. Override
	  // this method to provide your own template retrieval
	  // For asynchronous loading with AMD/RequireJS, consider
	  // using a template-loader plugin as described here:
	  // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
	  loadTemplate: function loadTemplate(templateId, options) {
	    var $template = this.findEls(templateId);

	    if (!$template.length) {
	      throw new MarionetteError({
	        name: 'NoTemplateError',
	        message: 'Could not find template: "' + templateId + '"'
	      });
	    }
	    return $template.html();
	  },


	  // Pre-compile the template before caching it. Override
	  // this method if you do not need to pre-compile a template
	  // (JST / RequireJS for example) or if you want to change
	  // the template engine used (Handebars, etc).
	  compileTemplate: function compileTemplate(rawTemplate, options) {
	    return _.template(rawTemplate, options);
	  }
	});

	// Implementation of the invoke method (http://underscorejs.org/#invoke) with support for
	// lodash v3, v4, and underscore.js
	var _invoke = _.invokeMap || _.invoke;

	function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

	// MixinOptions
	// - behaviors

	// Takes care of getting the behavior class
	// given options and a key.
	// If a user passes in options.behaviorClass
	// default to using that.
	// If a user passes in a Behavior Class directly, use that
	// Otherwise delegate the lookup to the users `behaviorsLookup` implementation.
	function getBehaviorClass(options, key) {
	  if (options.behaviorClass) {
	    return options.behaviorClass;
	    //treat functions as a Behavior constructor
	  } else if (_.isFunction(options)) {
	    return options;
	  }

	  // behaviorsLookup can be either a flat object or a method
	  if (_.isFunction(Marionette.Behaviors.behaviorsLookup)) {
	    return Marionette.Behaviors.behaviorsLookup(options, key)[key];
	  }

	  return Marionette.Behaviors.behaviorsLookup[key];
	}

	// Iterate over the behaviors object, for each behavior
	// instantiate it and get its grouped behaviors.
	// This accepts a list of behaviors in either an object or array form
	function parseBehaviors(view, behaviors) {
	  return _.chain(behaviors).map(function (options, key) {
	    var BehaviorClass = getBehaviorClass(options, key);
	    //if we're passed a class directly instead of an object
	    var _options = options === BehaviorClass ? {} : options;
	    var behavior = new BehaviorClass(_options, view);
	    var nestedBehaviors = parseBehaviors(view, _.result(behavior, 'behaviors'));

	    return [behavior].concat(nestedBehaviors);
	  }).flatten().value();
	}

	var BehaviorsMixin = {
	  _initBehaviors: function _initBehaviors() {
	    this._behaviors = this._getBehaviors();
	  },
	  _getBehaviors: function _getBehaviors() {
	    var behaviors = _.result(this, 'behaviors');

	    // Behaviors defined on a view can be a flat object literal
	    // or it can be a function that returns an object.
	    return _.isObject(behaviors) ? parseBehaviors(this, behaviors) : {};
	  },
	  _getBehaviorTriggers: function _getBehaviorTriggers() {
	    var triggers = _invoke(this._behaviors, 'getTriggers');
	    return _.extend.apply(_, [{}].concat(_toConsumableArray(triggers)));
	  },
	  _getBehaviorEvents: function _getBehaviorEvents() {
	    var events = _invoke(this._behaviors, 'getEvents');
	    return _.extend.apply(_, [{}].concat(_toConsumableArray(events)));
	  },


	  // proxy behavior $el to the view's $el.
	  _proxyBehaviorViewProperties: function _proxyBehaviorViewProperties() {
	    _invoke(this._behaviors, 'proxyViewProperties');
	  },


	  // delegate modelEvents and collectionEvents
	  _delegateBehaviorEntityEvents: function _delegateBehaviorEntityEvents() {
	    _invoke(this._behaviors, 'delegateEntityEvents');
	  },


	  // undelegate modelEvents and collectionEvents
	  _undelegateBehaviorEntityEvents: function _undelegateBehaviorEntityEvents() {
	    _invoke(this._behaviors, 'undelegateEntityEvents');
	  },
	  _destroyBehaviors: function _destroyBehaviors(args) {
	    // Call destroy on each behavior after
	    // destroying the view.
	    // This unbinds event listeners
	    // that behaviors have registered for.
	    _invoke.apply(undefined, [this._behaviors, 'destroy'].concat(_toConsumableArray(args)));
	  },
	  _bindBehaviorUIElements: function _bindBehaviorUIElements() {
	    _invoke(this._behaviors, 'bindUIElements');
	  },
	  _unbindBehaviorUIElements: function _unbindBehaviorUIElements() {
	    _invoke(this._behaviors, 'unbindUIElements');
	  },
	  _triggerEventOnBehaviors: function _triggerEventOnBehaviors() {
	    var behaviors = this._behaviors;
	    // Use good ol' for as this is a very hot function
	    for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
	      triggerMethod$1.apply(behaviors[i], arguments);
	    }
	  }
	};

	// MixinOptions
	// - collectionEvents
	// - modelEvents

	var DelegateEntityEventsMixin = {
	  // Handle `modelEvents`, and `collectionEvents` configuration
	  _delegateEntityEvents: function _delegateEntityEvents(model, collection) {
	    this._undelegateEntityEvents(model, collection);

	    var modelEvents = _.result(this, 'modelEvents');
	    bindEvents.call(this, model, modelEvents);

	    var collectionEvents = _.result(this, 'collectionEvents');
	    bindEvents.call(this, collection, collectionEvents);
	  },
	  _undelegateEntityEvents: function _undelegateEntityEvents(model, collection) {
	    var modelEvents = _.result(this, 'modelEvents');
	    unbindEvents.call(this, model, modelEvents);

	    var collectionEvents = _.result(this, 'collectionEvents');
	    unbindEvents.call(this, collection, collectionEvents);
	  }
	};

	// Borrow event splitter from Backbone
	var delegateEventSplitter = /^(\S+)\s*(.*)$/;

	function uniqueName(eventName, selector) {
	  return [eventName + _.uniqueId('.evt'), selector].join(' ');
	}

	// Set event name to be namespaced using a unique index
	// to generate a non colliding event namespace
	// http://api.jquery.com/event.namespace/
	var getUniqueEventName = function getUniqueEventName(eventName) {
	  var match = eventName.match(delegateEventSplitter);
	  return uniqueName(match[1], match[2]);
	};

	// Add Feature flags here
	// e.g. 'class' => false
	var FEATURES = {
	  triggersStopPropagation: true,
	  triggersPreventDefault: true
	};

	function isEnabled(name) {
	  return !!FEATURES[name];
	}

	function setEnabled(name, state) {
	  return FEATURES[name] = state;
	}

	// Internal method to create an event handler for a given `triggerDef` like
	// 'click:foo'
	function buildViewTrigger(view, triggerDef) {
	  if (_.isString(triggerDef)) {
	    triggerDef = { event: triggerDef };
	  }

	  var eventName = triggerDef.event;

	  var shouldPreventDefault = !!triggerDef.preventDefault;

	  if (isEnabled('triggersPreventDefault')) {
	    shouldPreventDefault = triggerDef.preventDefault !== false;
	  }

	  var shouldStopPropagation = !!triggerDef.stopPropagation;

	  if (isEnabled('triggersStopPropagation')) {
	    shouldStopPropagation = triggerDef.stopPropagation !== false;
	  }

	  return function (event) {
	    if (shouldPreventDefault) {
	      event.preventDefault();
	    }

	    if (shouldStopPropagation) {
	      event.stopPropagation();
	    }

	    view.triggerMethod(eventName, view, event);
	  };
	}

	var TriggersMixin = {

	  // Configure `triggers` to forward DOM events to view
	  // events. `triggers: {"click .foo": "do:foo"}`
	  _getViewTriggers: function _getViewTriggers(view, triggers) {
	    // Configure the triggers, prevent default
	    // action and stop propagation of DOM events
	    return _.reduce(triggers, function (events, value, key) {
	      key = getUniqueEventName(key);
	      events[key] = buildViewTrigger(view, value);
	      return events;
	    }, {});
	  }
	};

	// allows for the use of the @ui. syntax within
	// a given key for triggers and events
	// swaps the @ui with the associated selector.
	// Returns a new, non-mutated, parsed events hash.
	var _normalizeUIKeys = function _normalizeUIKeys(hash, ui) {
	  return _.reduce(hash, function (memo, val, key) {
	    var normalizedKey = _normalizeUIString(key, ui);
	    memo[normalizedKey] = val;
	    return memo;
	  }, {});
	};

	// utility method for parsing @ui. syntax strings
	// into associated selector
	var _normalizeUIString = function _normalizeUIString(uiString, ui) {
	  return uiString.replace(/@ui\.[a-zA-Z-_$0-9]*/g, function (r) {
	    return ui[r.slice(4)];
	  });
	};

	// allows for the use of the @ui. syntax within
	// a given value for regions
	// swaps the @ui with the associated selector
	var _normalizeUIValues = function _normalizeUIValues(hash, ui, properties) {
	  _.each(hash, function (val, key) {
	    if (_.isString(val)) {
	      hash[key] = _normalizeUIString(val, ui);
	    } else if (_.isObject(val) && _.isArray(properties)) {
	      _.extend(val, _normalizeUIValues(_.pick(val, properties), ui));
	      /* Value is an object, and we got an array of embedded property names to normalize. */
	      _.each(properties, function (property) {
	        var propertyVal = val[property];
	        if (_.isString(propertyVal)) {
	          val[property] = _normalizeUIString(propertyVal, ui);
	        }
	      });
	    }
	  });
	  return hash;
	};

	var UIMixin = {

	  // normalize the keys of passed hash with the views `ui` selectors.
	  // `{"@ui.foo": "bar"}`
	  normalizeUIKeys: function normalizeUIKeys(hash) {
	    var uiBindings = this._getUIBindings();
	    return _normalizeUIKeys(hash, uiBindings);
	  },


	  // normalize the passed string with the views `ui` selectors.
	  // `"@ui.bar"`
	  normalizeUIString: function normalizeUIString(uiString) {
	    var uiBindings = this._getUIBindings();
	    return _normalizeUIString(uiString, uiBindings);
	  },


	  // normalize the values of passed hash with the views `ui` selectors.
	  // `{foo: "@ui.bar"}`
	  normalizeUIValues: function normalizeUIValues(hash, properties) {
	    var uiBindings = this._getUIBindings();
	    return _normalizeUIValues(hash, uiBindings, properties);
	  },
	  _getUIBindings: function _getUIBindings() {
	    var uiBindings = _.result(this, '_uiBindings');
	    var ui = _.result(this, 'ui');
	    return uiBindings || ui;
	  },


	  // This method binds the elements specified in the "ui" hash inside the view's code with
	  // the associated jQuery selectors.
	  _bindUIElements: function _bindUIElements() {
	    var _this = this;

	    if (!this.ui) {
	      return;
	    }

	    // store the ui hash in _uiBindings so they can be reset later
	    // and so re-rendering the view will be able to find the bindings
	    if (!this._uiBindings) {
	      this._uiBindings = this.ui;
	    }

	    // get the bindings result, as a function or otherwise
	    var bindings = _.result(this, '_uiBindings');

	    // empty the ui so we don't have anything to start with
	    this._ui = {};

	    // bind each of the selectors
	    _.each(bindings, function (selector, key) {
	      _this._ui[key] = _this.$(selector);
	    });

	    this.ui = this._ui;
	  },
	  _unbindUIElements: function _unbindUIElements() {
	    var _this2 = this;

	    if (!this.ui || !this._uiBindings) {
	      return;
	    }

	    // delete all of the existing ui bindings
	    _.each(this.ui, function ($el, name) {
	      delete _this2.ui[name];
	    });

	    // reset the ui element to the original bindings configuration
	    this.ui = this._uiBindings;
	    delete this._uiBindings;
	    delete this._ui;
	  },
	  _getUI: function _getUI(name) {
	    return this._ui[name];
	  }
	};

	// ViewMixin
	//  ---------

	// MixinOptions
	// - behaviors
	// - childViewEventPrefix
	// - childViewEvents
	// - childViewTriggers
	// - collectionEvents
	// - modelEvents
	// - triggers
	// - ui


	var ViewMixin = {
	  supportsRenderLifecycle: true,
	  supportsDestroyLifecycle: true,

	  _isDestroyed: false,

	  isDestroyed: function isDestroyed() {
	    return !!this._isDestroyed;
	  },


	  _isRendered: false,

	  isRendered: function isRendered() {
	    return !!this._isRendered;
	  },


	  _isAttached: false,

	  isAttached: function isAttached() {
	    return !!this._isAttached;
	  },


	  // Overriding Backbone.View's `delegateEvents` to handle
	  // `events` and `triggers`
	  delegateEvents: function delegateEvents(eventsArg) {

	    this._proxyBehaviorViewProperties();
	    this._buildEventProxies();

	    var viewEvents = this._getEvents(eventsArg);

	    if (typeof eventsArg === 'undefined') {
	      this.events = viewEvents;
	    }

	    var combinedEvents = _.extend({}, this._getBehaviorEvents(), viewEvents, this._getBehaviorTriggers(), this.getTriggers());

	    Backbone.View.prototype.delegateEvents.call(this, combinedEvents);

	    return this;
	  },
	  _getEvents: function _getEvents(eventsArg) {
	    var events = eventsArg || this.events;

	    if (_.isFunction(events)) {
	      return this.normalizeUIKeys(events.call(this));
	    }

	    return this.normalizeUIKeys(events);
	  },


	  // Configure `triggers` to forward DOM events to view
	  // events. `triggers: {"click .foo": "do:foo"}`
	  getTriggers: function getTriggers() {
	    if (!this.triggers) {
	      return;
	    }

	    // Allow `triggers` to be configured as a function
	    var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));

	    // Configure the triggers, prevent default
	    // action and stop propagation of DOM events
	    return this._getViewTriggers(this, triggers);
	  },


	  // Handle `modelEvents`, and `collectionEvents` configuration
	  delegateEntityEvents: function delegateEntityEvents() {
	    this._delegateEntityEvents(this.model, this.collection);

	    // bind each behaviors model and collection events
	    this._delegateBehaviorEntityEvents();

	    return this;
	  },


	  // Handle unbinding `modelEvents`, and `collectionEvents` configuration
	  undelegateEntityEvents: function undelegateEntityEvents() {
	    this._undelegateEntityEvents(this.model, this.collection);

	    // unbind each behaviors model and collection events
	    this._undelegateBehaviorEntityEvents();

	    return this;
	  },


	  // Handle destroying the view and its children.
	  destroy: function destroy() {
	    if (this._isDestroyed) {
	      return this;
	    }
	    var shouldTriggerDetach = !!this._isAttached;

	    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
	      args[_key] = arguments[_key];
	    }

	    this.triggerMethod.apply(this, ['before:destroy', this].concat(args));
	    if (shouldTriggerDetach) {
	      this.triggerMethod('before:detach', this);
	    }

	    // unbind UI elements
	    this.unbindUIElements();

	    // remove the view from the DOM
	    this.removeEl(this.el);

	    if (shouldTriggerDetach) {
	      this._isAttached = false;
	      this.triggerMethod('detach', this);
	    }

	    // remove children after the remove to prevent extra paints
	    this._removeChildren();

	    this._destroyBehaviors(args);

	    this._isDestroyed = true;
	    this._isRendered = false;
	    this.triggerMethod.apply(this, ['destroy', this].concat(args));

	    this.stopListening();

	    return this;
	  },
	  bindUIElements: function bindUIElements() {
	    this._bindUIElements();
	    this._bindBehaviorUIElements();

	    return this;
	  },


	  // This method unbinds the elements specified in the "ui" hash
	  unbindUIElements: function unbindUIElements() {
	    this._unbindUIElements();
	    this._unbindBehaviorUIElements();

	    return this;
	  },
	  getUI: function getUI(name) {
	    return this._getUI(name);
	  },


	  // used as the prefix for child view events
	  // that are forwarded through the layoutview
	  childViewEventPrefix: 'childview',

	  // import the `triggerMethod` to trigger events with corresponding
	  // methods if the method exists
	  triggerMethod: function triggerMethod() {
	    var ret = triggerMethod$1.apply(this, arguments);

	    this._triggerEventOnBehaviors.apply(this, arguments);

	    return ret;
	  },


	  // Cache `childViewEvents` and `childViewTriggers`
	  _buildEventProxies: function _buildEventProxies() {
	    this._childViewEvents = _.result(this, 'childViewEvents');
	    this._childViewTriggers = _.result(this, 'childViewTriggers');
	  },
	  _proxyChildViewEvents: function _proxyChildViewEvents(view) {
	    this.listenTo(view, 'all', this._childViewEventHandler);
	  },
	  _childViewEventHandler: function _childViewEventHandler(eventName) {
	    var childViewEvents = this.normalizeMethods(this._childViewEvents);

	    // call collectionView childViewEvent if defined

	    for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
	      args[_key2 - 1] = arguments[_key2];
	    }

	    if (typeof childViewEvents !== 'undefined' && _.isFunction(childViewEvents[eventName])) {
	      childViewEvents[eventName].apply(this, args);
	    }

	    // use the parent view's proxyEvent handlers
	    var childViewTriggers = this._childViewTriggers;

	    // Call the event with the proxy name on the parent layout
	    if (childViewTriggers && _.isString(childViewTriggers[eventName])) {
	      this.triggerMethod.apply(this, [childViewTriggers[eventName]].concat(args));
	    }

	    var prefix = _.result(this, 'childViewEventPrefix');

	    if (prefix !== false) {
	      var childEventName = prefix + ':' + eventName;

	      this.triggerMethod.apply(this, [childEventName].concat(args));
	    }
	  }
	};

	_.extend(ViewMixin, DomMixin, BehaviorsMixin, CommonMixin, DelegateEntityEventsMixin, TriggersMixin, UIMixin);

	function destroyBackboneView(view) {
	  if (!view.supportsDestroyLifecycle) {
	    triggerMethodOn(view, 'before:destroy', view);
	  }

	  var shouldTriggerDetach = !!view._isAttached;

	  if (shouldTriggerDetach) {
	    triggerMethodOn(view, 'before:detach', view);
	  }

	  view.remove();

	  if (shouldTriggerDetach) {
	    view._isAttached = false;
	    triggerMethodOn(view, 'detach', view);
	  }

	  view._isDestroyed = true;

	  if (!view.supportsDestroyLifecycle) {
	    triggerMethodOn(view, 'destroy', view);
	  }
	}

	// Region
	// ------

	var ClassOptions$2 = ['allowMissingEl', 'parentEl', 'replaceElement'];

	var Region = MarionetteObject.extend({
	  cidPrefix: 'mnr',
	  replaceElement: false,
	  _isReplaced: false,

	  constructor: function constructor(options) {
	    this._setOptions(options);

	    this.mergeOptions(options, ClassOptions$2);

	    // getOption necessary because options.el may be passed as undefined
	    this._initEl = this.el = this.getOption('el');

	    // Handle when this.el is passed in as a $ wrapped element.
	    this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;

	    if (!this.el) {
	      throw new MarionetteError({
	        name: 'NoElError',
	        message: 'An "el" must be specified for a region.'
	      });
	    }

	    this.$el = this.getEl(this.el);
	    MarionetteObject.call(this, options);
	  },


	  // Displays a backbone view instance inside of the region. Handles calling the `render`
	  // method for you. Reads content directly from the `el` attribute. The `preventDestroy`
	  // option can be used to prevent a view from the old view being destroyed on show.
	  show: function show(view, options) {
	    if (!this._ensureElement(options)) {
	      return;
	    }

	    view = this._getView(view, options);

	    if (view === this.currentView) {
	      return this;
	    }

	    this.triggerMethod('before:show', this, view, options);

	    // Assume an attached view is already in the region for pre-existing DOM
	    if (!view._isAttached) {
	      this.empty(options);
	    }

	    this._setupChildView(view);

	    this._renderView(view);

	    this._attachView(view, options);

	    this.currentView = view;

	    this.triggerMethod('show', this, view, options);
	    return this;
	  },
	  _setupChildView: function _setupChildView(view) {
	    monitorViewEvents(view);

	    this._proxyChildViewEvents(view);

	    // We need to listen for if a view is destroyed in a way other than through the region.
	    // If this happens we need to remove the reference to the currentView since once a view
	    // has been destroyed we can not reuse it.
	    view.on('destroy', this._empty, this);
	  },
	  _proxyChildViewEvents: function _proxyChildViewEvents(view) {
	    var parentView = this._parentView;

	    if (!parentView) {
	      return;
	    }

	    parentView._proxyChildViewEvents(view);
	  },
	  _renderView: function _renderView(view) {
	    if (view._isRendered) {
	      return;
	    }

	    if (!view.supportsRenderLifecycle) {
	      triggerMethodOn(view, 'before:render', view);
	    }

	    view.render();

	    if (!view.supportsRenderLifecycle) {
	      view._isRendered = true;
	      triggerMethodOn(view, 'render', view);
	    }
	  },
	  _attachView: function _attachView(view) {
	    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    var shouldTriggerAttach = !view._isAttached && isNodeAttached(this.el);
	    var shouldReplaceEl = typeof options.replaceElement === 'undefined' ? !!_.result(this, 'replaceElement') : !!options.replaceElement;

	    if (shouldTriggerAttach) {
	      triggerMethodOn(view, 'before:attach', view);
	    }

	    if (shouldReplaceEl) {
	      this._replaceEl(view);
	    } else {
	      this.attachHtml(view);
	    }

	    if (shouldTriggerAttach) {
	      view._isAttached = true;
	      triggerMethodOn(view, 'attach', view);
	    }
	  },
	  _ensureElement: function _ensureElement() {
	    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

	    if (!_.isObject(this.el)) {
	      this.$el = this.getEl(this.el);
	      this.el = this.$el[0];
	    }

	    if (!this.$el || this.$el.length === 0) {
	      var allowMissingEl = typeof options.allowMissingEl === 'undefined' ? !!_.result(this, 'allowMissingEl') : !!options.allowMissingEl;

	      if (allowMissingEl) {
	        return false;
	      } else {
	        throw new MarionetteError('An "el" must exist in DOM for this region ' + this.cid);
	      }
	    }
	    return true;
	  },
	  _getView: function _getView(view) {
	    if (!view) {
	      throw new MarionetteError({
	        name: 'ViewNotValid',
	        message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
	      });
	    }

	    if (view._isDestroyed) {
	      throw new MarionetteError({
	        name: 'ViewDestroyedError',
	        message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
	      });
	    }

	    if (view instanceof Backbone.View) {
	      return view;
	    }

	    var viewOptions = this._getViewOptions(view);

	    return new View(viewOptions);
	  },


	  // This allows for a template or a static string to be
	  // used as a template
	  _getViewOptions: function _getViewOptions(viewOptions) {
	    if (_.isFunction(viewOptions)) {
	      return { template: viewOptions };
	    }

	    if (_.isObject(viewOptions)) {
	      return viewOptions;
	    }

	    var template = function template() {
	      return viewOptions;
	    };

	    return { template: template };
	  },


	  // Override this method to change how the region finds the DOM element that it manages. Return
	  // a jQuery selector object scoped to a provided parent el or the document if none exists.
	  getEl: function getEl(el) {
	    return this.findEls(el, _.result(this, 'parentEl'));
	  },
	  _replaceEl: function _replaceEl(view) {
	    // always restore the el to ensure the regions el is present before replacing
	    this._restoreEl();

	    view.on('before:destroy', this._restoreEl, this);

	    this.replaceEl(view.el, this.el);

	    this._isReplaced = true;
	  },


	  // Restore the region's element in the DOM.
	  _restoreEl: function _restoreEl() {
	    // There is nothing to replace
	    if (!this._isReplaced) {
	      return;
	    }

	    var view = this.currentView;

	    if (!view) {
	      return;
	    }

	    this.replaceEl(this.el, view.el);

	    this._isReplaced = false;
	  },


	  // Check to see if the region's el was replaced.
	  isReplaced: function isReplaced() {
	    return !!this._isReplaced;
	  },


	  // Override this method to change how the new view is appended to the `$el` that the
	  // region is managing
	  attachHtml: function attachHtml(view) {
	    this.appendChildren(this.el, view.el);
	  },


	  // Destroy the current view, if there is one. If there is no current view, it does
	  // nothing and returns immediately.
	  empty: function empty() {
	    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { allowMissingEl: true };

	    var view = this.currentView;

	    // If there is no view in the region we should only detach current html
	    if (!view) {
	      if (this._ensureElement(options)) {
	        this.detachHtml();
	      }
	      return this;
	    }

	    var shouldDestroy = !options.preventDestroy;

	    if (!shouldDestroy) {
	      deprecate('The preventDestroy option is deprecated. Use Region#detachView');
	    }

	    this._empty(view, shouldDestroy);
	    return this;
	  },
	  _empty: function _empty(view, shouldDestroy) {
	    view.off('destroy', this._empty, this);
	    this.triggerMethod('before:empty', this, view);

	    this._restoreEl();

	    delete this.currentView;

	    if (!view._isDestroyed) {
	      this._removeView(view, shouldDestroy);
	      this._stopChildViewEvents(view);
	    }

	    this.triggerMethod('empty', this, view);
	  },
	  _stopChildViewEvents: function _stopChildViewEvents(view) {
	    var parentView = this._parentView;

	    if (!parentView) {
	      return;
	    }

	    this._parentView.stopListening(view);
	  },
	  _removeView: function _removeView(view, shouldDestroy) {
	    if (!shouldDestroy) {
	      this._detachView(view);
	      return;
	    }

	    if (view.destroy) {
	      view.destroy();
	    } else {
	      destroyBackboneView(view);
	    }
	  },
	  detachView: function detachView() {
	    var view = this.currentView;

	    if (!view) {
	      return;
	    }

	    this._empty(view);

	    return view;
	  },
	  _detachView: function _detachView(view) {
	    var shouldTriggerDetach = !!view._isAttached;
	    if (shouldTriggerDetach) {
	      triggerMethodOn(view, 'before:detach', view);
	    }

	    this.detachHtml();

	    if (shouldTriggerDetach) {
	      view._isAttached = false;
	      triggerMethodOn(view, 'detach', view);
	    }
	  },


	  // Override this method to change how the region detaches current content
	  detachHtml: function detachHtml() {
	    this.detachContents(this.el);
	  },


	  // Checks whether a view is currently present within the region. Returns `true` if there is
	  // and `false` if no view is present.
	  hasView: function hasView() {
	    return !!this.currentView;
	  },


	  // Reset the region by destroying any existing view and clearing out the cached `$el`.
	  // The next time a view is shown via this region, the region will re-query the DOM for
	  // the region's `el`.
	  reset: function reset(options) {
	    this.empty(options);

	    if (this.$el) {
	      this.el = this._initEl;
	    }

	    delete this.$el;
	    return this;
	  },
	  destroy: function destroy(options) {
	    this.reset(options);
	    return MarionetteObject.prototype.destroy.apply(this, arguments);
	  }
	});

	_.extend(Region.prototype, DomMixin);

	// return the region instance from the definition
	var buildRegion = function (definition, defaults) {
	  if (definition instanceof Region) {
	    return definition;
	  }

	  return buildRegionFromDefinition(definition, defaults);
	};

	function buildRegionFromDefinition(definition, defaults) {
	  var opts = _.extend({}, defaults);

	  if (_.isString(definition)) {
	    _.extend(opts, { el: definition });

	    return buildRegionFromObject(opts);
	  }

	  if (_.isFunction(definition)) {
	    _.extend(opts, { regionClass: definition });

	    return buildRegionFromObject(opts);
	  }

	  if (_.isObject(definition)) {
	    if (definition.selector) {
	      deprecate('The selector option on a Region definition object is deprecated. Use el to pass a selector string');
	    }

	    _.extend(opts, { el: definition.selector }, definition);

	    return buildRegionFromObject(opts);
	  }

	  throw new MarionetteError({
	    message: 'Improper region configuration type.',
	    url: 'marionette.region.html#region-configuration-types'
	  });
	}

	function buildRegionFromObject(definition) {
	  var RegionClass = definition.regionClass;

	  var options = _.omit(definition, 'regionClass');

	  return new RegionClass(options);
	}

	// MixinOptions
	// - regions
	// - regionClass

	var RegionsMixin = {
	  regionClass: Region,

	  // Internal method to initialize the regions that have been defined in a
	  // `regions` attribute on this View.
	  _initRegions: function _initRegions() {

	    // init regions hash
	    this.regions = this.regions || {};
	    this._regions = {};

	    this.addRegions(_.result(this, 'regions'));
	  },


	  // Internal method to re-initialize all of the regions by updating
	  // the `el` that they point to
	  _reInitRegions: function _reInitRegions() {
	    _invoke(this._regions, 'reset');
	  },


	  // Add a single region, by name, to the View
	  addRegion: function addRegion(name, definition) {
	    var regions = {};
	    regions[name] = definition;
	    return this.addRegions(regions)[name];
	  },


	  // Add multiple regions as a {name: definition, name2: def2} object literal
	  addRegions: function addRegions(regions) {
	    // If there's nothing to add, stop here.
	    if (_.isEmpty(regions)) {
	      return;
	    }

	    // Normalize region selectors hash to allow
	    // a user to use the @ui. syntax.
	    regions = this.normalizeUIValues(regions, ['selector', 'el']);

	    // Add the regions definitions to the regions property
	    this.regions = _.extend({}, this.regions, regions);

	    return this._addRegions(regions);
	  },


	  // internal method to build and add regions
	  _addRegions: function _addRegions(regionDefinitions) {
	    var _this = this;

	    var defaults = {
	      regionClass: this.regionClass,
	      parentEl: _.partial(_.result, this, 'el')
	    };

	    return _.reduce(regionDefinitions, function (regions, definition, name) {
	      regions[name] = buildRegion(definition, defaults);
	      _this._addRegion(regions[name], name);
	      return regions;
	    }, {});
	  },
	  _addRegion: function _addRegion(region, name) {
	    this.triggerMethod('before:add:region', this, name, region);

	    region._parentView = this;

	    this._regions[name] = region;

	    this.triggerMethod('add:region', this, name, region);
	  },


	  // Remove a single region from the View, by name
	  removeRegion: function removeRegion(name) {
	    var region = this._regions[name];

	    this._removeRegion(region, name);

	    return region;
	  },


	  // Remove all regions from the View
	  removeRegions: function removeRegions() {
	    var regions = this.getRegions();

	    _.each(this._regions, _.bind(this._removeRegion, this));

	    return regions;
	  },
	  _removeRegion: function _removeRegion(region, name) {
	    this.triggerMethod('before:remove:region', this, name, region);

	    region.destroy();

	    delete this.regions[name];
	    delete this._regions[name];

	    this.triggerMethod('remove:region', this, name, region);
	  },


	  // Empty all regions in the region manager, but
	  // leave them attached
	  emptyRegions: function emptyRegions() {
	    var regions = this.getRegions();
	    _invoke(regions, 'empty');
	    return regions;
	  },


	  // Checks to see if view contains region
	  // Accepts the region name
	  // hasRegion('main')
	  hasRegion: function hasRegion(name) {
	    return !!this.getRegion(name);
	  },


	  // Provides access to regions
	  // Accepts the region name
	  // getRegion('main')
	  getRegion: function getRegion(name) {
	    return this._regions[name];
	  },


	  // Get all regions
	  getRegions: function getRegions() {
	    return _.clone(this._regions);
	  },
	  showChildView: function showChildView(name, view) {
	    var region = this.getRegion(name);

	    for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
	      args[_key - 2] = arguments[_key];
	    }

	    return region.show.apply(region, [view].concat(args));
	  },
	  detachChildView: function detachChildView(name) {
	    return this.getRegion(name).detachView();
	  },
	  getChildView: function getChildView(name) {
	    return this.getRegion(name).currentView;
	  }
	};

	// Renderer
	// --------

	// Render a template with data by passing in the template
	// selector and the data to render.
	var Renderer = {

	  // Render a template with data. The `template` parameter is
	  // passed to the `TemplateCache` object to retrieve the
	  // template function. Override this method to provide your own
	  // custom rendering and template handling for all of Marionette.
	  render: function render(template, data) {
	    if (!template) {
	      throw new MarionetteError({
	        name: 'TemplateNotFoundError',
	        message: 'Cannot render the template since its false, null or undefined.'
	      });
	    }

	    var templateFunc = _.isFunction(template) ? template : TemplateCache.get(template);

	    return templateFunc(data);
	  }
	};

	// View
	// ---------

	var ClassOptions$1 = ['behaviors', 'childViewEventPrefix', 'childViewEvents', 'childViewTriggers', 'collectionEvents', 'events', 'modelEvents', 'regionClass', 'regions', 'template', 'templateContext', 'triggers', 'ui'];

	// The standard view. Includes view events, automatic rendering
	// of Underscore templates, nested views, and more.
	var View = Backbone.View.extend({
	  constructor: function constructor(options) {
	    this.render = _.bind(this.render, this);

	    this._setOptions(options);

	    this.mergeOptions(options, ClassOptions$1);

	    monitorViewEvents(this);

	    this._initBehaviors();
	    this._initRegions();

	    var args = Array.prototype.slice.call(arguments);
	    args[0] = this.options;
	    Backbone.View.prototype.constructor.apply(this, args);

	    this.delegateEntityEvents();
	  },


	  // Serialize the view's model *or* collection, if
	  // it exists, for the template
	  serializeData: function serializeData() {
	    if (!this.model && !this.collection) {
	      return {};
	    }

	    // If we have a model, we serialize that
	    if (this.model) {
	      return this.serializeModel();
	    }

	    // Otherwise, we serialize the collection,
	    // making it available under the `items` property
	    return {
	      items: this.serializeCollection()
	    };
	  },


	  // Prepares the special `model` property of a view
	  // for being displayed in the template. By default
	  // we simply clone the attributes. Override this if
	  // you need a custom transformation for your view's model
	  serializeModel: function serializeModel() {
	    if (!this.model) {
	      return {};
	    }
	    return _.clone(this.model.attributes);
	  },


	  // Serialize a collection by cloning each of
	  // its model's attributes
	  serializeCollection: function serializeCollection() {
	    if (!this.collection) {
	      return {};
	    }
	    return this.collection.map(function (model) {
	      return _.clone(model.attributes);
	    });
	  },


	  // Overriding Backbone.View's `setElement` to handle
	  // if an el was previously defined. If so, the view might be
	  // rendered or attached on setElement.
	  setElement: function setElement() {
	    var hasEl = !!this.el;

	    Backbone.View.prototype.setElement.apply(this, arguments);

	    if (hasEl) {
	      this._isRendered = !!this.$el.length;
	      this._isAttached = isNodeAttached(this.el);
	    }

	    if (this._isRendered) {
	      this.bindUIElements();
	    }

	    return this;
	  },


	  // Render the view, defaulting to underscore.js templates.
	  // You can override this in your view definition to provide
	  // a very specific rendering for your view. In general, though,
	  // you should override the `Marionette.Renderer` object to
	  // change how Marionette renders views.
	  // Subsequent renders after the first will re-render all nested
	  // views.
	  render: function render() {
	    if (this._isDestroyed) {
	      return this;
	    }

	    this.triggerMethod('before:render', this);

	    // If this is not the first render call, then we need to
	    // re-initialize the `el` for each region
	    if (this._isRendered) {
	      this._reInitRegions();
	    }

	    this._renderTemplate();
	    this.bindUIElements();

	    this._isRendered = true;
	    this.triggerMethod('render', this);

	    return this;
	  },


	  // Internal method to render the template with the serialized data
	  // and template context via the `Marionette.Renderer` object.
	  _renderTemplate: function _renderTemplate() {
	    var template = this.getTemplate();

	    // Allow template-less views
	    if (template === false) {
	      return;
	    }

	    // Add in entity data and template context
	    var data = this.mixinTemplateContext(this.serializeData());

	    // Render and add to el
	    var html = Renderer.render(template, data, this);
	    this.attachElContent(html);
	  },


	  // Get the template for this view
	  // instance. You can set a `template` attribute in the view
	  // definition or pass a `template: "whatever"` parameter in
	  // to the constructor options.
	  getTemplate: function getTemplate() {
	    return this.template;
	  },


	  // Mix in template context methods. Looks for a
	  // `templateContext` attribute, which can either be an
	  // object literal, or a function that returns an object
	  // literal. All methods and attributes from this object
	  // are copies to the object passed in.
	  mixinTemplateContext: function mixinTemplateContext() {
	    var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

	    var templateContext = _.result(this, 'templateContext');
	    return _.extend(target, templateContext);
	  },


	  // Attaches the content of a given view.
	  // This method can be overridden to optimize rendering,
	  // or to render in a non standard way.
	  //
	  // For example, using `innerHTML` instead of `$el.html`
	  //
	  // ```js
	  // attachElContent(html) {
	  //   this.el.innerHTML = html;
	  //   return this;
	  // }
	  // ```
	  attachElContent: function attachElContent(html) {
	    this.setInnerContent(this.el, html);

	    return this;
	  },


	  // called by ViewMixin destroy
	  _removeChildren: function _removeChildren() {
	    this.removeRegions();
	  },
	  _getImmediateChildren: function _getImmediateChildren() {
	    return _.chain(this.getRegions()).map('currentView').compact().value();
	  }
	});

	_.extend(View.prototype, ViewMixin, RegionsMixin);

	// Mix in methods from Underscore, for iteration, and other
	// collection related features.
	// Borrowing this code from Backbone.Collection:
	// https://github.com/jashkenas/backbone/blob/1.1.2/backbone.js#L962

	var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest', 'last', 'without', 'isEmpty', 'pluck', 'reduce'];

	var emulateCollection = function emulateCollection(object, listProperty) {
	  _.each(methods, function (method) {
	    object[method] = function () {
	      var list = _.values(_.result(this, listProperty));
	      var args = [list].concat(_.toArray(arguments));
	      return _[method].apply(_, args);
	    };
	  });
	};

	// Provide a container to store, retrieve and
	// shut down child views.
	var Container = function Container(views) {
	  this._views = {};
	  this._indexByModel = {};
	  this._indexByCustom = {};
	  this._updateLength();

	  _.each(views, _.bind(this.add, this));
	};

	emulateCollection(Container.prototype, '_views');

	// Container Methods
	// -----------------

	_.extend(Container.prototype, {

	  // Add a view to this container. Stores the view
	  // by `cid` and makes it searchable by the model
	  // cid (and model itself). Optionally specify
	  // a custom key to store an retrieve the view.
	  add: function add(view, customIndex) {
	    return this._add(view, customIndex)._updateLength();
	  },


	  // To be used when avoiding call _updateLength
	  // When you are done adding all your new views
	  // call _updateLength
	  _add: function _add(view, customIndex) {
	    var viewCid = view.cid;

	    // store the view
	    this._views[viewCid] = view;

	    // index it by model
	    if (view.model) {
	      this._indexByModel[view.model.cid] = viewCid;
	    }

	    // index by custom
	    if (customIndex) {
	      this._indexByCustom[customIndex] = viewCid;
	    }

	    return this;
	  },


	  // Find a view by the model that was attached to
	  // it. Uses the model's `cid` to find it.
	  findByModel: function findByModel(model) {
	    return this.findByModelCid(model.cid);
	  },


	  // Find a view by the `cid` of the model that was attached to
	  // it. Uses the model's `cid` to find the view `cid` and
	  // retrieve the view using it.
	  findByModelCid: function findByModelCid(modelCid) {
	    var viewCid = this._indexByModel[modelCid];
	    return this.findByCid(viewCid);
	  },


	  // Find a view by a custom indexer.
	  findByCustom: function findByCustom(index) {
	    var viewCid = this._indexByCustom[index];
	    return this.findByCid(viewCid);
	  },


	  // Find by index. This is not guaranteed to be a
	  // stable index.
	  findByIndex: function findByIndex(index) {
	    return _.values(this._views)[index];
	  },


	  // retrieve a view by its `cid` directly
	  findByCid: function findByCid(cid) {
	    return this._views[cid];
	  },


	  // Remove a view
	  remove: function remove(view) {
	    return this._remove(view)._updateLength();
	  },


	  // To be used when avoiding call _updateLength
	  // When you are done adding all your new views
	  // call _updateLength
	  _remove: function _remove(view) {
	    var viewCid = view.cid;

	    // delete model index
	    if (view.model) {
	      delete this._indexByModel[view.model.cid];
	    }

	    // delete custom index
	    _.some(this._indexByCustom, _.bind(function (cid, key) {
	      if (cid === viewCid) {
	        delete this._indexByCustom[key];
	        return true;
	      }
	    }, this));

	    // remove the view from the container
	    delete this._views[viewCid];

	    return this;
	  },


	  // Update the `.length` attribute on this container
	  _updateLength: function _updateLength() {
	    this.length = _.size(this._views);

	    return this;
	  }
	});

	// Collection View
	// ---------------

	var ClassOptions$3 = ['behaviors', 'childView', 'childViewEventPrefix', 'childViewEvents', 'childViewOptions', 'childViewTriggers', 'collectionEvents', 'events', 'filter', 'emptyView', 'emptyViewOptions', 'modelEvents', 'reorderOnSort', 'sort', 'triggers', 'ui', 'viewComparator'];

	// A view that iterates over a Backbone.Collection
	// and renders an individual child view for each model.
	var CollectionView = Backbone.View.extend({

	  // flag for maintaining the sorted order of the collection
	  sort: true,

	  // constructor
	  // option to pass `{sort: false}` to prevent the `CollectionView` from
	  // maintaining the sorted order of the collection.
	  // This will fallback onto appending childView's to the end.
	  //
	  // option to pass `{viewComparator: compFunction()}` to allow the `CollectionView`
	  // to use a custom sort order for the collection.
	  constructor: function constructor(options) {
	    this.render = _.bind(this.render, this);

	    this._setOptions(options);

	    this.mergeOptions(options, ClassOptions$3);

	    monitorViewEvents(this);

	    this._initBehaviors();
	    this.once('render', this._initialEvents);
	    this._initChildViewStorage();
	    this._bufferedChildren = [];

	    var args = Array.prototype.slice.call(arguments);
	    args[0] = this.options;
	    Backbone.View.prototype.constructor.apply(this, args);

	    this.delegateEntityEvents();
	  },


	  // Instead of inserting elements one by one into the page, it's much more performant to insert
	  // elements into a document fragment and then insert that document fragment into the page
	  _startBuffering: function _startBuffering() {
	    this._isBuffering = true;
	  },
	  _endBuffering: function _endBuffering() {
	    var shouldTriggerAttach = !!this._isAttached;
	    var triggerOnChildren = shouldTriggerAttach ? this._getImmediateChildren() : [];

	    this._isBuffering = false;

	    _.each(triggerOnChildren, function (child) {
	      triggerMethodOn(child, 'before:attach', child);
	    });

	    this.attachBuffer(this, this._createBuffer());

	    _.each(triggerOnChildren, function (child) {
	      child._isAttached = true;
	      triggerMethodOn(child, 'attach', child);
	    });

	    this._bufferedChildren = [];
	  },
	  _getImmediateChildren: function _getImmediateChildren() {
	    return _.values(this.children._views);
	  },


	  // Configured the initial events that the collection view binds to.
	  _initialEvents: function _initialEvents() {
	    if (this.collection) {
	      this.listenTo(this.collection, 'add', this._onCollectionAdd);
	      this.listenTo(this.collection, 'update', this._onCollectionUpdate);
	      this.listenTo(this.collection, 'reset', this.render);

	      if (this.sort) {
	        this.listenTo(this.collection, 'sort', this._sortViews);
	      }
	    }
	  },


	  // Handle a child added to the collection
	  _onCollectionAdd: function _onCollectionAdd(child, collection, opts) {
	    // `index` is present when adding with `at` since BB 1.2; indexOf fallback for < 1.2
	    var index = opts.at !== undefined && (opts.index || collection.indexOf(child));

	    // When filtered or when there is no initial index, calculate index.
	    if (this.filter || index === false) {
	      index = _.indexOf(this._filteredSortedModels(index), child);
	    }

	    if (this._shouldAddChild(child, index)) {
	      this._destroyEmptyView();
	      this._addChild(child, index);
	    }
	  },


	  // Handle collection update model removals
	  _onCollectionUpdate: function _onCollectionUpdate(collection, options) {
	    var changes = options.changes;
	    this._removeChildModels(changes.removed);
	  },


	  // Remove the child views and destroy them.
	  // This function also updates the indices of later views
	  // in the collection in order to keep the children in sync with the collection.
	  // "models" is an array of models and the corresponding views
	  // will be removed and destroyed from the CollectionView
	  _removeChildModels: function _removeChildModels(models) {
	    // Used to determine where to update the remaining
	    // sibling view indices after these views are removed.
	    var removedViews = this._getRemovedViews(models);

	    if (!removedViews.length) {
	      return;
	    }

	    this.children._updateLength();

	    // decrement the index of views after this one
	    this._updateIndices(removedViews, false);

	    if (this.isEmpty()) {
	      this._showEmptyView();
	    }
	  },


	  // Returns the views that will be used for re-indexing
	  // through CollectionView#_updateIndices.
	  _getRemovedViews: function _getRemovedViews(models) {
	    var _this = this;

	    // Returning a view means something was removed.
	    return _.reduce(models, function (removingViews, model) {
	      var view = model && _this.children.findByModel(model);

	      if (!view || view._isDestroyed) {
	        return removingViews;
	      }

	      _this._removeChildView(view);

	      removingViews.push(view);

	      return removingViews;
	    }, []);
	  },
	  _removeChildView: function _removeChildView(view) {
	    this.triggerMethod('before:remove:child', this, view);

	    this.children._remove(view);
	    if (view.destroy) {
	      view.destroy();
	    } else {
	      destroyBackboneView(view);
	    }

	    this.stopListening(view);
	    this.triggerMethod('remove:child', this, view);
	  },


	  // Overriding Backbone.View's `setElement` to handle
	  // if an el was previously defined. If so, the view might be
	  // attached on setElement.
	  setElement: function setElement() {
	    var hasEl = !!this.el;

	    Backbone.View.prototype.setElement.apply(this, arguments);

	    if (hasEl) {
	      this._isAttached = isNodeAttached(this.el);
	    }

	    return this;
	  },


	  // Render children views. Override this method to provide your own implementation of a
	  // render function for the collection view.
	  render: function render() {
	    if (this._isDestroyed) {
	      return this;
	    }
	    this.triggerMethod('before:render', this);
	    this._renderChildren();
	    this._isRendered = true;
	    this.triggerMethod('render', this);
	    return this;
	  },


	  // An efficient rendering used for filtering. Instead of modifying the whole DOM for the
	  // collection view, we are only adding or removing the related childrenViews.
	  setFilter: function setFilter(filter) {
	    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
	        preventRender = _ref.preventRender;

	    var canBeRendered = this._isRendered && !this._isDestroyed;
	    var filterChanged = this.filter !== filter;
	    var shouldRender = canBeRendered && filterChanged && !preventRender;

	    if (shouldRender) {
	      var previousModels = this._filteredSortedModels();
	      this.filter = filter;
	      var models = this._filteredSortedModels();
	      this._applyModelDeltas(models, previousModels);
	    } else {
	      this.filter = filter;
	    }

	    return this;
	  },


	  // `removeFilter` is actually an alias for removing filters.
	  removeFilter: function removeFilter(options) {
	    return this.setFilter(null, options);
	  },


	  // Calculate and apply difference by cid between `models` and `previousModels`.
	  _applyModelDeltas: function _applyModelDeltas(models, previousModels) {
	    var _this2 = this;

	    var currentIds = {};
	    _.each(models, function (model, index) {
	      var addedChildNotExists = !_this2.children.findByModel(model);
	      if (addedChildNotExists) {
	        _this2._onCollectionAdd(model, _this2.collection, { at: index });
	      }
	      currentIds[model.cid] = true;
	    });

	    var removeModels = _.filter(previousModels, function (prevModel) {
	      return !currentIds[prevModel.cid] && _this2.children.findByModel(prevModel);
	    });

	    this._removeChildModels(removeModels);
	  },


	  // Reorder DOM after sorting. When your element's rendering do not use their index,
	  // you can pass reorderOnSort: true to only reorder the DOM after a sort instead of
	  // rendering all the collectionView.
	  reorder: function reorder() {
	    var _this3 = this;

	    var children = this.children;
	    var models = this._filteredSortedModels();

	    if (!models.length && this._showingEmptyView) {
	      return this;
	    }

	    var anyModelsAdded = _.some(models, function (model) {
	      return !children.findByModel(model);
	    });

	    // If there are any new models added due to filtering we need to add child views,
	    // so render as normal.
	    if (anyModelsAdded) {
	      this.render();
	    } else {
	      (function () {

	        var filteredOutModels = [];

	        // Get the DOM nodes in the same order as the models and
	        // find the model that were children before but aren't in this new order.
	        var elsToReorder = children.reduce(function (viewEls, view) {
	          var index = _.indexOf(models, view.model);

	          if (index === -1) {
	            filteredOutModels.push(view.model);
	            return viewEls;
	          }

	          view._index = index;

	          viewEls[index] = view.el;

	          return viewEls;
	        }, new Array(models.length));

	        _this3.triggerMethod('before:reorder', _this3);

	        // Since append moves elements that are already in the DOM, appending the elements
	        // will effectively reorder them.
	        _this3._appendReorderedChildren(elsToReorder);

	        // remove any views that have been filtered out
	        _this3._removeChildModels(filteredOutModels);

	        _this3.triggerMethod('reorder', _this3);
	      })();
	    }
	    return this;
	  },


	  // Render view after sorting. Override this method to change how the view renders
	  // after a `sort` on the collection.
	  resortView: function resortView() {
	    if (this.reorderOnSort) {
	      this.reorder();
	    } else {
	      this._renderChildren();
	    }
	    return this;
	  },


	  // Internal method. This checks for any changes in the order of the collection.
	  // If the index of any view doesn't match, it will render.
	  _sortViews: function _sortViews() {
	    var _this4 = this;

	    var models = this._filteredSortedModels();

	    // check for any changes in sort order of views
	    var orderChanged = _.find(models, function (item, index) {
	      var view = _this4.children.findByModel(item);
	      return !view || view._index !== index;
	    });

	    if (orderChanged) {
	      this.resortView();
	    }
	  },


	  // Internal reference to what index a `emptyView` is.
	  _emptyViewIndex: -1,

	  // Internal method. Separated so that CompositeView can append to the childViewContainer
	  // if necessary
	  _appendReorderedChildren: function _appendReorderedChildren(children) {
	    this.appendChildren(this.el, children);
	  },


	  // Internal method. Separated so that CompositeView can have more control over events
	  // being triggered, around the rendering process
	  _renderChildren: function _renderChildren() {
	    if (this._isRendered) {
	      this._destroyEmptyView();
	      this._destroyChildren();
	    }

	    var models = this._filteredSortedModels();
	    if (this.isEmpty({ processedModels: models })) {
	      this._showEmptyView();
	    } else {
	      this.triggerMethod('before:render:children', this);
	      this._startBuffering();
	      this._showCollection(models);
	      this._endBuffering();
	      this.triggerMethod('render:children', this);
	    }
	  },
	  _createView: function _createView(model, index) {
	    var ChildView = this._getChildView(model);
	    var childViewOptions = this._getChildViewOptions(model, index);
	    var view = this.buildChildView(model, ChildView, childViewOptions);
	    return view;
	  },
	  _setupChildView: function _setupChildView(view, index) {
	    monitorViewEvents(view);

	    // set up the child view event forwarding
	    this._proxyChildViewEvents(view);

	    if (this.sort) {
	      view._index = index;
	    }
	  },


	  // Internal method to loop through collection and show each child view.
	  _showCollection: function _showCollection(models) {
	    _.each(models, _.bind(this._addChild, this));
	    this.children._updateLength();
	  },


	  // Allow the collection to be sorted by a custom view comparator
	  _filteredSortedModels: function _filteredSortedModels(addedAt) {
	    if (!this.collection || !this.collection.length) {
	      return [];
	    }

	    var viewComparator = this.getViewComparator();
	    var models = this.collection.models;
	    addedAt = Math.min(Math.max(addedAt, 0), models.length - 1);

	    if (viewComparator) {
	      var addedModel = void 0;
	      // Preserve `at` location, even for a sorted view
	      if (addedAt) {
	        addedModel = models[addedAt];
	        models = models.slice(0, addedAt).concat(models.slice(addedAt + 1));
	      }
	      models = this._sortModelsBy(models, viewComparator);
	      if (addedModel) {
	        models.splice(addedAt, 0, addedModel);
	      }
	    }

	    // Filter after sorting in case the filter uses the index
	    models = this._filterModels(models);

	    return models;
	  },
	  getViewComparator: function getViewComparator() {
	    return this.viewComparator;
	  },


	  // Filter an array of models, if a filter exists
	  _filterModels: function _filterModels(models) {
	    var _this5 = this;

	    if (this.filter) {
	      models = _.filter(models, function (model, index) {
	        return _this5._shouldAddChild(model, index);
	      });
	    }
	    return models;
	  },
	  _sortModelsBy: function _sortModelsBy(models, comparator) {
	    if (typeof comparator === 'string') {
	      return _.sortBy(models, function (model) {
	        return model.get(comparator);
	      });
	    } else if (comparator.length === 1) {
	      return _.sortBy(models, _.bind(comparator, this));
	    } else {
	      return _.clone(models).sort(_.bind(comparator, this));
	    }
	  },


	  // Internal method to show an empty view in place of a collection of child views,
	  // when the collection is empty
	  _showEmptyView: function _showEmptyView() {
	    var EmptyView = this._getEmptyView();

	    if (EmptyView && !this._showingEmptyView) {
	      this._showingEmptyView = true;

	      var model = new Backbone.Model();
	      var emptyViewOptions = this.emptyViewOptions || this.childViewOptions;
	      if (_.isFunction(emptyViewOptions)) {
	        emptyViewOptions = emptyViewOptions.call(this, model, this._emptyViewIndex);
	      }

	      var view = this.buildChildView(model, EmptyView, emptyViewOptions);

	      this.triggerMethod('before:render:empty', this, view);
	      this.addChildView(view, 0);
	      this.triggerMethod('render:empty', this, view);
	    }
	  },


	  // Internal method to destroy an existing emptyView instance if one exists. Called when
	  // a collection view has been rendered empty, and then a child is added to the collection.
	  _destroyEmptyView: function _destroyEmptyView() {
	    if (this._showingEmptyView) {
	      this.triggerMethod('before:remove:empty', this);

	      this._destroyChildren();
	      delete this._showingEmptyView;

	      this.triggerMethod('remove:empty', this);
	    }
	  },


	  // Retrieve the empty view class
	  _getEmptyView: function _getEmptyView() {
	    var emptyView = this.emptyView;

	    if (!emptyView) {
	      return;
	    }

	    return this._getView(emptyView);
	  },


	  // Retrieve the `childView` class
	  // The `childView` property can be either a view class or a function that
	  // returns a view class. If it is a function, it will receive the model that
	  // will be passed to the view instance (created from the returned view class)
	  _getChildView: function _getChildView(child) {
	    var childView = this.childView;

	    if (!childView) {
	      throw new MarionetteError({
	        name: 'NoChildViewError',
	        message: 'A "childView" must be specified'
	      });
	    }

	    childView = this._getView(childView, child);

	    if (!childView) {
	      throw new MarionetteError({
	        name: 'InvalidChildViewError',
	        message: '"childView" must be a view class or a function that returns a view class'
	      });
	    }

	    return childView;
	  },


	  // First check if the `view` is a view class (the common case)
	  // Then check if it's a function (which we assume that returns a view class)
	  _getView: function _getView(view, child) {
	    if (view.prototype instanceof Backbone.View || view === Backbone.View) {
	      return view;
	    } else if (_.isFunction(view)) {
	      return view.call(this, child);
	    }
	  },


	  // Internal method for building and adding a child view
	  _addChild: function _addChild(child, index) {
	    var view = this._createView(child, index);
	    this.addChildView(view, index);

	    return view;
	  },
	  _getChildViewOptions: function _getChildViewOptions(child, index) {
	    if (_.isFunction(this.childViewOptions)) {
	      return this.childViewOptions(child, index);
	    }

	    return this.childViewOptions;
	  },


	  // Render the child's view and add it to the HTML for the collection view at a given index.
	  // This will also update the indices of later views in the collection in order to keep the
	  // children in sync with the collection.
	  addChildView: function addChildView(view, index) {
	    this.triggerMethod('before:add:child', this, view);
	    this._setupChildView(view, index);

	    // Store the child view itself so we can properly remove and/or destroy it later
	    if (this._isBuffering) {
	      // Add to children, but don't update children's length.
	      this.children._add(view);
	    } else {
	      // increment indices of views after this one
	      this._updateIndices(view, true);
	      this.children.add(view);
	    }

	    this._renderView(view);

	    this._attachView(view, index);

	    this.triggerMethod('add:child', this, view);

	    return view;
	  },


	  // Internal method. This decrements or increments the indices of views after the added/removed
	  // view to keep in sync with the collection.
	  _updateIndices: function _updateIndices(views, increment) {
	    if (!this.sort) {
	      return;
	    }

	    if (!increment) {
	      _.each(_.sortBy(this.children._views, '_index'), function (view, index) {
	        view._index = index;
	      });

	      return;
	    }

	    var view = _.isArray(views) ? _.max(views, '_index') : views;

	    if (_.isObject(view)) {
	      // update the indexes of views after this one
	      this.children.each(function (laterView) {
	        if (laterView._index >= view._index) {
	          laterView._index += 1;
	        }
	      });
	    }
	  },
	  _renderView: function _renderView(view) {
	    if (view._isRendered) {
	      return;
	    }

	    if (!view.supportsRenderLifecycle) {
	      triggerMethodOn(view, 'before:render', view);
	    }

	    view.render();

	    if (!view.supportsRenderLifecycle) {
	      view._isRendered = true;
	      triggerMethodOn(view, 'render', view);
	    }
	  },
	  _attachView: function _attachView(view, index) {
	    // Only trigger attach if already attached and not buffering,
	    // otherwise _endBuffering() or Region#show() handles this.
	    var shouldTriggerAttach = !view._isAttached && !this._isBuffering && this._isAttached;

	    if (shouldTriggerAttach) {
	      triggerMethodOn(view, 'before:attach', view);
	    }

	    this.attachHtml(this, view, index);

	    if (shouldTriggerAttach) {
	      view._isAttached = true;
	      triggerMethodOn(view, 'attach', view);
	    }
	  },


	  // Build a `childView` for a model in the collection.
	  buildChildView: function buildChildView(child, ChildViewClass, childViewOptions) {
	    var options = _.extend({ model: child }, childViewOptions);
	    return new ChildViewClass(options);
	  },


	  // Remove the child view and destroy it. This function also updates the indices of later views
	  // in the collection in order to keep the children in sync with the collection.
	  removeChildView: function removeChildView(view) {
	    if (!view || view._isDestroyed) {
	      return view;
	    }

	    this._removeChildView(view);
	    this.children._updateLength();
	    // decrement the index of views after this one
	    this._updateIndices(view, false);
	    return view;
	  },


	  // check if the collection is empty or optionally whether an array of pre-processed models is empty
	  isEmpty: function isEmpty(options) {
	    var models = void 0;
	    if (_.result(options, 'processedModels')) {
	      models = options.processedModels;
	    } else {
	      models = this.collection ? this.collection.models : [];
	      models = this._filterModels(models);
	    }
	    return models.length === 0;
	  },


	  // You might need to override this if you've overridden attachHtml
	  attachBuffer: function attachBuffer(collectionView, buffer) {
	    this.appendChildren(collectionView.el, buffer);
	  },


	  // Create a fragment buffer from the currently buffered children
	  _createBuffer: function _createBuffer() {
	    var _this6 = this;

	    var elBuffer = this.createBuffer();
	    _.each(this._bufferedChildren, function (b) {
	      _this6.appendChildren(elBuffer, b.el);
	    });
	    return elBuffer;
	  },


	  // Append the HTML to the collection's `el`. Override this method to do something other
	  // than `.append`.
	  attachHtml: function attachHtml(collectionView, childView, index) {
	    if (collectionView._isBuffering) {
	      // buffering happens on reset events and initial renders
	      // in order to reduce the number of inserts into the
	      // document, which are expensive.
	      collectionView._bufferedChildren.splice(index, 0, childView);
	    } else {
	      // If we've already rendered the main collection, append
	      // the new child into the correct order if we need to. Otherwise
	      // append to the end.
	      if (!collectionView._insertBefore(childView, index)) {
	        collectionView._insertAfter(childView);
	      }
	    }
	  },


	  // Internal method. Check whether we need to insert the view into the correct position.
	  _insertBefore: function _insertBefore(childView, index) {
	    var currentView = void 0;
	    var findPosition = this.sort && index < this.children.length - 1;
	    if (findPosition) {
	      // Find the view after this one
	      currentView = this.children.find(function (view) {
	        return view._index === index + 1;
	      });
	    }

	    if (currentView) {
	      this.beforeEl(currentView.el, childView.el);
	      return true;
	    }

	    return false;
	  },


	  // Internal method. Append a view to the end of the $el
	  _insertAfter: function _insertAfter(childView) {
	    this.appendChildren(this.el, childView.el);
	  },


	  // Internal method to set up the `children` object for storing all of the child views
	  _initChildViewStorage: function _initChildViewStorage() {
	    this.children = new Container();
	  },


	  // called by ViewMixin destroy
	  _removeChildren: function _removeChildren() {
	    this._destroyChildren();
	  },


	  // Destroy the child views that this collection view is holding on to, if any
	  _destroyChildren: function _destroyChildren(options) {
	    if (!this.children.length) {
	      return;
	    }

	    this.triggerMethod('before:destroy:children', this);
	    this.children.each(_.bind(this._removeChildView, this));
	    this.children._updateLength();
	    this.triggerMethod('destroy:children', this);
	  },


	  // Return true if the given child should be shown. Return false otherwise.
	  // The filter will be passed (child, index, collection), where
	  //  'child' is the given model
	  //  'index' is the index of that model in the collection
	  //  'collection' is the collection referenced by this CollectionView
	  _shouldAddChild: function _shouldAddChild(child, index) {
	    var filter = this.filter;
	    return !_.isFunction(filter) || filter.call(this, child, index, this.collection);
	  }
	});

	_.extend(CollectionView.prototype, ViewMixin);

	// Composite View
	// --------------

	var ClassOptions$4 = ['childViewContainer', 'template', 'templateContext'];

	// Used for rendering a branch-leaf, hierarchical structure.
	// Extends directly from CollectionView
	// @deprecated
	var CompositeView = CollectionView.extend({

	  // Setting up the inheritance chain which allows changes to
	  // Marionette.CollectionView.prototype.constructor which allows overriding
	  // option to pass '{sort: false}' to prevent the CompositeView from
	  // maintaining the sorted order of the collection.
	  // This will fallback onto appending childView's to the end.
	  constructor: function constructor(options) {
	    deprecate('CompositeView is deprecated. Convert to View at your earliest convenience');

	    this.mergeOptions(options, ClassOptions$4);

	    CollectionView.prototype.constructor.apply(this, arguments);
	  },


	  // Configured the initial events that the composite view
	  // binds to. Override this method to prevent the initial
	  // events, or to add your own initial events.
	  _initialEvents: function _initialEvents() {

	    // Bind only after composite view is rendered to avoid adding child views
	    // to nonexistent childViewContainer

	    if (this.collection) {
	      this.listenTo(this.collection, 'add', this._onCollectionAdd);
	      this.listenTo(this.collection, 'update', this._onCollectionUpdate);
	      this.listenTo(this.collection, 'reset', this.renderChildren);

	      if (this.sort) {
	        this.listenTo(this.collection, 'sort', this._sortViews);
	      }
	    }
	  },


	  // Retrieve the `childView` to be used when rendering each of
	  // the items in the collection. The default is to return
	  // `this.childView` or Marionette.CompositeView if no `childView`
	  // has been defined. As happens in CollectionView, `childView` can
	  // be a function (which should return a view class).
	  _getChildView: function _getChildView(child) {
	    var childView = this.childView;

	    // for CompositeView, if `childView` is not specified, we'll get the same
	    // composite view class rendered for each child in the collection
	    // then check if the `childView` is a view class (the common case)
	    // finally check if it's a function (which we assume that returns a view class)
	    if (!childView) {
	      return this.constructor;
	    }

	    childView = this._getView(childView, child);

	    if (!childView) {
	      throw new MarionetteError({
	        name: 'InvalidChildViewError',
	        message: '"childView" must be a view class or a function that returns a view class'
	      });
	    }

	    return childView;
	  },


	  // Return the serialized model
	  serializeData: function serializeData() {
	    return this.serializeModel();
	  },


	  // Renders the model and the collection.
	  render: function render() {
	    if (this._isDestroyed) {
	      return this;
	    }
	    this._isRendering = true;
	    this.resetChildViewContainer();

	    this.triggerMethod('before:render', this);

	    this._renderTemplate();
	    this.bindUIElements();
	    this.renderChildren();

	    this._isRendering = false;
	    this._isRendered = true;
	    this.triggerMethod('render', this);
	    return this;
	  },
	  renderChildren: function renderChildren() {
	    if (this._isRendered || this._isRendering) {
	      CollectionView.prototype._renderChildren.call(this);
	    }
	  },


	  // You might need to override this if you've overridden attachHtml
	  attachBuffer: function attachBuffer(compositeView, buffer) {
	    var $container = this.getChildViewContainer(compositeView);
	    this.appendChildren($container, buffer);
	  },


	  // Internal method. Append a view to the end of the $el.
	  // Overidden from CollectionView to ensure view is appended to
	  // childViewContainer
	  _insertAfter: function _insertAfter(childView) {
	    var $container = this.getChildViewContainer(this, childView);
	    this.appendChildren($container, childView.el);
	  },


	  // Internal method. Append reordered childView'.
	  // Overidden from CollectionView to ensure reordered views
	  // are appended to childViewContainer
	  _appendReorderedChildren: function _appendReorderedChildren(children) {
	    var $container = this.getChildViewContainer(this);
	    this.appendChildren($container, children);
	  },


	  // Internal method to ensure an `$childViewContainer` exists, for the
	  // `attachHtml` method to use.
	  getChildViewContainer: function getChildViewContainer(containerView, childView) {
	    if (!!containerView.$childViewContainer) {
	      return containerView.$childViewContainer;
	    }

	    var container = void 0;
	    var childViewContainer = containerView.childViewContainer;
	    if (childViewContainer) {

	      var selector = _.result(containerView, 'childViewContainer');

	      if (selector.charAt(0) === '@' && containerView.ui) {
	        container = containerView.ui[selector.substr(4)];
	      } else {
	        container = this.findEls(selector, containerView.$el);
	      }

	      if (container.length <= 0) {
	        throw new MarionetteError({
	          name: 'ChildViewContainerMissingError',
	          message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
	        });
	      }
	    } else {
	      container = containerView.$el;
	    }

	    containerView.$childViewContainer = container;
	    return container;
	  },


	  // Internal method to reset the `$childViewContainer` on render
	  resetChildViewContainer: function resetChildViewContainer() {
	    if (this.$childViewContainer) {
	      this.$childViewContainer = undefined;
	    }
	  }
	});

	// To prevent duplication but allow the best View organization
	// Certain View methods are mixed directly into the deprecated CompositeView
	var MixinFromView = _.pick(View.prototype, 'serializeModel', 'getTemplate', '_renderTemplate', 'mixinTemplateContext', 'attachElContent');
	_.extend(CompositeView.prototype, MixinFromView);

	// Behavior
	// --------

	// A Behavior is an isolated set of DOM /
	// user interactions that can be mixed into any View.
	// Behaviors allow you to blackbox View specific interactions
	// into portable logical chunks, keeping your views simple and your code DRY.

	var ClassOptions$5 = ['collectionEvents', 'events', 'modelEvents', 'triggers', 'ui'];

	var Behavior = MarionetteObject.extend({
	  cidPrefix: 'mnb',

	  constructor: function constructor(options, view) {
	    // Setup reference to the view.
	    // this comes in handle when a behavior
	    // wants to directly talk up the chain
	    // to the view.
	    this.view = view;
	    this.defaults = _.clone(_.result(this, 'defaults', {}));
	    this._setOptions(this.defaults, options);
	    this.mergeOptions(this.options, ClassOptions$5);

	    // Construct an internal UI hash using
	    // the behaviors UI hash and then the view UI hash.
	    // This allows the user to use UI hash elements
	    // defined in the parent view as well as those
	    // defined in the given behavior.
	    // This order will help the reuse and share of a behavior
	    // between multiple views, while letting a view override a
	    // selector under an UI key.
	    this.ui = _.extend({}, _.result(this, 'ui'), _.result(view, 'ui'));

	    MarionetteObject.apply(this, arguments);
	  },


	  // proxy behavior $ method to the view
	  // this is useful for doing jquery DOM lookups
	  // scoped to behaviors view.
	  $: function $() {
	    return this.view.$.apply(this.view, arguments);
	  },


	  // Stops the behavior from listening to events.
	  // Overrides Object#destroy to prevent additional events from being triggered.
	  destroy: function destroy() {
	    this.stopListening();

	    return this;
	  },
	  proxyViewProperties: function proxyViewProperties() {
	    this.$el = this.view.$el;
	    this.el = this.view.el;

	    return this;
	  },
	  bindUIElements: function bindUIElements() {
	    this._bindUIElements();

	    return this;
	  },
	  unbindUIElements: function unbindUIElements() {
	    this._unbindUIElements();

	    return this;
	  },
	  getUI: function getUI(name) {
	    return this._getUI(name);
	  },


	  // Handle `modelEvents`, and `collectionEvents` configuration
	  delegateEntityEvents: function delegateEntityEvents() {
	    this._delegateEntityEvents(this.view.model, this.view.collection);

	    return this;
	  },
	  undelegateEntityEvents: function undelegateEntityEvents() {
	    this._undelegateEntityEvents(this.view.model, this.view.collection);

	    return this;
	  },
	  getEvents: function getEvents() {
	    var _this = this;

	    // Normalize behavior events hash to allow
	    // a user to use the @ui. syntax.
	    var behaviorEvents = this.normalizeUIKeys(_.result(this, 'events'));

	    // binds the handler to the behavior and builds a unique eventName
	    return _.reduce(behaviorEvents, function (events, behaviorHandler, key) {
	      if (!_.isFunction(behaviorHandler)) {
	        behaviorHandler = _this[behaviorHandler];
	      }
	      if (!behaviorHandler) {
	        return;
	      }
	      key = getUniqueEventName(key);
	      events[key] = _.bind(behaviorHandler, _this);
	      return events;
	    }, {});
	  },


	  // Internal method to build all trigger handlers for a given behavior
	  getTriggers: function getTriggers() {
	    if (!this.triggers) {
	      return;
	    }

	    // Normalize behavior triggers hash to allow
	    // a user to use the @ui. syntax.
	    var behaviorTriggers = this.normalizeUIKeys(_.result(this, 'triggers'));

	    return this._getViewTriggers(this.view, behaviorTriggers);
	  }
	});

	_.extend(Behavior.prototype, DelegateEntityEventsMixin, TriggersMixin, UIMixin);

	// Application
	// -----------
	var ClassOptions$6 = ['region', 'regionClass'];

	// A container for a Marionette application.
	var Application = MarionetteObject.extend({
	  cidPrefix: 'mna',

	  constructor: function constructor(options) {
	    this._setOptions(options);

	    this.mergeOptions(options, ClassOptions$6);

	    this._initRegion();

	    MarionetteObject.prototype.constructor.apply(this, arguments);
	  },


	  regionClass: Region,

	  _initRegion: function _initRegion() {
	    var region = this.region;

	    if (!region) {
	      return;
	    }

	    var defaults = {
	      regionClass: this.regionClass
	    };

	    this._region = buildRegion(region, defaults);
	  },
	  getRegion: function getRegion() {
	    return this._region;
	  },
	  showView: function showView(view) {
	    var region = this.getRegion();

	    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
	      args[_key - 1] = arguments[_key];
	    }

	    return region.show.apply(region, [view].concat(args));
	  },
	  getView: function getView() {
	    return this.getRegion().currentView;
	  },


	  // kick off all of the application's processes.
	  start: function start(options) {
	    this.triggerMethod('before:start', this, options);
	    this.triggerMethod('start', this, options);
	    return this;
	  }
	});

	// App Router
	// ----------

	// Reduce the boilerplate code of handling route events
	// and then calling a single method on another object,
	// called a controller.
	// Have your routers configured to call the method on
	// your controller, directly.
	//
	// Configure an AppRouter with `appRoutes`.
	//
	// App routers can only take one `controller` object.
	// It is recommended that you divide your controller
	// objects in to smaller pieces of related functionality
	// and have multiple routers / controllers, instead of
	// just one giant router and controller.
	//
	// You can also add standard routes to an AppRouter.

	var ClassOptions$7 = ['appRoutes', 'controller'];

	var AppRouter = Backbone.Router.extend({
	  constructor: function constructor(options) {
	    this._setOptions(options);

	    this.mergeOptions(options, ClassOptions$7);

	    Backbone.Router.apply(this, arguments);

	    var appRoutes = this.appRoutes;
	    var controller = this._getController();
	    this.processAppRoutes(controller, appRoutes);
	    this.on('route', this._processOnRoute, this);
	  },


	  // Similar to route method on a Backbone Router but
	  // method is called on the controller
	  appRoute: function appRoute(route, methodName) {
	    var controller = this._getController();
	    this._addAppRoute(controller, route, methodName);
	    return this;
	  },


	  // process the route event and trigger the onRoute
	  // method call, if it exists
	  _processOnRoute: function _processOnRoute(routeName, routeArgs) {
	    // make sure an onRoute before trying to call it
	    if (_.isFunction(this.onRoute)) {
	      // find the path that matches the current route
	      var routePath = _.invert(this.appRoutes)[routeName];
	      this.onRoute(routeName, routePath, routeArgs);
	    }
	  },


	  // Internal method to process the `appRoutes` for the
	  // router, and turn them in to routes that trigger the
	  // specified method on the specified `controller`.
	  processAppRoutes: function processAppRoutes(controller, appRoutes) {
	    var _this = this;

	    if (!appRoutes) {
	      return this;
	    }

	    var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes

	    _.each(routeNames, function (route) {
	      _this._addAppRoute(controller, route, appRoutes[route]);
	    });

	    return this;
	  },
	  _getController: function _getController() {
	    return this.controller;
	  },
	  _addAppRoute: function _addAppRoute(controller, route, methodName) {
	    var method = controller[methodName];

	    if (!method) {
	      throw new MarionetteError('Method "' + methodName + '" was not found on the controller');
	    }

	    this.route(route, methodName, _.bind(method, controller));
	  },


	  triggerMethod: triggerMethod$1
	});

	_.extend(AppRouter.prototype, CommonMixin);

	// Placeholder method to be extended by the user.
	// The method should define the object that stores the behaviors.
	// i.e.
	//
	// ```js
	// Marionette.Behaviors.behaviorsLookup: function() {
	//   return App.Behaviors
	// }
	// ```
	function behaviorsLookup() {
	  throw new MarionetteError({
	    message: 'You must define where your behaviors are stored.',
	    url: 'marionette.behaviors.md#behaviorslookup'
	  });
	}

	var previousMarionette = Backbone.Marionette;
	var Marionette = Backbone.Marionette = {};

	// This allows you to run multiple instances of Marionette on the same
	// webapp. After loading the new version, call `noConflict()` to
	// get a reference to it. At the same time the old version will be
	// returned to Backbone.Marionette.
	Marionette.noConflict = function () {
	  Backbone.Marionette = previousMarionette;
	  return this;
	};

	// Utilities
	Marionette.bindEvents = proxy(bindEvents);
	Marionette.unbindEvents = proxy(unbindEvents);
	Marionette.bindRequests = proxy(bindRequests);
	Marionette.unbindRequests = proxy(unbindRequests);
	Marionette.mergeOptions = proxy(mergeOptions);
	Marionette.getOption = proxy(getOption);
	Marionette.normalizeMethods = proxy(normalizeMethods);
	Marionette.extend = extend;
	Marionette.isNodeAttached = isNodeAttached;
	Marionette.deprecate = deprecate;
	Marionette.triggerMethod = proxy(triggerMethod$1);
	Marionette.triggerMethodOn = triggerMethodOn;
	Marionette.isEnabled = isEnabled;
	Marionette.setEnabled = setEnabled;
	Marionette.monitorViewEvents = monitorViewEvents;

	Marionette.Behaviors = {};
	Marionette.Behaviors.behaviorsLookup = behaviorsLookup;

	// Classes
	Marionette.Application = Application;
	Marionette.AppRouter = AppRouter;
	Marionette.Renderer = Renderer;
	Marionette.TemplateCache = TemplateCache;
	Marionette.View = View;
	Marionette.CollectionView = CollectionView;
	Marionette.CompositeView = CompositeView;
	Marionette.Behavior = Behavior;
	Marionette.Region = Region;
	Marionette.Error = MarionetteError;
	Marionette.Object = MarionetteObject;

	// Configuration
	Marionette.DEV_MODE = false;
	Marionette.FEATURES = FEATURES;
	Marionette.VERSION = version;

	return Marionette;

	})));

	//# sourceMappingURL=backbone.marionette.js.map


/***/ },

/***/ 572:
/***/ function(module, exports, __webpack_require__) {

	// Backbone.Radio v2.0.0

	(function (global, factory) {
	   true ? module.exports = factory(__webpack_require__(280), __webpack_require__(544)) :
	  typeof define === 'function' && define.amd ? define(['underscore', 'backbone'], factory) :
	  (global.Backbone = global.Backbone || {}, global.Backbone.Radio = factory(global._,global.Backbone));
	}(this, function (_,Backbone) { 'use strict';

	  _ = 'default' in _ ? _['default'] : _;
	  Backbone = 'default' in Backbone ? Backbone['default'] : Backbone;

	  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
	    return typeof obj;
	  } : function (obj) {
	    return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj;
	  };

	  var previousRadio = Backbone.Radio;

	  var Radio = Backbone.Radio = {};

	  Radio.VERSION = '2.0.0';

	  // This allows you to run multiple instances of Radio on the same
	  // webapp. After loading the new version, call `noConflict()` to
	  // get a reference to it. At the same time the old version will be
	  // returned to Backbone.Radio.
	  Radio.noConflict = function () {
	    Backbone.Radio = previousRadio;
	    return this;
	  };

	  // Whether or not we're in DEBUG mode or not. DEBUG mode helps you
	  // get around the issues of lack of warnings when events are mis-typed.
	  Radio.DEBUG = false;

	  // Format debug text.
	  Radio._debugText = function (warning, eventName, channelName) {
	    return warning + (channelName ? ' on the ' + channelName + ' channel' : '') + ': "' + eventName + '"';
	  };

	  // This is the method that's called when an unregistered event was called.
	  // By default, it logs warning to the console. By overriding this you could
	  // make it throw an Error, for instance. This would make firing a nonexistent event
	  // have the same consequence as firing a nonexistent method on an Object.
	  Radio.debugLog = function (warning, eventName, channelName) {
	    if (Radio.DEBUG && console && console.warn) {
	      console.warn(Radio._debugText(warning, eventName, channelName));
	    }
	  };

	  var eventSplitter = /\s+/;

	  // An internal method used to handle Radio's method overloading for Requests.
	  // It's borrowed from Backbone.Events. It differs from Backbone's overload
	  // API (which is used in Backbone.Events) in that it doesn't support space-separated
	  // event names.
	  Radio._eventsApi = function (obj, action, name, rest) {
	    if (!name) {
	      return false;
	    }

	    var results = {};

	    // Handle event maps.
	    if ((typeof name === 'undefined' ? 'undefined' : _typeof(name)) === 'object') {
	      for (var key in name) {
	        var result = obj[action].apply(obj, [key, name[key]].concat(rest));
	        eventSplitter.test(key) ? _.extend(results, result) : results[key] = result;
	      }
	      return results;
	    }

	    // Handle space separated event names.
	    if (eventSplitter.test(name)) {
	      var names = name.split(eventSplitter);
	      for (var i = 0, l = names.length; i < l; i++) {
	        results[names[i]] = obj[action].apply(obj, [names[i]].concat(rest));
	      }
	      return results;
	    }

	    return false;
	  };

	  // An optimized way to execute callbacks.
	  Radio._callHandler = function (callback, context, args) {
	    var a1 = args[0],
	        a2 = args[1],
	        a3 = args[2];
	    switch (args.length) {
	      case 0:
	        return callback.call(context);
	      case 1:
	        return callback.call(context, a1);
	      case 2:
	        return callback.call(context, a1, a2);
	      case 3:
	        return callback.call(context, a1, a2, a3);
	      default:
	        return callback.apply(context, args);
	    }
	  };

	  // A helper used by `off` methods to the handler from the store
	  function removeHandler(store, name, callback, context) {
	    var event = store[name];
	    if ((!callback || callback === event.callback || callback === event.callback._callback) && (!context || context === event.context)) {
	      delete store[name];
	      return true;
	    }
	  }

	  function removeHandlers(store, name, callback, context) {
	    store || (store = {});
	    var names = name ? [name] : _.keys(store);
	    var matched = false;

	    for (var i = 0, length = names.length; i < length; i++) {
	      name = names[i];

	      // If there's no event by this name, log it and continue
	      // with the loop
	      if (!store[name]) {
	        continue;
	      }

	      if (removeHandler(store, name, callback, context)) {
	        matched = true;
	      }
	    }

	    return matched;
	  }

	  /*
	   * tune-in
	   * -------
	   * Get console logs of a channel's activity
	   *
	   */

	  var _logs = {};

	  // This is to produce an identical function in both tuneIn and tuneOut,
	  // so that Backbone.Events unregisters it.
	  function _partial(channelName) {
	    return _logs[channelName] || (_logs[channelName] = _.bind(Radio.log, Radio, channelName));
	  }

	  _.extend(Radio, {

	    // Log information about the channel and event
	    log: function log(channelName, eventName) {
	      if (typeof console === 'undefined') {
	        return;
	      }
	      var args = _.toArray(arguments).slice(2);
	      console.log('[' + channelName + '] "' + eventName + '"', args);
	    },

	    // Logs all events on this channel to the console. It sets an
	    // internal value on the channel telling it we're listening,
	    // then sets a listener on the Backbone.Events
	    tuneIn: function tuneIn(channelName) {
	      var channel = Radio.channel(channelName);
	      channel._tunedIn = true;
	      channel.on('all', _partial(channelName));
	      return this;
	    },

	    // Stop logging all of the activities on this channel to the console
	    tuneOut: function tuneOut(channelName) {
	      var channel = Radio.channel(channelName);
	      channel._tunedIn = false;
	      channel.off('all', _partial(channelName));
	      delete _logs[channelName];
	      return this;
	    }
	  });

	  /*
	   * Backbone.Radio.Requests
	   * -----------------------
	   * A messaging system for requesting data.
	   *
	   */

	  function makeCallback(callback) {
	    return _.isFunction(callback) ? callback : function () {
	      return callback;
	    };
	  }

	  Radio.Requests = {

	    // Make a request
	    request: function request(name) {
	      var args = _.toArray(arguments).slice(1);
	      var results = Radio._eventsApi(this, 'request', name, args);
	      if (results) {
	        return results;
	      }
	      var channelName = this.channelName;
	      var requests = this._requests;

	      // Check if we should log the request, and if so, do it
	      if (channelName && this._tunedIn) {
	        Radio.log.apply(this, [channelName, name].concat(args));
	      }

	      // If the request isn't handled, log it in DEBUG mode and exit
	      if (requests && (requests[name] || requests['default'])) {
	        var handler = requests[name] || requests['default'];
	        args = requests[name] ? args : arguments;
	        return Radio._callHandler(handler.callback, handler.context, args);
	      } else {
	        Radio.debugLog('An unhandled request was fired', name, channelName);
	      }
	    },

	    // Set up a handler for a request
	    reply: function reply(name, callback, context) {
	      if (Radio._eventsApi(this, 'reply', name, [callback, context])) {
	        return this;
	      }

	      this._requests || (this._requests = {});

	      if (this._requests[name]) {
	        Radio.debugLog('A request was overwritten', name, this.channelName);
	      }

	      this._requests[name] = {
	        callback: makeCallback(callback),
	        context: context || this
	      };

	      return this;
	    },

	    // Set up a handler that can only be requested once
	    replyOnce: function replyOnce(name, callback, context) {
	      if (Radio._eventsApi(this, 'replyOnce', name, [callback, context])) {
	        return this;
	      }

	      var self = this;

	      var once = _.once(function () {
	        self.stopReplying(name);
	        return makeCallback(callback).apply(this, arguments);
	      });

	      return this.reply(name, once, context);
	    },

	    // Remove handler(s)
	    stopReplying: function stopReplying(name, callback, context) {
	      if (Radio._eventsApi(this, 'stopReplying', name)) {
	        return this;
	      }

	      // Remove everything if there are no arguments passed
	      if (!name && !callback && !context) {
	        delete this._requests;
	      } else if (!removeHandlers(this._requests, name, callback, context)) {
	        Radio.debugLog('Attempted to remove the unregistered request', name, this.channelName);
	      }

	      return this;
	    }
	  };

	  /*
	   * Backbone.Radio.channel
	   * ----------------------
	   * Get a reference to a channel by name.
	   *
	   */

	  Radio._channels = {};

	  Radio.channel = function (channelName) {
	    if (!channelName) {
	      throw new Error('You must provide a name for the channel.');
	    }

	    if (Radio._channels[channelName]) {
	      return Radio._channels[channelName];
	    } else {
	      return Radio._channels[channelName] = new Radio.Channel(channelName);
	    }
	  };

	  /*
	   * Backbone.Radio.Channel
	   * ----------------------
	   * A Channel is an object that extends from Backbone.Events,
	   * and Radio.Requests.
	   *
	   */

	  Radio.Channel = function (channelName) {
	    this.channelName = channelName;
	  };

	  _.extend(Radio.Channel.prototype, Backbone.Events, Radio.Requests, {

	    // Remove all handlers from the messaging systems of this channel
	    reset: function reset() {
	      this.off();
	      this.stopListening();
	      this.stopReplying();
	      return this;
	    }
	  });

	  /*
	   * Top-level API
	   * -------------
	   * Supplies the 'top-level API' for working with Channels directly
	   * from Backbone.Radio.
	   *
	   */

	  var channel;
	  var args;
	  var systems = [Backbone.Events, Radio.Requests];
	  _.each(systems, function (system) {
	    _.each(system, function (method, methodName) {
	      Radio[methodName] = function (channelName) {
	        args = _.toArray(arguments).slice(1);
	        channel = this.channel(channelName);
	        return channel[methodName].apply(channel, args);
	      };
	    });
	  });

	  Radio.reset = function (channelName) {
	    var channels = !channelName ? this._channels : [this._channels[channelName]];
	    _.each(channels, function (channel) {
	      channel.reset();
	    });
	  };

	  return Radio;

	}));
	//# sourceMappingURL=./backbone.radio.js.map

/***/ },

/***/ 573:
/***/ function(module, exports) {

	Backbone.SuperModel = (function(_, Backbone){
	  var _triggerNestedChanges = function(path) {
	    for (var m = path.length - 1; m >= 0; m--) {
	      // splits the path into 2 different array
	      var p = path.slice(0, m); // the target path of the events
	      var q = path.slice(m); // an array of the remaining paths which will be used to form events

	      if (q.length == 1) {
	        // this case has been handled by the normal set
	        continue;
	      }

	      // identifies the target
	      p = p.join('.');
	      var target = this;
	      if (p.length > 0) {
	        target = this.get(p);
	      }

	      // based on the remaining paths, form an array of all possible events
	      // ['a', 'b', 'c'] results in 3 possible events a, a.b, a.b.c
	      var tmp = _.first(q);
	      var rest = _.rest(q);
	      var paths = [tmp];
	      for (var n = 0; n < rest.length; n++) {
	        tmp = [tmp, rest[n]].join('.');
	        paths.push(tmp);
	      }

	      for (var l = 0; l < paths.length; l++) {
	        var _p = paths[l];
	        var trigger = target.trigger;

	        if (trigger && _.isFunction(trigger)) {
	          trigger.call(target, 'change:'+_p, target, target.get(_p));
	        }
	      }               
	    }
	  };

	  var processKeyPath = function(keyPath) {
	    if (_.isString(keyPath)) {
	      keyPath = keyPath.split('.');
	    }
	    return keyPath;    
	  };

	  // http://stackoverflow.com/a/16190716/386378
	  var getObjectValue = function(obj, path, def){
	    path = processKeyPath(path);
	    
	    var len = path.length;
	    for(var i = 0; i < len; i++){
	        if(!obj || typeof obj !== 'object') return def;
	        obj = obj[path[i]];
	    }

	    if(obj === undefined) return def;
	    return obj;
	  };

	  // based on the concept of // http://stackoverflow.com/a/5484764/386378
	  // not recursively walk through the keyPath of obj
	  // when reaching the end call doThing
	  // and pass the last obj and last key
	  var walkObject = function(obj, keyPath, doThing) {
	    keyPath = processKeyPath(keyPath);

	    lastKeyIndex = keyPath.length-1;
	    for (var i = 0; i < lastKeyIndex; ++ i) {
	      key = keyPath[i];
	      if (!(key in obj))
	        obj[key] = {};
	      obj = obj[key];
	    }
	    doThing(obj, keyPath[lastKeyIndex]);
	  };

	  var setObjectValue = function(obj, keyPath, value) {
	    walkObject(obj, keyPath, function(destination, lastKey){
	      destination[lastKey] = value;
	    });
	  };

	  var deleteObjectKey = function(obj, keyPath) {
	    walkObject(obj, keyPath, function(destination, lastKey){
	      delete destination[lastKey];
	    });
	  };

	  var hasObjectKey = function(obj, keyPath) {
	    var hasKey = false;
	    walkObject(obj, keyPath, function(destination, lastKey){
	      hasKey = _.has(destination, lastKey);
	    });
	    return hasKey;
	  };

	  // recursively walk through a Backbone.Model model
	  // using keyPath
	  // when reaching the end, call doThing
	  // and pass the last model and last key
	  var walkNestedAttributes = function(model, keyPath, doThing) {
	    keyPath = processKeyPath(keyPath);

	    var first = _.first(keyPath);
	    var nestedModel = model.get(first);

	    if (nestedModel instanceof Backbone.Model) {
	      walkNestedAttributes(nestedModel, _.rest(keyPath), doThing);
	    }
	    doThing(model, keyPath);
	  };

	  var getRelation = function(obj, attr, value) {
	    var relation;

	    if (attr) {
	      var relations = _.result(obj, 'relations');
	      relation = relations[attr];
	    }

	    if (value && !relation) {
	      relation = Model;
	    }

	    // catch all the weird stuff
	    if (relation == void 0) {
	      relation = Model;
	    }

	    return relation;
	  };

	  var setupBackref = function(obj, instance, options) {
	    var name = _.result(obj, 'name');
	    // respect the attribute with the same name in relation
	    if (name && !instance[name]) {
	      instance[name] = obj;
	    }
	    return instance;
	  };

	  // a simple object is an object that does not come from "new"
	  var isSimpleObject = function(value) {
	    return value.constructor === Object;
	  };

	  var Model = Backbone.Model.extend({
	    relations: {},
	    unsafeAttributes: [],
	    name: null, // set name so that children can refer back to

	    _valueForCollection: function(value) {
	      if (_.isArray(value)) {
	        if (value.length >= 1) {
	          return _.isObject(value[0]);
	        }
	        return true;
	      }

	      return false;
	    },

	    _nestedSet: function(path, value, options) {
	      path = path.split('.');

	      var lastKeyIndex = path.length - 1;
	      var obj = this;
	      var previousObj = null;
	      var previousKey = null;

	      for (var i = 0; i < lastKeyIndex; ++i) {
	        var key = path[i];
	        
	        var check = obj.attributes[key];
	        if (!check) {
	          // initiate the relationship here
	          //var relation = Backbone.Model;
	          var relation = getRelation(obj, key, value);
	          var instance = new relation();
	          obj.attributes[key] = setupBackref(obj, instance, options);
	        }
	        obj = obj.attributes[key];
	      }

	      var finalPath = path[lastKeyIndex];

	      if (!_.isArray(value) && _.isObject(value) && isSimpleObject(value)) {
	        // special case when the object value is empty, just set it to an empty model
	        if (_.size(value) === 0) {
	          obj.attributes[finalPath] = new Model();
	        } else {
	          for (var j in value) {
	            var newPath = finalPath + '.' + j;
	            // let _nestedSet do its things
	            obj._nestedSet(newPath, value[j], options);
	          }
	        }
	      } else {
	        if (this._valueForCollection(value)) {
	          // here we need to initiate the collection manually
	          var _relation = getRelation(obj, finalPath, value);

	          if (_relation.prototype instanceof Backbone.Model) {
	            // if we dont have the Collection relation for this, use custom Collection
	            // because "value" should be used with a Collection
	            _relation = Collection;
	          }
	          var collection = new _relation(value);
	          collection = setupBackref(obj, collection, options);
	          obj.attributes[finalPath] = collection;
	        } else {
	          // prevent duplicated events due to "set"
	          if (path.length == 1) {
	            obj.attributes[finalPath] = value;
	          } else {
	            obj.set(finalPath, value, _.extend({skipNested: true, forceChange: true}, options));
	          }
	        }
	      }

	      if (!options.silent) {
	        _triggerNestedChanges.call(this, path);
	      }
	    },

	    _setChanging: function() {
	      this._previousAttributes = this.toJSON();
	      this.changed = {};
	    },

	    _triggerChanges: function(changes, options, changeValue) {
	      if (changes.length) this._pending = true;
	      for (var i = 0, l = changes.length; i < l; i++) {
	        if (!changeValue) {
	          changeValue = this.get(changes[i]);
	        }

	        // should only handle single attribute change event here
	        // change events for nested attributes should be handled by
	        // _triggerNestedChanges
	        if (changes[i].split('.').length == 1) {
	          this.trigger('change:' + changes[i], this, changeValue, options);
	        }
	      }
	    },

	    _setChange: function(attr, val, options) {
	      var currentValue = this.get(attr);
	      attr = attr.split('.');
	      if (!_.isEqual(currentValue, val) || options.forceChange) {
	        setObjectValue(this.changed, attr, val);
	        return true;
	      } else {
	        deleteObjectKey(this.changed, attr);
	        return false;
	      }
	    },

	    set: function(key, val, options) {
	      var attr, attrs, unset, changes, silent, changing, prev, current, skipNested;
	      if (key == null) return this;

	      // Handle both `"key", value` and `{key: value}` -style arguments.
	      if (typeof key === 'object') {
	        attrs = key;
	        options = val;
	      } else {
	        attrs = {};
	        attrs[key] = val;
	      }

	      options = options || {};

	      // Run validation.
	      // TODO: Need to work on this so that we can validate nested models
	      if (!this._validate(attrs, options)) return false;

	      // Extract attributes and options.
	      unset           = options.unset;
	      silent          = options.silent;
	      changes         = [];
	      changing        = this._changing;
	      skipNested      = options.skipNested;
	      this._changing  = true;

	      if (!changing) {
	        this._setChanging();
	      }

	      // Check for changes of `id`.
	      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];

	      // For each `set` attribute, update or delete the current value.
	      var unsetAttribute = function(destination, realKey){
	        delete destination.attributes[realKey];
	      };

	      for (attr in attrs) {
	        val = attrs[attr];
	        
	        if (this._setChange(attr, val, options)) {
	          changes.push(attr);
	        }

	        if (unset) {
	          walkNestedAttributes(this, attr, unsetAttribute);
	        } else {
	          if (skipNested) {
	            this.attributes[attr] = val;
	          } else {
	            this._nestedSet(attr, val, options);  
	          }
	        }
	      }

	      // Trigger all relevant attribute changes.
	      if (!silent) {
	        this._triggerChanges(changes, options);
	      }

	      // You might be wondering why there's a `while` loop here. Changes can
	      // be recursively nested within `"change"` events.
	      if (changing) return this;
	      if (!silent) {
	        while (this._pending) {
	          this._pending = false;
	          this.trigger('change', this, options);
	        }
	      }
	      this._pending = false;
	      this._changing = false;
	      return this;
	    },

	    get: function(attr) {
	      var nestedAttrs = attr ? attr.split('.') : [];

	      if (nestedAttrs.length > 1) {
	        var nestedAttr = this.attributes[_.first(nestedAttrs)];
	        if (!nestedAttr) {
	          return;
	        }

	        var rest = _.rest(nestedAttrs).join('.');

	        if (_.isFunction(nestedAttr.get)) {
	          return nestedAttr.get(rest);
	        }

	        return nestedAttr[rest];
	      }
	      return this.attributes[attr];
	    },

	    toJSON: function(options) {
	      options = options || {};
	      var unsafeAttributes = _.result(this, 'unsafeAttributes');

	      if (options.except) {
	        unsafeAttributes = _.union(unsafeAttributes, options.except);
	      }

	      var attributes = _.clone(this.attributes);
	      _.each(unsafeAttributes, function(attr){
	        delete attributes[attr];
	      });
	      
	      _.each(attributes, function(val, key){
	        if (val && _.isFunction(val.toJSON)) {
	          attributes[key] = val.toJSON();  
	        }
	      });

	      return attributes;
	    },

	    hasChanged: function(attr) {
	      if (attr == null) return !_.isEmpty(this.changed);
	      return hasObjectKey(this.changed, attr);
	    },

	    previous: function(attr) {
	      if (attr == null || !this._previousAttributes) return null;
	      return getObjectValue(this._previousAttributes, attr);
	    },

	    // Clear all attributes on the model, firing `"change"`.
	    clear: function(options) {
	      var attrs = {};
	      this.id = void 0;
	      
	      for (var key in this.attributes) {
	        var val = this.attributes[key];
	        if (val instanceof Backbone.Model) {
	          val.clear();
	        } else if (val instanceof Backbone.Collection) {
	          val.reset();
	        } else {
	          this.unset(key);
	        }
	      }
	      return this;
	    }
	  });

	  var Collection = Backbone.Collection.extend({
	    model: Model
	  });

	  return Model;
	})(_, Backbone);


	/*** EXPORTS FROM exports-loader ***/
	module.exports = Backbone.SuperModel;

/***/ },

/***/ 574:
/***/ function(module, exports, __webpack_require__) {

	/**
	 * interact.js v1.2.8
	 *
	 * Copyright (c) 2012-2015 Taye Adeyemi <dev@taye.me>
	 * Open source under the MIT License.
	 * https://raw.github.com/taye/interact.js/master/LICENSE
	 */
	(function (realWindow) {
	    'use strict';

	    // return early if there's no window to work with (eg. Node.js)
	    if (!realWindow) { return; }

	    var // get wrapped window if using Shadow DOM polyfill
	        window = (function () {
	            // create a TextNode
	            var el = realWindow.document.createTextNode('');

	            // check if it's wrapped by a polyfill
	            if (el.ownerDocument !== realWindow.document
	                && typeof realWindow.wrap === 'function'
	                && realWindow.wrap(el) === el) {
	                // return wrapped window
	                return realWindow.wrap(realWindow);
	            }

	            // no Shadow DOM polyfil or native implementation
	            return realWindow;
	        }()),

	        document           = window.document,
	        DocumentFragment   = window.DocumentFragment   || blank,
	        SVGElement         = window.SVGElement         || blank,
	        SVGSVGElement      = window.SVGSVGElement      || blank,
	        SVGElementInstance = window.SVGElementInstance || blank,
	        HTMLElement        = window.HTMLElement        || window.Element,

	        PointerEvent = (window.PointerEvent || window.MSPointerEvent),
	        pEventTypes,

	        hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); },

	        tmpXY = {},     // reduce object creation in getXY()

	        documents       = [],   // all documents being listened to

	        interactables   = [],   // all set interactables
	        interactions    = [],   // all interactions

	        dynamicDrop     = false,

	        // {
	        //      type: {
	        //          selectors: ['selector', ...],
	        //          contexts : [document, ...],
	        //          listeners: [[listener, useCapture], ...]
	        //      }
	        //  }
	        delegatedEvents = {},

	        defaultOptions = {
	            base: {
	                accept        : null,
	                actionChecker : null,
	                styleCursor   : true,
	                preventDefault: 'auto',
	                origin        : { x: 0, y: 0 },
	                deltaSource   : 'page',
	                allowFrom     : null,
	                ignoreFrom    : null,
	                _context      : document,
	                dropChecker   : null
	            },

	            drag: {
	                enabled: false,
	                manualStart: true,
	                max: Infinity,
	                maxPerElement: 1,

	                snap: null,
	                restrict: null,
	                inertia: null,
	                autoScroll: null,

	                axis: 'xy'
	            },

	            drop: {
	                enabled: false,
	                accept: null,
	                overlap: 'pointer'
	            },

	            resize: {
	                enabled: false,
	                manualStart: false,
	                max: Infinity,
	                maxPerElement: 1,

	                snap: null,
	                restrict: null,
	                inertia: null,
	                autoScroll: null,

	                square: false,
	                preserveAspectRatio: false,
	                axis: 'xy',

	                // use default margin
	                margin: NaN,

	                // object with props left, right, top, bottom which are
	                // true/false values to resize when the pointer is over that edge,
	                // CSS selectors to match the handles for each direction
	                // or the Elements for each handle
	                edges: null,

	                // a value of 'none' will limit the resize rect to a minimum of 0x0
	                // 'negate' will alow the rect to have negative width/height
	                // 'reposition' will keep the width/height positive by swapping
	                // the top and bottom edges and/or swapping the left and right edges
	                invert: 'none'
	            },

	            gesture: {
	                manualStart: false,
	                enabled: false,
	                max: Infinity,
	                maxPerElement: 1,

	                restrict: null
	            },

	            perAction: {
	                manualStart: false,
	                max: Infinity,
	                maxPerElement: 1,

	                snap: {
	                    enabled     : false,
	                    endOnly     : false,
	                    range       : Infinity,
	                    targets     : null,
	                    offsets     : null,

	                    relativePoints: null
	                },

	                restrict: {
	                    enabled: false,
	                    endOnly: false
	                },

	                autoScroll: {
	                    enabled     : false,
	                    container   : null,     // the item that is scrolled (Window or HTMLElement)
	                    margin      : 60,
	                    speed       : 300       // the scroll speed in pixels per second
	                },

	                inertia: {
	                    enabled          : false,
	                    resistance       : 10,    // the lambda in exponential decay
	                    minSpeed         : 100,   // target speed must be above this for inertia to start
	                    endSpeed         : 10,    // the speed at which inertia is slow enough to stop
	                    allowResume      : true,  // allow resuming an action in inertia phase
	                    zeroResumeDelta  : true,  // if an action is resumed after launch, set dx/dy to 0
	                    smoothEndDuration: 300    // animate to snap/restrict endOnly if there's no inertia
	                }
	            },

	            _holdDuration: 600
	        },

	        // Things related to autoScroll
	        autoScroll = {
	            interaction: null,
	            i: null,    // the handle returned by window.setInterval
	            x: 0, y: 0, // Direction each pulse is to scroll in

	            // scroll the window by the values in scroll.x/y
	            scroll: function () {
	                var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll,
	                    container = options.container || getWindow(autoScroll.interaction.element),
	                    now = new Date().getTime(),
	                    // change in time in seconds
	                    dtx = (now - autoScroll.prevTimeX) / 1000,
	                    dty = (now - autoScroll.prevTimeY) / 1000,
	                    vx, vy, sx, sy;

	                // displacement
	                if (options.velocity) {
	                  vx = options.velocity.x;
	                  vy = options.velocity.y;
	                }
	                else {
	                  vx = vy = options.speed
	                }
	 
	                sx = vx * dtx;
	                sy = vy * dty;

	                if (sx >= 1 || sy >= 1) {
	                    if (isWindow(container)) {
	                        container.scrollBy(autoScroll.x * sx, autoScroll.y * sy);
	                    }
	                    else if (container) {
	                        container.scrollLeft += autoScroll.x * sx;
	                        container.scrollTop  += autoScroll.y * sy;
	                    }

	                    if (sx >=1) autoScroll.prevTimeX = now;
	                    if (sy >= 1) autoScroll.prevTimeY = now;
	                }

	                if (autoScroll.isScrolling) {
	                    cancelFrame(autoScroll.i);
	                    autoScroll.i = reqFrame(autoScroll.scroll);
	                }
	            },

	            isScrolling: false,
	            prevTimeX: 0,
	            prevTimeY: 0,

	            start: function (interaction) {
	                autoScroll.isScrolling = true;
	                cancelFrame(autoScroll.i);

	                autoScroll.interaction = interaction;
	                autoScroll.prevTimeX = new Date().getTime();
	                autoScroll.prevTimeY = new Date().getTime();
	                autoScroll.i = reqFrame(autoScroll.scroll);
	            },

	            stop: function () {
	                autoScroll.isScrolling = false;
	                cancelFrame(autoScroll.i);
	            }
	        },

	        // Does the browser support touch input?
	        supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),

	        // Does the browser support PointerEvents
	        // Avoid PointerEvent bugs introduced in Chrome 55
	        supportsPointerEvent = PointerEvent && !/Chrome/.test(navigator.userAgent),

	        // Less Precision with touch input
	        margin = supportsTouch || supportsPointerEvent? 20: 10,

	        pointerMoveTolerance = 1,

	        // for ignoring browser's simulated mouse events
	        prevTouchTime = 0,

	        // Allow this many interactions to happen simultaneously
	        maxInteractions = Infinity,

	        // Check if is IE9 or older
	        actionCursors = (document.all && !window.atob) ? {
	            drag    : 'move',
	            resizex : 'e-resize',
	            resizey : 's-resize',
	            resizexy: 'se-resize',

	            resizetop        : 'n-resize',
	            resizeleft       : 'w-resize',
	            resizebottom     : 's-resize',
	            resizeright      : 'e-resize',
	            resizetopleft    : 'se-resize',
	            resizebottomright: 'se-resize',
	            resizetopright   : 'ne-resize',
	            resizebottomleft : 'ne-resize',

	            gesture : ''
	        } : {
	            drag    : 'move',
	            resizex : 'ew-resize',
	            resizey : 'ns-resize',
	            resizexy: 'nwse-resize',

	            resizetop        : 'ns-resize',
	            resizeleft       : 'ew-resize',
	            resizebottom     : 'ns-resize',
	            resizeright      : 'ew-resize',
	            resizetopleft    : 'nwse-resize',
	            resizebottomright: 'nwse-resize',
	            resizetopright   : 'nesw-resize',
	            resizebottomleft : 'nesw-resize',

	            gesture : ''
	        },

	        actionIsEnabled = {
	            drag   : true,
	            resize : true,
	            gesture: true
	        },

	        // because Webkit and Opera still use 'mousewheel' event type
	        wheelEvent = 'onmousewheel' in document? 'mousewheel': 'wheel',

	        eventTypes = [
	            'dragstart',
	            'dragmove',
	            'draginertiastart',
	            'dragend',
	            'dragenter',
	            'dragleave',
	            'dropactivate',
	            'dropdeactivate',
	            'dropmove',
	            'drop',
	            'resizestart',
	            'resizemove',
	            'resizeinertiastart',
	            'resizeend',
	            'gesturestart',
	            'gesturemove',
	            'gestureinertiastart',
	            'gestureend',

	            'down',
	            'move',
	            'up',
	            'cancel',
	            'tap',
	            'doubletap',
	            'hold'
	        ],

	        globalEvents = {},

	        // Opera Mobile must be handled differently
	        isOperaMobile = navigator.appName == 'Opera' &&
	            supportsTouch &&
	            navigator.userAgent.match('Presto'),

	        // scrolling doesn't change the result of getClientRects on iOS 7
	        isIOS7 = (/iP(hone|od|ad)/.test(navigator.platform)
	                         && /OS 7[^\d]/.test(navigator.appVersion)),

	        // prefix matchesSelector
	        prefixedMatchesSelector = 'matches' in Element.prototype?
	                'matches': 'webkitMatchesSelector' in Element.prototype?
	                    'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?
	                        'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?
	                            'oMatchesSelector': 'msMatchesSelector',

	        // will be polyfill function if browser is IE8
	        ie8MatchesSelector,

	        // native requestAnimationFrame or polyfill
	        reqFrame = realWindow.requestAnimationFrame,
	        cancelFrame = realWindow.cancelAnimationFrame,

	        // Events wrapper
	        events = (function () {
	            var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window),
	                addEvent       = useAttachEvent?  'attachEvent': 'addEventListener',
	                removeEvent    = useAttachEvent?  'detachEvent': 'removeEventListener',
	                on             = useAttachEvent? 'on': '',

	                elements          = [],
	                targets           = [],
	                attachedListeners = [];

	            function add (element, type, listener, useCapture) {
	                var elementIndex = indexOf(elements, element),
	                    target = targets[elementIndex];

	                if (!target) {
	                    target = {
	                        events: {},
	                        typeCount: 0
	                    };

	                    elementIndex = elements.push(element) - 1;
	                    targets.push(target);

	                    attachedListeners.push((useAttachEvent ? {
	                            supplied: [],
	                            wrapped : [],
	                            useCount: []
	                        } : null));
	                }

	                if (!target.events[type]) {
	                    target.events[type] = [];
	                    target.typeCount++;
	                }

	                if (!contains(target.events[type], listener)) {
	                    var ret;

	                    if (useAttachEvent) {
	                        var listeners = attachedListeners[elementIndex],
	                            listenerIndex = indexOf(listeners.supplied, listener);

	                        var wrapped = listeners.wrapped[listenerIndex] || function (event) {
	                            if (!event.immediatePropagationStopped) {
	                                event.target = event.srcElement;
	                                event.currentTarget = element;

	                                event.preventDefault = event.preventDefault || preventDef;
	                                event.stopPropagation = event.stopPropagation || stopProp;
	                                event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp;

	                                if (/mouse|click/.test(event.type)) {
	                                    event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft;
	                                    event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop;
	                                }

	                                listener(event);
	                            }
	                        };

	                        ret = element[addEvent](on + type, wrapped, Boolean(useCapture));

	                        if (listenerIndex === -1) {
	                            listeners.supplied.push(listener);
	                            listeners.wrapped.push(wrapped);
	                            listeners.useCount.push(1);
	                        }
	                        else {
	                            listeners.useCount[listenerIndex]++;
	                        }
	                    }
	                    else {
	                        ret = element[addEvent](type, listener, useCapture || false);
	                    }
	                    target.events[type].push(listener);

	                    return ret;
	                }
	            }

	            function remove (element, type, listener, useCapture) {
	                var i,
	                    elementIndex = indexOf(elements, element),
	                    target = targets[elementIndex],
	                    listeners,
	                    listenerIndex,
	                    wrapped = listener;

	                if (!target || !target.events) {
	                    return;
	                }

	                if (useAttachEvent) {
	                    listeners = attachedListeners[elementIndex];
	                    listenerIndex = indexOf(listeners.supplied, listener);
	                    wrapped = listeners.wrapped[listenerIndex];
	                }

	                if (type === 'all') {
	                    for (type in target.events) {
	                        if (target.events.hasOwnProperty(type)) {
	                            remove(element, type, 'all');
	                        }
	                    }
	                    return;
	                }

	                if (target.events[type]) {
	                    var len = target.events[type].length;

	                    if (listener === 'all') {
	                        for (i = 0; i < len; i++) {
	                            remove(element, type, target.events[type][i], Boolean(useCapture));
	                        }
	                        return;
	                    } else {
	                        for (i = 0; i < len; i++) {
	                            if (target.events[type][i] === listener) {
	                                element[removeEvent](on + type, wrapped, useCapture || false);
	                                target.events[type].splice(i, 1);

	                                if (useAttachEvent && listeners) {
	                                    listeners.useCount[listenerIndex]--;
	                                    if (listeners.useCount[listenerIndex] === 0) {
	                                        listeners.supplied.splice(listenerIndex, 1);
	                                        listeners.wrapped.splice(listenerIndex, 1);
	                                        listeners.useCount.splice(listenerIndex, 1);
	                                    }
	                                }

	                                break;
	                            }
	                        }
	                    }

	                    if (target.events[type] && target.events[type].length === 0) {
	                        target.events[type] = null;
	                        target.typeCount--;
	                    }
	                }

	                if (!target.typeCount) {
	                    targets.splice(elementIndex, 1);
	                    elements.splice(elementIndex, 1);
	                    attachedListeners.splice(elementIndex, 1);
	                }
	            }

	            function preventDef () {
	                this.returnValue = false;
	            }

	            function stopProp () {
	                this.cancelBubble = true;
	            }

	            function stopImmProp () {
	                this.cancelBubble = true;
	                this.immediatePropagationStopped = true;
	            }

	            return {
	                add: add,
	                remove: remove,
	                useAttachEvent: useAttachEvent,

	                _elements: elements,
	                _targets: targets,
	                _attachedListeners: attachedListeners
	            };
	        }());

	    function blank () {}

	    function isElement (o) {
	        if (!o || (typeof o !== 'object')) { return false; }

	        var _window = getWindow(o) || window;

	        return (/object|function/.test(typeof _window.Element)
	            ? o instanceof _window.Element //DOM2
	            : o.nodeType === 1 && typeof o.nodeName === "string");
	    }
	    function isWindow (thing) { return thing === window || !!(thing && thing.Window) && (thing instanceof thing.Window); }
	    function isDocFrag (thing) { return !!thing && thing instanceof DocumentFragment; }
	    function isArray (thing) {
	        return isObject(thing)
	                && (typeof thing.length !== undefined)
	                && isFunction(thing.splice);
	    }
	    function isObject   (thing) { return !!thing && (typeof thing === 'object'); }
	    function isFunction (thing) { return typeof thing === 'function'; }
	    function isNumber   (thing) { return typeof thing === 'number'  ; }
	    function isBool     (thing) { return typeof thing === 'boolean' ; }
	    function isString   (thing) { return typeof thing === 'string'  ; }

	    function trySelector (value) {
	        if (!isString(value)) { return false; }

	        // an exception will be raised if it is invalid
	        document.querySelector(value);
	        return true;
	    }

	    function extend (dest, source) {
	        for (var prop in source) {
	            dest[prop] = source[prop];
	        }
	        return dest;
	    }

	    var prefixedPropREs = {
	      webkit: /(Movement[XY]|Radius[XY]|RotationAngle|Force)$/
	    };

	    function pointerExtend (dest, source) {
	        for (var prop in source) {
	          var deprecated = false;

	          // skip deprecated prefixed properties
	          for (var vendor in prefixedPropREs) {
	            if (prop.indexOf(vendor) === 0 && prefixedPropREs[vendor].test(prop)) {
	              deprecated = true;
	              break;
	            }
	          }

	          if (!deprecated) {
	            dest[prop] = source[prop];
	          }
	        }
	        return dest;
	    }

	    function copyCoords (dest, src) {
	        dest.page = dest.page || {};
	        dest.page.x = src.page.x;
	        dest.page.y = src.page.y;

	        dest.client = dest.client || {};
	        dest.client.x = src.client.x;
	        dest.client.y = src.client.y;

	        dest.timeStamp = src.timeStamp;
	    }

	    function setEventXY (targetObj, pointers, interaction) {
	        var pointer = (pointers.length > 1
	                       ? pointerAverage(pointers)
	                       : pointers[0]);

	        getPageXY(pointer, tmpXY, interaction);
	        targetObj.page.x = tmpXY.x;
	        targetObj.page.y = tmpXY.y;

	        getClientXY(pointer, tmpXY, interaction);
	        targetObj.client.x = tmpXY.x;
	        targetObj.client.y = tmpXY.y;

	        targetObj.timeStamp = new Date().getTime();
	    }

	    function setEventDeltas (targetObj, prev, cur) {
	        targetObj.page.x     = cur.page.x      - prev.page.x;
	        targetObj.page.y     = cur.page.y      - prev.page.y;
	        targetObj.client.x   = cur.client.x    - prev.client.x;
	        targetObj.client.y   = cur.client.y    - prev.client.y;
	        targetObj.timeStamp = new Date().getTime() - prev.timeStamp;

	        // set pointer velocity
	        var dt = Math.max(targetObj.timeStamp / 1000, 0.001);
	        targetObj.page.speed   = hypot(targetObj.page.x, targetObj.page.y) / dt;
	        targetObj.page.vx      = targetObj.page.x / dt;
	        targetObj.page.vy      = targetObj.page.y / dt;

	        targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt;
	        targetObj.client.vx    = targetObj.client.x / dt;
	        targetObj.client.vy    = targetObj.client.y / dt;
	    }

	    function isNativePointer (pointer) {
	        return (pointer instanceof window.Event
	            || (supportsTouch && window.Touch && pointer instanceof window.Touch));
	    }

	    // Get specified X/Y coords for mouse or event.touches[0]
	    function getXY (type, pointer, xy) {
	        xy = xy || {};
	        type = type || 'page';

	        xy.x = pointer[type + 'X'];
	        xy.y = pointer[type + 'Y'];

	        return xy;
	    }

	    function getPageXY (pointer, page) {
	        page = page || {};

	        // Opera Mobile handles the viewport and scrolling oddly
	        if (isOperaMobile && isNativePointer(pointer)) {
	            getXY('screen', pointer, page);

	            page.x += window.scrollX;
	            page.y += window.scrollY;
	        }
	        else {
	            getXY('page', pointer, page);
	        }

	        return page;
	    }

	    function getClientXY (pointer, client) {
	        client = client || {};

	        if (isOperaMobile && isNativePointer(pointer)) {
	            // Opera Mobile handles the viewport and scrolling oddly
	            getXY('screen', pointer, client);
	        }
	        else {
	          getXY('client', pointer, client);
	        }

	        return client;
	    }

	    function getScrollXY (win) {
	        win = win || window;
	        return {
	            x: win.scrollX || win.document.documentElement.scrollLeft,
	            y: win.scrollY || win.document.documentElement.scrollTop
	        };
	    }

	    function getPointerId (pointer) {
	        return isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier;
	    }

	    function getActualElement (element) {
	        return (element instanceof SVGElementInstance
	            ? element.correspondingUseElement
	            : element);
	    }

	    function getWindow (node) {
	        if (isWindow(node)) {
	            return node;
	        }

	        var rootNode = (node.ownerDocument || node);

	        return rootNode.defaultView || rootNode.parentWindow || window;
	    }

	    function getElementClientRect (element) {
	        var clientRect = (element instanceof SVGElement
	                            ? element.getBoundingClientRect()
	                            : element.getClientRects()[0]);

	        return clientRect && {
	            left  : clientRect.left,
	            right : clientRect.right,
	            top   : clientRect.top,
	            bottom: clientRect.bottom,
	            width : clientRect.width || clientRect.right - clientRect.left,
	            height: clientRect.height || clientRect.bottom - clientRect.top
	        };
	    }

	    function getElementRect (element) {
	        var clientRect = getElementClientRect(element);

	        if (!isIOS7 && clientRect) {
	            var scroll = getScrollXY(getWindow(element));

	            clientRect.left   += scroll.x;
	            clientRect.right  += scroll.x;
	            clientRect.top    += scroll.y;
	            clientRect.bottom += scroll.y;
	        }

	        return clientRect;
	    }

	    function getTouchPair (event) {
	        var touches = [];

	        // array of touches is supplied
	        if (isArray(event)) {
	            touches[0] = event[0];
	            touches[1] = event[1];
	        }
	        // an event
	        else {
	            if (event.type === 'touchend') {
	                if (event.touches.length === 1) {
	                    touches[0] = event.touches[0];
	                    touches[1] = event.changedTouches[0];
	                }
	                else if (event.touches.length === 0) {
	                    touches[0] = event.changedTouches[0];
	                    touches[1] = event.changedTouches[1];
	                }
	            }
	            else {
	                touches[0] = event.touches[0];
	                touches[1] = event.touches[1];
	            }
	        }

	        return touches;
	    }

	    function pointerAverage (pointers) {
	        var average = {
	            pageX  : 0,
	            pageY  : 0,
	            clientX: 0,
	            clientY: 0,
	            screenX: 0,
	            screenY: 0
	        };
	        var prop;

	        for (var i = 0; i < pointers.length; i++) {
	            for (prop in average) {
	                average[prop] += pointers[i][prop];
	            }
	        }
	        for (prop in average) {
	            average[prop] /= pointers.length;
	        }

	        return average;
	    }

	    function touchBBox (event) {
	        if (!event.length && !(event.touches && event.touches.length > 1)) {
	            return;
	        }

	        var touches = getTouchPair(event),
	            minX = Math.min(touches[0].pageX, touches[1].pageX),
	            minY = Math.min(touches[0].pageY, touches[1].pageY),
	            maxX = Math.max(touches[0].pageX, touches[1].pageX),
	            maxY = Math.max(touches[0].pageY, touches[1].pageY);

	        return {
	            x: minX,
	            y: minY,
	            left: minX,
	            top: minY,
	            width: maxX - minX,
	            height: maxY - minY
	        };
	    }

	    function touchDistance (event, deltaSource) {
	        deltaSource = deltaSource || defaultOptions.deltaSource;

	        var sourceX = deltaSource + 'X',
	            sourceY = deltaSource + 'Y',
	            touches = getTouchPair(event);


	        var dx = touches[0][sourceX] - touches[1][sourceX],
	            dy = touches[0][sourceY] - touches[1][sourceY];

	        return hypot(dx, dy);
	    }

	    function touchAngle (event, prevAngle, deltaSource) {
	        deltaSource = deltaSource || defaultOptions.deltaSource;

	        var sourceX = deltaSource + 'X',
	            sourceY = deltaSource + 'Y',
	            touches = getTouchPair(event),
	            dx = touches[0][sourceX] - touches[1][sourceX],
	            dy = touches[0][sourceY] - touches[1][sourceY],
	            angle = 180 * Math.atan(dy / dx) / Math.PI;

	        if (isNumber(prevAngle)) {
	            var dr = angle - prevAngle,
	                drClamped = dr % 360;

	            if (drClamped > 315) {
	                angle -= 360 + (angle / 360)|0 * 360;
	            }
	            else if (drClamped > 135) {
	                angle -= 180 + (angle / 360)|0 * 360;
	            }
	            else if (drClamped < -315) {
	                angle += 360 + (angle / 360)|0 * 360;
	            }
	            else if (drClamped < -135) {
	                angle += 180 + (angle / 360)|0 * 360;
	            }
	        }

	        return  angle;
	    }

	    function getOriginXY (interactable, element) {
	        var origin = interactable
	                ? interactable.options.origin
	                : defaultOptions.origin;

	        if (origin === 'parent') {
	            origin = parentElement(element);
	        }
	        else if (origin === 'self') {
	            origin = interactable.getRect(element);
	        }
	        else if (trySelector(origin)) {
	            origin = closest(element, origin) || { x: 0, y: 0 };
	        }

	        if (isFunction(origin)) {
	            origin = origin(interactable && element);
	        }

	        if (isElement(origin))  {
	            origin = getElementRect(origin);
	        }

	        origin.x = ('x' in origin)? origin.x : origin.left;
	        origin.y = ('y' in origin)? origin.y : origin.top;

	        return origin;
	    }

	    // http://stackoverflow.com/a/5634528/2280888
	    function _getQBezierValue(t, p1, p2, p3) {
	        var iT = 1 - t;
	        return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
	    }

	    function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) {
	        return {
	            x:  _getQBezierValue(position, startX, cpX, endX),
	            y:  _getQBezierValue(position, startY, cpY, endY)
	        };
	    }

	    // http://gizma.com/easing/
	    function easeOutQuad (t, b, c, d) {
	        t /= d;
	        return -c * t*(t-2) + b;
	    }

	    function nodeContains (parent, child) {
	        while (child) {
	            if (child === parent) {
	                return true;
	            }

	            child = child.parentNode;
	        }

	        return false;
	    }

	    function closest (child, selector) {
	        var parent = parentElement(child);

	        while (isElement(parent)) {
	            if (matchesSelector(parent, selector)) { return parent; }

	            parent = parentElement(parent);
	        }

	        return null;
	    }

	    function parentElement (node) {
	        var parent = node.parentNode;

	        if (isDocFrag(parent)) {
	            // skip past #shado-root fragments
	            while ((parent = parent.host) && isDocFrag(parent)) {}

	            return parent;
	        }

	        return parent;
	    }

	    function inContext (interactable, element) {
	        return interactable._context === element.ownerDocument
	                || nodeContains(interactable._context, element);
	    }

	    function testIgnore (interactable, interactableElement, element) {
	        var ignoreFrom = interactable.options.ignoreFrom;

	        if (!ignoreFrom || !isElement(element)) { return false; }

	        if (isString(ignoreFrom)) {
	            return matchesUpTo(element, ignoreFrom, interactableElement);
	        }
	        else if (isElement(ignoreFrom)) {
	            return nodeContains(ignoreFrom, element);
	        }

	        return false;
	    }

	    function testAllow (interactable, interactableElement, element) {
	        var allowFrom = interactable.options.allowFrom;

	        if (!allowFrom) { return true; }

	        if (!isElement(element)) { return false; }

	        if (isString(allowFrom)) {
	            return matchesUpTo(element, allowFrom, interactableElement);
	        }
	        else if (isElement(allowFrom)) {
	            return nodeContains(allowFrom, element);
	        }

	        return false;
	    }

	    function checkAxis (axis, interactable) {
	        if (!interactable) { return false; }

	        var thisAxis = interactable.options.drag.axis;

	        return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis);
	    }

	    function checkSnap (interactable, action) {
	        var options = interactable.options;

	        if (/^resize/.test(action)) {
	            action = 'resize';
	        }

	        return options[action].snap && options[action].snap.enabled;
	    }

	    function checkRestrict (interactable, action) {
	        var options = interactable.options;

	        if (/^resize/.test(action)) {
	            action = 'resize';
	        }

	        return  options[action].restrict && options[action].restrict.enabled;
	    }

	    function checkAutoScroll (interactable, action) {
	        var options = interactable.options;

	        if (/^resize/.test(action)) {
	            action = 'resize';
	        }

	        return  options[action].autoScroll && options[action].autoScroll.enabled;
	    }

	    function withinInteractionLimit (interactable, element, action) {
	        var options = interactable.options,
	            maxActions = options[action.name].max,
	            maxPerElement = options[action.name].maxPerElement,
	            activeInteractions = 0,
	            targetCount = 0,
	            targetElementCount = 0;

	        for (var i = 0, len = interactions.length; i < len; i++) {
	            var interaction = interactions[i],
	                otherAction = interaction.prepared.name,
	                active = interaction.interacting();

	            if (!active) { continue; }

	            activeInteractions++;

	            if (activeInteractions >= maxInteractions) {
	                return false;
	            }

	            if (interaction.target !== interactable) { continue; }

	            targetCount += (otherAction === action.name)|0;

	            if (targetCount >= maxActions) {
	                return false;
	            }

	            if (interaction.element === element) {
	                targetElementCount++;

	                if (otherAction !== action.name || targetElementCount >= maxPerElement) {
	                    return false;
	                }
	            }
	        }

	        return maxInteractions > 0;
	    }

	    // Test for the element that's "above" all other qualifiers
	    function indexOfDeepestElement (elements) {
	        var dropzone,
	            deepestZone = elements[0],
	            index = deepestZone? 0: -1,
	            parent,
	            deepestZoneParents = [],
	            dropzoneParents = [],
	            child,
	            i,
	            n;

	        for (i = 1; i < elements.length; i++) {
	            dropzone = elements[i];

	            // an element might belong to multiple selector dropzones
	            if (!dropzone || dropzone === deepestZone) {
	                continue;
	            }

	            if (!deepestZone) {
	                deepestZone = dropzone;
	                index = i;
	                continue;
	            }

	            // check if the deepest or current are document.documentElement or document.rootElement
	            // - if the current dropzone is, do nothing and continue
	            if (dropzone.parentNode === dropzone.ownerDocument) {
	                continue;
	            }
	            // - if deepest is, update with the current dropzone and continue to next
	            else if (deepestZone.parentNode === dropzone.ownerDocument) {
	                deepestZone = dropzone;
	                index = i;
	                continue;
	            }

	            if (!deepestZoneParents.length) {
	                parent = deepestZone;
	                while (parent.parentNode && parent.parentNode !== parent.ownerDocument) {
	                    deepestZoneParents.unshift(parent);
	                    parent = parent.parentNode;
	                }
	            }

	            // if this element is an svg element and the current deepest is
	            // an HTMLElement
	            if (deepestZone instanceof HTMLElement
	                && dropzone instanceof SVGElement
	                && !(dropzone instanceof SVGSVGElement)) {

	                if (dropzone === deepestZone.parentNode) {
	                    continue;
	                }

	                parent = dropzone.ownerSVGElement;
	            }
	            else {
	                parent = dropzone;
	            }

	            dropzoneParents = [];

	            while (parent.parentNode !== parent.ownerDocument) {
	                dropzoneParents.unshift(parent);
	                parent = parent.parentNode;
	            }

	            n = 0;

	            // get (position of last common ancestor) + 1
	            while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) {
	                n++;
	            }

	            var parents = [
	                dropzoneParents[n - 1],
	                dropzoneParents[n],
	                deepestZoneParents[n]
	            ];

	            child = parents[0].lastChild;

	            while (child) {
	                if (child === parents[1]) {
	                    deepestZone = dropzone;
	                    index = i;
	                    deepestZoneParents = [];

	                    break;
	                }
	                else if (child === parents[2]) {
	                    break;
	                }

	                child = child.previousSibling;
	            }
	        }

	        return index;
	    }

	    function Interaction () {
	        this.target          = null; // current interactable being interacted with
	        this.element         = null; // the target element of the interactable
	        this.dropTarget      = null; // the dropzone a drag target might be dropped into
	        this.dropElement     = null; // the element at the time of checking
	        this.prevDropTarget  = null; // the dropzone that was recently dragged away from
	        this.prevDropElement = null; // the element at the time of checking

	        this.prepared        = {     // action that's ready to be fired on next move event
	            name : null,
	            axis : null,
	            edges: null
	        };

	        this.matches         = [];   // all selectors that are matched by target element
	        this.matchElements   = [];   // corresponding elements

	        this.inertiaStatus = {
	            active       : false,
	            smoothEnd    : false,
	            ending       : false,

	            startEvent: null,
	            upCoords: {},

	            xe: 0, ye: 0,
	            sx: 0, sy: 0,

	            t0: 0,
	            vx0: 0, vys: 0,
	            duration: 0,

	            resumeDx: 0,
	            resumeDy: 0,

	            lambda_v0: 0,
	            one_ve_v0: 0,
	            i  : null
	        };

	        if (isFunction(Function.prototype.bind)) {
	            this.boundInertiaFrame = this.inertiaFrame.bind(this);
	            this.boundSmoothEndFrame = this.smoothEndFrame.bind(this);
	        }
	        else {
	            var that = this;

	            this.boundInertiaFrame = function () { return that.inertiaFrame(); };
	            this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); };
	        }

	        this.activeDrops = {
	            dropzones: [],      // the dropzones that are mentioned below
	            elements : [],      // elements of dropzones that accept the target draggable
	            rects    : []       // the rects of the elements mentioned above
	        };

	        // keep track of added pointers
	        this.pointers    = [];
	        this.pointerIds  = [];
	        this.downTargets = [];
	        this.downTimes   = [];
	        this.holdTimers  = [];

	        // Previous native pointer move event coordinates
	        this.prevCoords = {
	            page     : { x: 0, y: 0 },
	            client   : { x: 0, y: 0 },
	            timeStamp: 0
	        };
	        // current native pointer move event coordinates
	        this.curCoords = {
	            page     : { x: 0, y: 0 },
	            client   : { x: 0, y: 0 },
	            timeStamp: 0
	        };

	        // Starting InteractEvent pointer coordinates
	        this.startCoords = {
	            page     : { x: 0, y: 0 },
	            client   : { x: 0, y: 0 },
	            timeStamp: 0
	        };

	        // Change in coordinates and time of the pointer
	        this.pointerDelta = {
	            page     : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
	            client   : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
	            timeStamp: 0
	        };

	        this.downEvent   = null;    // pointerdown/mousedown/touchstart event
	        this.downPointer = {};

	        this._eventTarget    = null;
	        this._curEventTarget = null;

	        this.prevEvent = null;      // previous action event
	        this.tapTime   = 0;         // time of the most recent tap event
	        this.prevTap   = null;

	        this.startOffset    = { left: 0, right: 0, top: 0, bottom: 0 };
	        this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 };
	        this.snapOffsets    = [];

	        this.gesture = {
	            start: { x: 0, y: 0 },

	            startDistance: 0,   // distance between two touches of touchStart
	            prevDistance : 0,
	            distance     : 0,

	            scale: 1,           // gesture.distance / gesture.startDistance

	            startAngle: 0,      // angle of line joining two touches
	            prevAngle : 0       // angle of the previous gesture event
	        };

	        this.snapStatus = {
	            x       : 0, y       : 0,
	            dx      : 0, dy      : 0,
	            realX   : 0, realY   : 0,
	            snappedX: 0, snappedY: 0,
	            targets : [],
	            locked  : false,
	            changed : false
	        };

	        this.restrictStatus = {
	            dx         : 0, dy         : 0,
	            restrictedX: 0, restrictedY: 0,
	            snap       : null,
	            restricted : false,
	            changed    : false
	        };

	        this.restrictStatus.snap = this.snapStatus;

	        this.pointerIsDown   = false;
	        this.pointerWasMoved = false;
	        this.gesturing       = false;
	        this.dragging        = false;
	        this.resizing        = false;
	        this.resizeAxes      = 'xy';

	        this.mouse = false;

	        interactions.push(this);
	    }

	    Interaction.prototype = {
	        getPageXY  : function (pointer, xy) { return   getPageXY(pointer, xy, this); },
	        getClientXY: function (pointer, xy) { return getClientXY(pointer, xy, this); },
	        setEventXY : function (target, ptr) { return  setEventXY(target, ptr, this); },

	        pointerOver: function (pointer, event, eventTarget) {
	            if (this.prepared.name || !this.mouse) { return; }

	            var curMatches = [],
	                curMatchElements = [],
	                prevTargetElement = this.element;

	            this.addPointer(pointer);

	            if (this.target
	                && (testIgnore(this.target, this.element, eventTarget)
	                    || !testAllow(this.target, this.element, eventTarget))) {
	                // if the eventTarget should be ignored or shouldn't be allowed
	                // clear the previous target
	                this.target = null;
	                this.element = null;
	                this.matches = [];
	                this.matchElements = [];
	            }

	            var elementInteractable = interactables.get(eventTarget),
	                elementAction = (elementInteractable
	                                 && !testIgnore(elementInteractable, eventTarget, eventTarget)
	                                 && testAllow(elementInteractable, eventTarget, eventTarget)
	                                 && validateAction(
	                                     elementInteractable.getAction(pointer, event, this, eventTarget),
	                                     elementInteractable));

	            if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) {
	                 elementAction = null;
	            }

	            function pushCurMatches (interactable, selector) {
	                if (interactable
	                    && inContext(interactable, eventTarget)
	                    && !testIgnore(interactable, eventTarget, eventTarget)
	                    && testAllow(interactable, eventTarget, eventTarget)
	                    && matchesSelector(eventTarget, selector)) {

	                    curMatches.push(interactable);
	                    curMatchElements.push(eventTarget);
	                }
	            }

	            if (elementAction) {
	                this.target = elementInteractable;
	                this.element = eventTarget;
	                this.matches = [];
	                this.matchElements = [];
	            }
	            else {
	                interactables.forEachSelector(pushCurMatches);

	                if (this.validateSelector(pointer, event, curMatches, curMatchElements)) {
	                    this.matches = curMatches;
	                    this.matchElements = curMatchElements;

	                    this.pointerHover(pointer, event, this.matches, this.matchElements);
	                    events.add(eventTarget,
	                                        supportsPointerEvent? pEventTypes.move : 'mousemove',
	                                        listeners.pointerHover);
	                }
	                else if (this.target) {
	                    if (nodeContains(prevTargetElement, eventTarget)) {
	                        this.pointerHover(pointer, event, this.matches, this.matchElements);
	                        events.add(this.element,
	                                            supportsPointerEvent? pEventTypes.move : 'mousemove',
	                                            listeners.pointerHover);
	                    }
	                    else {
	                        this.target = null;
	                        this.element = null;
	                        this.matches = [];
	                        this.matchElements = [];
	                    }
	                }
	            }
	        },

	        // Check what action would be performed on pointerMove target if a mouse
	        // button were pressed and change the cursor accordingly
	        pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) {
	            var target = this.target;

	            if (!this.prepared.name && this.mouse) {

	                var action;

	                // update pointer coords for defaultActionChecker to use
	                this.setEventXY(this.curCoords, [pointer]);

	                if (matches) {
	                    action = this.validateSelector(pointer, event, matches, matchElements);
	                }
	                else if (target) {
	                    action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target);
	                }

	                if (target && target.options.styleCursor) {
	                    if (action) {
	                        target._doc.documentElement.style.cursor = getActionCursor(action);
	                    }
	                    else {
	                        target._doc.documentElement.style.cursor = '';
	                    }
	                }
	            }
	            else if (this.prepared.name) {
	                this.checkAndPreventDefault(event, target, this.element);
	            }
	        },

	        pointerOut: function (pointer, event, eventTarget) {
	            if (this.prepared.name) { return; }

	            // Remove temporary event listeners for selector Interactables
	            if (!interactables.get(eventTarget)) {
	                events.remove(eventTarget,
	                                       supportsPointerEvent? pEventTypes.move : 'mousemove',
	                                       listeners.pointerHover);
	            }

	            if (this.target && this.target.options.styleCursor && !this.interacting()) {
	                this.target._doc.documentElement.style.cursor = '';
	            }
	        },

	        selectorDown: function (pointer, event, eventTarget, curEventTarget) {
	            var that = this,
	                // copy event to be used in timeout for IE8
	                eventCopy = events.useAttachEvent? extend({}, event) : event,
	                element = eventTarget,
	                pointerIndex = this.addPointer(pointer),
	                action;

	            this.holdTimers[pointerIndex] = setTimeout(function () {
	                that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget);
	            }, defaultOptions._holdDuration);

	            this.pointerIsDown = true;

	            // Check if the down event hits the current inertia target
	            if (this.inertiaStatus.active && this.target.selector) {
	                // climb up the DOM tree from the event target
	                while (isElement(element)) {

	                    // if this element is the current inertia target element
	                    if (element === this.element
	                        // and the prospective action is the same as the ongoing one
	                        && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) {

	                        // stop inertia so that the next move will be a normal one
	                        cancelFrame(this.inertiaStatus.i);
	                        this.inertiaStatus.active = false;

	                        this.collectEventTargets(pointer, event, eventTarget, 'down');
	                        return;
	                    }
	                    element = parentElement(element);
	                }
	            }

	            // do nothing if interacting
	            if (this.interacting()) {
	                this.collectEventTargets(pointer, event, eventTarget, 'down');
	                return;
	            }

	            function pushMatches (interactable, selector, context) {
	                var elements = ie8MatchesSelector
	                    ? context.querySelectorAll(selector)
	                    : undefined;

	                if (inContext(interactable, element)
	                    && !testIgnore(interactable, element, eventTarget)
	                    && testAllow(interactable, element, eventTarget)
	                    && matchesSelector(element, selector, elements)) {

	                    that.matches.push(interactable);
	                    that.matchElements.push(element);
	                }
	            }

	            // update pointer coords for defaultActionChecker to use
	            this.setEventXY(this.curCoords, [pointer]);
	            this.downEvent = event;

	            while (isElement(element) && !action) {
	                this.matches = [];
	                this.matchElements = [];

	                interactables.forEachSelector(pushMatches);

	                action = this.validateSelector(pointer, event, this.matches, this.matchElements);
	                element = parentElement(element);
	            }

	            if (action) {
	                this.prepared.name  = action.name;
	                this.prepared.axis  = action.axis;
	                this.prepared.edges = action.edges;

	                this.collectEventTargets(pointer, event, eventTarget, 'down');

	                return this.pointerDown(pointer, event, eventTarget, curEventTarget, action);
	            }
	            else {
	                // do these now since pointerDown isn't being called from here
	                this.downTimes[pointerIndex] = new Date().getTime();
	                this.downTargets[pointerIndex] = eventTarget;
	                pointerExtend(this.downPointer, pointer);

	                copyCoords(this.prevCoords, this.curCoords);
	                this.pointerWasMoved = false;
	            }

	            this.collectEventTargets(pointer, event, eventTarget, 'down');
	        },

	        // Determine action to be performed on next pointerMove and add appropriate
	        // style and event Listeners
	        pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) {
	            if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) {
	                this.checkAndPreventDefault(event, this.target, this.element);

	                return;
	            }

	            this.pointerIsDown = true;
	            this.downEvent = event;

	            var pointerIndex = this.addPointer(pointer),
	                action;

	            // If it is the second touch of a multi-touch gesture, keep the
	            // target the same and get a new action if a target was set by the
	            // first touch
	            if (this.pointerIds.length > 1 && this.target._element === this.element) {
	                var newAction = validateAction(forceAction || this.target.getAction(pointer, event, this, this.element), this.target);

	                if (withinInteractionLimit(this.target, this.element, newAction)) {
	                    action = newAction;
	                }

	                this.prepared.name = null;
	            }
	            // Otherwise, set the target if there is no action prepared
	            else if (!this.prepared.name) {
	                var interactable = interactables.get(curEventTarget);

	                if (interactable
	                    && !testIgnore(interactable, curEventTarget, eventTarget)
	                    && testAllow(interactable, curEventTarget, eventTarget)
	                    && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget))
	                    && withinInteractionLimit(interactable, curEventTarget, action)) {
	                    this.target = interactable;
	                    this.element = curEventTarget;
	                }
	            }

	            var target = this.target,
	                options = target && target.options;

	            if (target && (forceAction || !this.prepared.name)) {
	                action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element);

	                this.setEventXY(this.startCoords, this.pointers);

	                if (!action) { return; }

	                if (options.styleCursor) {
	                    target._doc.documentElement.style.cursor = getActionCursor(action);
	                }

	                this.resizeAxes = action.name === 'resize'? action.axis : null;

	                if (action === 'gesture' && this.pointerIds.length < 2) {
	                    action = null;
	                }

	                this.prepared.name  = action.name;
	                this.prepared.axis  = action.axis;
	                this.prepared.edges = action.edges;

	                this.snapStatus.snappedX = this.snapStatus.snappedY =
	                    this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN;

	                this.downTimes[pointerIndex] = new Date().getTime();
	                this.downTargets[pointerIndex] = eventTarget;
	                pointerExtend(this.downPointer, pointer);

	                copyCoords(this.prevCoords, this.startCoords);
	                this.pointerWasMoved = false;

	                this.checkAndPreventDefault(event, target, this.element);
	            }
	            // if inertia is active try to resume action
	            else if (this.inertiaStatus.active
	                && curEventTarget === this.element
	                && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) {

	                cancelFrame(this.inertiaStatus.i);
	                this.inertiaStatus.active = false;

	                this.checkAndPreventDefault(event, target, this.element);
	            }
	        },

	        setModifications: function (coords, preEnd) {
	            var target         = this.target,
	                shouldMove     = true,
	                shouldSnap     = checkSnap(target, this.prepared.name)     && (!target.options[this.prepared.name].snap.endOnly     || preEnd),
	                shouldRestrict = checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd);

	            if (shouldSnap    ) { this.setSnapping   (coords); } else { this.snapStatus    .locked     = false; }
	            if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; }

	            if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) {
	                shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed;
	            }
	            else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) {
	                shouldMove = false;
	            }

	            return shouldMove;
	        },

	        setStartOffsets: function (action, interactable, element) {
	            var rect = interactable.getRect(element),
	                origin = getOriginXY(interactable, element),
	                snap = interactable.options[this.prepared.name].snap,
	                restrict = interactable.options[this.prepared.name].restrict,
	                width, height;

	            if (rect) {
	                this.startOffset.left = this.startCoords.page.x - rect.left;
	                this.startOffset.top  = this.startCoords.page.y - rect.top;

	                this.startOffset.right  = rect.right  - this.startCoords.page.x;
	                this.startOffset.bottom = rect.bottom - this.startCoords.page.y;

	                if ('width' in rect) { width = rect.width; }
	                else { width = rect.right - rect.left; }
	                if ('height' in rect) { height = rect.height; }
	                else { height = rect.bottom - rect.top; }
	            }
	            else {
	                this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0;
	            }

	            this.snapOffsets.splice(0);

	            var snapOffset = snap && snap.offset === 'startCoords'
	                                ? {
	                                    x: this.startCoords.page.x - origin.x,
	                                    y: this.startCoords.page.y - origin.y
	                                }
	                                : snap && snap.offset || { x: 0, y: 0 };

	            if (rect && snap && snap.relativePoints && snap.relativePoints.length) {
	                for (var i = 0; i < snap.relativePoints.length; i++) {
	                    this.snapOffsets.push({
	                        x: this.startOffset.left - (width  * snap.relativePoints[i].x) + snapOffset.x,
	                        y: this.startOffset.top  - (height * snap.relativePoints[i].y) + snapOffset.y
	                    });
	                }
	            }
	            else {
	                this.snapOffsets.push(snapOffset);
	            }

	            if (rect && restrict.elementRect) {
	                this.restrictOffset.left = this.startOffset.left - (width  * restrict.elementRect.left);
	                this.restrictOffset.top  = this.startOffset.top  - (height * restrict.elementRect.top);

	                this.restrictOffset.right  = this.startOffset.right  - (width  * (1 - restrict.elementRect.right));
	                this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom));
	            }
	            else {
	                this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0;
	            }
	        },

	        /*\
	         * Interaction.start
	         [ method ]
	         *
	         * Start an action with the given Interactable and Element as tartgets. The
	         * action must be enabled for the target Interactable and an appropriate number
	         * of pointers must be held down – 1 for drag/resize, 2 for gesture.
	         *
	         * Use it with `interactable.<action>able({ manualStart: false })` to always
	         * [start actions manually](https://github.com/taye/interact.js/issues/114)
	         *
	         - action       (object)  The action to be performed - drag, resize, etc.
	         - interactable (Interactable) The Interactable to target
	         - element      (Element) The DOM Element to target
	         = (object) interact
	         **
	         | interact(target)
	         |   .draggable({
	         |     // disable the default drag start by down->move
	         |     manualStart: true
	         |   })
	         |   // start dragging after the user holds the pointer down
	         |   .on('hold', function (event) {
	         |     var interaction = event.interaction;
	         |
	         |     if (!interaction.interacting()) {
	         |       interaction.start({ name: 'drag' },
	         |                         event.interactable,
	         |                         event.currentTarget);
	         |     }
	         | });
	        \*/
	        start: function (action, interactable, element) {
	            if (this.interacting()
	                || !this.pointerIsDown
	                || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) {
	                return;
	            }

	            // if this interaction had been removed after stopping
	            // add it back
	            if (indexOf(interactions, this) === -1) {
	                interactions.push(this);
	            }

	            // set the startCoords if there was no prepared action
	            if (!this.prepared.name) {
	                this.setEventXY(this.startCoords, this.pointers);
	            }

	            this.prepared.name  = action.name;
	            this.prepared.axis  = action.axis;
	            this.prepared.edges = action.edges;
	            this.target         = interactable;
	            this.element        = element;

	            this.setStartOffsets(action.name, interactable, element);
	            this.setModifications(this.startCoords.page);

	            this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent);
	        },

	        pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) {
	            if (this.inertiaStatus.active) {
	                var pageUp   = this.inertiaStatus.upCoords.page;
	                var clientUp = this.inertiaStatus.upCoords.client;

	                var inertiaPosition = {
	                    pageX  : pageUp.x   + this.inertiaStatus.sx,
	                    pageY  : pageUp.y   + this.inertiaStatus.sy,
	                    clientX: clientUp.x + this.inertiaStatus.sx,
	                    clientY: clientUp.y + this.inertiaStatus.sy
	                };

	                this.setEventXY(this.curCoords, [inertiaPosition]);
	            }
	            else {
	                this.recordPointer(pointer);
	                this.setEventXY(this.curCoords, this.pointers);
	            }

	            var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x
	                                 && this.curCoords.page.y === this.prevCoords.page.y
	                                 && this.curCoords.client.x === this.prevCoords.client.x
	                                 && this.curCoords.client.y === this.prevCoords.client.y);

	            var dx, dy,
	                pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));

	            // register movement greater than pointerMoveTolerance
	            if (this.pointerIsDown && !this.pointerWasMoved) {
	                dx = this.curCoords.client.x - this.startCoords.client.x;
	                dy = this.curCoords.client.y - this.startCoords.client.y;

	                this.pointerWasMoved = hypot(dx, dy) > pointerMoveTolerance;
	            }

	            if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) {
	                if (this.pointerIsDown) {
	                    clearTimeout(this.holdTimers[pointerIndex]);
	                }

	                this.collectEventTargets(pointer, event, eventTarget, 'move');
	            }

	            if (!this.pointerIsDown) { return; }

	            if (duplicateMove && this.pointerWasMoved && !preEnd) {
	                this.checkAndPreventDefault(event, this.target, this.element);
	                return;
	            }

	            // set pointer coordinate, time changes and speeds
	            setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);

	            if (!this.prepared.name) { return; }

	            if (this.pointerWasMoved
	                // ignore movement while inertia is active
	                && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) {

	                // if just starting an action, calculate the pointer speed now
	                if (!this.interacting()) {
	                    setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);

	                    // check if a drag is in the correct axis
	                    if (this.prepared.name === 'drag') {
	                        var absX = Math.abs(dx),
	                            absY = Math.abs(dy),
	                            targetAxis = this.target.options.drag.axis,
	                            axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy');

	                        // if the movement isn't in the axis of the interactable
	                        if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) {
	                            // cancel the prepared action
	                            this.prepared.name = null;

	                            // then try to get a drag from another ineractable

	                            var element = eventTarget;

	                            // check element interactables
	                            while (isElement(element)) {
	                                var elementInteractable = interactables.get(element);

	                                if (elementInteractable
	                                    && elementInteractable !== this.target
	                                    && !elementInteractable.options.drag.manualStart
	                                    && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag'
	                                    && checkAxis(axis, elementInteractable)) {

	                                    this.prepared.name = 'drag';
	                                    this.target = elementInteractable;
	                                    this.element = element;
	                                    break;
	                                }

	                                element = parentElement(element);
	                            }

	                            // if there's no drag from element interactables,
	                            // check the selector interactables
	                            if (!this.prepared.name) {
	                                var thisInteraction = this;

	                                var getDraggable = function (interactable, selector, context) {
	                                    var elements = ie8MatchesSelector
	                                        ? context.querySelectorAll(selector)
	                                        : undefined;

	                                    if (interactable === thisInteraction.target) { return; }

	                                    if (inContext(interactable, eventTarget)
	                                        && !interactable.options.drag.manualStart
	                                        && !testIgnore(interactable, element, eventTarget)
	                                        && testAllow(interactable, element, eventTarget)
	                                        && matchesSelector(element, selector, elements)
	                                        && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag'
	                                        && checkAxis(axis, interactable)
	                                        && withinInteractionLimit(interactable, element, 'drag')) {

	                                        return interactable;
	                                    }
	                                };

	                                element = eventTarget;

	                                while (isElement(element)) {
	                                    var selectorInteractable = interactables.forEachSelector(getDraggable);

	                                    if (selectorInteractable) {
	                                        this.prepared.name = 'drag';
	                                        this.target = selectorInteractable;
	                                        this.element = element;
	                                        break;
	                                    }

	                                    element = parentElement(element);
	                                }
	                            }
	                        }
	                    }
	                }

	                var starting = !!this.prepared.name && !this.interacting();

	                if (starting
	                    && (this.target.options[this.prepared.name].manualStart
	                        || !withinInteractionLimit(this.target, this.element, this.prepared))) {
	                    this.stop(event);
	                    return;
	                }

	                if (this.prepared.name && this.target) {
	                    if (starting) {
	                        this.start(this.prepared, this.target, this.element);
	                    }

	                    var shouldMove = this.setModifications(this.curCoords.page, preEnd);

	                    // move if snapping or restriction doesn't prevent it
	                    if (shouldMove || starting) {
	                        this.prevEvent = this[this.prepared.name + 'Move'](event);
	                    }

	                    this.checkAndPreventDefault(event, this.target, this.element);
	                }
	            }

	            copyCoords(this.prevCoords, this.curCoords);

	            if (this.dragging || this.resizing) {
	                this.autoScrollMove(pointer);
	            }
	        },

	        dragStart: function (event) {
	            var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element);

	            this.dragging = true;
	            this.target.fire(dragEvent);

	            // reset active dropzones
	            this.activeDrops.dropzones = [];
	            this.activeDrops.elements  = [];
	            this.activeDrops.rects     = [];

	            if (!this.dynamicDrop) {
	                this.setActiveDrops(this.element);
	            }

	            var dropEvents = this.getDropEvents(event, dragEvent);

	            if (dropEvents.activate) {
	                this.fireActiveDrops(dropEvents.activate);
	            }

	            return dragEvent;
	        },

	        dragMove: function (event) {
	            var target = this.target,
	                dragEvent  = new InteractEvent(this, event, 'drag', 'move', this.element),
	                draggableElement = this.element,
	                drop = this.getDrop(dragEvent, event, draggableElement);

	            this.dropTarget = drop.dropzone;
	            this.dropElement = drop.element;

	            var dropEvents = this.getDropEvents(event, dragEvent);

	            target.fire(dragEvent);

	            if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }
	            if (dropEvents.enter) {     this.dropTarget.fire(dropEvents.enter); }
	            if (dropEvents.move ) {     this.dropTarget.fire(dropEvents.move ); }

	            this.prevDropTarget  = this.dropTarget;
	            this.prevDropElement = this.dropElement;

	            return dragEvent;
	        },

	        resizeStart: function (event) {
	            var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element);

	            if (this.prepared.edges) {
	                var startRect = this.target.getRect(this.element);

	                /*
	                 * When using the `resizable.square` or `resizable.preserveAspectRatio` options, resizing from one edge
	                 * will affect another. E.g. with `resizable.square`, resizing to make the right edge larger will make
	                 * the bottom edge larger by the same amount. We call these 'linked' edges. Any linked edges will depend
	                 * on the active edges and the edge being interacted with.
	                 */
	                if (this.target.options.resize.square || this.target.options.resize.preserveAspectRatio) {
	                    var linkedEdges = extend({}, this.prepared.edges);

	                    linkedEdges.top    = linkedEdges.top    || (linkedEdges.left   && !linkedEdges.bottom);
	                    linkedEdges.left   = linkedEdges.left   || (linkedEdges.top    && !linkedEdges.right );
	                    linkedEdges.bottom = linkedEdges.bottom || (linkedEdges.right  && !linkedEdges.top   );
	                    linkedEdges.right  = linkedEdges.right  || (linkedEdges.bottom && !linkedEdges.left  );

	                    this.prepared._linkedEdges = linkedEdges;
	                }
	                else {
	                    this.prepared._linkedEdges = null;
	                }

	                // if using `resizable.preserveAspectRatio` option, record aspect ratio at the start of the resize
	                if (this.target.options.resize.preserveAspectRatio) {
	                    this.resizeStartAspectRatio = startRect.width / startRect.height;
	                }

	                this.resizeRects = {
	                    start     : startRect,
	                    current   : extend({}, startRect),
	                    restricted: extend({}, startRect),
	                    previous  : extend({}, startRect),
	                    delta     : {
	                        left: 0, right : 0, width : 0,
	                        top : 0, bottom: 0, height: 0
	                    }
	                };

	                resizeEvent.rect = this.resizeRects.restricted;
	                resizeEvent.deltaRect = this.resizeRects.delta;
	            }

	            this.target.fire(resizeEvent);

	            this.resizing = true;

	            return resizeEvent;
	        },

	        resizeMove: function (event) {
	            var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element);

	            var edges = this.prepared.edges,
	                invert = this.target.options.resize.invert,
	                invertible = invert === 'reposition' || invert === 'negate';

	            if (edges) {
	                var dx = resizeEvent.dx,
	                    dy = resizeEvent.dy,

	                    start      = this.resizeRects.start,
	                    current    = this.resizeRects.current,
	                    restricted = this.resizeRects.restricted,
	                    delta      = this.resizeRects.delta,
	                    previous   = extend(this.resizeRects.previous, restricted),

	                    originalEdges = edges;

	                // `resize.preserveAspectRatio` takes precedence over `resize.square`
	                if (this.target.options.resize.preserveAspectRatio) {
	                    var resizeStartAspectRatio = this.resizeStartAspectRatio;

	                    edges = this.prepared._linkedEdges;

	                    if ((originalEdges.left && originalEdges.bottom)
	                        || (originalEdges.right && originalEdges.top)) {
	                        dy = -dx / resizeStartAspectRatio;
	                    }
	                    else if (originalEdges.left || originalEdges.right) { dy = dx / resizeStartAspectRatio; }
	                    else if (originalEdges.top || originalEdges.bottom) { dx = dy * resizeStartAspectRatio; }
	                }
	                else if (this.target.options.resize.square) {
	                    edges = this.prepared._linkedEdges;

	                    if ((originalEdges.left && originalEdges.bottom)
	                        || (originalEdges.right && originalEdges.top)) {
	                        dy = -dx;
	                    }
	                    else if (originalEdges.left || originalEdges.right) { dy = dx; }
	                    else if (originalEdges.top || originalEdges.bottom) { dx = dy; }
	                }

	                // update the 'current' rect without modifications
	                if (edges.top   ) { current.top    += dy; }
	                if (edges.bottom) { current.bottom += dy; }
	                if (edges.left  ) { current.left   += dx; }
	                if (edges.right ) { current.right  += dx; }

	                if (invertible) {
	                    // if invertible, copy the current rect
	                    extend(restricted, current);

	                    if (invert === 'reposition') {
	                        // swap edge values if necessary to keep width/height positive
	                        var swap;

	                        if (restricted.top > restricted.bottom) {
	                            swap = restricted.top;

	                            restricted.top = restricted.bottom;
	                            restricted.bottom = swap;
	                        }
	                        if (restricted.left > restricted.right) {
	                            swap = restricted.left;

	                            restricted.left = restricted.right;
	                            restricted.right = swap;
	                        }
	                    }
	                }
	                else {
	                    // if not invertible, restrict to minimum of 0x0 rect
	                    restricted.top    = Math.min(current.top, start.bottom);
	                    restricted.bottom = Math.max(current.bottom, start.top);
	                    restricted.left   = Math.min(current.left, start.right);
	                    restricted.right  = Math.max(current.right, start.left);
	                }

	                restricted.width  = restricted.right  - restricted.left;
	                restricted.height = restricted.bottom - restricted.top ;

	                for (var edge in restricted) {
	                    delta[edge] = restricted[edge] - previous[edge];
	                }

	                resizeEvent.edges = this.prepared.edges;
	                resizeEvent.rect = restricted;
	                resizeEvent.deltaRect = delta;
	            }

	            this.target.fire(resizeEvent);

	            return resizeEvent;
	        },

	        gestureStart: function (event) {
	            var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element);

	            gestureEvent.ds = 0;

	            this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance;
	            this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle;
	            this.gesture.scale = 1;

	            this.gesturing = true;

	            this.target.fire(gestureEvent);

	            return gestureEvent;
	        },

	        gestureMove: function (event) {
	            if (!this.pointerIds.length) {
	                return this.prevEvent;
	            }

	            var gestureEvent;

	            gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element);
	            gestureEvent.ds = gestureEvent.scale - this.gesture.scale;

	            this.target.fire(gestureEvent);

	            this.gesture.prevAngle = gestureEvent.angle;
	            this.gesture.prevDistance = gestureEvent.distance;

	            if (gestureEvent.scale !== Infinity &&
	                gestureEvent.scale !== null &&
	                gestureEvent.scale !== undefined  &&
	                !isNaN(gestureEvent.scale)) {

	                this.gesture.scale = gestureEvent.scale;
	            }

	            return gestureEvent;
	        },

	        pointerHold: function (pointer, event, eventTarget) {
	            this.collectEventTargets(pointer, event, eventTarget, 'hold');
	        },

	        pointerUp: function (pointer, event, eventTarget, curEventTarget) {
	            var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));

	            clearTimeout(this.holdTimers[pointerIndex]);

	            this.collectEventTargets(pointer, event, eventTarget, 'up' );
	            this.collectEventTargets(pointer, event, eventTarget, 'tap');

	            this.pointerEnd(pointer, event, eventTarget, curEventTarget);

	            this.removePointer(pointer);
	        },

	        pointerCancel: function (pointer, event, eventTarget, curEventTarget) {
	            var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));

	            clearTimeout(this.holdTimers[pointerIndex]);

	            this.collectEventTargets(pointer, event, eventTarget, 'cancel');
	            this.pointerEnd(pointer, event, eventTarget, curEventTarget);

	            this.removePointer(pointer);
	        },

	        // http://www.quirksmode.org/dom/events/click.html
	        // >Events leading to dblclick
	        //
	        // IE8 doesn't fire down event before dblclick.
	        // This workaround tries to fire a tap and doubletap after dblclick
	        ie8Dblclick: function (pointer, event, eventTarget) {
	            if (this.prevTap
	                && event.clientX === this.prevTap.clientX
	                && event.clientY === this.prevTap.clientY
	                && eventTarget   === this.prevTap.target) {

	                this.downTargets[0] = eventTarget;
	                this.downTimes[0] = new Date().getTime();
	                this.collectEventTargets(pointer, event, eventTarget, 'tap');
	            }
	        },

	        // End interact move events and stop auto-scroll unless inertia is enabled
	        pointerEnd: function (pointer, event, eventTarget, curEventTarget) {
	            var endEvent,
	                target = this.target,
	                options = target && target.options,
	                inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia,
	                inertiaStatus = this.inertiaStatus;

	            if (this.interacting()) {

	                if (inertiaStatus.active && !inertiaStatus.ending) { return; }

	                var pointerSpeed,
	                    now = new Date().getTime(),
	                    inertiaPossible = false,
	                    inertia = false,
	                    smoothEnd = false,
	                    endSnap = checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly,
	                    endRestrict = checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly,
	                    dx = 0,
	                    dy = 0,
	                    startEvent;

	                if (this.dragging) {
	                    if      (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); }
	                    else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); }
	                    else   /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; }
	                }
	                else {
	                    pointerSpeed = this.pointerDelta.client.speed;
	                }

	                // check if inertia should be started
	                inertiaPossible = (inertiaOptions && inertiaOptions.enabled
	                                   && this.prepared.name !== 'gesture'
	                                   && event !== inertiaStatus.startEvent);

	                inertia = (inertiaPossible
	                           && (now - this.curCoords.timeStamp) < 50
	                           && pointerSpeed > inertiaOptions.minSpeed
	                           && pointerSpeed > inertiaOptions.endSpeed);

	                if (inertiaPossible && !inertia && (endSnap || endRestrict)) {

	                    var snapRestrict = {};

	                    snapRestrict.snap = snapRestrict.restrict = snapRestrict;

	                    if (endSnap) {
	                        this.setSnapping(this.curCoords.page, snapRestrict);
	                        if (snapRestrict.locked) {
	                            dx += snapRestrict.dx;
	                            dy += snapRestrict.dy;
	                        }
	                    }

	                    if (endRestrict) {
	                        this.setRestriction(this.curCoords.page, snapRestrict);
	                        if (snapRestrict.restricted) {
	                            dx += snapRestrict.dx;
	                            dy += snapRestrict.dy;
	                        }
	                    }

	                    if (dx || dy) {
	                        smoothEnd = true;
	                    }
	                }

	                if (inertia || smoothEnd) {
	                    copyCoords(inertiaStatus.upCoords, this.curCoords);

	                    this.pointers[0] = inertiaStatus.startEvent = startEvent =
	                        new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element);

	                    inertiaStatus.t0 = now;

	                    target.fire(inertiaStatus.startEvent);

	                    if (inertia) {
	                        inertiaStatus.vx0 = this.pointerDelta.client.vx;
	                        inertiaStatus.vy0 = this.pointerDelta.client.vy;
	                        inertiaStatus.v0 = pointerSpeed;

	                        this.calcInertia(inertiaStatus);

	                        var page = extend({}, this.curCoords.page),
	                            origin = getOriginXY(target, this.element),
	                            statusObject;

	                        page.x = page.x + inertiaStatus.xe - origin.x;
	                        page.y = page.y + inertiaStatus.ye - origin.y;

	                        statusObject = {
	                            useStatusXY: true,
	                            x: page.x,
	                            y: page.y,
	                            dx: 0,
	                            dy: 0,
	                            snap: null
	                        };

	                        statusObject.snap = statusObject;

	                        dx = dy = 0;

	                        if (endSnap) {
	                            var snap = this.setSnapping(this.curCoords.page, statusObject);

	                            if (snap.locked) {
	                                dx += snap.dx;
	                                dy += snap.dy;
	                            }
	                        }

	                        if (endRestrict) {
	                            var restrict = this.setRestriction(this.curCoords.page, statusObject);

	                            if (restrict.restricted) {
	                                dx += restrict.dx;
	                                dy += restrict.dy;
	                            }
	                        }

	                        inertiaStatus.modifiedXe += dx;
	                        inertiaStatus.modifiedYe += dy;

	                        inertiaStatus.i = reqFrame(this.boundInertiaFrame);
	                    }
	                    else {
	                        inertiaStatus.smoothEnd = true;
	                        inertiaStatus.xe = dx;
	                        inertiaStatus.ye = dy;

	                        inertiaStatus.sx = inertiaStatus.sy = 0;

	                        inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
	                    }

	                    inertiaStatus.active = true;
	                    return;
	                }

	                if (endSnap || endRestrict) {
	                    // fire a move event at the snapped coordinates
	                    this.pointerMove(pointer, event, eventTarget, curEventTarget, true);
	                }
	            }

	            if (this.dragging) {
	                endEvent = new InteractEvent(this, event, 'drag', 'end', this.element);

	                var draggableElement = this.element,
	                    drop = this.getDrop(endEvent, event, draggableElement);

	                this.dropTarget = drop.dropzone;
	                this.dropElement = drop.element;

	                var dropEvents = this.getDropEvents(event, endEvent);

	                if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }
	                if (dropEvents.enter) {     this.dropTarget.fire(dropEvents.enter); }
	                if (dropEvents.drop ) {     this.dropTarget.fire(dropEvents.drop ); }
	                if (dropEvents.deactivate) {
	                    this.fireActiveDrops(dropEvents.deactivate);
	                }

	                target.fire(endEvent);
	            }
	            else if (this.resizing) {
	                endEvent = new InteractEvent(this, event, 'resize', 'end', this.element);
	                target.fire(endEvent);
	            }
	            else if (this.gesturing) {
	                endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element);
	                target.fire(endEvent);
	            }

	            this.stop(event);
	        },

	        collectDrops: function (element) {
	            var drops = [],
	                elements = [],
	                i;

	            element = element || this.element;

	            // collect all dropzones and their elements which qualify for a drop
	            for (i = 0; i < interactables.length; i++) {
	                if (!interactables[i].options.drop.enabled) { continue; }

	                var current = interactables[i],
	                    accept = current.options.drop.accept;

	                // test the draggable element against the dropzone's accept setting
	                if ((isElement(accept) && accept !== element)
	                    || (isString(accept)
	                        && !matchesSelector(element, accept))) {

	                    continue;
	                }

	                // query for new elements if necessary
	                var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element];

	                for (var j = 0, len = dropElements.length; j < len; j++) {
	                    var currentElement = dropElements[j];

	                    if (currentElement === element) {
	                        continue;
	                    }

	                    drops.push(current);
	                    elements.push(currentElement);
	                }
	            }

	            return {
	                dropzones: drops,
	                elements: elements
	            };
	        },

	        fireActiveDrops: function (event) {
	            var i,
	                current,
	                currentElement,
	                prevElement;

	            // loop through all active dropzones and trigger event
	            for (i = 0; i < this.activeDrops.dropzones.length; i++) {
	                current = this.activeDrops.dropzones[i];
	                currentElement = this.activeDrops.elements [i];

	                // prevent trigger of duplicate events on same element
	                if (currentElement !== prevElement) {
	                    // set current element as event target
	                    event.target = currentElement;
	                    current.fire(event);
	                }
	                prevElement = currentElement;
	            }
	        },

	        // Collect a new set of possible drops and save them in activeDrops.
	        // setActiveDrops should always be called when a drag has just started or a
	        // drag event happens while dynamicDrop is true
	        setActiveDrops: function (dragElement) {
	            // get dropzones and their elements that could receive the draggable
	            var possibleDrops = this.collectDrops(dragElement, true);

	            this.activeDrops.dropzones = possibleDrops.dropzones;
	            this.activeDrops.elements  = possibleDrops.elements;
	            this.activeDrops.rects     = [];

	            for (var i = 0; i < this.activeDrops.dropzones.length; i++) {
	                this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]);
	            }
	        },

	        getDrop: function (dragEvent, event, dragElement) {
	            var validDrops = [];

	            if (dynamicDrop) {
	                this.setActiveDrops(dragElement);
	            }

	            // collect all dropzones and their elements which qualify for a drop
	            for (var j = 0; j < this.activeDrops.dropzones.length; j++) {
	                var current        = this.activeDrops.dropzones[j],
	                    currentElement = this.activeDrops.elements [j],
	                    rect           = this.activeDrops.rects    [j];

	                validDrops.push(current.dropCheck(dragEvent, event, this.target, dragElement, currentElement, rect)
	                                ? currentElement
	                                : null);
	            }

	            // get the most appropriate dropzone based on DOM depth and order
	            var dropIndex = indexOfDeepestElement(validDrops),
	                dropzone  = this.activeDrops.dropzones[dropIndex] || null,
	                element   = this.activeDrops.elements [dropIndex] || null;

	            return {
	                dropzone: dropzone,
	                element: element
	            };
	        },

	        getDropEvents: function (pointerEvent, dragEvent) {
	            var dropEvents = {
	                enter     : null,
	                leave     : null,
	                activate  : null,
	                deactivate: null,
	                move      : null,
	                drop      : null
	            };

	            if (this.dropElement !== this.prevDropElement) {
	                // if there was a prevDropTarget, create a dragleave event
	                if (this.prevDropTarget) {
	                    dropEvents.leave = {
	                        target       : this.prevDropElement,
	                        dropzone     : this.prevDropTarget,
	                        relatedTarget: dragEvent.target,
	                        draggable    : dragEvent.interactable,
	                        dragEvent    : dragEvent,
	                        interaction  : this,
	                        timeStamp    : dragEvent.timeStamp,
	                        type         : 'dragleave'
	                    };

	                    dragEvent.dragLeave = this.prevDropElement;
	                    dragEvent.prevDropzone = this.prevDropTarget;
	                }
	                // if the dropTarget is not null, create a dragenter event
	                if (this.dropTarget) {
	                    dropEvents.enter = {
	                        target       : this.dropElement,
	                        dropzone     : this.dropTarget,
	                        relatedTarget: dragEvent.target,
	                        draggable    : dragEvent.interactable,
	                        dragEvent    : dragEvent,
	                        interaction  : this,
	                        timeStamp    : dragEvent.timeStamp,
	                        type         : 'dragenter'
	                    };

	                    dragEvent.dragEnter = this.dropElement;
	                    dragEvent.dropzone = this.dropTarget;
	                }
	            }

	            if (dragEvent.type === 'dragend' && this.dropTarget) {
	                dropEvents.drop = {
	                    target       : this.dropElement,
	                    dropzone     : this.dropTarget,
	                    relatedTarget: dragEvent.target,
	                    draggable    : dragEvent.interactable,
	                    dragEvent    : dragEvent,
	                    interaction  : this,
	                    timeStamp    : dragEvent.timeStamp,
	                    type         : 'drop'
	                };

	                dragEvent.dropzone = this.dropTarget;
	            }
	            if (dragEvent.type === 'dragstart') {
	                dropEvents.activate = {
	                    target       : null,
	                    dropzone     : null,
	                    relatedTarget: dragEvent.target,
	                    draggable    : dragEvent.interactable,
	                    dragEvent    : dragEvent,
	                    interaction  : this,
	                    timeStamp    : dragEvent.timeStamp,
	                    type         : 'dropactivate'
	                };
	            }
	            if (dragEvent.type === 'dragend') {
	                dropEvents.deactivate = {
	                    target       : null,
	                    dropzone     : null,
	                    relatedTarget: dragEvent.target,
	                    draggable    : dragEvent.interactable,
	                    dragEvent    : dragEvent,
	                    interaction  : this,
	                    timeStamp    : dragEvent.timeStamp,
	                    type         : 'dropdeactivate'
	                };
	            }
	            if (dragEvent.type === 'dragmove' && this.dropTarget) {
	                dropEvents.move = {
	                    target       : this.dropElement,
	                    dropzone     : this.dropTarget,
	                    relatedTarget: dragEvent.target,
	                    draggable    : dragEvent.interactable,
	                    dragEvent    : dragEvent,
	                    interaction  : this,
	                    dragmove     : dragEvent,
	                    timeStamp    : dragEvent.timeStamp,
	                    type         : 'dropmove'
	                };
	                dragEvent.dropzone = this.dropTarget;
	            }

	            return dropEvents;
	        },

	        currentAction: function () {
	            return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null;
	        },

	        interacting: function () {
	            return this.dragging || this.resizing || this.gesturing;
	        },

	        clearTargets: function () {
	            this.target = this.element = null;

	            this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null;
	        },

	        stop: function (event) {
	            if (this.interacting()) {
	                autoScroll.stop();
	                this.matches = [];
	                this.matchElements = [];

	                var target = this.target;

	                if (target.options.styleCursor) {
	                    target._doc.documentElement.style.cursor = '';
	                }

	                // prevent Default only if were previously interacting
	                if (event && isFunction(event.preventDefault)) {
	                    this.checkAndPreventDefault(event, target, this.element);
	                }

	                if (this.dragging) {
	                    this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null;
	                }
	            }

	            this.clearTargets();

	            this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false;
	            this.prepared.name = this.prevEvent = null;
	            this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0;

	            // remove pointers if their ID isn't in this.pointerIds
	            for (var i = 0; i < this.pointers.length; i++) {
	                if (indexOf(this.pointerIds, getPointerId(this.pointers[i])) === -1) {
	                    this.pointers.splice(i, 1);
	                }
	            }
	        },

	        inertiaFrame: function () {
	            var inertiaStatus = this.inertiaStatus,
	                options = this.target.options[this.prepared.name].inertia,
	                lambda = options.resistance,
	                t = new Date().getTime() / 1000 - inertiaStatus.t0;

	            if (t < inertiaStatus.te) {

	                var progress =  1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0;

	                if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) {
	                    inertiaStatus.sx = inertiaStatus.xe * progress;
	                    inertiaStatus.sy = inertiaStatus.ye * progress;
	                }
	                else {
	                    var quadPoint = getQuadraticCurvePoint(
	                            0, 0,
	                            inertiaStatus.xe, inertiaStatus.ye,
	                            inertiaStatus.modifiedXe, inertiaStatus.modifiedYe,
	                            progress);

	                    inertiaStatus.sx = quadPoint.x;
	                    inertiaStatus.sy = quadPoint.y;
	                }

	                this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);

	                inertiaStatus.i = reqFrame(this.boundInertiaFrame);
	            }
	            else {
	                inertiaStatus.ending = true;

	                inertiaStatus.sx = inertiaStatus.modifiedXe;
	                inertiaStatus.sy = inertiaStatus.modifiedYe;

	                this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
	                this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);

	                inertiaStatus.active = inertiaStatus.ending = false;
	            }
	        },

	        smoothEndFrame: function () {
	            var inertiaStatus = this.inertiaStatus,
	                t = new Date().getTime() - inertiaStatus.t0,
	                duration = this.target.options[this.prepared.name].inertia.smoothEndDuration;

	            if (t < duration) {
	                inertiaStatus.sx = easeOutQuad(t, 0, inertiaStatus.xe, duration);
	                inertiaStatus.sy = easeOutQuad(t, 0, inertiaStatus.ye, duration);

	                this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);

	                inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
	            }
	            else {
	                inertiaStatus.ending = true;

	                inertiaStatus.sx = inertiaStatus.xe;
	                inertiaStatus.sy = inertiaStatus.ye;

	                this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
	                this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);

	                inertiaStatus.smoothEnd =
	                  inertiaStatus.active = inertiaStatus.ending = false;
	            }
	        },

	        addPointer: function (pointer) {
	            var id = getPointerId(pointer),
	                index = this.mouse? 0 : indexOf(this.pointerIds, id);

	            if (index === -1) {
	                index = this.pointerIds.length;
	            }

	            this.pointerIds[index] = id;
	            this.pointers[index] = pointer;

	            return index;
	        },

	        removePointer: function (pointer) {
	            var id = getPointerId(pointer),
	                index = this.mouse? 0 : indexOf(this.pointerIds, id);

	            if (index === -1) { return; }

	            this.pointers   .splice(index, 1);
	            this.pointerIds .splice(index, 1);
	            this.downTargets.splice(index, 1);
	            this.downTimes  .splice(index, 1);
	            this.holdTimers .splice(index, 1);
	        },

	        recordPointer: function (pointer) {
	            var index = this.mouse? 0: indexOf(this.pointerIds, getPointerId(pointer));

	            if (index === -1) { return; }

	            this.pointers[index] = pointer;
	        },

	        collectEventTargets: function (pointer, event, eventTarget, eventType) {
	            var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));

	            // do not fire a tap event if the pointer was moved before being lifted
	            if (eventType === 'tap' && (this.pointerWasMoved
	                // or if the pointerup target is different to the pointerdown target
	                || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) {
	                return;
	            }

	            var targets = [],
	                elements = [],
	                element = eventTarget;

	            function collectSelectors (interactable, selector, context) {
	                var els = ie8MatchesSelector
	                        ? context.querySelectorAll(selector)
	                        : undefined;

	                if (interactable._iEvents[eventType]
	                    && isElement(element)
	                    && inContext(interactable, element)
	                    && !testIgnore(interactable, element, eventTarget)
	                    && testAllow(interactable, element, eventTarget)
	                    && matchesSelector(element, selector, els)) {

	                    targets.push(interactable);
	                    elements.push(element);
	                }
	            }

	            while (element) {
	                if (interact.isSet(element) && interact(element)._iEvents[eventType]) {
	                    targets.push(interact(element));
	                    elements.push(element);
	                }

	                interactables.forEachSelector(collectSelectors);

	                element = parentElement(element);
	            }

	            // create the tap event even if there are no listeners so that
	            // doubletap can still be created and fired
	            if (targets.length || eventType === 'tap') {
	                this.firePointers(pointer, event, eventTarget, targets, elements, eventType);
	            }
	        },

	        firePointers: function (pointer, event, eventTarget, targets, elements, eventType) {
	            var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)),
	                pointerEvent = {},
	                i,
	                // for tap events
	                interval, createNewDoubleTap;

	            // if it's a doubletap then the event properties would have been
	            // copied from the tap event and provided as the pointer argument
	            if (eventType === 'doubletap') {
	                pointerEvent = pointer;
	            }
	            else {
	                pointerExtend(pointerEvent, event);
	                if (event !== pointer) {
	                    pointerExtend(pointerEvent, pointer);
	                }

	                pointerEvent.preventDefault           = preventOriginalDefault;
	                pointerEvent.stopPropagation          = InteractEvent.prototype.stopPropagation;
	                pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation;
	                pointerEvent.interaction              = this;

	                pointerEvent.timeStamp       = new Date().getTime();
	                pointerEvent.originalEvent   = event;
	                pointerEvent.originalPointer = pointer;
	                pointerEvent.type            = eventType;
	                pointerEvent.pointerId       = getPointerId(pointer);
	                pointerEvent.pointerType     = this.mouse? 'mouse' : !supportsPointerEvent? 'touch'
	                                                    : isString(pointer.pointerType)
	                                                        ? pointer.pointerType
	                                                        : [,,'touch', 'pen', 'mouse'][pointer.pointerType];
	            }

	            if (eventType === 'tap') {
	                pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex];

	                interval = pointerEvent.timeStamp - this.tapTime;
	                createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap'
	                       && this.prevTap.target === pointerEvent.target
	                       && interval < 500);

	                pointerEvent.double = createNewDoubleTap;

	                this.tapTime = pointerEvent.timeStamp;
	            }

	            for (i = 0; i < targets.length; i++) {
	                pointerEvent.currentTarget = elements[i];
	                pointerEvent.interactable = targets[i];
	                targets[i].fire(pointerEvent);

	                if (pointerEvent.immediatePropagationStopped
	                    ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) {
	                    break;
	                }
	            }

	            if (createNewDoubleTap) {
	                var doubleTap = {};

	                extend(doubleTap, pointerEvent);

	                doubleTap.dt   = interval;
	                doubleTap.type = 'doubletap';

	                this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap');

	                this.prevTap = doubleTap;
	            }
	            else if (eventType === 'tap') {
	                this.prevTap = pointerEvent;
	            }
	        },

	        validateSelector: function (pointer, event, matches, matchElements) {
	            for (var i = 0, len = matches.length; i < len; i++) {
	                var match = matches[i],
	                    matchElement = matchElements[i],
	                    action = validateAction(match.getAction(pointer, event, this, matchElement), match);

	                if (action && withinInteractionLimit(match, matchElement, action)) {
	                    this.target = match;
	                    this.element = matchElement;

	                    return action;
	                }
	            }
	        },

	        setSnapping: function (pageCoords, status) {
	            var snap = this.target.options[this.prepared.name].snap,
	                targets = [],
	                target,
	                page,
	                i;

	            status = status || this.snapStatus;

	            if (status.useStatusXY) {
	                page = { x: status.x, y: status.y };
	            }
	            else {
	                var origin = getOriginXY(this.target, this.element);

	                page = extend({}, pageCoords);

	                page.x -= origin.x;
	                page.y -= origin.y;
	            }

	            status.realX = page.x;
	            status.realY = page.y;

	            page.x = page.x - this.inertiaStatus.resumeDx;
	            page.y = page.y - this.inertiaStatus.resumeDy;

	            var len = snap.targets? snap.targets.length : 0;

	            for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) {
	                var relative = {
	                    x: page.x - this.snapOffsets[relIndex].x,
	                    y: page.y - this.snapOffsets[relIndex].y
	                };

	                for (i = 0; i < len; i++) {
	                    if (isFunction(snap.targets[i])) {
	                        target = snap.targets[i](relative.x, relative.y, this);
	                    }
	                    else {
	                        target = snap.targets[i];
	                    }

	                    if (!target) { continue; }

	                    targets.push({
	                        x: isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x,
	                        y: isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y,

	                        range: isNumber(target.range)? target.range: snap.range
	                    });
	                }
	            }

	            var closest = {
	                    target: null,
	                    inRange: false,
	                    distance: 0,
	                    range: 0,
	                    dx: 0,
	                    dy: 0
	                };

	            for (i = 0, len = targets.length; i < len; i++) {
	                target = targets[i];

	                var range = target.range,
	                    dx = target.x - page.x,
	                    dy = target.y - page.y,
	                    distance = hypot(dx, dy),
	                    inRange = distance <= range;

	                // Infinite targets count as being out of range
	                // compared to non infinite ones that are in range
	                if (range === Infinity && closest.inRange && closest.range !== Infinity) {
	                    inRange = false;
	                }

	                if (!closest.target || (inRange
	                    // is the closest target in range?
	                    ? (closest.inRange && range !== Infinity
	                        // the pointer is relatively deeper in this target
	                        ? distance / range < closest.distance / closest.range
	                        // this target has Infinite range and the closest doesn't
	                        : (range === Infinity && closest.range !== Infinity)
	                            // OR this target is closer that the previous closest
	                            || distance < closest.distance)
	                    // The other is not in range and the pointer is closer to this target
	                    : (!closest.inRange && distance < closest.distance))) {

	                    if (range === Infinity) {
	                        inRange = true;
	                    }

	                    closest.target = target;
	                    closest.distance = distance;
	                    closest.range = range;
	                    closest.inRange = inRange;
	                    closest.dx = dx;
	                    closest.dy = dy;

	                    status.range = range;
	                }
	            }

	            var snapChanged;

	            if (closest.target) {
	                snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y);

	                status.snappedX = closest.target.x;
	                status.snappedY = closest.target.y;
	            }
	            else {
	                snapChanged = true;

	                status.snappedX = NaN;
	                status.snappedY = NaN;
	            }

	            status.dx = closest.dx;
	            status.dy = closest.dy;

	            status.changed = (snapChanged || (closest.inRange && !status.locked));
	            status.locked = closest.inRange;

	            return status;
	        },

	        setRestriction: function (pageCoords, status) {
	            var target = this.target,
	                restrict = target && target.options[this.prepared.name].restrict,
	                restriction = restrict && restrict.restriction,
	                page;

	            if (!restriction) {
	                return status;
	            }

	            status = status || this.restrictStatus;

	            page = status.useStatusXY
	                    ? page = { x: status.x, y: status.y }
	                    : page = extend({}, pageCoords);

	            if (status.snap && status.snap.locked) {
	                page.x += status.snap.dx || 0;
	                page.y += status.snap.dy || 0;
	            }

	            page.x -= this.inertiaStatus.resumeDx;
	            page.y -= this.inertiaStatus.resumeDy;

	            status.dx = 0;
	            status.dy = 0;
	            status.restricted = false;

	            var rect, restrictedX, restrictedY;

	            if (isString(restriction)) {
	                if (restriction === 'parent') {
	                    restriction = parentElement(this.element);
	                }
	                else if (restriction === 'self') {
	                    restriction = target.getRect(this.element);
	                }
	                else {
	                    restriction = closest(this.element, restriction);
	                }

	                if (!restriction) { return status; }
	            }

	            if (isFunction(restriction)) {
	                restriction = restriction(page.x, page.y, this.element);
	            }

	            if (isElement(restriction)) {
	                restriction = getElementRect(restriction);
	            }

	            rect = restriction;

	            if (!restriction) {
	                restrictedX = page.x;
	                restrictedY = page.y;
	            }
	            // object is assumed to have
	            // x, y, width, height or
	            // left, top, right, bottom
	            else if ('x' in restriction && 'y' in restriction) {
	                restrictedX = Math.max(Math.min(rect.x + rect.width  - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left);
	                restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top );
	            }
	            else {
	                restrictedX = Math.max(Math.min(rect.right  - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left);
	                restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top  + this.restrictOffset.top );
	            }

	            status.dx = restrictedX - page.x;
	            status.dy = restrictedY - page.y;

	            status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY;
	            status.restricted = !!(status.dx || status.dy);

	            status.restrictedX = restrictedX;
	            status.restrictedY = restrictedY;

	            return status;
	        },

	        checkAndPreventDefault: function (event, interactable, element) {
	            if (!(interactable = interactable || this.target)) { return; }

	            var options = interactable.options,
	                prevent = options.preventDefault;

	            if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) {
	                // do not preventDefault on pointerdown if the prepared action is a drag
	                // and dragging can only start from a certain direction - this allows
	                // a touch to pan the viewport if a drag isn't in the right direction
	                if (/down|start/i.test(event.type)
	                    && this.prepared.name === 'drag' && options.drag.axis !== 'xy') {

	                    return;
	                }

	                // with manualStart, only preventDefault while interacting
	                if (options[this.prepared.name] && options[this.prepared.name].manualStart
	                    && !this.interacting()) {
	                    return;
	                }

	                event.preventDefault();
	                return;
	            }

	            if (prevent === 'always') {
	                event.preventDefault();
	                return;
	            }
	        },

	        calcInertia: function (status) {
	            var inertiaOptions = this.target.options[this.prepared.name].inertia,
	                lambda = inertiaOptions.resistance,
	                inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda;

	            status.x0 = this.prevEvent.pageX;
	            status.y0 = this.prevEvent.pageY;
	            status.t0 = status.startEvent.timeStamp / 1000;
	            status.sx = status.sy = 0;

	            status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;
	            status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;
	            status.te = inertiaDur;

	            status.lambda_v0 = lambda / status.v0;
	            status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0;
	        },

	        autoScrollMove: function (pointer) {
	            if (!(this.interacting()
	                && checkAutoScroll(this.target, this.prepared.name))) {
	                return;
	            }

	            if (this.inertiaStatus.active) {
	                autoScroll.x = autoScroll.y = 0;
	                return;
	            }

	            var top,
	                right,
	                bottom,
	                left,
	                options = this.target.options[this.prepared.name].autoScroll,
	                container = options.container || getWindow(this.element);

	            if (isWindow(container)) {
	                left   = pointer.clientX < autoScroll.margin;
	                top    = pointer.clientY < autoScroll.margin;
	                right  = pointer.clientX > container.innerWidth  - autoScroll.margin;
	                bottom = pointer.clientY > container.innerHeight - autoScroll.margin;
	            }
	            else {
	                var rect = getElementClientRect(container);

	                left   = pointer.clientX < rect.left   + autoScroll.margin;
	                top    = pointer.clientY < rect.top    + autoScroll.margin;
	                right  = pointer.clientX > rect.right  - autoScroll.margin;
	                bottom = pointer.clientY > rect.bottom - autoScroll.margin;
	            }

	            autoScroll.x = (right ? 1: left? -1: 0);
	            autoScroll.y = (bottom? 1:  top? -1: 0);

	            if (!autoScroll.isScrolling) {
	                // set the autoScroll properties to those of the target
	                autoScroll.margin = options.margin;
	                autoScroll.speed  = options.speed;

	                autoScroll.start(this);
	            }
	        },

	        _updateEventTargets: function (target, currentTarget) {
	            this._eventTarget    = target;
	            this._curEventTarget = currentTarget;
	        }

	    };

	    function getInteractionFromPointer (pointer, eventType, eventTarget) {
	        var i = 0, len = interactions.length,
	            mouseEvent = (/mouse/i.test(pointer.pointerType || eventType)
	                          // MSPointerEvent.MSPOINTER_TYPE_MOUSE
	                          || pointer.pointerType === 4),
	            interaction;

	        var id = getPointerId(pointer);

	        // try to resume inertia with a new pointer
	        if (/down|start/i.test(eventType)) {
	            for (i = 0; i < len; i++) {
	                interaction = interactions[i];

	                var element = eventTarget;

	                if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume
	                    && (interaction.mouse === mouseEvent)) {
	                    while (element) {
	                        // if the element is the interaction element
	                        if (element === interaction.element) {
	                            return interaction;
	                        }
	                        element = parentElement(element);
	                    }
	                }
	            }
	        }

	        // if it's a mouse interaction
	        if (mouseEvent || !(supportsTouch || supportsPointerEvent)) {

	            // find a mouse interaction that's not in inertia phase
	            for (i = 0; i < len; i++) {
	                if (interactions[i].mouse && !interactions[i].inertiaStatus.active) {
	                    return interactions[i];
	                }
	            }

	            // find any interaction specifically for mouse.
	            // if the eventType is a mousedown, and inertia is active
	            // ignore the interaction
	            for (i = 0; i < len; i++) {
	                if (interactions[i].mouse && !(/down/.test(eventType) && interactions[i].inertiaStatus.active)) {
	                    return interaction;
	                }
	            }

	            // create a new interaction for mouse
	            interaction = new Interaction();
	            interaction.mouse = true;

	            return interaction;
	        }

	        // get interaction that has this pointer
	        for (i = 0; i < len; i++) {
	            if (contains(interactions[i].pointerIds, id)) {
	                return interactions[i];
	            }
	        }

	        // at this stage, a pointerUp should not return an interaction
	        if (/up|end|out/i.test(eventType)) {
	            return null;
	        }

	        // get first idle interaction
	        for (i = 0; i < len; i++) {
	            interaction = interactions[i];

	            if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled))
	                && !interaction.interacting()
	                && !(!mouseEvent && interaction.mouse)) {

	                return interaction;
	            }
	        }

	        return new Interaction();
	    }

	    function doOnInteractions (method) {
	        return (function (event) {
	            var interaction,
	                eventTarget = getActualElement(event.path
	                                               ? event.path[0]
	                                               : event.target),
	                curEventTarget = getActualElement(event.currentTarget),
	                i;

	            if (supportsTouch && /touch/.test(event.type)) {
	                prevTouchTime = new Date().getTime();

	                for (i = 0; i < event.changedTouches.length; i++) {
	                    var pointer = event.changedTouches[i];

	                    interaction = getInteractionFromPointer(pointer, event.type, eventTarget);

	                    if (!interaction) { continue; }

	                    interaction._updateEventTargets(eventTarget, curEventTarget);

	                    interaction[method](pointer, event, eventTarget, curEventTarget);
	                }
	            }
	            else {
	                if (!supportsPointerEvent && /mouse/.test(event.type)) {
	                    // ignore mouse events while touch interactions are active
	                    for (i = 0; i < interactions.length; i++) {
	                        if (!interactions[i].mouse && interactions[i].pointerIsDown) {
	                            return;
	                        }
	                    }

	                    // try to ignore mouse events that are simulated by the browser
	                    // after a touch event
	                    if (new Date().getTime() - prevTouchTime < 500) {
	                        return;
	                    }
	                }

	                interaction = getInteractionFromPointer(event, event.type, eventTarget);

	                if (!interaction) { return; }

	                interaction._updateEventTargets(eventTarget, curEventTarget);

	                interaction[method](event, event, eventTarget, curEventTarget);
	            }
	        });
	    }

	    function InteractEvent (interaction, event, action, phase, element, related) {
	        var client,
	            page,
	            target      = interaction.target,
	            snapStatus  = interaction.snapStatus,
	            restrictStatus  = interaction.restrictStatus,
	            pointers    = interaction.pointers,
	            deltaSource = (target && target.options || defaultOptions).deltaSource,
	            sourceX     = deltaSource + 'X',
	            sourceY     = deltaSource + 'Y',
	            options     = target? target.options: defaultOptions,
	            origin      = getOriginXY(target, element),
	            starting    = phase === 'start',
	            ending      = phase === 'end',
	            coords      = starting? interaction.startCoords : interaction.curCoords;

	        element = element || interaction.element;

	        page   = extend({}, coords.page);
	        client = extend({}, coords.client);

	        page.x -= origin.x;
	        page.y -= origin.y;

	        client.x -= origin.x;
	        client.y -= origin.y;

	        var relativePoints = options[action].snap && options[action].snap.relativePoints ;

	        if (checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) {
	            this.snap = {
	                range  : snapStatus.range,
	                locked : snapStatus.locked,
	                x      : snapStatus.snappedX,
	                y      : snapStatus.snappedY,
	                realX  : snapStatus.realX,
	                realY  : snapStatus.realY,
	                dx     : snapStatus.dx,
	                dy     : snapStatus.dy
	            };

	            if (snapStatus.locked) {
	                page.x += snapStatus.dx;
	                page.y += snapStatus.dy;
	                client.x += snapStatus.dx;
	                client.y += snapStatus.dy;
	            }
	        }

	        if (checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) {
	            page.x += restrictStatus.dx;
	            page.y += restrictStatus.dy;
	            client.x += restrictStatus.dx;
	            client.y += restrictStatus.dy;

	            this.restrict = {
	                dx: restrictStatus.dx,
	                dy: restrictStatus.dy
	            };
	        }

	        this.pageX     = page.x;
	        this.pageY     = page.y;
	        this.clientX   = client.x;
	        this.clientY   = client.y;

	        this.x0        = interaction.startCoords.page.x - origin.x;
	        this.y0        = interaction.startCoords.page.y - origin.y;
	        this.clientX0  = interaction.startCoords.client.x - origin.x;
	        this.clientY0  = interaction.startCoords.client.y - origin.y;
	        this.ctrlKey   = event.ctrlKey;
	        this.altKey    = event.altKey;
	        this.shiftKey  = event.shiftKey;
	        this.metaKey   = event.metaKey;
	        this.button    = event.button;
	        this.buttons   = event.buttons;
	        this.target    = element;
	        this.t0        = interaction.downTimes[0];
	        this.type      = action + (phase || '');

	        this.interaction = interaction;
	        this.interactable = target;

	        var inertiaStatus = interaction.inertiaStatus;

	        if (inertiaStatus.active) {
	            this.detail = 'inertia';
	        }

	        if (related) {
	            this.relatedTarget = related;
	        }

	        // end event dx, dy is difference between start and end points
	        if (ending) {
	            if (deltaSource === 'client') {
	                this.dx = client.x - interaction.startCoords.client.x;
	                this.dy = client.y - interaction.startCoords.client.y;
	            }
	            else {
	                this.dx = page.x - interaction.startCoords.page.x;
	                this.dy = page.y - interaction.startCoords.page.y;
	            }
	        }
	        else if (starting) {
	            this.dx = 0;
	            this.dy = 0;
	        }
	        // copy properties from previousmove if starting inertia
	        else if (phase === 'inertiastart') {
	            this.dx = interaction.prevEvent.dx;
	            this.dy = interaction.prevEvent.dy;
	        }
	        else {
	            if (deltaSource === 'client') {
	                this.dx = client.x - interaction.prevEvent.clientX;
	                this.dy = client.y - interaction.prevEvent.clientY;
	            }
	            else {
	                this.dx = page.x - interaction.prevEvent.pageX;
	                this.dy = page.y - interaction.prevEvent.pageY;
	            }
	        }
	        if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia'
	            && !inertiaStatus.active
	            && options[action].inertia && options[action].inertia.zeroResumeDelta) {

	            inertiaStatus.resumeDx += this.dx;
	            inertiaStatus.resumeDy += this.dy;

	            this.dx = this.dy = 0;
	        }

	        if (action === 'resize' && interaction.resizeAxes) {
	            if (options.resize.square) {
	                if (interaction.resizeAxes === 'y') {
	                    this.dx = this.dy;
	                }
	                else {
	                    this.dy = this.dx;
	                }
	                this.axes = 'xy';
	            }
	            else {
	                this.axes = interaction.resizeAxes;

	                if (interaction.resizeAxes === 'x') {
	                    this.dy = 0;
	                }
	                else if (interaction.resizeAxes === 'y') {
	                    this.dx = 0;
	                }
	            }
	        }
	        else if (action === 'gesture') {
	            this.touches = [pointers[0], pointers[1]];

	            if (starting) {
	                this.distance = touchDistance(pointers, deltaSource);
	                this.box      = touchBBox(pointers);
	                this.scale    = 1;
	                this.ds       = 0;
	                this.angle    = touchAngle(pointers, undefined, deltaSource);
	                this.da       = 0;
	            }
	            else if (ending || event instanceof InteractEvent) {
	                this.distance = interaction.prevEvent.distance;
	                this.box      = interaction.prevEvent.box;
	                this.scale    = interaction.prevEvent.scale;
	                this.ds       = this.scale - 1;
	                this.angle    = interaction.prevEvent.angle;
	                this.da       = this.angle - interaction.gesture.startAngle;
	            }
	            else {
	                this.distance = touchDistance(pointers, deltaSource);
	                this.box      = touchBBox(pointers);
	                this.scale    = this.distance / interaction.gesture.startDistance;
	                this.angle    = touchAngle(pointers, interaction.gesture.prevAngle, deltaSource);

	                this.ds = this.scale - interaction.gesture.prevScale;
	                this.da = this.angle - interaction.gesture.prevAngle;
	            }
	        }

	        if (starting) {
	            this.timeStamp = interaction.downTimes[0];
	            this.dt        = 0;
	            this.duration  = 0;
	            this.speed     = 0;
	            this.velocityX = 0;
	            this.velocityY = 0;
	        }
	        else if (phase === 'inertiastart') {
	            this.timeStamp = interaction.prevEvent.timeStamp;
	            this.dt        = interaction.prevEvent.dt;
	            this.duration  = interaction.prevEvent.duration;
	            this.speed     = interaction.prevEvent.speed;
	            this.velocityX = interaction.prevEvent.velocityX;
	            this.velocityY = interaction.prevEvent.velocityY;
	        }
	        else {
	            this.timeStamp = new Date().getTime();
	            this.dt        = this.timeStamp - interaction.prevEvent.timeStamp;
	            this.duration  = this.timeStamp - interaction.downTimes[0];

	            if (event instanceof InteractEvent) {
	                var dx = this[sourceX] - interaction.prevEvent[sourceX],
	                    dy = this[sourceY] - interaction.prevEvent[sourceY],
	                    dt = this.dt / 1000;

	                this.speed = hypot(dx, dy) / dt;
	                this.velocityX = dx / dt;
	                this.velocityY = dy / dt;
	            }
	            // if normal move or end event, use previous user event coords
	            else {
	                // speed and velocity in pixels per second
	                this.speed = interaction.pointerDelta[deltaSource].speed;
	                this.velocityX = interaction.pointerDelta[deltaSource].vx;
	                this.velocityY = interaction.pointerDelta[deltaSource].vy;
	            }
	        }

	        if ((ending || phase === 'inertiastart')
	            && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) {

	            var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI,
	                overlap = 22.5;

	            if (angle < 0) {
	                angle += 360;
	            }

	            var left = 135 - overlap <= angle && angle < 225 + overlap,
	                up   = 225 - overlap <= angle && angle < 315 + overlap,

	                right = !left && (315 - overlap <= angle || angle <  45 + overlap),
	                down  = !up   &&   45 - overlap <= angle && angle < 135 + overlap;

	            this.swipe = {
	                up   : up,
	                down : down,
	                left : left,
	                right: right,
	                angle: angle,
	                speed: interaction.prevEvent.speed,
	                velocity: {
	                    x: interaction.prevEvent.velocityX,
	                    y: interaction.prevEvent.velocityY
	                }
	            };
	        }
	    }

	    InteractEvent.prototype = {
	        preventDefault: blank,
	        stopImmediatePropagation: function () {
	            this.immediatePropagationStopped = this.propagationStopped = true;
	        },
	        stopPropagation: function () {
	            this.propagationStopped = true;
	        }
	    };

	    function preventOriginalDefault () {
	        this.originalEvent.preventDefault();
	    }

	    function getActionCursor (action) {
	        var cursor = '';

	        if (action.name === 'drag') {
	            cursor =  actionCursors.drag;
	        }
	        if (action.name === 'resize') {
	            if (action.axis) {
	                cursor =  actionCursors[action.name + action.axis];
	            }
	            else if (action.edges) {
	                var cursorKey = 'resize',
	                    edgeNames = ['top', 'bottom', 'left', 'right'];

	                for (var i = 0; i < 4; i++) {
	                    if (action.edges[edgeNames[i]]) {
	                        cursorKey += edgeNames[i];
	                    }
	                }

	                cursor = actionCursors[cursorKey];
	            }
	        }

	        return cursor;
	    }

	    function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) {
	        // false, '', undefined, null
	        if (!value) { return false; }

	        // true value, use pointer coords and element rect
	        if (value === true) {
	            // if dimensions are negative, "switch" edges
	            var width = isNumber(rect.width)? rect.width : rect.right - rect.left,
	                height = isNumber(rect.height)? rect.height : rect.bottom - rect.top;

	            if (width < 0) {
	                if      (name === 'left' ) { name = 'right'; }
	                else if (name === 'right') { name = 'left' ; }
	            }
	            if (height < 0) {
	                if      (name === 'top'   ) { name = 'bottom'; }
	                else if (name === 'bottom') { name = 'top'   ; }
	            }

	            if (name === 'left'  ) { return page.x < ((width  >= 0? rect.left: rect.right ) + margin); }
	            if (name === 'top'   ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); }

	            if (name === 'right' ) { return page.x > ((width  >= 0? rect.right : rect.left) - margin); }
	            if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); }
	        }

	        // the remaining checks require an element
	        if (!isElement(element)) { return false; }

	        return isElement(value)
	                    // the value is an element to use as a resize handle
	                    ? value === element
	                    // otherwise check if element matches value as selector
	                    : matchesUpTo(element, value, interactableElement);
	    }

	    function defaultActionChecker (pointer, interaction, element) {
	        var rect = this.getRect(element),
	            shouldResize = false,
	            action = null,
	            resizeAxes = null,
	            resizeEdges,
	            page = extend({}, interaction.curCoords.page),
	            options = this.options;

	        if (!rect) { return null; }

	        if (actionIsEnabled.resize && options.resize.enabled) {
	            var resizeOptions = options.resize;

	            resizeEdges = {
	                left: false, right: false, top: false, bottom: false
	            };

	            // if using resize.edges
	            if (isObject(resizeOptions.edges)) {
	                for (var edge in resizeEdges) {
	                    resizeEdges[edge] = checkResizeEdge(edge,
	                                                        resizeOptions.edges[edge],
	                                                        page,
	                                                        interaction._eventTarget,
	                                                        element,
	                                                        rect,
	                                                        resizeOptions.margin || margin);
	                }

	                resizeEdges.left = resizeEdges.left && !resizeEdges.right;
	                resizeEdges.top  = resizeEdges.top  && !resizeEdges.bottom;

	                shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom;
	            }
	            else {
	                var right  = options.resize.axis !== 'y' && page.x > (rect.right  - margin),
	                    bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - margin);

	                shouldResize = right || bottom;
	                resizeAxes = (right? 'x' : '') + (bottom? 'y' : '');
	            }
	        }

	        action = shouldResize
	            ? 'resize'
	            : actionIsEnabled.drag && options.drag.enabled
	                ? 'drag'
	                : null;

	        if (actionIsEnabled.gesture
	            && interaction.pointerIds.length >=2
	            && !(interaction.dragging || interaction.resizing)) {
	            action = 'gesture';
	        }

	        if (action) {
	            return {
	                name: action,
	                axis: resizeAxes,
	                edges: resizeEdges
	            };
	        }

	        return null;
	    }

	    // Check if action is enabled globally and the current target supports it
	    // If so, return the validated action. Otherwise, return null
	    function validateAction (action, interactable) {
	        if (!isObject(action)) { return null; }

	        var actionName = action.name,
	            options = interactable.options;

	        if ((  (actionName  === 'resize'   && options.resize.enabled )
	            || (actionName      === 'drag'     && options.drag.enabled  )
	            || (actionName      === 'gesture'  && options.gesture.enabled))
	            && actionIsEnabled[actionName]) {

	            if (actionName === 'resize' || actionName === 'resizeyx') {
	                actionName = 'resizexy';
	            }

	            return action;
	        }
	        return null;
	    }

	    var listeners = {},
	        interactionListeners = [
	            'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove',
	            'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown',
	            'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd',
	            'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove'
	        ];

	    for (var i = 0, len = interactionListeners.length; i < len; i++) {
	        var name = interactionListeners[i];

	        listeners[name] = doOnInteractions(name);
	    }

	    // bound to the interactable context when a DOM event
	    // listener is added to a selector interactable
	    function delegateListener (event, useCapture) {
	        var fakeEvent = {},
	            delegated = delegatedEvents[event.type],
	            eventTarget = getActualElement(event.path
	                                           ? event.path[0]
	                                           : event.target),
	            element = eventTarget;

	        useCapture = useCapture? true: false;

	        // duplicate the event so that currentTarget can be changed
	        for (var prop in event) {
	            fakeEvent[prop] = event[prop];
	        }

	        fakeEvent.originalEvent = event;
	        fakeEvent.preventDefault = preventOriginalDefault;

	        // climb up document tree looking for selector matches
	        while (isElement(element)) {
	            for (var i = 0; i < delegated.selectors.length; i++) {
	                var selector = delegated.selectors[i],
	                    context = delegated.contexts[i];

	                if (matchesSelector(element, selector)
	                    && nodeContains(context, eventTarget)
	                    && nodeContains(context, element)) {

	                    var listeners = delegated.listeners[i];

	                    fakeEvent.currentTarget = element;

	                    for (var j = 0; j < listeners.length; j++) {
	                        if (listeners[j][1] === useCapture) {
	                            listeners[j][0](fakeEvent);
	                        }
	                    }
	                }
	            }

	            element = parentElement(element);
	        }
	    }

	    function delegateUseCapture (event) {
	        return delegateListener.call(this, event, true);
	    }

	    interactables.indexOfElement = function indexOfElement (element, context) {
	        context = context || document;

	        for (var i = 0; i < this.length; i++) {
	            var interactable = this[i];

	            if ((interactable.selector === element
	                && (interactable._context === context))
	                || (!interactable.selector && interactable._element === element)) {

	                return i;
	            }
	        }
	        return -1;
	    };

	    interactables.get = function interactableGet (element, options) {
	        return this[this.indexOfElement(element, options && options.context)];
	    };

	    interactables.forEachSelector = function (callback) {
	        for (var i = 0; i < this.length; i++) {
	            var interactable = this[i];

	            if (!interactable.selector) {
	                continue;
	            }

	            var ret = callback(interactable, interactable.selector, interactable._context, i, this);

	            if (ret !== undefined) {
	                return ret;
	            }
	        }
	    };

	    /*\
	     * interact
	     [ method ]
	     *
	     * The methods of this variable can be used to set elements as
	     * interactables and also to change various default settings.
	     *
	     * Calling it as a function and passing an element or a valid CSS selector
	     * string returns an Interactable object which has various methods to
	     * configure it.
	     *
	     - element (Element | string) The HTML or SVG Element to interact with or CSS selector
	     = (object) An @Interactable
	     *
	     > Usage
	     | interact(document.getElementById('draggable')).draggable(true);
	     |
	     | var rectables = interact('rect');
	     | rectables
	     |     .gesturable(true)
	     |     .on('gesturemove', function (event) {
	     |         // something cool...
	     |     })
	     |     .autoScroll(true);
	    \*/
	    function interact (element, options) {
	        return interactables.get(element, options) || new Interactable(element, options);
	    }

	    /*\
	     * Interactable
	     [ property ]
	     **
	     * Object type returned by @interact
	    \*/
	    function Interactable (element, options) {
	        this._element = element;
	        this._iEvents = this._iEvents || {};

	        var _window;

	        if (trySelector(element)) {
	            this.selector = element;

	            var context = options && options.context;

	            _window = context? getWindow(context) : window;

	            if (context && (_window.Node
	                    ? context instanceof _window.Node
	                    : (isElement(context) || context === _window.document))) {

	                this._context = context;
	            }
	        }
	        else {
	            _window = getWindow(element);

	            if (isElement(element, _window)) {

	                if (supportsPointerEvent) {
	                    events.add(this._element, pEventTypes.down, listeners.pointerDown );
	                    events.add(this._element, pEventTypes.move, listeners.pointerHover);
	                }
	                else {
	                    events.add(this._element, 'mousedown' , listeners.pointerDown );
	                    events.add(this._element, 'mousemove' , listeners.pointerHover);
	                    events.add(this._element, 'touchstart', listeners.pointerDown );
	                    events.add(this._element, 'touchmove' , listeners.pointerHover);
	                }
	            }
	        }

	        this._doc = _window.document;

	        if (!contains(documents, this._doc)) {
	            listenToDocument(this._doc);
	        }

	        interactables.push(this);

	        this.set(options);
	    }

	    Interactable.prototype = {
	        setOnEvents: function (action, phases) {
	            if (action === 'drop') {
	                if (isFunction(phases.ondrop)          ) { this.ondrop           = phases.ondrop          ; }
	                if (isFunction(phases.ondropactivate)  ) { this.ondropactivate   = phases.ondropactivate  ; }
	                if (isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; }
	                if (isFunction(phases.ondragenter)     ) { this.ondragenter      = phases.ondragenter     ; }
	                if (isFunction(phases.ondragleave)     ) { this.ondragleave      = phases.ondragleave     ; }
	                if (isFunction(phases.ondropmove)      ) { this.ondropmove       = phases.ondropmove      ; }
	            }
	            else {
	                action = 'on' + action;

	                if (isFunction(phases.onstart)       ) { this[action + 'start'         ] = phases.onstart         ; }
	                if (isFunction(phases.onmove)        ) { this[action + 'move'          ] = phases.onmove          ; }
	                if (isFunction(phases.onend)         ) { this[action + 'end'           ] = phases.onend           ; }
	                if (isFunction(phases.oninertiastart)) { this[action + 'inertiastart'  ] = phases.oninertiastart  ; }
	            }

	            return this;
	        },

	        /*\
	         * Interactable.draggable
	         [ method ]
	         *
	         * Gets or sets whether drag actions can be performed on the
	         * Interactable
	         *
	         = (boolean) Indicates if this can be the target of drag events
	         | var isDraggable = interact('ul li').draggable();
	         * or
	         - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable)
	         = (object) This Interactable
	         | interact(element).draggable({
	         |     onstart: function (event) {},
	         |     onmove : function (event) {},
	         |     onend  : function (event) {},
	         |
	         |     // the axis in which the first movement must be
	         |     // for the drag sequence to start
	         |     // 'xy' by default - any direction
	         |     axis: 'x' || 'y' || 'xy',
	         |
	         |     // max number of drags that can happen concurrently
	         |     // with elements of this Interactable. Infinity by default
	         |     max: Infinity,
	         |
	         |     // max number of drags that can target the same element+Interactable
	         |     // 1 by default
	         |     maxPerElement: 2
	         | });
	        \*/
	        draggable: function (options) {
	            if (isObject(options)) {
	                this.options.drag.enabled = options.enabled === false? false: true;
	                this.setPerAction('drag', options);
	                this.setOnEvents('drag', options);

	                if (/^x$|^y$|^xy$/.test(options.axis)) {
	                    this.options.drag.axis = options.axis;
	                }
	                else if (options.axis === null) {
	                    delete this.options.drag.axis;
	                }

	                return this;
	            }

	            if (isBool(options)) {
	                this.options.drag.enabled = options;

	                return this;
	            }

	            return this.options.drag;
	        },

	        setPerAction: function (action, options) {
	            // for all the default per-action options
	            for (var option in options) {
	                // if this option exists for this action
	                if (option in defaultOptions[action]) {
	                    // if the option in the options arg is an object value
	                    if (isObject(options[option])) {
	                        // duplicate the object
	                        this.options[action][option] = extend(this.options[action][option] || {}, options[option]);

	                        if (isObject(defaultOptions.perAction[option]) && 'enabled' in defaultOptions.perAction[option]) {
	                            this.options[action][option].enabled = options[option].enabled === false? false : true;
	                        }
	                    }
	                    else if (isBool(options[option]) && isObject(defaultOptions.perAction[option])) {
	                        this.options[action][option].enabled = options[option];
	                    }
	                    else if (options[option] !== undefined) {
	                        // or if it's not undefined, do a plain assignment
	                        this.options[action][option] = options[option];
	                    }
	                }
	            }
	        },

	        /*\
	         * Interactable.dropzone
	         [ method ]
	         *
	         * Returns or sets whether elements can be dropped onto this
	         * Interactable to trigger drop events
	         *
	         * Dropzones can receive the following events:
	         *  - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends
	         *  - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone
	         *  - `dragmove` when a draggable that has entered the dropzone is moved
	         *  - `drop` when a draggable is dropped into this dropzone
	         *
	         *  Use the `accept` option to allow only elements that match the given CSS selector or element.
	         *
	         *  Use the `overlap` option to set how drops are checked for. The allowed values are:
	         *   - `'pointer'`, the pointer must be over the dropzone (default)
	         *   - `'center'`, the draggable element's center must be over the dropzone
	         *   - a number from 0-1 which is the `(intersection area) / (draggable area)`.
	         *       e.g. `0.5` for drop to happen when half of the area of the
	         *       draggable is over the dropzone
	         *
	         - options (boolean | object | null) #optional The new value to be set.
	         | interact('.drop').dropzone({
	         |   accept: '.can-drop' || document.getElementById('single-drop'),
	         |   overlap: 'pointer' || 'center' || zeroToOne
	         | }
	         = (boolean | object) The current setting or this Interactable
	        \*/
	        dropzone: function (options) {
	            if (isObject(options)) {
	                this.options.drop.enabled = options.enabled === false? false: true;
	                this.setOnEvents('drop', options);

	                if (/^(pointer|center)$/.test(options.overlap)) {
	                    this.options.drop.overlap = options.overlap;
	                }
	                else if (isNumber(options.overlap)) {
	                    this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);
	                }
	                if ('accept' in options) {
	                  this.options.drop.accept = options.accept;
	                }
	                if ('checker' in options) {
	                  this.options.drop.checker = options.checker;
	                }

	                return this;
	            }

	            if (isBool(options)) {
	                this.options.drop.enabled = options;

	                return this;
	            }

	            return this.options.drop;
	        },

	        dropCheck: function (dragEvent, event, draggable, draggableElement, dropElement, rect) {
	            var dropped = false;

	            // if the dropzone has no rect (eg. display: none)
	            // call the custom dropChecker or just return false
	            if (!(rect = rect || this.getRect(dropElement))) {
	                return (this.options.drop.checker
	                    ? this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement)
	                    : false);
	            }

	            var dropOverlap = this.options.drop.overlap;

	            if (dropOverlap === 'pointer') {
	                var page = getPageXY(dragEvent),
	                    origin = getOriginXY(draggable, draggableElement),
	                    horizontal,
	                    vertical;

	                page.x += origin.x;
	                page.y += origin.y;

	                horizontal = (page.x > rect.left) && (page.x < rect.right);
	                vertical   = (page.y > rect.top ) && (page.y < rect.bottom);

	                dropped = horizontal && vertical;
	            }

	            var dragRect = draggable.getRect(draggableElement);

	            if (dropOverlap === 'center') {
	                var cx = dragRect.left + dragRect.width  / 2,
	                    cy = dragRect.top  + dragRect.height / 2;

	                dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;
	            }

	            if (isNumber(dropOverlap)) {
	                var overlapArea  = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))
	                                  * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))),
	                    overlapRatio = overlapArea / (dragRect.width * dragRect.height);

	                dropped = overlapRatio >= dropOverlap;
	            }

	            if (this.options.drop.checker) {
	                dropped = this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement);
	            }

	            return dropped;
	        },

	        /*\
	         * Interactable.dropChecker
	         [ method ]
	         *
	         * DEPRECATED. Use interactable.dropzone({ checker: function... }) instead.
	         *
	         * Gets or sets the function used to check if a dragged element is
	         * over this Interactable.
	         *
	         - checker (function) #optional The function that will be called when checking for a drop
	         = (Function | Interactable) The checker function or this Interactable
	         *
	         * The checker function takes the following arguments:
	         *
	         - dragEvent (InteractEvent) The related dragmove or dragend event
	         - event (TouchEvent | PointerEvent | MouseEvent) The user move/up/end Event related to the dragEvent
	         - dropped (boolean) The value from the default drop checker
	         - dropzone (Interactable) The dropzone interactable
	         - dropElement (Element) The dropzone element
	         - draggable (Interactable) The Interactable being dragged
	         - draggableElement (Element) The actual element that's being dragged
	         *
	         > Usage:
	         | interact(target)
	         | .dropChecker(function(dragEvent,         // related dragmove or dragend event
	         |                       event,             // TouchEvent/PointerEvent/MouseEvent
	         |                       dropped,           // bool result of the default checker
	         |                       dropzone,          // dropzone Interactable
	         |                       dropElement,       // dropzone elemnt
	         |                       draggable,         // draggable Interactable
	         |                       draggableElement) {// draggable element
	         |
	         |   return dropped && event.target.hasAttribute('allow-drop');
	         | }
	        \*/
	        dropChecker: function (checker) {
	            if (isFunction(checker)) {
	                this.options.drop.checker = checker;

	                return this;
	            }
	            if (checker === null) {
	                delete this.options.getRect;

	                return this;
	            }

	            return this.options.drop.checker;
	        },

	        /*\
	         * Interactable.accept
	         [ method ]
	         *
	         * Deprecated. add an `accept` property to the options object passed to
	         * @Interactable.dropzone instead.
	         *
	         * Gets or sets the Element or CSS selector match that this
	         * Interactable accepts if it is a dropzone.
	         *
	         - newValue (Element | string | null) #optional
	         * If it is an Element, then only that element can be dropped into this dropzone.
	         * If it is a string, the element being dragged must match it as a selector.
	         * If it is null, the accept options is cleared - it accepts any element.
	         *
	         = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable
	        \*/
	        accept: function (newValue) {
	            if (isElement(newValue)) {
	                this.options.drop.accept = newValue;

	                return this;
	            }

	            // test if it is a valid CSS selector
	            if (trySelector(newValue)) {
	                this.options.drop.accept = newValue;

	                return this;
	            }

	            if (newValue === null) {
	                delete this.options.drop.accept;

	                return this;
	            }

	            return this.options.drop.accept;
	        },

	        /*\
	         * Interactable.resizable
	         [ method ]
	         *
	         * Gets or sets whether resize actions can be performed on the
	         * Interactable
	         *
	         = (boolean) Indicates if this can be the target of resize elements
	         | var isResizeable = interact('input[type=text]').resizable();
	         * or
	         - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable)
	         = (object) This Interactable
	         | interact(element).resizable({
	         |     onstart: function (event) {},
	         |     onmove : function (event) {},
	         |     onend  : function (event) {},
	         |
	         |     edges: {
	         |       top   : true,       // Use pointer coords to check for resize.
	         |       left  : false,      // Disable resizing from left edge.
	         |       bottom: '.resize-s',// Resize if pointer target matches selector
	         |       right : handleEl    // Resize if pointer target is the given Element
	         |     },
	         |
	         |     // Width and height can be adjusted independently. When `true`, width and
	         |     // height are adjusted at a 1:1 ratio.
	         |     square: false,
	         |
	         |     // Width and height can be adjusted independently. When `true`, width and
	         |     // height maintain the aspect ratio they had when resizing started.
	         |     preserveAspectRatio: false,
	         |
	         |     // a value of 'none' will limit the resize rect to a minimum of 0x0
	         |     // 'negate' will allow the rect to have negative width/height
	         |     // 'reposition' will keep the width/height positive by swapping
	         |     // the top and bottom edges and/or swapping the left and right edges
	         |     invert: 'none' || 'negate' || 'reposition'
	         |
	         |     // limit multiple resizes.
	         |     // See the explanation in the @Interactable.draggable example
	         |     max: Infinity,
	         |     maxPerElement: 1,
	         | });
	        \*/
	        resizable: function (options) {
	            if (isObject(options)) {
	                this.options.resize.enabled = options.enabled === false? false: true;
	                this.setPerAction('resize', options);
	                this.setOnEvents('resize', options);

	                if (/^x$|^y$|^xy$/.test(options.axis)) {
	                    this.options.resize.axis = options.axis;
	                }
	                else if (options.axis === null) {
	                    this.options.resize.axis = defaultOptions.resize.axis;
	                }

	                if (isBool(options.preserveAspectRatio)) {
	                    this.options.resize.preserveAspectRatio = options.preserveAspectRatio;
	                }
	                else if (isBool(options.square)) {
	                    this.options.resize.square = options.square;
	                }

	                return this;
	            }
	            if (isBool(options)) {
	                this.options.resize.enabled = options;

	                return this;
	            }
	            return this.options.resize;
	        },

	        /*\
	         * Interactable.squareResize
	         [ method ]
	         *
	         * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead
	         *
	         * Gets or sets whether resizing is forced 1:1 aspect
	         *
	         = (boolean) Current setting
	         *
	         * or
	         *
	         - newValue (boolean) #optional
	         = (object) this Interactable
	        \*/
	        squareResize: function (newValue) {
	            if (isBool(newValue)) {
	                this.options.resize.square = newValue;

	                return this;
	            }

	            if (newValue === null) {
	                delete this.options.resize.square;

	                return this;
	            }

	            return this.options.resize.square;
	        },

	        /*\
	         * Interactable.gesturable
	         [ method ]
	         *
	         * Gets or sets whether multitouch gestures can be performed on the
	         * Interactable's element
	         *
	         = (boolean) Indicates if this can be the target of gesture events
	         | var isGestureable = interact(element).gesturable();
	         * or
	         - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable)
	         = (object) this Interactable
	         | interact(element).gesturable({
	         |     onstart: function (event) {},
	         |     onmove : function (event) {},
	         |     onend  : function (event) {},
	         |
	         |     // limit multiple gestures.
	         |     // See the explanation in @Interactable.draggable example
	         |     max: Infinity,
	         |     maxPerElement: 1,
	         | });
	        \*/
	        gesturable: function (options) {
	            if (isObject(options)) {
	                this.options.gesture.enabled = options.enabled === false? false: true;
	                this.setPerAction('gesture', options);
	                this.setOnEvents('gesture', options);

	                return this;
	            }

	            if (isBool(options)) {
	                this.options.gesture.enabled = options;

	                return this;
	            }

	            return this.options.gesture;
	        },

	        /*\
	         * Interactable.autoScroll
	         [ method ]
	         **
	         * Deprecated. Add an `autoscroll` property to the options object
	         * passed to @Interactable.draggable or @Interactable.resizable instead.
	         *
	         * Returns or sets whether dragging and resizing near the edges of the
	         * window/container trigger autoScroll for this Interactable
	         *
	         = (object) Object with autoScroll properties
	         *
	         * or
	         *
	         - options (object | boolean) #optional
	         * options can be:
	         * - an object with margin, distance and interval properties,
	         * - true or false to enable or disable autoScroll or
	         = (Interactable) this Interactable
	        \*/
	        autoScroll: function (options) {
	            if (isObject(options)) {
	                options = extend({ actions: ['drag', 'resize']}, options);
	            }
	            else if (isBool(options)) {
	                options = { actions: ['drag', 'resize'], enabled: options };
	            }

	            return this.setOptions('autoScroll', options);
	        },

	        /*\
	         * Interactable.snap
	         [ method ]
	         **
	         * Deprecated. Add a `snap` property to the options object passed
	         * to @Interactable.draggable or @Interactable.resizable instead.
	         *
	         * Returns or sets if and how action coordinates are snapped. By
	         * default, snapping is relative to the pointer coordinates. You can
	         * change this by setting the
	         * [`elementOrigin`](https://github.com/taye/interact.js/pull/72).
	         **
	         = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled
	         **
	         * or
	         **
	         - options (object | boolean | null) #optional
	         = (Interactable) this Interactable
	         > Usage
	         | interact(document.querySelector('#thing')).snap({
	         |     targets: [
	         |         // snap to this specific point
	         |         {
	         |             x: 100,
	         |             y: 100,
	         |             range: 25
	         |         },
	         |         // give this function the x and y page coords and snap to the object returned
	         |         function (x, y) {
	         |             return {
	         |                 x: x,
	         |                 y: (75 + 50 * Math.sin(x * 0.04)),
	         |                 range: 40
	         |             };
	         |         },
	         |         // create a function that snaps to a grid
	         |         interact.createSnapGrid({
	         |             x: 50,
	         |             y: 50,
	         |             range: 10,              // optional
	         |             offset: { x: 5, y: 10 } // optional
	         |         })
	         |     ],
	         |     // do not snap during normal movement.
	         |     // Instead, trigger only one snapped move event
	         |     // immediately before the end event.
	         |     endOnly: true,
	         |
	         |     relativePoints: [
	         |         { x: 0, y: 0 },  // snap relative to the top left of the element
	         |         { x: 1, y: 1 },  // and also to the bottom right
	         |     ],  
	         |
	         |     // offset the snap target coordinates
	         |     // can be an object with x/y or 'startCoords'
	         |     offset: { x: 50, y: 50 }
	         |   }
	         | });
	        \*/
	        snap: function (options) {
	            var ret = this.setOptions('snap', options);

	            if (ret === this) { return this; }

	            return ret.drag;
	        },

	        setOptions: function (option, options) {
	            var actions = options && isArray(options.actions)
	                    ? options.actions
	                    : ['drag'];

	            var i;

	            if (isObject(options) || isBool(options)) {
	                for (i = 0; i < actions.length; i++) {
	                    var action = /resize/.test(actions[i])? 'resize' : actions[i];

	                    if (!isObject(this.options[action])) { continue; }

	                    var thisOption = this.options[action][option];

	                    if (isObject(options)) {
	                        extend(thisOption, options);
	                        thisOption.enabled = options.enabled === false? false: true;

	                        if (option === 'snap') {
	                            if (thisOption.mode === 'grid') {
	                                thisOption.targets = [
	                                    interact.createSnapGrid(extend({
	                                        offset: thisOption.gridOffset || { x: 0, y: 0 }
	                                    }, thisOption.grid || {}))
	                                ];
	                            }
	                            else if (thisOption.mode === 'anchor') {
	                                thisOption.targets = thisOption.anchors;
	                            }
	                            else if (thisOption.mode === 'path') {
	                                thisOption.targets = thisOption.paths;
	                            }

	                            if ('elementOrigin' in options) {
	                                thisOption.relativePoints = [options.elementOrigin];
	                            }
	                        }
	                    }
	                    else if (isBool(options)) {
	                        thisOption.enabled = options;
	                    }
	                }

	                return this;
	            }

	            var ret = {},
	                allActions = ['drag', 'resize', 'gesture'];

	            for (i = 0; i < allActions.length; i++) {
	                if (option in defaultOptions[allActions[i]]) {
	                    ret[allActions[i]] = this.options[allActions[i]][option];
	                }
	            }

	            return ret;
	        },


	        /*\
	         * Interactable.inertia
	         [ method ]
	         **
	         * Deprecated. Add an `inertia` property to the options object passed
	         * to @Interactable.draggable or @Interactable.resizable instead.
	         *
	         * Returns or sets if and how events continue to run after the pointer is released
	         **
	         = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled
	         **
	         * or
	         **
	         - options (object | boolean | null) #optional
	         = (Interactable) this Interactable
	         > Usage
	         | // enable and use default settings
	         | interact(element).inertia(true);
	         |
	         | // enable and use custom settings
	         | interact(element).inertia({
	         |     // value greater than 0
	         |     // high values slow the object down more quickly
	         |     resistance     : 16,
	         |
	         |     // the minimum launch speed (pixels per second) that results in inertia start
	         |     minSpeed       : 200,
	         |
	         |     // inertia will stop when the object slows down to this speed
	         |     endSpeed       : 20,
	         |
	         |     // boolean; should actions be resumed when the pointer goes down during inertia
	         |     allowResume    : true,
	         |
	         |     // boolean; should the jump when resuming from inertia be ignored in event.dx/dy
	         |     zeroResumeDelta: false,
	         |
	         |     // if snap/restrict are set to be endOnly and inertia is enabled, releasing
	         |     // the pointer without triggering inertia will animate from the release
	         |     // point to the snaped/restricted point in the given amount of time (ms)
	         |     smoothEndDuration: 300,
	         |
	         |     // an array of action types that can have inertia (no gesture)
	         |     actions        : ['drag', 'resize']
	         | });
	         |
	         | // reset custom settings and use all defaults
	         | interact(element).inertia(null);
	        \*/
	        inertia: function (options) {
	            var ret = this.setOptions('inertia', options);

	            if (ret === this) { return this; }

	            return ret.drag;
	        },

	        getAction: function (pointer, event, interaction, element) {
	            var action = this.defaultActionChecker(pointer, interaction, element);

	            if (this.options.actionChecker) {
	                return this.options.actionChecker(pointer, event, action, this, element, interaction);
	            }

	            return action;
	        },

	        defaultActionChecker: defaultActionChecker,

	        /*\
	         * Interactable.actionChecker
	         [ method ]
	         *
	         * Gets or sets the function used to check action to be performed on
	         * pointerDown
	         *
	         - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props.
	         = (Function | Interactable) The checker function or this Interactable
	         *
	         | interact('.resize-drag')
	         |   .resizable(true)
	         |   .draggable(true)
	         |   .actionChecker(function (pointer, event, action, interactable, element, interaction) {
	         |
	         |   if (interact.matchesSelector(event.target, '.drag-handle') {
	         |     // force drag with handle target
	         |     action.name = drag;
	         |   }
	         |   else {
	         |     // resize from the top and right edges
	         |     action.name  = 'resize';
	         |     action.edges = { top: true, right: true };
	         |   }
	         |
	         |   return action;
	         | });
	        \*/
	        actionChecker: function (checker) {
	            if (isFunction(checker)) {
	                this.options.actionChecker = checker;

	                return this;
	            }

	            if (checker === null) {
	                delete this.options.actionChecker;

	                return this;
	            }

	            return this.options.actionChecker;
	        },

	        /*\
	         * Interactable.getRect
	         [ method ]
	         *
	         * The default function to get an Interactables bounding rect. Can be
	         * overridden using @Interactable.rectChecker.
	         *
	         - element (Element) #optional The element to measure.
	         = (object) The object's bounding rectangle.
	         o {
	         o     top   : 0,
	         o     left  : 0,
	         o     bottom: 0,
	         o     right : 0,
	         o     width : 0,
	         o     height: 0
	         o }
	        \*/
	        getRect: function rectCheck (element) {
	            element = element || this._element;

	            if (this.selector && !(isElement(element))) {
	                element = this._context.querySelector(this.selector);
	            }

	            return getElementRect(element);
	        },

	        /*\
	         * Interactable.rectChecker
	         [ method ]
	         *
	         * Returns or sets the function used to calculate the interactable's
	         * element's rectangle
	         *
	         - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect
	         = (function | object) The checker function or this Interactable
	        \*/
	        rectChecker: function (checker) {
	            if (isFunction(checker)) {
	                this.getRect = checker;

	                return this;
	            }

	            if (checker === null) {
	                delete this.options.getRect;

	                return this;
	            }

	            return this.getRect;
	        },

	        /*\
	         * Interactable.styleCursor
	         [ method ]
	         *
	         * Returns or sets whether the action that would be performed when the
	         * mouse on the element are checked on `mousemove` so that the cursor
	         * may be styled appropriately
	         *
	         - newValue (boolean) #optional
	         = (boolean | Interactable) The current setting or this Interactable
	        \*/
	        styleCursor: function (newValue) {
	            if (isBool(newValue)) {
	                this.options.styleCursor = newValue;

	                return this;
	            }

	            if (newValue === null) {
	                delete this.options.styleCursor;

	                return this;
	            }

	            return this.options.styleCursor;
	        },

	        /*\
	         * Interactable.preventDefault
	         [ method ]
	         *
	         * Returns or sets whether to prevent the browser's default behaviour
	         * in response to pointer events. Can be set to:
	         *  - `'always'` to always prevent
	         *  - `'never'` to never prevent
	         *  - `'auto'` to let interact.js try to determine what would be best
	         *
	         - newValue (string) #optional `true`, `false` or `'auto'`
	         = (string | Interactable) The current setting or this Interactable
	        \*/
	        preventDefault: function (newValue) {
	            if (/^(always|never|auto)$/.test(newValue)) {
	                this.options.preventDefault = newValue;
	                return this;
	            }

	            if (isBool(newValue)) {
	                this.options.preventDefault = newValue? 'always' : 'never';
	                return this;
	            }

	            return this.options.preventDefault;
	        },

	        /*\
	         * Interactable.origin
	         [ method ]
	         *
	         * Gets or sets the origin of the Interactable's element.  The x and y
	         * of the origin will be subtracted from action event coordinates.
	         *
	         - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector
	         * OR
	         - origin (Element) #optional An HTML or SVG Element whose rect will be used
	         **
	         = (object) The current origin or this Interactable
	        \*/
	        origin: function (newValue) {
	            if (trySelector(newValue)) {
	                this.options.origin = newValue;
	                return this;
	            }
	            else if (isObject(newValue)) {
	                this.options.origin = newValue;
	                return this;
	            }

	            return this.options.origin;
	        },

	        /*\
	         * Interactable.deltaSource
	         [ method ]
	         *
	         * Returns or sets the mouse coordinate types used to calculate the
	         * movement of the pointer.
	         *
	         - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work
	         = (string | object) The current deltaSource or this Interactable
	        \*/
	        deltaSource: function (newValue) {
	            if (newValue === 'page' || newValue === 'client') {
	                this.options.deltaSource = newValue;

	                return this;
	            }

	            return this.options.deltaSource;
	        },

	        /*\
	         * Interactable.restrict
	         [ method ]
	         **
	         * Deprecated. Add a `restrict` property to the options object passed to
	         * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead.
	         *
	         * Returns or sets the rectangles within which actions on this
	         * interactable (after snap calculations) are restricted. By default,
	         * restricting is relative to the pointer coordinates. You can change
	         * this by setting the
	         * [`elementRect`](https://github.com/taye/interact.js/pull/72).
	         **
	         - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self'
	         = (object) The current restrictions object or this Interactable
	         **
	         | interact(element).restrict({
	         |     // the rect will be `interact.getElementRect(element.parentNode)`
	         |     drag: element.parentNode,
	         |
	         |     // x and y are relative to the the interactable's origin
	         |     resize: { x: 100, y: 100, width: 200, height: 200 }
	         | })
	         |
	         | interact('.draggable').restrict({
	         |     // the rect will be the selected element's parent
	         |     drag: 'parent',
	         |
	         |     // do not restrict during normal movement.
	         |     // Instead, trigger only one restricted move event
	         |     // immediately before the end event.
	         |     endOnly: true,
	         |
	         |     // https://github.com/taye/interact.js/pull/72#issue-41813493
	         |     elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
	         | });
	        \*/
	        restrict: function (options) {
	            if (!isObject(options)) {
	                return this.setOptions('restrict', options);
	            }

	            var actions = ['drag', 'resize', 'gesture'],
	                ret;

	            for (var i = 0; i < actions.length; i++) {
	                var action = actions[i];

	                if (action in options) {
	                    var perAction = extend({
	                            actions: [action],
	                            restriction: options[action]
	                        }, options);

	                    ret = this.setOptions('restrict', perAction);
	                }
	            }

	            return ret;
	        },

	        /*\
	         * Interactable.context
	         [ method ]
	         *
	         * Gets the selector context Node of the Interactable. The default is `window.document`.
	         *
	         = (Node) The context Node of this Interactable
	         **
	        \*/
	        context: function () {
	            return this._context;
	        },

	        _context: document,

	        /*\
	         * Interactable.ignoreFrom
	         [ method ]
	         *
	         * If the target of the `mousedown`, `pointerdown` or `touchstart`
	         * event or any of it's parents match the given CSS selector or
	         * Element, no drag/resize/gesture is started.
	         *
	         - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements
	         = (string | Element | object) The current ignoreFrom value or this Interactable
	         **
	         | interact(element, { ignoreFrom: document.getElementById('no-action') });
	         | // or
	         | interact(element).ignoreFrom('input, textarea, a');
	        \*/
	        ignoreFrom: function (newValue) {
	            if (trySelector(newValue)) {            // CSS selector to match event.target
	                this.options.ignoreFrom = newValue;
	                return this;
	            }

	            if (isElement(newValue)) {              // specific element
	                this.options.ignoreFrom = newValue;
	                return this;
	            }

	            return this.options.ignoreFrom;
	        },

	        /*\
	         * Interactable.allowFrom
	         [ method ]
	         *
	         * A drag/resize/gesture is started only If the target of the
	         * `mousedown`, `pointerdown` or `touchstart` event or any of it's
	         * parents match the given CSS selector or Element.
	         *
	         - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element
	         = (string | Element | object) The current allowFrom value or this Interactable
	         **
	         | interact(element, { allowFrom: document.getElementById('drag-handle') });
	         | // or
	         | interact(element).allowFrom('.handle');
	        \*/
	        allowFrom: function (newValue) {
	            if (trySelector(newValue)) {            // CSS selector to match event.target
	                this.options.allowFrom = newValue;
	                return this;
	            }

	            if (isElement(newValue)) {              // specific element
	                this.options.allowFrom = newValue;
	                return this;
	            }

	            return this.options.allowFrom;
	        },

	        /*\
	         * Interactable.element
	         [ method ]
	         *
	         * If this is not a selector Interactable, it returns the element this
	         * interactable represents
	         *
	         = (Element) HTML / SVG Element
	        \*/
	        element: function () {
	            return this._element;
	        },

	        /*\
	         * Interactable.fire
	         [ method ]
	         *
	         * Calls listeners for the given InteractEvent type bound globally
	         * and directly to this Interactable
	         *
	         - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable
	         = (Interactable) this Interactable
	        \*/
	        fire: function (iEvent) {
	            if (!(iEvent && iEvent.type) || !contains(eventTypes, iEvent.type)) {
	                return this;
	            }

	            var listeners,
	                i,
	                len,
	                onEvent = 'on' + iEvent.type,
	                funcName = '';

	            // Interactable#on() listeners
	            if (iEvent.type in this._iEvents) {
	                listeners = this._iEvents[iEvent.type];

	                for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {
	                    funcName = listeners[i].name;
	                    listeners[i](iEvent);
	                }
	            }

	            // interactable.onevent listener
	            if (isFunction(this[onEvent])) {
	                funcName = this[onEvent].name;
	                this[onEvent](iEvent);
	            }

	            // interact.on() listeners
	            if (iEvent.type in globalEvents && (listeners = globalEvents[iEvent.type]))  {

	                for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {
	                    funcName = listeners[i].name;
	                    listeners[i](iEvent);
	                }
	            }

	            return this;
	        },

	        /*\
	         * Interactable.on
	         [ method ]
	         *
	         * Binds a listener for an InteractEvent or DOM event.
	         *
	         - eventType  (string | array | object) The types of events to listen for
	         - listener   (function) The function to be called on the given event(s)
	         - useCapture (boolean) #optional useCapture flag for addEventListener
	         = (object) This Interactable
	        \*/
	        on: function (eventType, listener, useCapture) {
	            var i;

	            if (isString(eventType) && eventType.search(' ') !== -1) {
	                eventType = eventType.trim().split(/ +/);
	            }

	            if (isArray(eventType)) {
	                for (i = 0; i < eventType.length; i++) {
	                    this.on(eventType[i], listener, useCapture);
	                }

	                return this;
	            }

	            if (isObject(eventType)) {
	                for (var prop in eventType) {
	                    this.on(prop, eventType[prop], listener);
	                }

	                return this;
	            }

	            if (eventType === 'wheel') {
	                eventType = wheelEvent;
	            }

	            // convert to boolean
	            useCapture = useCapture? true: false;

	            if (contains(eventTypes, eventType)) {
	                // if this type of event was never bound to this Interactable
	                if (!(eventType in this._iEvents)) {
	                    this._iEvents[eventType] = [listener];
	                }
	                else {
	                    this._iEvents[eventType].push(listener);
	                }
	            }
	            // delegated event for selector
	            else if (this.selector) {
	                if (!delegatedEvents[eventType]) {
	                    delegatedEvents[eventType] = {
	                        selectors: [],
	                        contexts : [],
	                        listeners: []
	                    };

	                    // add delegate listener functions
	                    for (i = 0; i < documents.length; i++) {
	                        events.add(documents[i], eventType, delegateListener);
	                        events.add(documents[i], eventType, delegateUseCapture, true);
	                    }
	                }

	                var delegated = delegatedEvents[eventType],
	                    index;

	                for (index = delegated.selectors.length - 1; index >= 0; index--) {
	                    if (delegated.selectors[index] === this.selector
	                        && delegated.contexts[index] === this._context) {
	                        break;
	                    }
	                }

	                if (index === -1) {
	                    index = delegated.selectors.length;

	                    delegated.selectors.push(this.selector);
	                    delegated.contexts .push(this._context);
	                    delegated.listeners.push([]);
	                }

	                // keep listener and useCapture flag
	                delegated.listeners[index].push([listener, useCapture]);
	            }
	            else {
	                events.add(this._element, eventType, listener, useCapture);
	            }

	            return this;
	        },

	        /*\
	         * Interactable.off
	         [ method ]
	         *
	         * Removes an InteractEvent or DOM event listener
	         *
	         - eventType  (string | array | object) The types of events that were listened for
	         - listener   (function) The listener function to be removed
	         - useCapture (boolean) #optional useCapture flag for removeEventListener
	         = (object) This Interactable
	        \*/
	        off: function (eventType, listener, useCapture) {
	            var i;

	            if (isString(eventType) && eventType.search(' ') !== -1) {
	                eventType = eventType.trim().split(/ +/);
	            }

	            if (isArray(eventType)) {
	                for (i = 0; i < eventType.length; i++) {
	                    this.off(eventType[i], listener, useCapture);
	                }

	                return this;
	            }

	            if (isObject(eventType)) {
	                for (var prop in eventType) {
	                    this.off(prop, eventType[prop], listener);
	                }

	                return this;
	            }

	            var eventList,
	                index = -1;

	            // convert to boolean
	            useCapture = useCapture? true: false;

	            if (eventType === 'wheel') {
	                eventType = wheelEvent;
	            }

	            // if it is an action event type
	            if (contains(eventTypes, eventType)) {
	                eventList = this._iEvents[eventType];

	                if (eventList && (index = indexOf(eventList, listener)) !== -1) {
	                    this._iEvents[eventType].splice(index, 1);
	                }
	            }
	            // delegated event
	            else if (this.selector) {
	                var delegated = delegatedEvents[eventType],
	                    matchFound = false;

	                if (!delegated) { return this; }

	                // count from last index of delegated to 0
	                for (index = delegated.selectors.length - 1; index >= 0; index--) {
	                    // look for matching selector and context Node
	                    if (delegated.selectors[index] === this.selector
	                        && delegated.contexts[index] === this._context) {

	                        var listeners = delegated.listeners[index];

	                        // each item of the listeners array is an array: [function, useCaptureFlag]
	                        for (i = listeners.length - 1; i >= 0; i--) {
	                            var fn = listeners[i][0],
	                                useCap = listeners[i][1];

	                            // check if the listener functions and useCapture flags match
	                            if (fn === listener && useCap === useCapture) {
	                                // remove the listener from the array of listeners
	                                listeners.splice(i, 1);

	                                // if all listeners for this interactable have been removed
	                                // remove the interactable from the delegated arrays
	                                if (!listeners.length) {
	                                    delegated.selectors.splice(index, 1);
	                                    delegated.contexts .splice(index, 1);
	                                    delegated.listeners.splice(index, 1);

	                                    // remove delegate function from context
	                                    events.remove(this._context, eventType, delegateListener);
	                                    events.remove(this._context, eventType, delegateUseCapture, true);

	                                    // remove the arrays if they are empty
	                                    if (!delegated.selectors.length) {
	                                        delegatedEvents[eventType] = null;
	                                    }
	                                }

	                                // only remove one listener
	                                matchFound = true;
	                                break;
	                            }
	                        }

	                        if (matchFound) { break; }
	                    }
	                }
	            }
	            // remove listener from this Interatable's element
	            else {
	                events.remove(this._element, eventType, listener, useCapture);
	            }

	            return this;
	        },

	        /*\
	         * Interactable.set
	         [ method ]
	         *
	         * Reset the options of this Interactable
	         - options (object) The new settings to apply
	         = (object) This Interactable
	        \*/
	        set: function (options) {
	            if (!isObject(options)) {
	                options = {};
	            }

	            this.options = extend({}, defaultOptions.base);

	            var i,
	                actions = ['drag', 'drop', 'resize', 'gesture'],
	                methods = ['draggable', 'dropzone', 'resizable', 'gesturable'],
	                perActions = extend(extend({}, defaultOptions.perAction), options[action] || {});

	            for (i = 0; i < actions.length; i++) {
	                var action = actions[i];

	                this.options[action] = extend({}, defaultOptions[action]);

	                this.setPerAction(action, perActions);

	                this[methods[i]](options[action]);
	            }

	            var settings = [
	                    'accept', 'actionChecker', 'allowFrom', 'deltaSource',
	                    'dropChecker', 'ignoreFrom', 'origin', 'preventDefault',
	                    'rectChecker', 'styleCursor'
	                ];

	            for (i = 0, len = settings.length; i < len; i++) {
	                var setting = settings[i];

	                this.options[setting] = defaultOptions.base[setting];

	                if (setting in options) {
	                    this[setting](options[setting]);
	                }
	            }

	            return this;
	        },

	        /*\
	         * Interactable.unset
	         [ method ]
	         *
	         * Remove this interactable from the list of interactables and remove
	         * it's drag, drop, resize and gesture capabilities
	         *
	         = (object) @interact
	        \*/
	        unset: function () {
	            events.remove(this._element, 'all');

	            if (!isString(this.selector)) {
	                events.remove(this, 'all');
	                if (this.options.styleCursor) {
	                    this._element.style.cursor = '';
	                }
	            }
	            else {
	                // remove delegated events
	                for (var type in delegatedEvents) {
	                    var delegated = delegatedEvents[type];

	                    for (var i = 0; i < delegated.selectors.length; i++) {
	                        if (delegated.selectors[i] === this.selector
	                            && delegated.contexts[i] === this._context) {

	                            delegated.selectors.splice(i, 1);
	                            delegated.contexts .splice(i, 1);
	                            delegated.listeners.splice(i, 1);

	                            // remove the arrays if they are empty
	                            if (!delegated.selectors.length) {
	                                delegatedEvents[type] = null;
	                            }
	                        }

	                        events.remove(this._context, type, delegateListener);
	                        events.remove(this._context, type, delegateUseCapture, true);

	                        break;
	                    }
	                }
	            }

	            this.dropzone(false);

	            interactables.splice(indexOf(interactables, this), 1);

	            return interact;
	        }
	    };

	    function warnOnce (method, message) {
	        var warned = false;

	        return function () {
	            if (!warned) {
	                window.console.warn(message);
	                warned = true;
	            }

	            return method.apply(this, arguments);
	        };
	    }

	    Interactable.prototype.snap = warnOnce(Interactable.prototype.snap,
	         'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping');
	    Interactable.prototype.restrict = warnOnce(Interactable.prototype.restrict,
	         'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction');
	    Interactable.prototype.inertia = warnOnce(Interactable.prototype.inertia,
	         'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia');
	    Interactable.prototype.autoScroll = warnOnce(Interactable.prototype.autoScroll,
	         'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll');
	    Interactable.prototype.squareResize = warnOnce(Interactable.prototype.squareResize,
	         'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square');

	    Interactable.prototype.accept = warnOnce(Interactable.prototype.accept,
	         'Interactable#accept is deprecated. use Interactable#dropzone({ accept: target }) instead');
	    Interactable.prototype.dropChecker = warnOnce(Interactable.prototype.dropChecker,
	         'Interactable#dropChecker is deprecated. use Interactable#dropzone({ dropChecker: checkerFunction }) instead');
	    Interactable.prototype.context = warnOnce(Interactable.prototype.context,
	         'Interactable#context as a method is deprecated. It will soon be a DOM Node instead');

	    /*\
	     * interact.isSet
	     [ method ]
	     *
	     * Check if an element has been set
	     - element (Element) The Element being searched for
	     = (boolean) Indicates if the element or CSS selector was previously passed to interact
	    \*/
	    interact.isSet = function(element, options) {
	        return interactables.indexOfElement(element, options && options.context) !== -1;
	    };

	    /*\
	     * interact.on
	     [ method ]
	     *
	     * Adds a global listener for an InteractEvent or adds a DOM event to
	     * `document`
	     *
	     - type       (string | array | object) The types of events to listen for
	     - listener   (function) The function to be called on the given event(s)
	     - useCapture (boolean) #optional useCapture flag for addEventListener
	     = (object) interact
	    \*/
	    interact.on = function (type, listener, useCapture) {
	        if (isString(type) && type.search(' ') !== -1) {
	            type = type.trim().split(/ +/);
	        }

	        if (isArray(type)) {
	            for (var i = 0; i < type.length; i++) {
	                interact.on(type[i], listener, useCapture);
	            }

	            return interact;
	        }

	        if (isObject(type)) {
	            for (var prop in type) {
	                interact.on(prop, type[prop], listener);
	            }

	            return interact;
	        }

	        // if it is an InteractEvent type, add listener to globalEvents
	        if (contains(eventTypes, type)) {
	            // if this type of event was never bound
	            if (!globalEvents[type]) {
	                globalEvents[type] = [listener];
	            }
	            else {
	                globalEvents[type].push(listener);
	            }
	        }
	        // If non InteractEvent type, addEventListener to document
	        else {
	            events.add(document, type, listener, useCapture);
	        }

	        return interact;
	    };

	    /*\
	     * interact.off
	     [ method ]
	     *
	     * Removes a global InteractEvent listener or DOM event from `document`
	     *
	     - type       (string | array | object) The types of events that were listened for
	     - listener   (function) The listener function to be removed
	     - useCapture (boolean) #optional useCapture flag for removeEventListener
	     = (object) interact
	     \*/
	    interact.off = function (type, listener, useCapture) {
	        if (isString(type) && type.search(' ') !== -1) {
	            type = type.trim().split(/ +/);
	        }

	        if (isArray(type)) {
	            for (var i = 0; i < type.length; i++) {
	                interact.off(type[i], listener, useCapture);
	            }

	            return interact;
	        }

	        if (isObject(type)) {
	            for (var prop in type) {
	                interact.off(prop, type[prop], listener);
	            }

	            return interact;
	        }

	        if (!contains(eventTypes, type)) {
	            events.remove(document, type, listener, useCapture);
	        }
	        else {
	            var index;

	            if (type in globalEvents
	                && (index = indexOf(globalEvents[type], listener)) !== -1) {
	                globalEvents[type].splice(index, 1);
	            }
	        }

	        return interact;
	    };

	    /*\
	     * interact.enableDragging
	     [ method ]
	     *
	     * Deprecated.
	     *
	     * Returns or sets whether dragging is enabled for any Interactables
	     *
	     - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
	     = (boolean | object) The current setting or interact
	    \*/
	    interact.enableDragging = warnOnce(function (newValue) {
	        if (newValue !== null && newValue !== undefined) {
	            actionIsEnabled.drag = newValue;

	            return interact;
	        }
	        return actionIsEnabled.drag;
	    }, 'interact.enableDragging is deprecated and will soon be removed.');

	    /*\
	     * interact.enableResizing
	     [ method ]
	     *
	     * Deprecated.
	     *
	     * Returns or sets whether resizing is enabled for any Interactables
	     *
	     - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
	     = (boolean | object) The current setting or interact
	    \*/
	    interact.enableResizing = warnOnce(function (newValue) {
	        if (newValue !== null && newValue !== undefined) {
	            actionIsEnabled.resize = newValue;

	            return interact;
	        }
	        return actionIsEnabled.resize;
	    }, 'interact.enableResizing is deprecated and will soon be removed.');

	    /*\
	     * interact.enableGesturing
	     [ method ]
	     *
	     * Deprecated.
	     *
	     * Returns or sets whether gesturing is enabled for any Interactables
	     *
	     - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
	     = (boolean | object) The current setting or interact
	    \*/
	    interact.enableGesturing = warnOnce(function (newValue) {
	        if (newValue !== null && newValue !== undefined) {
	            actionIsEnabled.gesture = newValue;

	            return interact;
	        }
	        return actionIsEnabled.gesture;
	    }, 'interact.enableGesturing is deprecated and will soon be removed.');

	    interact.eventTypes = eventTypes;

	    /*\
	     * interact.debug
	     [ method ]
	     *
	     * Returns debugging data
	     = (object) An object with properties that outline the current state and expose internal functions and variables
	    \*/
	    interact.debug = function () {
	        var interaction = interactions[0] || new Interaction();

	        return {
	            interactions          : interactions,
	            target                : interaction.target,
	            dragging              : interaction.dragging,
	            resizing              : interaction.resizing,
	            gesturing             : interaction.gesturing,
	            prepared              : interaction.prepared,
	            matches               : interaction.matches,
	            matchElements         : interaction.matchElements,

	            prevCoords            : interaction.prevCoords,
	            startCoords           : interaction.startCoords,

	            pointerIds            : interaction.pointerIds,
	            pointers              : interaction.pointers,
	            addPointer            : listeners.addPointer,
	            removePointer         : listeners.removePointer,
	            recordPointer        : listeners.recordPointer,

	            snap                  : interaction.snapStatus,
	            restrict              : interaction.restrictStatus,
	            inertia               : interaction.inertiaStatus,

	            downTime              : interaction.downTimes[0],
	            downEvent             : interaction.downEvent,
	            downPointer           : interaction.downPointer,
	            prevEvent             : interaction.prevEvent,

	            Interactable          : Interactable,
	            interactables         : interactables,
	            pointerIsDown         : interaction.pointerIsDown,
	            defaultOptions        : defaultOptions,
	            defaultActionChecker  : defaultActionChecker,

	            actionCursors         : actionCursors,
	            dragMove              : listeners.dragMove,
	            resizeMove            : listeners.resizeMove,
	            gestureMove           : listeners.gestureMove,
	            pointerUp             : listeners.pointerUp,
	            pointerDown           : listeners.pointerDown,
	            pointerMove           : listeners.pointerMove,
	            pointerHover          : listeners.pointerHover,

	            eventTypes            : eventTypes,

	            events                : events,
	            globalEvents          : globalEvents,
	            delegatedEvents       : delegatedEvents,

	            prefixedPropREs       : prefixedPropREs
	        };
	    };

	    // expose the functions used to calculate multi-touch properties
	    interact.getPointerAverage = pointerAverage;
	    interact.getTouchBBox     = touchBBox;
	    interact.getTouchDistance = touchDistance;
	    interact.getTouchAngle    = touchAngle;

	    interact.getElementRect         = getElementRect;
	    interact.getElementClientRect   = getElementClientRect;
	    interact.matchesSelector        = matchesSelector;
	    interact.closest                = closest;

	    /*\
	     * interact.margin
	     [ method ]
	     *
	     * Deprecated. Use `interact(target).resizable({ margin: number });` instead.
	     * Returns or sets the margin for autocheck resizing used in
	     * @Interactable.getAction. That is the distance from the bottom and right
	     * edges of an element clicking in which will start resizing
	     *
	     - newValue (number) #optional
	     = (number | interact) The current margin value or interact
	    \*/
	    interact.margin = warnOnce(function (newvalue) {
	        if (isNumber(newvalue)) {
	            margin = newvalue;

	            return interact;
	        }
	        return margin;
	    },
	    'interact.margin is deprecated. Use interact(target).resizable({ margin: number }); instead.') ;

	    /*\
	     * interact.supportsTouch
	     [ method ]
	     *
	     = (boolean) Whether or not the browser supports touch input
	    \*/
	    interact.supportsTouch = function () {
	        return supportsTouch;
	    };

	    /*\
	     * interact.supportsPointerEvent
	     [ method ]
	     *
	     = (boolean) Whether or not the browser supports PointerEvents
	    \*/
	    interact.supportsPointerEvent = function () {
	        return supportsPointerEvent;
	    };

	    /*\
	     * interact.stop
	     [ method ]
	     *
	     * Cancels all interactions (end events are not fired)
	     *
	     - event (Event) An event on which to call preventDefault()
	     = (object) interact
	    \*/
	    interact.stop = function (event) {
	        for (var i = interactions.length - 1; i >= 0; i--) {
	            interactions[i].stop(event);
	        }

	        return interact;
	    };

	    /*\
	     * interact.dynamicDrop
	     [ method ]
	     *
	     * Returns or sets whether the dimensions of dropzone elements are
	     * calculated on every dragmove or only on dragstart for the default
	     * dropChecker
	     *
	     - newValue (boolean) #optional True to check on each move. False to check only before start
	     = (boolean | interact) The current setting or interact
	    \*/
	    interact.dynamicDrop = function (newValue) {
	        if (isBool(newValue)) {
	            //if (dragging && dynamicDrop !== newValue && !newValue) {
	                //calcRects(dropzones);
	            //}

	            dynamicDrop = newValue;

	            return interact;
	        }
	        return dynamicDrop;
	    };

	    /*\
	     * interact.pointerMoveTolerance
	     [ method ]
	     * Returns or sets the distance the pointer must be moved before an action
	     * sequence occurs. This also affects tolerance for tap events.
	     *
	     - newValue (number) #optional The movement from the start position must be greater than this value
	     = (number | Interactable) The current setting or interact
	    \*/
	    interact.pointerMoveTolerance = function (newValue) {
	        if (isNumber(newValue)) {
	            pointerMoveTolerance = newValue;

	            return this;
	        }

	        return pointerMoveTolerance;
	    };

	    /*\
	     * interact.maxInteractions
	     [ method ]
	     **
	     * Returns or sets the maximum number of concurrent interactions allowed.
	     * By default only 1 interaction is allowed at a time (for backwards
	     * compatibility). To allow multiple interactions on the same Interactables
	     * and elements, you need to enable it in the draggable, resizable and
	     * gesturable `'max'` and `'maxPerElement'` options.
	     **
	     - newValue (number) #optional Any number. newValue <= 0 means no interactions.
	    \*/
	    interact.maxInteractions = function (newValue) {
	        if (isNumber(newValue)) {
	            maxInteractions = newValue;

	            return this;
	        }

	        return maxInteractions;
	    };

	    interact.createSnapGrid = function (grid) {
	        return function (x, y) {
	            var offsetX = 0,
	                offsetY = 0;

	            if (isObject(grid.offset)) {
	                offsetX = grid.offset.x;
	                offsetY = grid.offset.y;
	            }

	            var gridx = Math.round((x - offsetX) / grid.x),
	                gridy = Math.round((y - offsetY) / grid.y),

	                newX = gridx * grid.x + offsetX,
	                newY = gridy * grid.y + offsetY;

	            return {
	                x: newX,
	                y: newY,
	                range: grid.range
	            };
	        };
	    };

	    function endAllInteractions (event) {
	        for (var i = 0; i < interactions.length; i++) {
	            interactions[i].pointerEnd(event, event);
	        }
	    }

	    function listenToDocument (doc) {
	        if (contains(documents, doc)) { return; }

	        var win = doc.defaultView || doc.parentWindow;

	        // add delegate event listener
	        for (var eventType in delegatedEvents) {
	            events.add(doc, eventType, delegateListener);
	            events.add(doc, eventType, delegateUseCapture, true);
	        }

	        if (supportsPointerEvent) {
	            if (PointerEvent === win.MSPointerEvent) {
	                pEventTypes = {
	                    up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',
	                    out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' };
	            }
	            else {
	                pEventTypes = {
	                    up: 'pointerup', down: 'pointerdown', over: 'pointerover',
	                    out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' };
	            }

	            events.add(doc, pEventTypes.down  , listeners.selectorDown );
	            events.add(doc, pEventTypes.move  , listeners.pointerMove  );
	            events.add(doc, pEventTypes.over  , listeners.pointerOver  );
	            events.add(doc, pEventTypes.out   , listeners.pointerOut   );
	            events.add(doc, pEventTypes.up    , listeners.pointerUp    );
	            events.add(doc, pEventTypes.cancel, listeners.pointerCancel);

	            // autoscroll
	            events.add(doc, pEventTypes.move, listeners.autoScrollMove);
	        }
	        else {
	            events.add(doc, 'mousedown', listeners.selectorDown);
	            events.add(doc, 'mousemove', listeners.pointerMove );
	            events.add(doc, 'mouseup'  , listeners.pointerUp   );
	            events.add(doc, 'mouseover', listeners.pointerOver );
	            events.add(doc, 'mouseout' , listeners.pointerOut  );

	            events.add(doc, 'touchstart' , listeners.selectorDown );
	            events.add(doc, 'touchmove'  , listeners.pointerMove  );
	            events.add(doc, 'touchend'   , listeners.pointerUp    );
	            events.add(doc, 'touchcancel', listeners.pointerCancel);

	            // autoscroll
	            events.add(doc, 'mousemove', listeners.autoScrollMove);
	            events.add(doc, 'touchmove', listeners.autoScrollMove);
	        }

	        events.add(win, 'blur', endAllInteractions);

	        try {
	            if (win.frameElement) {
	                var parentDoc = win.frameElement.ownerDocument,
	                    parentWindow = parentDoc.defaultView;

	                events.add(parentDoc   , 'mouseup'      , listeners.pointerEnd);
	                events.add(parentDoc   , 'touchend'     , listeners.pointerEnd);
	                events.add(parentDoc   , 'touchcancel'  , listeners.pointerEnd);
	                events.add(parentDoc   , 'pointerup'    , listeners.pointerEnd);
	                events.add(parentDoc   , 'MSPointerUp'  , listeners.pointerEnd);
	                events.add(parentWindow, 'blur'         , endAllInteractions );
	            }
	        }
	        catch (error) {
	            interact.windowParentError = error;
	        }

	        // prevent native HTML5 drag on interact.js target elements
	        events.add(doc, 'dragstart', function (event) {
	            for (var i = 0; i < interactions.length; i++) {
	                var interaction = interactions[i];

	                if (interaction.element
	                    && (interaction.element === event.target
	                        || nodeContains(interaction.element, event.target))) {

	                    interaction.checkAndPreventDefault(event, interaction.target, interaction.element);
	                    return;
	                }
	            }
	        });

	        if (events.useAttachEvent) {
	            // For IE's lack of Event#preventDefault
	            events.add(doc, 'selectstart', function (event) {
	                var interaction = interactions[0];

	                if (interaction.currentAction()) {
	                    interaction.checkAndPreventDefault(event);
	                }
	            });

	            // For IE's bad dblclick event sequence
	            events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick'));
	        }

	        documents.push(doc);
	    }

	    listenToDocument(document);

	    function indexOf (array, target) {
	        for (var i = 0, len = array.length; i < len; i++) {
	            if (array[i] === target) {
	                return i;
	            }
	        }

	        return -1;
	    }

	    function contains (array, target) {
	        return indexOf(array, target) !== -1;
	    }

	    function matchesSelector (element, selector, nodeList) {
	        if (ie8MatchesSelector) {
	            return ie8MatchesSelector(element, selector, nodeList);
	        }

	        // remove /deep/ from selectors if shadowDOM polyfill is used
	        if (window !== realWindow) {
	            selector = selector.replace(/\/deep\//g, ' ');
	        }

	        return element[prefixedMatchesSelector](selector);
	    }

	    function matchesUpTo (element, selector, limit) {
	        while (isElement(element)) {
	            if (matchesSelector(element, selector)) {
	                return true;
	            }

	            element = parentElement(element);

	            if (element === limit) {
	                return matchesSelector(element, selector);
	            }
	        }

	        return false;
	    }

	    // For IE8's lack of an Element#matchesSelector
	    // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified
	    if (!(prefixedMatchesSelector in Element.prototype) || !isFunction(Element.prototype[prefixedMatchesSelector])) {
	        ie8MatchesSelector = function (element, selector, elems) {
	            elems = elems || element.parentNode.querySelectorAll(selector);

	            for (var i = 0, len = elems.length; i < len; i++) {
	                if (elems[i] === element) {
	                    return true;
	                }
	            }

	            return false;
	        };
	    }

	    // requestAnimationFrame polyfill
	    (function() {
	        var lastTime = 0,
	            vendors = ['ms', 'moz', 'webkit', 'o'];

	        for(var x = 0; x < vendors.length && !realWindow.requestAnimationFrame; ++x) {
	            reqFrame = realWindow[vendors[x]+'RequestAnimationFrame'];
	            cancelFrame = realWindow[vendors[x]+'CancelAnimationFrame'] || realWindow[vendors[x]+'CancelRequestAnimationFrame'];
	        }

	        if (!reqFrame) {
	            reqFrame = function(callback) {
	                var currTime = new Date().getTime(),
	                    timeToCall = Math.max(0, 16 - (currTime - lastTime)),
	                    id = setTimeout(function() { callback(currTime + timeToCall); },
	                  timeToCall);
	                lastTime = currTime + timeToCall;
	                return id;
	            };
	        }

	        if (!cancelFrame) {
	            cancelFrame = function(id) {
	                clearTimeout(id);
	            };
	        }
	    }());

	    /* global exports: true, module, define */

	    // http://documentcloud.github.io/underscore/docs/underscore.html#section-11
	    if (true) {
	        if (typeof module !== 'undefined' && module.exports) {
	            exports = module.exports = interact;
	        }
	        exports.interact = interact;
	    }
	    // AMD
	    else if (typeof define === 'function' && define.amd) {
	        define('interact', function() {
	            return interact;
	        });
	    }
	    else {
	        realWindow.interact = interact;
	    }

	} (typeof window === 'undefined'? undefined : window));


/***/ },

/***/ 575:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Spectrum Colorpicker v1.8.0
	// https://github.com/bgrins/spectrum
	// Author: Brian Grinstead
	// License: MIT

	(function (factory) {
	    "use strict";

	    if (true) { // AMD
	        !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(275)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	    }
	    else if (typeof exports == "object" && typeof module == "object") { // CommonJS
	        module.exports = factory(require('jquery'));
	    }
	    else { // Browser
	        factory(jQuery);
	    }
	})(function($, undefined) {
	    "use strict";

	    var defaultOpts = {

	        // Callbacks
	        beforeShow: noop,
	        move: noop,
	        change: noop,
	        show: noop,
	        hide: noop,

	        // Options
	        color: false,
	        flat: false,
	        showInput: false,
	        allowEmpty: false,
	        showButtons: true,
	        clickoutFiresChange: true,
	        showInitial: false,
	        showPalette: false,
	        showPaletteOnly: false,
	        hideAfterPaletteSelect: false,
	        togglePaletteOnly: false,
	        showSelectionPalette: true,
	        localStorageKey: false,
	        appendTo: "body",
	        maxSelectionSize: 7,
	        cancelText: "cancel",
	        chooseText: "choose",
	        togglePaletteMoreText: "more",
	        togglePaletteLessText: "less",
	        clearText: "Clear Color Selection",
	        noColorSelectedText: "No Color Selected",
	        preferredFormat: false,
	        className: "", // Deprecated - use containerClassName and replacerClassName instead.
	        containerClassName: "",
	        replacerClassName: "",
	        showAlpha: false,
	        theme: "sp-light",
	        palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]],
	        selectionPalette: [],
	        disabled: false,
	        offset: null
	    },
	    spectrums = [],
	    IE = !!/msie/i.exec( window.navigator.userAgent ),
	    rgbaSupport = (function() {
	        function contains( str, substr ) {
	            return !!~('' + str).indexOf(substr);
	        }

	        var elem = document.createElement('div');
	        var style = elem.style;
	        style.cssText = 'background-color:rgba(0,0,0,.5)';
	        return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
	    })(),
	    replaceInput = [
	        "<div class='sp-replacer'>",
	            "<div class='sp-preview'><div class='sp-preview-inner'></div></div>",
	            "<div class='sp-dd'>&#9660;</div>",
	        "</div>"
	    ].join(''),
	    markup = (function () {

	        // IE does not support gradients with multiple stops, so we need to simulate
	        //  that for the rainbow slider with 8 divs that each have a single gradient
	        var gradientFix = "";
	        if (IE) {
	            for (var i = 1; i <= 6; i++) {
	                gradientFix += "<div class='sp-" + i + "'></div>";
	            }
	        }

	        return [
	            "<div class='sp-container sp-hidden'>",
	                "<div class='sp-palette-container'>",
	                    "<div class='sp-palette sp-thumb sp-cf'></div>",
	                    "<div class='sp-palette-button-container sp-cf'>",
	                        "<button type='button' class='sp-palette-toggle'></button>",
	                    "</div>",
	                "</div>",
	                "<div class='sp-picker-container'>",
	                    "<div class='sp-top sp-cf'>",
	                        "<div class='sp-fill'></div>",
	                        "<div class='sp-top-inner'>",
	                            "<div class='sp-color'>",
	                                "<div class='sp-sat'>",
	                                    "<div class='sp-val'>",
	                                        "<div class='sp-dragger'></div>",
	                                    "</div>",
	                                "</div>",
	                            "</div>",
	                            "<div class='sp-clear sp-clear-display'>",
	                            "</div>",
	                            "<div class='sp-hue'>",
	                                "<div class='sp-slider'></div>",
	                                gradientFix,
	                            "</div>",
	                        "</div>",
	                        "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>",
	                    "</div>",
	                    "<div class='sp-input-container sp-cf'>",
	                        "<input class='sp-input' type='text' spellcheck='false'  />",
	                    "</div>",
	                    "<div class='sp-initial sp-thumb sp-cf'></div>",
	                    "<div class='sp-button-container sp-cf'>",
	                        "<a class='sp-cancel' href='#'></a>",
	                        "<button type='button' class='sp-choose'></button>",
	                    "</div>",
	                "</div>",
	            "</div>"
	        ].join("");
	    })();

	    function paletteTemplate (p, color, className, opts) {
	        var html = [];
	        for (var i = 0; i < p.length; i++) {
	            var current = p[i];
	            if(current) {
	                var tiny = tinycolor(current);
	                var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
	                c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
	                var formattedString = tiny.toString(opts.preferredFormat || "rgb");
	                var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
	                html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>');
	            } else {
	                var cls = 'sp-clear-display';
	                html.push($('<div />')
	                    .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>')
	                        .attr('title', opts.noColorSelectedText)
	                    )
	                    .html()
	                );
	            }
	        }
	        return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>";
	    }

	    function hideAll() {
	        for (var i = 0; i < spectrums.length; i++) {
	            if (spectrums[i]) {
	                spectrums[i].hide();
	            }
	        }
	    }

	    function instanceOptions(o, callbackContext) {
	        var opts = $.extend({}, defaultOpts, o);
	        opts.callbacks = {
	            'move': bind(opts.move, callbackContext),
	            'change': bind(opts.change, callbackContext),
	            'show': bind(opts.show, callbackContext),
	            'hide': bind(opts.hide, callbackContext),
	            'beforeShow': bind(opts.beforeShow, callbackContext)
	        };

	        return opts;
	    }

	    function spectrum(element, o) {

	        var opts = instanceOptions(o, element),
	            flat = opts.flat,
	            showSelectionPalette = opts.showSelectionPalette,
	            localStorageKey = opts.localStorageKey,
	            theme = opts.theme,
	            callbacks = opts.callbacks,
	            resize = throttle(reflow, 10),
	            visible = false,
	            isDragging = false,
	            dragWidth = 0,
	            dragHeight = 0,
	            dragHelperHeight = 0,
	            slideHeight = 0,
	            slideWidth = 0,
	            alphaWidth = 0,
	            alphaSlideHelperWidth = 0,
	            slideHelperHeight = 0,
	            currentHue = 0,
	            currentSaturation = 0,
	            currentValue = 0,
	            currentAlpha = 1,
	            palette = [],
	            paletteArray = [],
	            paletteLookup = {},
	            selectionPalette = opts.selectionPalette.slice(0),
	            maxSelectionSize = opts.maxSelectionSize,
	            draggingClass = "sp-dragging",
	            shiftMovementDirection = null;

	        var doc = element.ownerDocument,
	            body = doc.body,
	            boundElement = $(element),
	            disabled = false,
	            container = $(markup, doc).addClass(theme),
	            pickerContainer = container.find(".sp-picker-container"),
	            dragger = container.find(".sp-color"),
	            dragHelper = container.find(".sp-dragger"),
	            slider = container.find(".sp-hue"),
	            slideHelper = container.find(".sp-slider"),
	            alphaSliderInner = container.find(".sp-alpha-inner"),
	            alphaSlider = container.find(".sp-alpha"),
	            alphaSlideHelper = container.find(".sp-alpha-handle"),
	            textInput = container.find(".sp-input"),
	            paletteContainer = container.find(".sp-palette"),
	            initialColorContainer = container.find(".sp-initial"),
	            cancelButton = container.find(".sp-cancel"),
	            clearButton = container.find(".sp-clear"),
	            chooseButton = container.find(".sp-choose"),
	            toggleButton = container.find(".sp-palette-toggle"),
	            isInput = boundElement.is("input"),
	            isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(),
	            shouldReplace = isInput && !flat,
	            replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]),
	            offsetElement = (shouldReplace) ? replacer : boundElement,
	            previewElement = replacer.find(".sp-preview-inner"),
	            initialColor = opts.color || (isInput && boundElement.val()),
	            colorOnShow = false,
	            currentPreferredFormat = opts.preferredFormat,
	            clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
	            isEmpty = !initialColor,
	            allowEmpty = opts.allowEmpty && !isInputTypeColor;

	        function applyOptions() {

	            if (opts.showPaletteOnly) {
	                opts.showPalette = true;
	            }

	            toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);

	            if (opts.palette) {
	                palette = opts.palette.slice(0);
	                paletteArray = $.isArray(palette[0]) ? palette : [palette];
	                paletteLookup = {};
	                for (var i = 0; i < paletteArray.length; i++) {
	                    for (var j = 0; j < paletteArray[i].length; j++) {
	                        var rgb = tinycolor(paletteArray[i][j]).toRgbString();
	                        paletteLookup[rgb] = true;
	                    }
	                }
	            }

	            container.toggleClass("sp-flat", flat);
	            container.toggleClass("sp-input-disabled", !opts.showInput);
	            container.toggleClass("sp-alpha-enabled", opts.showAlpha);
	            container.toggleClass("sp-clear-enabled", allowEmpty);
	            container.toggleClass("sp-buttons-disabled", !opts.showButtons);
	            container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly);
	            container.toggleClass("sp-palette-disabled", !opts.showPalette);
	            container.toggleClass("sp-palette-only", opts.showPaletteOnly);
	            container.toggleClass("sp-initial-disabled", !opts.showInitial);
	            container.addClass(opts.className).addClass(opts.containerClassName);

	            reflow();
	        }

	        function initialize() {

	            if (IE) {
	                container.find("*:not(input)").attr("unselectable", "on");
	            }

	            applyOptions();

	            if (shouldReplace) {
	                boundElement.after(replacer).hide();
	            }

	            if (!allowEmpty) {
	                clearButton.hide();
	            }

	            if (flat) {
	                boundElement.after(container).hide();
	            }
	            else {

	                var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
	                if (appendTo.length !== 1) {
	                    appendTo = $("body");
	                }

	                appendTo.append(container);
	            }

	            updateSelectionPaletteFromStorage();

	            offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
	                if (!disabled) {
	                    toggle();
	                }

	                e.stopPropagation();

	                if (!$(e.target).is("input")) {
	                    e.preventDefault();
	                }
	            });

	            if(boundElement.is(":disabled") || (opts.disabled === true)) {
	                disable();
	            }

	            // Prevent clicks from bubbling up to document.  This would cause it to be hidden.
	            container.click(stopPropagation);

	            // Handle user typed input
	            textInput.change(setFromTextInput);
	            textInput.bind("paste", function () {
	                setTimeout(setFromTextInput, 1);
	            });
	            textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });

	            cancelButton.text(opts.cancelText);
	            cancelButton.bind("click.spectrum", function (e) {
	                e.stopPropagation();
	                e.preventDefault();
	                revert();
	                hide();
	            });

	            clearButton.attr("title", opts.clearText);
	            clearButton.bind("click.spectrum", function (e) {
	                e.stopPropagation();
	                e.preventDefault();
	                isEmpty = true;
	                move();

	                if(flat) {
	                    //for the flat style, this is a change event
	                    updateOriginalInput(true);
	                }
	            });

	            chooseButton.text(opts.chooseText);
	            chooseButton.bind("click.spectrum", function (e) {
	                e.stopPropagation();
	                e.preventDefault();

	                if (IE && textInput.is(":focus")) {
	                    textInput.trigger('change');
	                }

	                if (isValid()) {
	                    updateOriginalInput(true);
	                    hide();
	                }
	            });

	            toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
	            toggleButton.bind("click.spectrum", function (e) {
	                e.stopPropagation();
	                e.preventDefault();

	                opts.showPaletteOnly = !opts.showPaletteOnly;

	                // To make sure the Picker area is drawn on the right, next to the
	                // Palette area (and not below the palette), first move the Palette
	                // to the left to make space for the picker, plus 5px extra.
	                // The 'applyOptions' function puts the whole container back into place
	                // and takes care of the button-text and the sp-palette-only CSS class.
	                if (!opts.showPaletteOnly && !flat) {
	                    container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5));
	                }
	                applyOptions();
	            });

	            draggable(alphaSlider, function (dragX, dragY, e) {
	                currentAlpha = (dragX / alphaWidth);
	                isEmpty = false;
	                if (e.shiftKey) {
	                    currentAlpha = Math.round(currentAlpha * 10) / 10;
	                }

	                move();
	            }, dragStart, dragStop);

	            draggable(slider, function (dragX, dragY) {
	                currentHue = parseFloat(dragY / slideHeight);
	                isEmpty = false;
	                if (!opts.showAlpha) {
	                    currentAlpha = 1;
	                }
	                move();
	            }, dragStart, dragStop);

	            draggable(dragger, function (dragX, dragY, e) {

	                // shift+drag should snap the movement to either the x or y axis.
	                if (!e.shiftKey) {
	                    shiftMovementDirection = null;
	                }
	                else if (!shiftMovementDirection) {
	                    var oldDragX = currentSaturation * dragWidth;
	                    var oldDragY = dragHeight - (currentValue * dragHeight);
	                    var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);

	                    shiftMovementDirection = furtherFromX ? "x" : "y";
	                }

	                var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
	                var setValue = !shiftMovementDirection || shiftMovementDirection === "y";

	                if (setSaturation) {
	                    currentSaturation = parseFloat(dragX / dragWidth);
	                }
	                if (setValue) {
	                    currentValue = parseFloat((dragHeight - dragY) / dragHeight);
	                }

	                isEmpty = false;
	                if (!opts.showAlpha) {
	                    currentAlpha = 1;
	                }

	                move();

	            }, dragStart, dragStop);

	            if (!!initialColor) {
	                set(initialColor);

	                // In case color was black - update the preview UI and set the format
	                // since the set function will not run (default color is black).
	                updateUI();
	                currentPreferredFormat = opts.preferredFormat || tinycolor(initialColor).format;

	                addColorToSelectionPalette(initialColor);
	            }
	            else {
	                updateUI();
	            }

	            if (flat) {
	                show();
	            }

	            function paletteElementClick(e) {
	                if (e.data && e.data.ignore) {
	                    set($(e.target).closest(".sp-thumb-el").data("color"));
	                    move();
	                }
	                else {
	                    set($(e.target).closest(".sp-thumb-el").data("color"));
	                    move();
	                    updateOriginalInput(true);
	                    if (opts.hideAfterPaletteSelect) {
	                      hide();
	                    }
	                }

	                return false;
	            }

	            var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
	            paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick);
	            initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick);
	        }

	        function updateSelectionPaletteFromStorage() {

	            if (localStorageKey && window.localStorage) {

	                // Migrate old palettes over to new format.  May want to remove this eventually.
	                try {
	                    var oldPalette = window.localStorage[localStorageKey].split(",#");
	                    if (oldPalette.length > 1) {
	                        delete window.localStorage[localStorageKey];
	                        $.each(oldPalette, function(i, c) {
	                             addColorToSelectionPalette(c);
	                        });
	                    }
	                }
	                catch(e) { }

	                try {
	                    selectionPalette = window.localStorage[localStorageKey].split(";");
	                }
	                catch (e) { }
	            }
	        }

	        function addColorToSelectionPalette(color) {
	            if (showSelectionPalette) {
	                var rgb = tinycolor(color).toRgbString();
	                if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) {
	                    selectionPalette.push(rgb);
	                    while(selectionPalette.length > maxSelectionSize) {
	                        selectionPalette.shift();
	                    }
	                }

	                if (localStorageKey && window.localStorage) {
	                    try {
	                        window.localStorage[localStorageKey] = selectionPalette.join(";");
	                    }
	                    catch(e) { }
	                }
	            }
	        }

	        function getUniqueSelectionPalette() {
	            var unique = [];
	            if (opts.showPalette) {
	                for (var i = 0; i < selectionPalette.length; i++) {
	                    var rgb = tinycolor(selectionPalette[i]).toRgbString();

	                    if (!paletteLookup[rgb]) {
	                        unique.push(selectionPalette[i]);
	                    }
	                }
	            }

	            return unique.reverse().slice(0, opts.maxSelectionSize);
	        }

	        function drawPalette() {

	            var currentColor = get();

	            var html = $.map(paletteArray, function (palette, i) {
	                return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts);
	            });

	            updateSelectionPaletteFromStorage();

	            if (selectionPalette) {
	                html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts));
	            }

	            paletteContainer.html(html.join(""));
	        }

	        function drawInitial() {
	            if (opts.showInitial) {
	                var initial = colorOnShow;
	                var current = get();
	                initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts));
	            }
	        }

	        function dragStart() {
	            if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) {
	                reflow();
	            }
	            isDragging = true;
	            container.addClass(draggingClass);
	            shiftMovementDirection = null;
	            boundElement.trigger('dragstart.spectrum', [ get() ]);
	        }

	        function dragStop() {
	            isDragging = false;
	            container.removeClass(draggingClass);
	            boundElement.trigger('dragstop.spectrum', [ get() ]);
	        }

	        function setFromTextInput() {

	            var value = textInput.val();

	            if ((value === null || value === "") && allowEmpty) {
	                set(null);
	                updateOriginalInput(true);
	            }
	            else {
	                var tiny = tinycolor(value);
	                if (tiny.isValid()) {
	                    set(tiny);
	                    updateOriginalInput(true);
	                }
	                else {
	                    textInput.addClass("sp-validation-error");
	                }
	            }
	        }

	        function toggle() {
	            if (visible) {
	                hide();
	            }
	            else {
	                show();
	            }
	        }

	        function show() {
	            var event = $.Event('beforeShow.spectrum');

	            if (visible) {
	                reflow();
	                return;
	            }

	            boundElement.trigger(event, [ get() ]);

	            if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
	                return;
	            }

	            hideAll();
	            visible = true;

	            $(doc).bind("keydown.spectrum", onkeydown);
	            $(doc).bind("click.spectrum", clickout);
	            $(window).bind("resize.spectrum", resize);
	            replacer.addClass("sp-active");
	            container.removeClass("sp-hidden");

	            reflow();
	            updateUI();

	            colorOnShow = get();

	            drawInitial();
	            callbacks.show(colorOnShow);
	            boundElement.trigger('show.spectrum', [ colorOnShow ]);
	        }

	        function onkeydown(e) {
	            // Close on ESC
	            if (e.keyCode === 27) {
	                hide();
	            }
	        }

	        function clickout(e) {
	            // Return on right click.
	            if (e.button == 2) { return; }

	            // If a drag event was happening during the mouseup, don't hide
	            // on click.
	            if (isDragging) { return; }

	            if (clickoutFiresChange) {
	                updateOriginalInput(true);
	            }
	            else {
	                revert();
	            }
	            hide();
	        }

	        function hide() {
	            // Return if hiding is unnecessary
	            if (!visible || flat) { return; }
	            visible = false;

	            $(doc).unbind("keydown.spectrum", onkeydown);
	            $(doc).unbind("click.spectrum", clickout);
	            $(window).unbind("resize.spectrum", resize);

	            replacer.removeClass("sp-active");
	            container.addClass("sp-hidden");

	            callbacks.hide(get());
	            boundElement.trigger('hide.spectrum', [ get() ]);
	        }

	        function revert() {
	            set(colorOnShow, true);
	        }

	        function set(color, ignoreFormatChange) {
	            if (tinycolor.equals(color, get())) {
	                // Update UI just in case a validation error needs
	                // to be cleared.
	                updateUI();
	                return;
	            }

	            var newColor, newHsv;
	            if (!color && allowEmpty) {
	                isEmpty = true;
	            } else {
	                isEmpty = false;
	                newColor = tinycolor(color);
	                newHsv = newColor.toHsv();

	                currentHue = (newHsv.h % 360) / 360;
	                currentSaturation = newHsv.s;
	                currentValue = newHsv.v;
	                currentAlpha = newHsv.a;
	            }
	            updateUI();

	            if (newColor && newColor.isValid() && !ignoreFormatChange) {
	                currentPreferredFormat = opts.preferredFormat || newColor.getFormat();
	            }
	        }

	        function get(opts) {
	            opts = opts || { };

	            if (allowEmpty && isEmpty) {
	                return null;
	            }

	            return tinycolor.fromRatio({
	                h: currentHue,
	                s: currentSaturation,
	                v: currentValue,
	                a: Math.round(currentAlpha * 100) / 100
	            }, { format: opts.format || currentPreferredFormat });
	        }

	        function isValid() {
	            return !textInput.hasClass("sp-validation-error");
	        }

	        function move() {
	            updateUI();

	            callbacks.move(get());
	            boundElement.trigger('move.spectrum', [ get() ]);
	        }

	        function updateUI() {

	            textInput.removeClass("sp-validation-error");

	            updateHelperLocations();

	            // Update dragger background color (gradients take care of saturation and value).
	            var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
	            dragger.css("background-color", flatColor.toHexString());

	            // Get a format that alpha will be included in (hex and names ignore alpha)
	            var format = currentPreferredFormat;
	            if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) {
	                if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
	                    format = "rgb";
	                }
	            }

	            var realColor = get({ format: format }),
	                displayColor = '';

	             //reset background info for preview element
	            previewElement.removeClass("sp-clear-display");
	            previewElement.css('background-color', 'transparent');

	            if (!realColor && allowEmpty) {
	                // Update the replaced elements background with icon indicating no color selection
	                previewElement.addClass("sp-clear-display");
	            }
	            else {
	                var realHex = realColor.toHexString(),
	                    realRgb = realColor.toRgbString();

	                // Update the replaced elements background color (with actual selected color)
	                if (rgbaSupport || realColor.alpha === 1) {
	                    previewElement.css("background-color", realRgb);
	                }
	                else {
	                    previewElement.css("background-color", "transparent");
	                    previewElement.css("filter", realColor.toFilter());
	                }

	                if (opts.showAlpha) {
	                    var rgb = realColor.toRgb();
	                    rgb.a = 0;
	                    var realAlpha = tinycolor(rgb).toRgbString();
	                    var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";

	                    if (IE) {
	                        alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
	                    }
	                    else {
	                        alphaSliderInner.css("background", "-webkit-" + gradient);
	                        alphaSliderInner.css("background", "-moz-" + gradient);
	                        alphaSliderInner.css("background", "-ms-" + gradient);
	                        // Use current syntax gradient on unprefixed property.
	                        alphaSliderInner.css("background",
	                            "linear-gradient(to right, " + realAlpha + ", " + realHex + ")");
	                    }
	                }

	                displayColor = realColor.toString(format);
	            }

	            // Update the text entry input as it changes happen
	            if (opts.showInput) {
	                textInput.val(displayColor);
	            }

	            if (opts.showPalette) {
	                drawPalette();
	            }

	            drawInitial();
	        }

	        function updateHelperLocations() {
	            var s = currentSaturation;
	            var v = currentValue;

	            if(allowEmpty && isEmpty) {
	                //if selected color is empty, hide the helpers
	                alphaSlideHelper.hide();
	                slideHelper.hide();
	                dragHelper.hide();
	            }
	            else {
	                //make sure helpers are visible
	                alphaSlideHelper.show();
	                slideHelper.show();
	                dragHelper.show();

	                // Where to show the little circle in that displays your current selected color
	                var dragX = s * dragWidth;
	                var dragY = dragHeight - (v * dragHeight);
	                dragX = Math.max(
	                    -dragHelperHeight,
	                    Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
	                );
	                dragY = Math.max(
	                    -dragHelperHeight,
	                    Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
	                );
	                dragHelper.css({
	                    "top": dragY + "px",
	                    "left": dragX + "px"
	                });

	                var alphaX = currentAlpha * alphaWidth;
	                alphaSlideHelper.css({
	                    "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px"
	                });

	                // Where to show the bar that displays your current selected hue
	                var slideY = (currentHue) * slideHeight;
	                slideHelper.css({
	                    "top": (slideY - slideHelperHeight) + "px"
	                });
	            }
	        }

	        function updateOriginalInput(fireCallback) {
	            var color = get(),
	                displayColor = '',
	                hasChanged = !tinycolor.equals(color, colorOnShow);

	            if (color) {
	                displayColor = color.toString(currentPreferredFormat);
	                // Update the selection palette with the current color
	                addColorToSelectionPalette(color);
	            }

	            if (isInput) {
	                boundElement.val(displayColor);
	            }

	            if (fireCallback && hasChanged) {
	                callbacks.change(color);
	                boundElement.trigger('change', [ color ]);
	            }
	        }

	        function reflow() {
	            if (!visible) {
	                return; // Calculations would be useless and wouldn't be reliable anyways
	            }
	            dragWidth = dragger.width();
	            dragHeight = dragger.height();
	            dragHelperHeight = dragHelper.height();
	            slideWidth = slider.width();
	            slideHeight = slider.height();
	            slideHelperHeight = slideHelper.height();
	            alphaWidth = alphaSlider.width();
	            alphaSlideHelperWidth = alphaSlideHelper.width();

	            if (!flat) {
	                container.css("position", "absolute");
	                if (opts.offset) {
	                    container.offset(opts.offset);
	                } else {
	                    container.offset(getOffset(container, offsetElement));
	                }
	            }

	            updateHelperLocations();

	            if (opts.showPalette) {
	                drawPalette();
	            }

	            boundElement.trigger('reflow.spectrum');
	        }

	        function destroy() {
	            boundElement.show();
	            offsetElement.unbind("click.spectrum touchstart.spectrum");
	            container.remove();
	            replacer.remove();
	            spectrums[spect.id] = null;
	        }

	        function option(optionName, optionValue) {
	            if (optionName === undefined) {
	                return $.extend({}, opts);
	            }
	            if (optionValue === undefined) {
	                return opts[optionName];
	            }

	            opts[optionName] = optionValue;

	            if (optionName === "preferredFormat") {
	                currentPreferredFormat = opts.preferredFormat;
	            }
	            applyOptions();
	        }

	        function enable() {
	            disabled = false;
	            boundElement.attr("disabled", false);
	            offsetElement.removeClass("sp-disabled");
	        }

	        function disable() {
	            hide();
	            disabled = true;
	            boundElement.attr("disabled", true);
	            offsetElement.addClass("sp-disabled");
	        }

	        function setOffset(coord) {
	            opts.offset = coord;
	            reflow();
	        }

	        initialize();

	        var spect = {
	            show: show,
	            hide: hide,
	            toggle: toggle,
	            reflow: reflow,
	            option: option,
	            enable: enable,
	            disable: disable,
	            offset: setOffset,
	            set: function (c) {
	                set(c);
	                updateOriginalInput();
	            },
	            get: get,
	            destroy: destroy,
	            container: container
	        };

	        spect.id = spectrums.push(spect) - 1;

	        return spect;
	    }

	    /**
	    * checkOffset - get the offset below/above and left/right element depending on screen position
	    * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
	    */
	    function getOffset(picker, input) {
	        var extraY = 0;
	        var dpWidth = picker.outerWidth();
	        var dpHeight = picker.outerHeight();
	        var inputHeight = input.outerHeight();
	        var doc = picker[0].ownerDocument;
	        var docElem = doc.documentElement;
	        var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
	        var viewHeight = docElem.clientHeight + $(doc).scrollTop();
	        var offset = input.offset();
	        offset.top += inputHeight;

	        offset.left -=
	            Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
	            Math.abs(offset.left + dpWidth - viewWidth) : 0);

	        offset.top -=
	            Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
	            Math.abs(dpHeight + inputHeight - extraY) : extraY));

	        return offset;
	    }

	    /**
	    * noop - do nothing
	    */
	    function noop() {

	    }

	    /**
	    * stopPropagation - makes the code only doing this a little easier to read in line
	    */
	    function stopPropagation(e) {
	        e.stopPropagation();
	    }

	    /**
	    * Create a function bound to a given object
	    * Thanks to underscore.js
	    */
	    function bind(func, obj) {
	        var slice = Array.prototype.slice;
	        var args = slice.call(arguments, 2);
	        return function () {
	            return func.apply(obj, args.concat(slice.call(arguments)));
	        };
	    }

	    /**
	    * Lightweight drag helper.  Handles containment within the element, so that
	    * when dragging, the x is within [0,element.width] and y is within [0,element.height]
	    */
	    function draggable(element, onmove, onstart, onstop) {
	        onmove = onmove || function () { };
	        onstart = onstart || function () { };
	        onstop = onstop || function () { };
	        var doc = document;
	        var dragging = false;
	        var offset = {};
	        var maxHeight = 0;
	        var maxWidth = 0;
	        var hasTouch = ('ontouchstart' in window);

	        var duringDragEvents = {};
	        duringDragEvents["selectstart"] = prevent;
	        duringDragEvents["dragstart"] = prevent;
	        duringDragEvents["touchmove mousemove"] = move;
	        duringDragEvents["touchend mouseup"] = stop;

	        function prevent(e) {
	            if (e.stopPropagation) {
	                e.stopPropagation();
	            }
	            if (e.preventDefault) {
	                e.preventDefault();
	            }
	            e.returnValue = false;
	        }

	        function move(e) {
	            if (dragging) {
	                // Mouseup happened outside of window
	                if (IE && doc.documentMode < 9 && !e.button) {
	                    return stop();
	                }

	                var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
	                var pageX = t0 && t0.pageX || e.pageX;
	                var pageY = t0 && t0.pageY || e.pageY;

	                var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
	                var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));

	                if (hasTouch) {
	                    // Stop scrolling in iOS
	                    prevent(e);
	                }

	                onmove.apply(element, [dragX, dragY, e]);
	            }
	        }

	        function start(e) {
	            var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);

	            if (!rightclick && !dragging) {
	                if (onstart.apply(element, arguments) !== false) {
	                    dragging = true;
	                    maxHeight = $(element).height();
	                    maxWidth = $(element).width();
	                    offset = $(element).offset();

	                    $(doc).bind(duringDragEvents);
	                    $(doc.body).addClass("sp-dragging");

	                    move(e);

	                    prevent(e);
	                }
	            }
	        }

	        function stop() {
	            if (dragging) {
	                $(doc).unbind(duringDragEvents);
	                $(doc.body).removeClass("sp-dragging");

	                // Wait a tick before notifying observers to allow the click event
	                // to fire in Chrome.
	                setTimeout(function() {
	                    onstop.apply(element, arguments);
	                }, 0);
	            }
	            dragging = false;
	        }

	        $(element).bind("touchstart mousedown", start);
	    }

	    function throttle(func, wait, debounce) {
	        var timeout;
	        return function () {
	            var context = this, args = arguments;
	            var throttler = function () {
	                timeout = null;
	                func.apply(context, args);
	            };
	            if (debounce) clearTimeout(timeout);
	            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
	        };
	    }

	    function inputTypeColorSupport() {
	        return $.fn.spectrum.inputTypeColorSupport();
	    }

	    /**
	    * Define a jQuery plugin
	    */
	    var dataID = "spectrum.id";
	    $.fn.spectrum = function (opts, extra) {

	        if (typeof opts == "string") {

	            var returnValue = this;
	            var args = Array.prototype.slice.call( arguments, 1 );

	            this.each(function () {
	                var spect = spectrums[$(this).data(dataID)];
	                if (spect) {
	                    var method = spect[opts];
	                    if (!method) {
	                        throw new Error( "Spectrum: no such method: '" + opts + "'" );
	                    }

	                    if (opts == "get") {
	                        returnValue = spect.get();
	                    }
	                    else if (opts == "container") {
	                        returnValue = spect.container;
	                    }
	                    else if (opts == "option") {
	                        returnValue = spect.option.apply(spect, args);
	                    }
	                    else if (opts == "destroy") {
	                        spect.destroy();
	                        $(this).removeData(dataID);
	                    }
	                    else {
	                        method.apply(spect, args);
	                    }
	                }
	            });

	            return returnValue;
	        }

	        // Initializing a new instance of spectrum
	        return this.spectrum("destroy").each(function () {
	            var options = $.extend({}, opts, $(this).data());
	            var spect = spectrum(this, options);
	            $(this).data(dataID, spect.id);
	        });
	    };

	    $.fn.spectrum.load = true;
	    $.fn.spectrum.loadOpts = {};
	    $.fn.spectrum.draggable = draggable;
	    $.fn.spectrum.defaults = defaultOpts;
	    $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() {
	        if (typeof inputTypeColorSupport._cachedResult === "undefined") {
	            var colorInput = $("<input type='color'/>")[0]; // if color element is supported, value will default to not null
	            inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== "";
	        }
	        return inputTypeColorSupport._cachedResult;
	    };

	    $.spectrum = { };
	    $.spectrum.localization = { };
	    $.spectrum.palettes = { };

	    $.fn.spectrum.processNativeColorInputs = function () {
	        var colorInputs = $("input[type=color]");
	        if (colorInputs.length && !inputTypeColorSupport()) {
	            colorInputs.spectrum({
	                preferredFormat: "hex6"
	            });
	        }
	    };

	    // TinyColor v1.1.2
	    // https://github.com/bgrins/TinyColor
	    // Brian Grinstead, MIT License

	    (function() {

	    var trimLeft = /^[\s,#]+/,
	        trimRight = /\s+$/,
	        tinyCounter = 0,
	        math = Math,
	        mathRound = math.round,
	        mathMin = math.min,
	        mathMax = math.max,
	        mathRandom = math.random;

	    var tinycolor = function(color, opts) {

	        color = (color) ? color : '';
	        opts = opts || { };

	        // If input is already a tinycolor, return itself
	        if (color instanceof tinycolor) {
	           return color;
	        }
	        // If we are called as a function, call using new instead
	        if (!(this instanceof tinycolor)) {
	            return new tinycolor(color, opts);
	        }

	        var rgb = inputToRGB(color);
	        this._originalInput = color,
	        this._r = rgb.r,
	        this._g = rgb.g,
	        this._b = rgb.b,
	        this._a = rgb.a,
	        this._roundA = mathRound(100*this._a) / 100,
	        this._format = opts.format || rgb.format;
	        this._gradientType = opts.gradientType;

	        // Don't let the range of [0,255] come back in [0,1].
	        // Potentially lose a little bit of precision here, but will fix issues where
	        // .5 gets interpreted as half of the total, instead of half of 1
	        // If it was supposed to be 128, this was already taken care of by `inputToRgb`
	        if (this._r < 1) { this._r = mathRound(this._r); }
	        if (this._g < 1) { this._g = mathRound(this._g); }
	        if (this._b < 1) { this._b = mathRound(this._b); }

	        this._ok = rgb.ok;
	        this._tc_id = tinyCounter++;
	    };

	    tinycolor.prototype = {
	        isDark: function() {
	            return this.getBrightness() < 128;
	        },
	        isLight: function() {
	            return !this.isDark();
	        },
	        isValid: function() {
	            return this._ok;
	        },
	        getOriginalInput: function() {
	          return this._originalInput;
	        },
	        getFormat: function() {
	            return this._format;
	        },
	        getAlpha: function() {
	            return this._a;
	        },
	        getBrightness: function() {
	            var rgb = this.toRgb();
	            return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
	        },
	        setAlpha: function(value) {
	            this._a = boundAlpha(value);
	            this._roundA = mathRound(100*this._a) / 100;
	            return this;
	        },
	        toHsv: function() {
	            var hsv = rgbToHsv(this._r, this._g, this._b);
	            return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
	        },
	        toHsvString: function() {
	            var hsv = rgbToHsv(this._r, this._g, this._b);
	            var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
	            return (this._a == 1) ?
	              "hsv("  + h + ", " + s + "%, " + v + "%)" :
	              "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
	        },
	        toHsl: function() {
	            var hsl = rgbToHsl(this._r, this._g, this._b);
	            return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
	        },
	        toHslString: function() {
	            var hsl = rgbToHsl(this._r, this._g, this._b);
	            var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
	            return (this._a == 1) ?
	              "hsl("  + h + ", " + s + "%, " + l + "%)" :
	              "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
	        },
	        toHex: function(allow3Char) {
	            return rgbToHex(this._r, this._g, this._b, allow3Char);
	        },
	        toHexString: function(allow3Char) {
	            return '#' + this.toHex(allow3Char);
	        },
	        toHex8: function() {
	            return rgbaToHex(this._r, this._g, this._b, this._a);
	        },
	        toHex8String: function() {
	            return '#' + this.toHex8();
	        },
	        toRgb: function() {
	            return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
	        },
	        toRgbString: function() {
	            return (this._a == 1) ?
	              "rgb("  + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
	              "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
	        },
	        toPercentageRgb: function() {
	            return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
	        },
	        toPercentageRgbString: function() {
	            return (this._a == 1) ?
	              "rgb("  + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
	              "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
	        },
	        toName: function() {
	            if (this._a === 0) {
	                return "transparent";
	            }

	            if (this._a < 1) {
	                return false;
	            }

	            return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
	        },
	        toFilter: function(secondColor) {
	            var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
	            var secondHex8String = hex8String;
	            var gradientType = this._gradientType ? "GradientType = 1, " : "";

	            if (secondColor) {
	                var s = tinycolor(secondColor);
	                secondHex8String = s.toHex8String();
	            }

	            return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
	        },
	        toString: function(format) {
	            var formatSet = !!format;
	            format = format || this._format;

	            var formattedString = false;
	            var hasAlpha = this._a < 1 && this._a >= 0;
	            var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");

	            if (needsAlphaFormat) {
	                // Special case for "transparent", all other non-alpha formats
	                // will return rgba when there is transparency.
	                if (format === "name" && this._a === 0) {
	                    return this.toName();
	                }
	                return this.toRgbString();
	            }
	            if (format === "rgb") {
	                formattedString = this.toRgbString();
	            }
	            if (format === "prgb") {
	                formattedString = this.toPercentageRgbString();
	            }
	            if (format === "hex" || format === "hex6") {
	                formattedString = this.toHexString();
	            }
	            if (format === "hex3") {
	                formattedString = this.toHexString(true);
	            }
	            if (format === "hex8") {
	                formattedString = this.toHex8String();
	            }
	            if (format === "name") {
	                formattedString = this.toName();
	            }
	            if (format === "hsl") {
	                formattedString = this.toHslString();
	            }
	            if (format === "hsv") {
	                formattedString = this.toHsvString();
	            }

	            return formattedString || this.toHexString();
	        },

	        _applyModification: function(fn, args) {
	            var color = fn.apply(null, [this].concat([].slice.call(args)));
	            this._r = color._r;
	            this._g = color._g;
	            this._b = color._b;
	            this.setAlpha(color._a);
	            return this;
	        },
	        lighten: function() {
	            return this._applyModification(lighten, arguments);
	        },
	        brighten: function() {
	            return this._applyModification(brighten, arguments);
	        },
	        darken: function() {
	            return this._applyModification(darken, arguments);
	        },
	        desaturate: function() {
	            return this._applyModification(desaturate, arguments);
	        },
	        saturate: function() {
	            return this._applyModification(saturate, arguments);
	        },
	        greyscale: function() {
	            return this._applyModification(greyscale, arguments);
	        },
	        spin: function() {
	            return this._applyModification(spin, arguments);
	        },

	        _applyCombination: function(fn, args) {
	            return fn.apply(null, [this].concat([].slice.call(args)));
	        },
	        analogous: function() {
	            return this._applyCombination(analogous, arguments);
	        },
	        complement: function() {
	            return this._applyCombination(complement, arguments);
	        },
	        monochromatic: function() {
	            return this._applyCombination(monochromatic, arguments);
	        },
	        splitcomplement: function() {
	            return this._applyCombination(splitcomplement, arguments);
	        },
	        triad: function() {
	            return this._applyCombination(triad, arguments);
	        },
	        tetrad: function() {
	            return this._applyCombination(tetrad, arguments);
	        }
	    };

	    // If input is an object, force 1 into "1.0" to handle ratios properly
	    // String input requires "1.0" as input, so 1 will be treated as 1
	    tinycolor.fromRatio = function(color, opts) {
	        if (typeof color == "object") {
	            var newColor = {};
	            for (var i in color) {
	                if (color.hasOwnProperty(i)) {
	                    if (i === "a") {
	                        newColor[i] = color[i];
	                    }
	                    else {
	                        newColor[i] = convertToPercentage(color[i]);
	                    }
	                }
	            }
	            color = newColor;
	        }

	        return tinycolor(color, opts);
	    };

	    // Given a string or object, convert that input to RGB
	    // Possible string inputs:
	    //
	    //     "red"
	    //     "#f00" or "f00"
	    //     "#ff0000" or "ff0000"
	    //     "#ff000000" or "ff000000"
	    //     "rgb 255 0 0" or "rgb (255, 0, 0)"
	    //     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
	    //     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
	    //     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
	    //     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
	    //     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
	    //     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
	    //
	    function inputToRGB(color) {

	        var rgb = { r: 0, g: 0, b: 0 };
	        var a = 1;
	        var ok = false;
	        var format = false;

	        if (typeof color == "string") {
	            color = stringInputToObject(color);
	        }

	        if (typeof color == "object") {
	            if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
	                rgb = rgbToRgb(color.r, color.g, color.b);
	                ok = true;
	                format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
	            }
	            else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
	                color.s = convertToPercentage(color.s);
	                color.v = convertToPercentage(color.v);
	                rgb = hsvToRgb(color.h, color.s, color.v);
	                ok = true;
	                format = "hsv";
	            }
	            else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
	                color.s = convertToPercentage(color.s);
	                color.l = convertToPercentage(color.l);
	                rgb = hslToRgb(color.h, color.s, color.l);
	                ok = true;
	                format = "hsl";
	            }

	            if (color.hasOwnProperty("a")) {
	                a = color.a;
	            }
	        }

	        a = boundAlpha(a);

	        return {
	            ok: ok,
	            format: color.format || format,
	            r: mathMin(255, mathMax(rgb.r, 0)),
	            g: mathMin(255, mathMax(rgb.g, 0)),
	            b: mathMin(255, mathMax(rgb.b, 0)),
	            a: a
	        };
	    }


	    // Conversion Functions
	    // --------------------

	    // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
	    // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>

	    // `rgbToRgb`
	    // Handle bounds / percentage checking to conform to CSS color spec
	    // <http://www.w3.org/TR/css3-color/>
	    // *Assumes:* r, g, b in [0, 255] or [0, 1]
	    // *Returns:* { r, g, b } in [0, 255]
	    function rgbToRgb(r, g, b){
	        return {
	            r: bound01(r, 255) * 255,
	            g: bound01(g, 255) * 255,
	            b: bound01(b, 255) * 255
	        };
	    }

	    // `rgbToHsl`
	    // Converts an RGB color value to HSL.
	    // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
	    // *Returns:* { h, s, l } in [0,1]
	    function rgbToHsl(r, g, b) {

	        r = bound01(r, 255);
	        g = bound01(g, 255);
	        b = bound01(b, 255);

	        var max = mathMax(r, g, b), min = mathMin(r, g, b);
	        var h, s, l = (max + min) / 2;

	        if(max == min) {
	            h = s = 0; // achromatic
	        }
	        else {
	            var d = max - min;
	            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
	            switch(max) {
	                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
	                case g: h = (b - r) / d + 2; break;
	                case b: h = (r - g) / d + 4; break;
	            }

	            h /= 6;
	        }

	        return { h: h, s: s, l: l };
	    }

	    // `hslToRgb`
	    // Converts an HSL color value to RGB.
	    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
	    // *Returns:* { r, g, b } in the set [0, 255]
	    function hslToRgb(h, s, l) {
	        var r, g, b;

	        h = bound01(h, 360);
	        s = bound01(s, 100);
	        l = bound01(l, 100);

	        function hue2rgb(p, q, t) {
	            if(t < 0) t += 1;
	            if(t > 1) t -= 1;
	            if(t < 1/6) return p + (q - p) * 6 * t;
	            if(t < 1/2) return q;
	            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
	            return p;
	        }

	        if(s === 0) {
	            r = g = b = l; // achromatic
	        }
	        else {
	            var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
	            var p = 2 * l - q;
	            r = hue2rgb(p, q, h + 1/3);
	            g = hue2rgb(p, q, h);
	            b = hue2rgb(p, q, h - 1/3);
	        }

	        return { r: r * 255, g: g * 255, b: b * 255 };
	    }

	    // `rgbToHsv`
	    // Converts an RGB color value to HSV
	    // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
	    // *Returns:* { h, s, v } in [0,1]
	    function rgbToHsv(r, g, b) {

	        r = bound01(r, 255);
	        g = bound01(g, 255);
	        b = bound01(b, 255);

	        var max = mathMax(r, g, b), min = mathMin(r, g, b);
	        var h, s, v = max;

	        var d = max - min;
	        s = max === 0 ? 0 : d / max;

	        if(max == min) {
	            h = 0; // achromatic
	        }
	        else {
	            switch(max) {
	                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
	                case g: h = (b - r) / d + 2; break;
	                case b: h = (r - g) / d + 4; break;
	            }
	            h /= 6;
	        }
	        return { h: h, s: s, v: v };
	    }

	    // `hsvToRgb`
	    // Converts an HSV color value to RGB.
	    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
	    // *Returns:* { r, g, b } in the set [0, 255]
	     function hsvToRgb(h, s, v) {

	        h = bound01(h, 360) * 6;
	        s = bound01(s, 100);
	        v = bound01(v, 100);

	        var i = math.floor(h),
	            f = h - i,
	            p = v * (1 - s),
	            q = v * (1 - f * s),
	            t = v * (1 - (1 - f) * s),
	            mod = i % 6,
	            r = [v, q, p, p, t, v][mod],
	            g = [t, v, v, q, p, p][mod],
	            b = [p, p, t, v, v, q][mod];

	        return { r: r * 255, g: g * 255, b: b * 255 };
	    }

	    // `rgbToHex`
	    // Converts an RGB color to hex
	    // Assumes r, g, and b are contained in the set [0, 255]
	    // Returns a 3 or 6 character hex
	    function rgbToHex(r, g, b, allow3Char) {

	        var hex = [
	            pad2(mathRound(r).toString(16)),
	            pad2(mathRound(g).toString(16)),
	            pad2(mathRound(b).toString(16))
	        ];

	        // Return a 3 character hex if possible
	        if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
	            return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
	        }

	        return hex.join("");
	    }
	        // `rgbaToHex`
	        // Converts an RGBA color plus alpha transparency to hex
	        // Assumes r, g, b and a are contained in the set [0, 255]
	        // Returns an 8 character hex
	        function rgbaToHex(r, g, b, a) {

	            var hex = [
	                pad2(convertDecimalToHex(a)),
	                pad2(mathRound(r).toString(16)),
	                pad2(mathRound(g).toString(16)),
	                pad2(mathRound(b).toString(16))
	            ];

	            return hex.join("");
	        }

	    // `equals`
	    // Can be called with any tinycolor input
	    tinycolor.equals = function (color1, color2) {
	        if (!color1 || !color2) { return false; }
	        return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
	    };
	    tinycolor.random = function() {
	        return tinycolor.fromRatio({
	            r: mathRandom(),
	            g: mathRandom(),
	            b: mathRandom()
	        });
	    };


	    // Modification Functions
	    // ----------------------
	    // Thanks to less.js for some of the basics here
	    // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>

	    function desaturate(color, amount) {
	        amount = (amount === 0) ? 0 : (amount || 10);
	        var hsl = tinycolor(color).toHsl();
	        hsl.s -= amount / 100;
	        hsl.s = clamp01(hsl.s);
	        return tinycolor(hsl);
	    }

	    function saturate(color, amount) {
	        amount = (amount === 0) ? 0 : (amount || 10);
	        var hsl = tinycolor(color).toHsl();
	        hsl.s += amount / 100;
	        hsl.s = clamp01(hsl.s);
	        return tinycolor(hsl);
	    }

	    function greyscale(color) {
	        return tinycolor(color).desaturate(100);
	    }

	    function lighten (color, amount) {
	        amount = (amount === 0) ? 0 : (amount || 10);
	        var hsl = tinycolor(color).toHsl();
	        hsl.l += amount / 100;
	        hsl.l = clamp01(hsl.l);
	        return tinycolor(hsl);
	    }

	    function brighten(color, amount) {
	        amount = (amount === 0) ? 0 : (amount || 10);
	        var rgb = tinycolor(color).toRgb();
	        rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
	        rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
	        rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
	        return tinycolor(rgb);
	    }

	    function darken (color, amount) {
	        amount = (amount === 0) ? 0 : (amount || 10);
	        var hsl = tinycolor(color).toHsl();
	        hsl.l -= amount / 100;
	        hsl.l = clamp01(hsl.l);
	        return tinycolor(hsl);
	    }

	    // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
	    // Values outside of this range will be wrapped into this range.
	    function spin(color, amount) {
	        var hsl = tinycolor(color).toHsl();
	        var hue = (mathRound(hsl.h) + amount) % 360;
	        hsl.h = hue < 0 ? 360 + hue : hue;
	        return tinycolor(hsl);
	    }

	    // Combination Functions
	    // ---------------------
	    // Thanks to jQuery xColor for some of the ideas behind these
	    // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>

	    function complement(color) {
	        var hsl = tinycolor(color).toHsl();
	        hsl.h = (hsl.h + 180) % 360;
	        return tinycolor(hsl);
	    }

	    function triad(color) {
	        var hsl = tinycolor(color).toHsl();
	        var h = hsl.h;
	        return [
	            tinycolor(color),
	            tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
	            tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
	        ];
	    }

	    function tetrad(color) {
	        var hsl = tinycolor(color).toHsl();
	        var h = hsl.h;
	        return [
	            tinycolor(color),
	            tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
	            tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
	            tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
	        ];
	    }

	    function splitcomplement(color) {
	        var hsl = tinycolor(color).toHsl();
	        var h = hsl.h;
	        return [
	            tinycolor(color),
	            tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
	            tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
	        ];
	    }

	    function analogous(color, results, slices) {
	        results = results || 6;
	        slices = slices || 30;

	        var hsl = tinycolor(color).toHsl();
	        var part = 360 / slices;
	        var ret = [tinycolor(color)];

	        for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
	            hsl.h = (hsl.h + part) % 360;
	            ret.push(tinycolor(hsl));
	        }
	        return ret;
	    }

	    function monochromatic(color, results) {
	        results = results || 6;
	        var hsv = tinycolor(color).toHsv();
	        var h = hsv.h, s = hsv.s, v = hsv.v;
	        var ret = [];
	        var modification = 1 / results;

	        while (results--) {
	            ret.push(tinycolor({ h: h, s: s, v: v}));
	            v = (v + modification) % 1;
	        }

	        return ret;
	    }

	    // Utility Functions
	    // ---------------------

	    tinycolor.mix = function(color1, color2, amount) {
	        amount = (amount === 0) ? 0 : (amount || 50);

	        var rgb1 = tinycolor(color1).toRgb();
	        var rgb2 = tinycolor(color2).toRgb();

	        var p = amount / 100;
	        var w = p * 2 - 1;
	        var a = rgb2.a - rgb1.a;

	        var w1;

	        if (w * a == -1) {
	            w1 = w;
	        } else {
	            w1 = (w + a) / (1 + w * a);
	        }

	        w1 = (w1 + 1) / 2;

	        var w2 = 1 - w1;

	        var rgba = {
	            r: rgb2.r * w1 + rgb1.r * w2,
	            g: rgb2.g * w1 + rgb1.g * w2,
	            b: rgb2.b * w1 + rgb1.b * w2,
	            a: rgb2.a * p  + rgb1.a * (1 - p)
	        };

	        return tinycolor(rgba);
	    };


	    // Readability Functions
	    // ---------------------
	    // <http://www.w3.org/TR/AERT#color-contrast>

	    // `readability`
	    // Analyze the 2 colors and returns an object with the following properties:
	    //    `brightness`: difference in brightness between the two colors
	    //    `color`: difference in color/hue between the two colors
	    tinycolor.readability = function(color1, color2) {
	        var c1 = tinycolor(color1);
	        var c2 = tinycolor(color2);
	        var rgb1 = c1.toRgb();
	        var rgb2 = c2.toRgb();
	        var brightnessA = c1.getBrightness();
	        var brightnessB = c2.getBrightness();
	        var colorDiff = (
	            Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) +
	            Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) +
	            Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b)
	        );

	        return {
	            brightness: Math.abs(brightnessA - brightnessB),
	            color: colorDiff
	        };
	    };

	    // `readable`
	    // http://www.w3.org/TR/AERT#color-contrast
	    // Ensure that foreground and background color combinations provide sufficient contrast.
	    // *Example*
	    //    tinycolor.isReadable("#000", "#111") => false
	    tinycolor.isReadable = function(color1, color2) {
	        var readability = tinycolor.readability(color1, color2);
	        return readability.brightness > 125 && readability.color > 500;
	    };

	    // `mostReadable`
	    // Given a base color and a list of possible foreground or background
	    // colors for that base, returns the most readable color.
	    // *Example*
	    //    tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
	    tinycolor.mostReadable = function(baseColor, colorList) {
	        var bestColor = null;
	        var bestScore = 0;
	        var bestIsReadable = false;
	        for (var i=0; i < colorList.length; i++) {

	            // We normalize both around the "acceptable" breaking point,
	            // but rank brightness constrast higher than hue.

	            var readability = tinycolor.readability(baseColor, colorList[i]);
	            var readable = readability.brightness > 125 && readability.color > 500;
	            var score = 3 * (readability.brightness / 125) + (readability.color / 500);

	            if ((readable && ! bestIsReadable) ||
	                (readable && bestIsReadable && score > bestScore) ||
	                ((! readable) && (! bestIsReadable) && score > bestScore)) {
	                bestIsReadable = readable;
	                bestScore = score;
	                bestColor = tinycolor(colorList[i]);
	            }
	        }
	        return bestColor;
	    };


	    // Big List of Colors
	    // ------------------
	    // <http://www.w3.org/TR/css3-color/#svg-color>
	    var names = tinycolor.names = {
	        aliceblue: "f0f8ff",
	        antiquewhite: "faebd7",
	        aqua: "0ff",
	        aquamarine: "7fffd4",
	        azure: "f0ffff",
	        beige: "f5f5dc",
	        bisque: "ffe4c4",
	        black: "000",
	        blanchedalmond: "ffebcd",
	        blue: "00f",
	        blueviolet: "8a2be2",
	        brown: "a52a2a",
	        burlywood: "deb887",
	        burntsienna: "ea7e5d",
	        cadetblue: "5f9ea0",
	        chartreuse: "7fff00",
	        chocolate: "d2691e",
	        coral: "ff7f50",
	        cornflowerblue: "6495ed",
	        cornsilk: "fff8dc",
	        crimson: "dc143c",
	        cyan: "0ff",
	        darkblue: "00008b",
	        darkcyan: "008b8b",
	        darkgoldenrod: "b8860b",
	        darkgray: "a9a9a9",
	        darkgreen: "006400",
	        darkgrey: "a9a9a9",
	        darkkhaki: "bdb76b",
	        darkmagenta: "8b008b",
	        darkolivegreen: "556b2f",
	        darkorange: "ff8c00",
	        darkorchid: "9932cc",
	        darkred: "8b0000",
	        darksalmon: "e9967a",
	        darkseagreen: "8fbc8f",
	        darkslateblue: "483d8b",
	        darkslategray: "2f4f4f",
	        darkslategrey: "2f4f4f",
	        darkturquoise: "00ced1",
	        darkviolet: "9400d3",
	        deeppink: "ff1493",
	        deepskyblue: "00bfff",
	        dimgray: "696969",
	        dimgrey: "696969",
	        dodgerblue: "1e90ff",
	        firebrick: "b22222",
	        floralwhite: "fffaf0",
	        forestgreen: "228b22",
	        fuchsia: "f0f",
	        gainsboro: "dcdcdc",
	        ghostwhite: "f8f8ff",
	        gold: "ffd700",
	        goldenrod: "daa520",
	        gray: "808080",
	        green: "008000",
	        greenyellow: "adff2f",
	        grey: "808080",
	        honeydew: "f0fff0",
	        hotpink: "ff69b4",
	        indianred: "cd5c5c",
	        indigo: "4b0082",
	        ivory: "fffff0",
	        khaki: "f0e68c",
	        lavender: "e6e6fa",
	        lavenderblush: "fff0f5",
	        lawngreen: "7cfc00",
	        lemonchiffon: "fffacd",
	        lightblue: "add8e6",
	        lightcoral: "f08080",
	        lightcyan: "e0ffff",
	        lightgoldenrodyellow: "fafad2",
	        lightgray: "d3d3d3",
	        lightgreen: "90ee90",
	        lightgrey: "d3d3d3",
	        lightpink: "ffb6c1",
	        lightsalmon: "ffa07a",
	        lightseagreen: "20b2aa",
	        lightskyblue: "87cefa",
	        lightslategray: "789",
	        lightslategrey: "789",
	        lightsteelblue: "b0c4de",
	        lightyellow: "ffffe0",
	        lime: "0f0",
	        limegreen: "32cd32",
	        linen: "faf0e6",
	        magenta: "f0f",
	        maroon: "800000",
	        mediumaquamarine: "66cdaa",
	        mediumblue: "0000cd",
	        mediumorchid: "ba55d3",
	        mediumpurple: "9370db",
	        mediumseagreen: "3cb371",
	        mediumslateblue: "7b68ee",
	        mediumspringgreen: "00fa9a",
	        mediumturquoise: "48d1cc",
	        mediumvioletred: "c71585",
	        midnightblue: "191970",
	        mintcream: "f5fffa",
	        mistyrose: "ffe4e1",
	        moccasin: "ffe4b5",
	        navajowhite: "ffdead",
	        navy: "000080",
	        oldlace: "fdf5e6",
	        olive: "808000",
	        olivedrab: "6b8e23",
	        orange: "ffa500",
	        orangered: "ff4500",
	        orchid: "da70d6",
	        palegoldenrod: "eee8aa",
	        palegreen: "98fb98",
	        paleturquoise: "afeeee",
	        palevioletred: "db7093",
	        papayawhip: "ffefd5",
	        peachpuff: "ffdab9",
	        peru: "cd853f",
	        pink: "ffc0cb",
	        plum: "dda0dd",
	        powderblue: "b0e0e6",
	        purple: "800080",
	        rebeccapurple: "663399",
	        red: "f00",
	        rosybrown: "bc8f8f",
	        royalblue: "4169e1",
	        saddlebrown: "8b4513",
	        salmon: "fa8072",
	        sandybrown: "f4a460",
	        seagreen: "2e8b57",
	        seashell: "fff5ee",
	        sienna: "a0522d",
	        silver: "c0c0c0",
	        skyblue: "87ceeb",
	        slateblue: "6a5acd",
	        slategray: "708090",
	        slategrey: "708090",
	        snow: "fffafa",
	        springgreen: "00ff7f",
	        steelblue: "4682b4",
	        tan: "d2b48c",
	        teal: "008080",
	        thistle: "d8bfd8",
	        tomato: "ff6347",
	        turquoise: "40e0d0",
	        violet: "ee82ee",
	        wheat: "f5deb3",
	        white: "fff",
	        whitesmoke: "f5f5f5",
	        yellow: "ff0",
	        yellowgreen: "9acd32"
	    };

	    // Make it easy to access colors via `hexNames[hex]`
	    var hexNames = tinycolor.hexNames = flip(names);


	    // Utilities
	    // ---------

	    // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
	    function flip(o) {
	        var flipped = { };
	        for (var i in o) {
	            if (o.hasOwnProperty(i)) {
	                flipped[o[i]] = i;
	            }
	        }
	        return flipped;
	    }

	    // Return a valid alpha value [0,1] with all invalid values being set to 1
	    function boundAlpha(a) {
	        a = parseFloat(a);

	        if (isNaN(a) || a < 0 || a > 1) {
	            a = 1;
	        }

	        return a;
	    }

	    // Take input from [0, n] and return it as [0, 1]
	    function bound01(n, max) {
	        if (isOnePointZero(n)) { n = "100%"; }

	        var processPercent = isPercentage(n);
	        n = mathMin(max, mathMax(0, parseFloat(n)));

	        // Automatically convert percentage into number
	        if (processPercent) {
	            n = parseInt(n * max, 10) / 100;
	        }

	        // Handle floating point rounding errors
	        if ((math.abs(n - max) < 0.000001)) {
	            return 1;
	        }

	        // Convert into [0, 1] range if it isn't already
	        return (n % max) / parseFloat(max);
	    }

	    // Force a number between 0 and 1
	    function clamp01(val) {
	        return mathMin(1, mathMax(0, val));
	    }

	    // Parse a base-16 hex value into a base-10 integer
	    function parseIntFromHex(val) {
	        return parseInt(val, 16);
	    }

	    // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
	    // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
	    function isOnePointZero(n) {
	        return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
	    }

	    // Check to see if string passed in is a percentage
	    function isPercentage(n) {
	        return typeof n === "string" && n.indexOf('%') != -1;
	    }

	    // Force a hex value to have 2 characters
	    function pad2(c) {
	        return c.length == 1 ? '0' + c : '' + c;
	    }

	    // Replace a decimal with it's percentage value
	    function convertToPercentage(n) {
	        if (n <= 1) {
	            n = (n * 100) + "%";
	        }

	        return n;
	    }

	    // Converts a decimal to a hex value
	    function convertDecimalToHex(d) {
	        return Math.round(parseFloat(d) * 255).toString(16);
	    }
	    // Converts a hex value to a decimal
	    function convertHexToDecimal(h) {
	        return (parseIntFromHex(h) / 255);
	    }

	    var matchers = (function() {

	        // <http://www.w3.org/TR/css3-values/#integers>
	        var CSS_INTEGER = "[-\\+]?\\d+%?";

	        // <http://www.w3.org/TR/css3-values/#number-value>
	        var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";

	        // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
	        var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";

	        // Actual matching.
	        // Parentheses and commas are optional, but not required.
	        // Whitespace can take the place of commas or opening paren
	        var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
	        var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";

	        return {
	            rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
	            rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
	            hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
	            hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
	            hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
	            hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
	            hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
	            hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
	            hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
	        };
	    })();

	    // `stringInputToObject`
	    // Permissive string parsing.  Take in a number of formats, and output an object
	    // based on detected format.  Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
	    function stringInputToObject(color) {

	        color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
	        var named = false;
	        if (names[color]) {
	            color = names[color];
	            named = true;
	        }
	        else if (color == 'transparent') {
	            return { r: 0, g: 0, b: 0, a: 0, format: "name" };
	        }

	        // Try to match string input using regular expressions.
	        // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
	        // Just return an object and let the conversion functions handle that.
	        // This way the result will be the same whether the tinycolor is initialized with string or object.
	        var match;
	        if ((match = matchers.rgb.exec(color))) {
	            return { r: match[1], g: match[2], b: match[3] };
	        }
	        if ((match = matchers.rgba.exec(color))) {
	            return { r: match[1], g: match[2], b: match[3], a: match[4] };
	        }
	        if ((match = matchers.hsl.exec(color))) {
	            return { h: match[1], s: match[2], l: match[3] };
	        }
	        if ((match = matchers.hsla.exec(color))) {
	            return { h: match[1], s: match[2], l: match[3], a: match[4] };
	        }
	        if ((match = matchers.hsv.exec(color))) {
	            return { h: match[1], s: match[2], v: match[3] };
	        }
	        if ((match = matchers.hsva.exec(color))) {
	            return { h: match[1], s: match[2], v: match[3], a: match[4] };
	        }
	        if ((match = matchers.hex8.exec(color))) {
	            return {
	                a: convertHexToDecimal(match[1]),
	                r: parseIntFromHex(match[2]),
	                g: parseIntFromHex(match[3]),
	                b: parseIntFromHex(match[4]),
	                format: named ? "name" : "hex8"
	            };
	        }
	        if ((match = matchers.hex6.exec(color))) {
	            return {
	                r: parseIntFromHex(match[1]),
	                g: parseIntFromHex(match[2]),
	                b: parseIntFromHex(match[3]),
	                format: named ? "name" : "hex"
	            };
	        }
	        if ((match = matchers.hex3.exec(color))) {
	            return {
	                r: parseIntFromHex(match[1] + '' + match[1]),
	                g: parseIntFromHex(match[2] + '' + match[2]),
	                b: parseIntFromHex(match[3] + '' + match[3]),
	                format: named ? "name" : "hex"
	            };
	        }

	        return false;
	    }

	    window.tinycolor = tinycolor;
	    })();

	    $(function () {
	        if ($.fn.spectrum.load) {
	            $.fn.spectrum.processNativeColorInputs();
	        }
	    });

	});


/***/ },

/***/ 576:
/***/ function(module, exports) {

	// Generated by CoffeeScript 1.9.2

	/**
	@license Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
	 */

	(function() {
	  var $, win;

	  $ = this.jQuery || window.jQuery;

	  win = $(window);

	  $.fn.stick_in_parent = function(opts) {
	    var doc, elm, enable_bottoming, fn, i, inner_scrolling, len, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class;
	    if (opts == null) {
	      opts = {};
	    }
	    sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming;
	    if (offset_top == null) {
	      offset_top = 0;
	    }
	    if (parent_selector == null) {
	      parent_selector = void 0;
	    }
	    if (inner_scrolling == null) {
	      inner_scrolling = true;
	    }
	    if (sticky_class == null) {
	      sticky_class = "is_stuck";
	    }
	    doc = $(document);
	    if (enable_bottoming == null) {
	      enable_bottoming = true;
	    }
	    outer_width = function(el) {
	      var _el, computed, w;
	      if (window.getComputedStyle) {
	        _el = el[0];
	        computed = window.getComputedStyle(el[0]);
	        w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right"));
	        if (computed.getPropertyValue("box-sizing") !== "border-box") {
	          w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right"));
	        }
	        return w;
	      } else {
	        return el.outerWidth(true);
	      }
	    };
	    fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) {
	      var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick;
	      if (elm.data("sticky_kit")) {
	        return;
	      }
	      elm.data("sticky_kit", true);
	      last_scroll_height = doc.height();
	      parent = elm.parent();
	      if (parent_selector != null) {
	        parent = parent.closest(parent_selector);
	      }
	      if (!parent.length) {
	        throw "failed to find stick parent";
	      }
	      fixed = false;
	      bottomed = false;
	      spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("<div />");
	      if (spacer) {
	        spacer.css('position', elm.css('position'));
	      }
	      recalc = function() {
	        var border_top, padding_top, restore;
	        if (detached) {
	          return;
	        }
	        last_scroll_height = doc.height();
	        border_top = parseInt(parent.css("border-top-width"), 10);
	        padding_top = parseInt(parent.css("padding-top"), 10);
	        padding_bottom = parseInt(parent.css("padding-bottom"), 10);
	        parent_top = parent.offset().top + border_top + padding_top;
	        parent_height = parent.height();
	        if (fixed) {
	          fixed = false;
	          bottomed = false;
	          if (manual_spacer == null) {
	            elm.insertAfter(spacer);
	            spacer.detach();
	          }
	          elm.css({
	            position: "",
	            top: "",
	            width: "",
	            bottom: ""
	          }).removeClass(sticky_class);
	          restore = true;
	        }
	        top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top;
	        height = elm.outerHeight(true);
	        el_float = elm.css("float");
	        if (spacer) {
	          spacer.css({
	            width: outer_width(elm),
	            height: height,
	            display: elm.css("display"),
	            "vertical-align": elm.css("vertical-align"),
	            "float": el_float
	          });
	        }
	        if (restore) {
	          return tick();
	        }
	      };
	      recalc();

	      last_pos = void 0;
	      offset = offset_top;
	      recalc_counter = recalc_every;
	      tick = function() {
	        var css, delta, recalced, scroll, will_bottom, win_height;
	        if (detached) {
	          return;
	        }
	        recalced = false;
	        if (recalc_counter != null) {
	          recalc_counter -= 1;
	          if (recalc_counter <= 0) {
	            recalc_counter = recalc_every;
	            recalc();
	            recalced = true;
	          }
	        }
	        if (!recalced && doc.height() !== last_scroll_height) {
	          recalc();
	          recalced = true;
	        }
	        scroll = win.scrollTop();
	        if (last_pos != null) {
	          delta = scroll - last_pos;
	        }
	        last_pos = scroll;
	        if (fixed) {
	          if (enable_bottoming) {
	            will_bottom = scroll + height + offset > parent_height + parent_top;
	            if (bottomed && !will_bottom) {
	              bottomed = false;
	              elm.css({
	                position: "fixed",
	                bottom: "",
	                top: offset
	              }).trigger("sticky_kit:unbottom");
	            }
	          }
	          if (scroll < top) {
	            fixed = false;
	            offset = offset_top;
	            if (manual_spacer == null) {
	              if (el_float === "left" || el_float === "right") {
	                elm.insertAfter(spacer);
	              }
	              spacer.detach();
	            }
	            css = {
	              position: "",
	              width: "",
	              top: ""
	            };
	            elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick");
	          }
	          if (inner_scrolling) {
	            win_height = win.height();
	            if (height + offset_top > win_height) {
	              if (!bottomed) {
	                offset -= delta;
	                offset = Math.max(win_height - height, offset);
	                offset = Math.min(offset_top, offset);
	                if (fixed) {
	                  elm.css({
	                    top: offset + "px"
	                  });
	                }
	              }
	            }
	          }
	        } else {
	          if (scroll > top) {
	            fixed = true;
	            css = {
	              position: "fixed",
	              top: offset
	            };
	            css.width = elm.css("box-sizing") === "border-box" ? elm.outerWidth() + "px" : elm.width() + "px";
	            elm.css(css).addClass(sticky_class);
	            if (manual_spacer == null) {
	              elm.after(spacer);
	              if (el_float === "left" || el_float === "right") {
	                spacer.append(elm);
	              }
	            }
	            elm.trigger("sticky_kit:stick");
	          }
	        }
	        if (fixed && enable_bottoming) {
	          if (will_bottom == null) {
	            will_bottom = scroll + height + offset > parent_height + parent_top;
	          }
	          if (!bottomed && will_bottom) {
	            bottomed = true;
	            if (parent.css("position") === "static") {
	              parent.css({
	                position: "relative"
	              });
	            }
	            return elm.css({
	              position: "absolute",
	              bottom: padding_bottom,
	              top: "auto"
	            }).trigger("sticky_kit:bottom");
	          }
	        }
	      };
	      recalc_and_tick = function() {
	        recalc();
	        return tick();
	      };
	      detach = function() {
	        detached = true;
	        win.off("touchmove", tick);
	        win.off("scroll", tick);
	        win.off("resize", recalc_and_tick);
	        $(document.body).off("sticky_kit:recalc", recalc_and_tick);
	        elm.off("sticky_kit:detach", detach);
	        elm.removeData("sticky_kit");
	        elm.css({
	          position: "",
	          bottom: "",
	          top: "",
	          width: ""
	        });
	        parent.position("position", "");
	        if (fixed) {
	          if (manual_spacer == null) {
	            if (el_float === "left" || el_float === "right") {
	              elm.insertAfter(spacer);
	            }
	            spacer.remove();
	          }
	          return elm.removeClass(sticky_class);
	        }
	      };
	      win.on("touchmove", tick);
	      win.on("scroll", tick);
	      win.on("resize", recalc_and_tick);
	      $(document.body).on("sticky_kit:recalc", recalc_and_tick);
	      elm.on("sticky_kit:detach", detach);
	      return setTimeout(tick, 0);
	    };
	    for (i = 0, len = this.length; i < len; i++) {
	      elm = this[i];
	      fn($(elm));
	    }
	    return this;
	  };

	}).call(this);



/***/ },

/***/ 577:
/***/ function(module, exports) {

	/* Blob.js
	 * A Blob implementation.
	 * 2014-07-24
	 *
	 * By Eli Grey, http://eligrey.com
	 * By Devin Samarin, https://github.com/dsamarin
	 * License: MIT
	 *   See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
	 */

	/*global self, unescape */
	/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
	  plusplus: true */

	/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */

	(function (view) {
		"use strict";

		view.URL = view.URL || view.webkitURL;

		if (view.Blob && view.URL) {
			try {
				new Blob;
				return;
			} catch (e) {}
		}

		// Internally we use a BlobBuilder implementation to base Blob off of
		// in order to support older browsers that only have BlobBuilder
		var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
			var
				  get_class = function(object) {
					return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
				}
				, FakeBlobBuilder = function BlobBuilder() {
					this.data = [];
				}
				, FakeBlob = function Blob(data, type, encoding) {
					this.data = data;
					this.size = data.length;
					this.type = type;
					this.encoding = encoding;
				}
				, FBB_proto = FakeBlobBuilder.prototype
				, FB_proto = FakeBlob.prototype
				, FileReaderSync = view.FileReaderSync
				, FileException = function(type) {
					this.code = this[this.name = type];
				}
				, file_ex_codes = (
					  "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
					+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
				).split(" ")
				, file_ex_code = file_ex_codes.length
				, real_URL = view.URL || view.webkitURL || view
				, real_create_object_URL = real_URL.createObjectURL
				, real_revoke_object_URL = real_URL.revokeObjectURL
				, URL = real_URL
				, btoa = view.btoa
				, atob = view.atob

				, ArrayBuffer = view.ArrayBuffer
				, Uint8Array = view.Uint8Array

				, origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
			;
			FakeBlob.fake = FB_proto.fake = true;
			while (file_ex_code--) {
				FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
			}
			// Polyfill URL
			if (!real_URL.createObjectURL) {
				URL = view.URL = function(uri) {
					var
						  uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
						, uri_origin
					;
					uri_info.href = uri;
					if (!("origin" in uri_info)) {
						if (uri_info.protocol.toLowerCase() === "data:") {
							uri_info.origin = null;
						} else {
							uri_origin = uri.match(origin);
							uri_info.origin = uri_origin && uri_origin[1];
						}
					}
					return uri_info;
				};
			}
			URL.createObjectURL = function(blob) {
				var
					  type = blob.type
					, data_URI_header
				;
				if (type === null) {
					type = "application/octet-stream";
				}
				if (blob instanceof FakeBlob) {
					data_URI_header = "data:" + type;
					if (blob.encoding === "base64") {
						return data_URI_header + ";base64," + blob.data;
					} else if (blob.encoding === "URI") {
						return data_URI_header + "," + decodeURIComponent(blob.data);
					} if (btoa) {
						return data_URI_header + ";base64," + btoa(blob.data);
					} else {
						return data_URI_header + "," + encodeURIComponent(blob.data);
					}
				} else if (real_create_object_URL) {
					return real_create_object_URL.call(real_URL, blob);
				}
			};
			URL.revokeObjectURL = function(object_URL) {
				if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
					real_revoke_object_URL.call(real_URL, object_URL);
				}
			};
			FBB_proto.append = function(data/*, endings*/) {
				var bb = this.data;
				// decode data to a binary string
				if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
					var
						  str = ""
						, buf = new Uint8Array(data)
						, i = 0
						, buf_len = buf.length
					;
					for (; i < buf_len; i++) {
						str += String.fromCharCode(buf[i]);
					}
					bb.push(str);
				} else if (get_class(data) === "Blob" || get_class(data) === "File") {
					if (FileReaderSync) {
						var fr = new FileReaderSync;
						bb.push(fr.readAsBinaryString(data));
					} else {
						// async FileReader won't work as BlobBuilder is sync
						throw new FileException("NOT_READABLE_ERR");
					}
				} else if (data instanceof FakeBlob) {
					if (data.encoding === "base64" && atob) {
						bb.push(atob(data.data));
					} else if (data.encoding === "URI") {
						bb.push(decodeURIComponent(data.data));
					} else if (data.encoding === "raw") {
						bb.push(data.data);
					}
				} else {
					if (typeof data !== "string") {
						data += ""; // convert unsupported types to strings
					}
					// decode UTF-16 to binary string
					bb.push(unescape(encodeURIComponent(data)));
				}
			};
			FBB_proto.getBlob = function(type) {
				if (!arguments.length) {
					type = null;
				}
				return new FakeBlob(this.data.join(""), type, "raw");
			};
			FBB_proto.toString = function() {
				return "[object BlobBuilder]";
			};
			FB_proto.slice = function(start, end, type) {
				var args = arguments.length;
				if (args < 3) {
					type = null;
				}
				return new FakeBlob(
					  this.data.slice(start, args > 1 ? end : this.data.length)
					, type
					, this.encoding
				);
			};
			FB_proto.toString = function() {
				return "[object Blob]";
			};
			FB_proto.close = function() {
				this.size = 0;
				delete this.data;
			};
			return FakeBlobBuilder;
		}(view));

		view.Blob = function(blobParts, options) {
			var type = options ? (options.type || "") : "";
			var builder = new BlobBuilder();
			if (blobParts) {
				for (var i = 0, len = blobParts.length; i < len; i++) {
					if (Uint8Array && blobParts[i] instanceof Uint8Array) {
						builder.append(blobParts[i].buffer);
					}
					else {
						builder.append(blobParts[i]);
					}
				}
			}
			var blob = builder.getBlob(type);
			if (!blob.slice && blob.webkitSlice) {
				blob.slice = blob.webkitSlice;
			}
			return blob;
		};

		var getPrototypeOf = Object.getPrototypeOf || function(object) {
			return object.__proto__;
		};
		view.Blob.prototype = getPrototypeOf(new view.Blob());
	}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));


	/*** EXPORTS FROM exports-loader ***/
	module.exports = window.Blob;

/***/ },

/***/ 578:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_RESULT__;/* FileSaver.js
	 * A saveAs() FileSaver implementation.
	 * 1.3.2
	 * 2016-06-16 18:25:19
	 *
	 * By Eli Grey, http://eligrey.com
	 * License: MIT
	 *   See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
	 */

	/*global self */
	/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */

	/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

	var saveAs = saveAs || (function(view) {
		"use strict";
		// IE <10 is explicitly unsupported
		if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
			return;
		}
		var
			  doc = view.document
			  // only get URL when necessary in case Blob.js hasn't overridden it yet
			, get_URL = function() {
				return view.URL || view.webkitURL || view;
			}
			, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
			, can_use_save_link = "download" in save_link
			, click = function(node) {
				var event = new MouseEvent("click");
				node.dispatchEvent(event);
			}
			, is_safari = /constructor/i.test(view.HTMLElement) || view.safari
			, is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
			, throw_outside = function(ex) {
				(view.setImmediate || view.setTimeout)(function() {
					throw ex;
				}, 0);
			}
			, force_saveable_type = "application/octet-stream"
			// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
			, arbitrary_revoke_timeout = 1000 * 40 // in ms
			, revoke = function(file) {
				var revoker = function() {
					if (typeof file === "string") { // file is an object URL
						get_URL().revokeObjectURL(file);
					} else { // file is a File
						file.remove();
					}
				};
				setTimeout(revoker, arbitrary_revoke_timeout);
			}
			, dispatch = function(filesaver, event_types, event) {
				event_types = [].concat(event_types);
				var i = event_types.length;
				while (i--) {
					var listener = filesaver["on" + event_types[i]];
					if (typeof listener === "function") {
						try {
							listener.call(filesaver, event || filesaver);
						} catch (ex) {
							throw_outside(ex);
						}
					}
				}
			}
			, auto_bom = function(blob) {
				// prepend BOM for UTF-8 XML and text/* types (including HTML)
				// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
				if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
					return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
				}
				return blob;
			}
			, FileSaver = function(blob, name, no_auto_bom) {
				if (!no_auto_bom) {
					blob = auto_bom(blob);
				}
				// First try a.download, then web filesystem, then object URLs
				var
					  filesaver = this
					, type = blob.type
					, force = type === force_saveable_type
					, object_url
					, dispatch_all = function() {
						dispatch(filesaver, "writestart progress write writeend".split(" "));
					}
					// on any filesys errors revert to saving with object URLs
					, fs_error = function() {
						if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
							// Safari doesn't allow downloading of blob urls
							var reader = new FileReader();
							reader.onloadend = function() {
								var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
								var popup = view.open(url, '_blank');
								if(!popup) view.location.href = url;
								url=undefined; // release reference before dispatching
								filesaver.readyState = filesaver.DONE;
								dispatch_all();
							};
							reader.readAsDataURL(blob);
							filesaver.readyState = filesaver.INIT;
							return;
						}
						// don't create more object URLs than needed
						if (!object_url) {
							object_url = get_URL().createObjectURL(blob);
						}
						if (force) {
							view.location.href = object_url;
						} else {
							var opened = view.open(object_url, "_blank");
							if (!opened) {
								// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
								view.location.href = object_url;
							}
						}
						filesaver.readyState = filesaver.DONE;
						dispatch_all();
						revoke(object_url);
					}
				;
				filesaver.readyState = filesaver.INIT;

				if (can_use_save_link) {
					object_url = get_URL().createObjectURL(blob);
					setTimeout(function() {
						save_link.href = object_url;
						save_link.download = name;
						click(save_link);
						dispatch_all();
						revoke(object_url);
						filesaver.readyState = filesaver.DONE;
					});
					return;
				}

				fs_error();
			}
			, FS_proto = FileSaver.prototype
			, saveAs = function(blob, name, no_auto_bom) {
				return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
			}
		;
		// IE 10+ (native saveAs)
		if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
			return function(blob, name, no_auto_bom) {
				name = name || blob.name || "download";

				if (!no_auto_bom) {
					blob = auto_bom(blob);
				}
				return navigator.msSaveOrOpenBlob(blob, name);
			};
		}

		FS_proto.abort = function(){};
		FS_proto.readyState = FS_proto.INIT = 0;
		FS_proto.WRITING = 1;
		FS_proto.DONE = 2;

		FS_proto.error =
		FS_proto.onwritestart =
		FS_proto.onprogress =
		FS_proto.onwrite =
		FS_proto.onabort =
		FS_proto.onerror =
		FS_proto.onwriteend =
			null;

		return saveAs;
	}(
		   typeof self !== "undefined" && self
		|| typeof window !== "undefined" && window
		|| this.content
	));
	// `self` is undefined in Firefox for Android content script context
	// while `this` is nsIContentFrameMessageManager
	// with an attribute `content` that corresponds to the window

	if (typeof module !== "undefined" && module.exports) {
	  module.exports.saveAs = saveAs;
	} else if (("function" !== "undefined" && __webpack_require__(579) !== null) && (__webpack_require__(580) !== null)) {
	  !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
	    return saveAs;
	  }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	}


/***/ },

/***/ 579:
/***/ function(module, exports) {

	module.exports = function() { throw new Error("define cannot be used indirect"); };


/***/ },

/***/ 580:
/***/ function(module, exports) {

	/* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__;

	/* WEBPACK VAR INJECTION */}.call(exports, {}))

/***/ },

/***/ 581:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/*** IMPORTS FROM imports-loader ***/
	var jQuery = __webpack_require__(275);

	/*! VelocityJS.org (1.2.3). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */

	/*************************
	   Velocity jQuery Shim
	*************************/

	/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */

	/* This file contains the jQuery functions that Velocity relies on, thereby removing Velocity's dependency on a full copy of jQuery, and allowing it to work in any environment. */
	/* These shimmed functions are only used if jQuery isn't present. If both this shim and jQuery are loaded, Velocity defaults to jQuery proper. */
	/* Browser support: Using this shim instead of jQuery proper removes support for IE8. */

	;(function (window) {
	    /***************
	         Setup
	    ***************/

	    /* If jQuery is already loaded, there's no point in loading this shim. */
	    if (window.jQuery) {
	        return;
	    }

	    /* jQuery base. */
	    var $ = function (selector, context) {
	        return new $.fn.init(selector, context);
	    };

	    /********************
	       Private Methods
	    ********************/

	    /* jQuery */
	    $.isWindow = function (obj) {
	        /* jshint eqeqeq: false */
	        return obj != null && obj == obj.window;
	    };

	    /* jQuery */
	    $.type = function (obj) {
	        if (obj == null) {
	            return obj + "";
	        }

	        return typeof obj === "object" || typeof obj === "function" ?
	            class2type[toString.call(obj)] || "object" :
	            typeof obj;
	    };

	    /* jQuery */
	    $.isArray = Array.isArray || function (obj) {
	        return $.type(obj) === "array";
	    };

	    /* jQuery */
	    function isArraylike (obj) {
	        var length = obj.length,
	            type = $.type(obj);

	        if (type === "function" || $.isWindow(obj)) {
	            return false;
	        }

	        if (obj.nodeType === 1 && length) {
	            return true;
	        }

	        return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;
	    }

	    /***************
	       $ Methods
	    ***************/

	    /* jQuery: Support removed for IE<9. */
	    $.isPlainObject = function (obj) {
	        var key;

	        if (!obj || $.type(obj) !== "object" || obj.nodeType || $.isWindow(obj)) {
	            return false;
	        }

	        try {
	            if (obj.constructor &&
	                !hasOwn.call(obj, "constructor") &&
	                !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
	                return false;
	            }
	        } catch (e) {
	            return false;
	        }

	        for (key in obj) {}

	        return key === undefined || hasOwn.call(obj, key);
	    };

	    /* jQuery */
	    $.each = function(obj, callback, args) {
	        var value,
	            i = 0,
	            length = obj.length,
	            isArray = isArraylike(obj);

	        if (args) {
	            if (isArray) {
	                for (; i < length; i++) {
	                    value = callback.apply(obj[i], args);

	                    if (value === false) {
	                        break;
	                    }
	                }
	            } else {
	                for (i in obj) {
	                    value = callback.apply(obj[i], args);

	                    if (value === false) {
	                        break;
	                    }
	                }
	            }

	        } else {
	            if (isArray) {
	                for (; i < length; i++) {
	                    value = callback.call(obj[i], i, obj[i]);

	                    if (value === false) {
	                        break;
	                    }
	                }
	            } else {
	                for (i in obj) {
	                    value = callback.call(obj[i], i, obj[i]);

	                    if (value === false) {
	                        break;
	                    }
	                }
	            }
	        }

	        return obj;
	    };

	    /* Custom */
	    $.data = function (node, key, value) {
	        /* $.getData() */
	        if (value === undefined) {
	            var id = node[$.expando],
	                store = id && cache[id];

	            if (key === undefined) {
	                return store;
	            } else if (store) {
	                if (key in store) {
	                    return store[key];
	                }
	            }
	        /* $.setData() */
	        } else if (key !== undefined) {
	            var id = node[$.expando] || (node[$.expando] = ++$.uuid);

	            cache[id] = cache[id] || {};
	            cache[id][key] = value;

	            return value;
	        }
	    };

	    /* Custom */
	    $.removeData = function (node, keys) {
	        var id = node[$.expando],
	            store = id && cache[id];

	        if (store) {
	            $.each(keys, function(_, key) {
	                delete store[key];
	            });
	        }
	    };

	    /* jQuery */
	    $.extend = function () {
	        var src, copyIsArray, copy, name, options, clone,
	            target = arguments[0] || {},
	            i = 1,
	            length = arguments.length,
	            deep = false;

	        if (typeof target === "boolean") {
	            deep = target;

	            target = arguments[i] || {};
	            i++;
	        }

	        if (typeof target !== "object" && $.type(target) !== "function") {
	            target = {};
	        }

	        if (i === length) {
	            target = this;
	            i--;
	        }

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

	                    if (target === copy) {
	                        continue;
	                    }

	                    if (deep && copy && ($.isPlainObject(copy) || (copyIsArray = $.isArray(copy)))) {
	                        if (copyIsArray) {
	                            copyIsArray = false;
	                            clone = src && $.isArray(src) ? src : [];

	                        } else {
	                            clone = src && $.isPlainObject(src) ? src : {};
	                        }

	                        target[name] = $.extend(deep, clone, copy);

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

	        return target;
	    };

	    /* jQuery 1.4.3 */
	    $.queue = function (elem, type, data) {
	        function $makeArray (arr, results) {
	            var ret = results || [];

	            if (arr != null) {
	                if (isArraylike(Object(arr))) {
	                    /* $.merge */
	                    (function(first, second) {
	                        var len = +second.length,
	                            j = 0,
	                            i = first.length;

	                        while (j < len) {
	                            first[i++] = second[j++];
	                        }

	                        if (len !== len) {
	                            while (second[j] !== undefined) {
	                                first[i++] = second[j++];
	                            }
	                        }

	                        first.length = i;

	                        return first;
	                    })(ret, typeof arr === "string" ? [arr] : arr);
	                } else {
	                    [].push.call(ret, arr);
	                }
	            }

	            return ret;
	        }

	        if (!elem) {
	            return;
	        }

	        type = (type || "fx") + "queue";

	        var q = $.data(elem, type);

	        if (!data) {
	            return q || [];
	        }

	        if (!q || $.isArray(data)) {
	            q = $.data(elem, type, $makeArray(data));
	        } else {
	            q.push(data);
	        }

	        return q;
	    };

	    /* jQuery 1.4.3 */
	    $.dequeue = function (elems, type) {
	        /* Custom: Embed element iteration. */
	        $.each(elems.nodeType ? [ elems ] : elems, function(i, elem) {
	            type = type || "fx";

	            var queue = $.queue(elem, type),
	                fn = queue.shift();

	            if (fn === "inprogress") {
	                fn = queue.shift();
	            }

	            if (fn) {
	                if (type === "fx") {
	                    queue.unshift("inprogress");
	                }

	                fn.call(elem, function() {
	                    $.dequeue(elem, type);
	                });
	            }
	        });
	    };

	    /******************
	       $.fn Methods
	    ******************/

	    /* jQuery */
	    $.fn = $.prototype = {
	        init: function (selector) {
	            /* Just return the element wrapped inside an array; don't proceed with the actual jQuery node wrapping process. */
	            if (selector.nodeType) {
	                this[0] = selector;

	                return this;
	            } else {
	                throw new Error("Not a DOM node.");
	            }
	        },

	        offset: function () {
	            /* jQuery altered code: Dropped disconnected DOM node checking. */
	            var box = this[0].getBoundingClientRect ? this[0].getBoundingClientRect() : { top: 0, left: 0 };

	            return {
	                top: box.top + (window.pageYOffset || document.scrollTop  || 0)  - (document.clientTop  || 0),
	                left: box.left + (window.pageXOffset || document.scrollLeft  || 0) - (document.clientLeft || 0)
	            };
	        },

	        position: function () {
	            /* jQuery */
	            function offsetParent() {
	                var offsetParent = this.offsetParent || document;

	                while (offsetParent && (!offsetParent.nodeType.toLowerCase === "html" && offsetParent.style.position === "static")) {
	                    offsetParent = offsetParent.offsetParent;
	                }

	                return offsetParent || document;
	            }

	            /* Zepto */
	            var elem = this[0],
	                offsetParent = offsetParent.apply(elem),
	                offset = this.offset(),
	                parentOffset = /^(?:body|html)$/i.test(offsetParent.nodeName) ? { top: 0, left: 0 } : $(offsetParent).offset()

	            offset.top -= parseFloat(elem.style.marginTop) || 0;
	            offset.left -= parseFloat(elem.style.marginLeft) || 0;

	            if (offsetParent.style) {
	                parentOffset.top += parseFloat(offsetParent.style.borderTopWidth) || 0
	                parentOffset.left += parseFloat(offsetParent.style.borderLeftWidth) || 0
	            }

	            return {
	                top: offset.top - parentOffset.top,
	                left: offset.left - parentOffset.left
	            };
	        }
	    };

	    /**********************
	       Private Variables
	    **********************/

	    /* For $.data() */
	    var cache = {};
	    $.expando = "velocity" + (new Date().getTime());
	    $.uuid = 0;

	    /* For $.queue() */
	    var class2type = {},
	        hasOwn = class2type.hasOwnProperty,
	        toString = class2type.toString;

	    var types = "Boolean Number String Function Array Date RegExp Object Error".split(" ");
	    for (var i = 0; i < types.length; i++) {
	        class2type["[object " + types[i] + "]"] = types[i].toLowerCase();
	    }

	    /* Makes $(node) possible, without having to call init. */
	    $.fn.init.prototype = $.fn;

	    /* Globalize Velocity onto the window, and assign its Utilities property. */
	    window.Velocity = { Utilities: $ };
	})(window);

	/******************
	    Velocity.js
	******************/

	;(function (factory) {
	    /* CommonJS module. */
	    if (typeof module === "object" && typeof module.exports === "object") {
	        module.exports = factory();
	    /* AMD module. */
	    } else if (true) {
	        !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	    /* Browser globals. */
	    } else {
	        factory();
	    }
	}(function() {
	return function (global, window, document, undefined) {

	    /***************
	        Summary
	    ***************/

	    /*
	    - CSS: CSS stack that works independently from the rest of Velocity.
	    - animate(): Core animation method that iterates over the targeted elements and queues the incoming call onto each element individually.
	      - Pre-Queueing: Prepare the element for animation by instantiating its data cache and processing the call's options.
	      - Queueing: The logic that runs once the call has reached its point of execution in the element's $.queue() stack.
	                  Most logic is placed here to avoid risking it becoming stale (if the element's properties have changed).
	      - Pushing: Consolidation of the tween data followed by its push onto the global in-progress calls container.
	    - tick(): The single requestAnimationFrame loop responsible for tweening all in-progress calls.
	    - completeCall(): Handles the cleanup process for each Velocity call.
	    */

	    /*********************
	       Helper Functions
	    *********************/

	    /* IE detection. Gist: https://gist.github.com/julianshapiro/9098609 */
	    var IE = (function() {
	        if (document.documentMode) {
	            return document.documentMode;
	        } else {
	            for (var i = 7; i > 4; i--) {
	                var div = document.createElement("div");

	                div.innerHTML = "<!--[if IE " + i + "]><span></span><![endif]-->";

	                if (div.getElementsByTagName("span").length) {
	                    div = null;

	                    return i;
	                }
	            }
	        }

	        return undefined;
	    })();

	    /* rAF shim. Gist: https://gist.github.com/julianshapiro/9497513 */
	    var rAFShim = (function() {
	        var timeLast = 0;

	        return window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) {
	            var timeCurrent = (new Date()).getTime(),
	                timeDelta;

	            /* Dynamically set delay on a per-tick basis to match 60fps. */
	            /* Technique by Erik Moller. MIT license: https://gist.github.com/paulirish/1579671 */
	            timeDelta = Math.max(0, 16 - (timeCurrent - timeLast));
	            timeLast = timeCurrent + timeDelta;

	            return setTimeout(function() { callback(timeCurrent + timeDelta); }, timeDelta);
	        };
	    })();

	    /* Array compacting. Copyright Lo-Dash. MIT License: https://github.com/lodash/lodash/blob/master/LICENSE.txt */
	    function compactSparseArray (array) {
	        var index = -1,
	            length = array ? array.length : 0,
	            result = [];

	        while (++index < length) {
	            var value = array[index];

	            if (value) {
	                result.push(value);
	            }
	        }

	        return result;
	    }

	    function sanitizeElements (elements) {
	        /* Unwrap jQuery/Zepto objects. */
	        if (Type.isWrapped(elements)) {
	            elements = [].slice.call(elements);
	        /* Wrap a single element in an array so that $.each() can iterate with the element instead of its node's children. */
	        } else if (Type.isNode(elements)) {
	            elements = [ elements ];
	        }

	        return elements;
	    }

	    var Type = {
	        isString: function (variable) {
	            return (typeof variable === "string");
	        },
	        isArray: Array.isArray || function (variable) {
	            return Object.prototype.toString.call(variable) === "[object Array]";
	        },
	        isFunction: function (variable) {
	            return Object.prototype.toString.call(variable) === "[object Function]";
	        },
	        isNode: function (variable) {
	            return variable && variable.nodeType;
	        },
	        /* Copyright Martin Bohm. MIT License: https://gist.github.com/Tomalak/818a78a226a0738eaade */
	        isNodeList: function (variable) {
	            return typeof variable === "object" &&
	                /^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(variable)) &&
	                variable.length !== undefined &&
	                (variable.length === 0 || (typeof variable[0] === "object" && variable[0].nodeType > 0));
	        },
	        /* Determine if variable is a wrapped jQuery or Zepto element. */
	        isWrapped: function (variable) {
	            return variable && (variable.jquery || (window.Zepto && window.Zepto.zepto.isZ(variable)));
	        },
	        isSVG: function (variable) {
	            return window.SVGElement && (variable instanceof window.SVGElement);
	        },
	        isEmptyObject: function (variable) {
	            for (var name in variable) {
	                return false;
	            }

	            return true;
	        }
	    };

	    /*****************
	       Dependencies
	    *****************/

	    var $,
	        isJQuery = false;

	    if (global.fn && global.fn.jquery) {
	        $ = global;
	        isJQuery = true;
	    } else {
	        $ = window.Velocity.Utilities;
	    }

	    if (IE <= 8 && !isJQuery) {
	        throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");
	    } else if (IE <= 7) {
	        /* Revert to jQuery's $.animate(), and lose Velocity's extra features. */
	        jQuery.fn.velocity = jQuery.fn.animate;

	        /* Now that $.fn.velocity is aliased, abort this Velocity declaration. */
	        return;
	    }

	    /*****************
	        Constants
	    *****************/

	    var DURATION_DEFAULT = 400,
	        EASING_DEFAULT = "swing";

	    /*************
	        State
	    *************/

	    var Velocity = {
	        /* Container for page-wide Velocity state data. */
	        State: {
	            /* Detect mobile devices to determine if mobileHA should be turned on. */
	            isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
	            /* The mobileHA option's behavior changes on older Android devices (Gingerbread, versions 2.3.3-2.3.7). */
	            isAndroid: /Android/i.test(navigator.userAgent),
	            isGingerbread: /Android 2\.3\.[3-7]/i.test(navigator.userAgent),
	            isChrome: window.chrome,
	            isFirefox: /Firefox/i.test(navigator.userAgent),
	            /* Create a cached element for re-use when checking for CSS property prefixes. */
	            prefixElement: document.createElement("div"),
	            /* Cache every prefix match to avoid repeating lookups. */
	            prefixMatches: {},
	            /* Cache the anchor used for animating window scrolling. */
	            scrollAnchor: null,
	            /* Cache the browser-specific property names associated with the scroll anchor. */
	            scrollPropertyLeft: null,
	            scrollPropertyTop: null,
	            /* Keep track of whether our RAF tick is running. */
	            isTicking: false,
	            /* Container for every in-progress call to Velocity. */
	            calls: []
	        },
	        /* Velocity's custom CSS stack. Made global for unit testing. */
	        CSS: { /* Defined below. */ },
	        /* A shim of the jQuery utility functions used by Velocity -- provided by Velocity's optional jQuery shim. */
	        Utilities: $,
	        /* Container for the user's custom animation redirects that are referenced by name in place of the properties map argument. */
	        Redirects: { /* Manually registered by the user. */ },
	        Easings: { /* Defined below. */ },
	        /* Attempt to use ES6 Promises by default. Users can override this with a third-party promises library. */
	        Promise: window.Promise,
	        /* Velocity option defaults, which can be overriden by the user. */
	        defaults: {
	            queue: "",
	            duration: DURATION_DEFAULT,
	            easing: EASING_DEFAULT,
	            begin: undefined,
	            complete: undefined,
	            progress: undefined,
	            display: undefined,
	            visibility: undefined,
	            loop: false,
	            delay: false,
	            mobileHA: true,
	            /* Advanced: Set to false to prevent property values from being cached between consecutive Velocity-initiated chain calls. */
	            _cacheValues: true
	        },
	        /* A design goal of Velocity is to cache data wherever possible in order to avoid DOM requerying. Accordingly, each element has a data cache. */
	        init: function (element) {
	            $.data(element, "velocity", {
	                /* Store whether this is an SVG element, since its properties are retrieved and updated differently than standard HTML elements. */
	                isSVG: Type.isSVG(element),
	                /* Keep track of whether the element is currently being animated by Velocity.
	                   This is used to ensure that property values are not transferred between non-consecutive (stale) calls. */
	                isAnimating: false,
	                /* A reference to the element's live computedStyle object. Learn more here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */
	                computedStyle: null,
	                /* Tween data is cached for each animation on the element so that data can be passed across calls --
	                   in particular, end values are used as subsequent start values in consecutive Velocity calls. */
	                tweensContainer: null,
	                /* The full root property values of each CSS hook being animated on this element are cached so that:
	                   1) Concurrently-animating hooks sharing the same root can have their root values' merged into one while tweening.
	                   2) Post-hook-injection root values can be transferred over to consecutively chained Velocity calls as starting root values. */
	                rootPropertyValueCache: {},
	                /* A cache for transform updates, which must be manually flushed via CSS.flushTransformCache(). */
	                transformCache: {}
	            });
	        },
	        /* A parallel to jQuery's $.css(), used for getting/setting Velocity's hooked CSS properties. */
	        hook: null, /* Defined below. */
	        /* Velocity-wide animation time remapping for testing purposes. */
	        mock: false,
	        version: { major: 1, minor: 2, patch: 2 },
	        /* Set to 1 or 2 (most verbose) to output debug info to console. */
	        debug: false
	    };

	    /* Retrieve the appropriate scroll anchor and property name for the browser: https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY */
	    if (window.pageYOffset !== undefined) {
	        Velocity.State.scrollAnchor = window;
	        Velocity.State.scrollPropertyLeft = "pageXOffset";
	        Velocity.State.scrollPropertyTop = "pageYOffset";
	    } else {
	        Velocity.State.scrollAnchor = document.documentElement || document.body.parentNode || document.body;
	        Velocity.State.scrollPropertyLeft = "scrollLeft";
	        Velocity.State.scrollPropertyTop = "scrollTop";
	    }

	    /* Shorthand alias for jQuery's $.data() utility. */
	    function Data (element) {
	        /* Hardcode a reference to the plugin name. */
	        var response = $.data(element, "velocity");

	        /* jQuery <=1.4.2 returns null instead of undefined when no match is found. We normalize this behavior. */
	        return response === null ? undefined : response;
	    };

	    /**************
	        Easing
	    **************/

	    /* Step easing generator. */
	    function generateStep (steps) {
	        return function (p) {
	            return Math.round(p * steps) * (1 / steps);
	        };
	    }

	    /* Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */
	    function generateBezier (mX1, mY1, mX2, mY2) {
	        var NEWTON_ITERATIONS = 4,
	            NEWTON_MIN_SLOPE = 0.001,
	            SUBDIVISION_PRECISION = 0.0000001,
	            SUBDIVISION_MAX_ITERATIONS = 10,
	            kSplineTableSize = 11,
	            kSampleStepSize = 1.0 / (kSplineTableSize - 1.0),
	            float32ArraySupported = "Float32Array" in window;

	        /* Must contain four arguments. */
	        if (arguments.length !== 4) {
	            return false;
	        }

	        /* Arguments must be numbers. */
	        for (var i = 0; i < 4; ++i) {
	            if (typeof arguments[i] !== "number" || isNaN(arguments[i]) || !isFinite(arguments[i])) {
	                return false;
	            }
	        }

	        /* X values must be in the [0, 1] range. */
	        mX1 = Math.min(mX1, 1);
	        mX2 = Math.min(mX2, 1);
	        mX1 = Math.max(mX1, 0);
	        mX2 = Math.max(mX2, 0);

	        var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);

	        function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
	        function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
	        function C (aA1)      { return 3.0 * aA1; }

	        function calcBezier (aT, aA1, aA2) {
	            return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
	        }

	        function getSlope (aT, aA1, aA2) {
	            return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
	        }

	        function newtonRaphsonIterate (aX, aGuessT) {
	            for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
	                var currentSlope = getSlope(aGuessT, mX1, mX2);

	                if (currentSlope === 0.0) return aGuessT;

	                var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
	                aGuessT -= currentX / currentSlope;
	            }

	            return aGuessT;
	        }

	        function calcSampleValues () {
	            for (var i = 0; i < kSplineTableSize; ++i) {
	                mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
	            }
	        }

	        function binarySubdivide (aX, aA, aB) {
	            var currentX, currentT, i = 0;

	            do {
	                currentT = aA + (aB - aA) / 2.0;
	                currentX = calcBezier(currentT, mX1, mX2) - aX;
	                if (currentX > 0.0) {
	                  aB = currentT;
	                } else {
	                  aA = currentT;
	                }
	            } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);

	            return currentT;
	        }

	        function getTForX (aX) {
	            var intervalStart = 0.0,
	                currentSample = 1,
	                lastSample = kSplineTableSize - 1;

	            for (; currentSample != lastSample && mSampleValues[currentSample] <= aX; ++currentSample) {
	                intervalStart += kSampleStepSize;
	            }

	            --currentSample;

	            var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample+1] - mSampleValues[currentSample]),
	                guessForT = intervalStart + dist * kSampleStepSize,
	                initialSlope = getSlope(guessForT, mX1, mX2);

	            if (initialSlope >= NEWTON_MIN_SLOPE) {
	                return newtonRaphsonIterate(aX, guessForT);
	            } else if (initialSlope == 0.0) {
	                return guessForT;
	            } else {
	                return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize);
	            }
	        }

	        var _precomputed = false;

	        function precompute() {
	            _precomputed = true;
	            if (mX1 != mY1 || mX2 != mY2) calcSampleValues();
	        }

	        var f = function (aX) {
	            if (!_precomputed) precompute();
	            if (mX1 === mY1 && mX2 === mY2) return aX;
	            if (aX === 0) return 0;
	            if (aX === 1) return 1;

	            return calcBezier(getTForX(aX), mY1, mY2);
	        };

	        f.getControlPoints = function() { return [{ x: mX1, y: mY1 }, { x: mX2, y: mY2 }]; };

	        var str = "generateBezier(" + [mX1, mY1, mX2, mY2] + ")";
	        f.toString = function () { return str; };

	        return f;
	    }

	    /* Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */
	    /* Given a tension, friction, and duration, a simulation at 60FPS will first run without a defined duration in order to calculate the full path. A second pass
	       then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */
	    var generateSpringRK4 = (function () {
	        function springAccelerationForState (state) {
	            return (-state.tension * state.x) - (state.friction * state.v);
	        }

	        function springEvaluateStateWithDerivative (initialState, dt, derivative) {
	            var state = {
	                x: initialState.x + derivative.dx * dt,
	                v: initialState.v + derivative.dv * dt,
	                tension: initialState.tension,
	                friction: initialState.friction
	            };

	            return { dx: state.v, dv: springAccelerationForState(state) };
	        }

	        function springIntegrateState (state, dt) {
	            var a = {
	                    dx: state.v,
	                    dv: springAccelerationForState(state)
	                },
	                b = springEvaluateStateWithDerivative(state, dt * 0.5, a),
	                c = springEvaluateStateWithDerivative(state, dt * 0.5, b),
	                d = springEvaluateStateWithDerivative(state, dt, c),
	                dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx),
	                dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv);

	            state.x = state.x + dxdt * dt;
	            state.v = state.v + dvdt * dt;

	            return state;
	        }

	        return function springRK4Factory (tension, friction, duration) {

	            var initState = {
	                    x: -1,
	                    v: 0,
	                    tension: null,
	                    friction: null
	                },
	                path = [0],
	                time_lapsed = 0,
	                tolerance = 1 / 10000,
	                DT = 16 / 1000,
	                have_duration, dt, last_state;

	            tension = parseFloat(tension) || 500;
	            friction = parseFloat(friction) || 20;
	            duration = duration || null;

	            initState.tension = tension;
	            initState.friction = friction;

	            have_duration = duration !== null;

	            /* Calculate the actual time it takes for this animation to complete with the provided conditions. */
	            if (have_duration) {
	                /* Run the simulation without a duration. */
	                time_lapsed = springRK4Factory(tension, friction);
	                /* Compute the adjusted time delta. */
	                dt = time_lapsed / duration * DT;
	            } else {
	                dt = DT;
	            }

	            while (true) {
	                /* Next/step function .*/
	                last_state = springIntegrateState(last_state || initState, dt);
	                /* Store the position. */
	                path.push(1 + last_state.x);
	                time_lapsed += 16;
	                /* If the change threshold is reached, break. */
	                if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) {
	                    break;
	                }
	            }

	            /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the
	               computed path and returns a snapshot of the position according to a given percentComplete. */
	            return !have_duration ? time_lapsed : function(percentComplete) { return path[ (percentComplete * (path.length - 1)) | 0 ]; };
	        };
	    }());

	    /* jQuery easings. */
	    Velocity.Easings = {
	        linear: function(p) { return p; },
	        swing: function(p) { return 0.5 - Math.cos( p * Math.PI ) / 2 },
	        /* Bonus "spring" easing, which is a less exaggerated version of easeInOutElastic. */
	        spring: function(p) { return 1 - (Math.cos(p * 4.5 * Math.PI) * Math.exp(-p * 6)); }
	    };

	    /* CSS3 and Robert Penner easings. */
	    $.each(
	        [
	            [ "ease", [ 0.25, 0.1, 0.25, 1.0 ] ],
	            [ "ease-in", [ 0.42, 0.0, 1.00, 1.0 ] ],
	            [ "ease-out", [ 0.00, 0.0, 0.58, 1.0 ] ],
	            [ "ease-in-out", [ 0.42, 0.0, 0.58, 1.0 ] ],
	            [ "easeInSine", [ 0.47, 0, 0.745, 0.715 ] ],
	            [ "easeOutSine", [ 0.39, 0.575, 0.565, 1 ] ],
	            [ "easeInOutSine", [ 0.445, 0.05, 0.55, 0.95 ] ],
	            [ "easeInQuad", [ 0.55, 0.085, 0.68, 0.53 ] ],
	            [ "easeOutQuad", [ 0.25, 0.46, 0.45, 0.94 ] ],
	            [ "easeInOutQuad", [ 0.455, 0.03, 0.515, 0.955 ] ],
	            [ "easeInCubic", [ 0.55, 0.055, 0.675, 0.19 ] ],
	            [ "easeOutCubic", [ 0.215, 0.61, 0.355, 1 ] ],
	            [ "easeInOutCubic", [ 0.645, 0.045, 0.355, 1 ] ],
	            [ "easeInQuart", [ 0.895, 0.03, 0.685, 0.22 ] ],
	            [ "easeOutQuart", [ 0.165, 0.84, 0.44, 1 ] ],
	            [ "easeInOutQuart", [ 0.77, 0, 0.175, 1 ] ],
	            [ "easeInQuint", [ 0.755, 0.05, 0.855, 0.06 ] ],
	            [ "easeOutQuint", [ 0.23, 1, 0.32, 1 ] ],
	            [ "easeInOutQuint", [ 0.86, 0, 0.07, 1 ] ],
	            [ "easeInExpo", [ 0.95, 0.05, 0.795, 0.035 ] ],
	            [ "easeOutExpo", [ 0.19, 1, 0.22, 1 ] ],
	            [ "easeInOutExpo", [ 1, 0, 0, 1 ] ],
	            [ "easeInCirc", [ 0.6, 0.04, 0.98, 0.335 ] ],
	            [ "easeOutCirc", [ 0.075, 0.82, 0.165, 1 ] ],
	            [ "easeInOutCirc", [ 0.785, 0.135, 0.15, 0.86 ] ]
	        ], function(i, easingArray) {
	            Velocity.Easings[easingArray[0]] = generateBezier.apply(null, easingArray[1]);
	        });

	    /* Determine the appropriate easing type given an easing input. */
	    function getEasing(value, duration) {
	        var easing = value;

	        /* The easing option can either be a string that references a pre-registered easing,
	           or it can be a two-/four-item array of integers to be converted into a bezier/spring function. */
	        if (Type.isString(value)) {
	            /* Ensure that the easing has been assigned to jQuery's Velocity.Easings object. */
	            if (!Velocity.Easings[value]) {
	                easing = false;
	            }
	        } else if (Type.isArray(value) && value.length === 1) {
	            easing = generateStep.apply(null, value);
	        } else if (Type.isArray(value) && value.length === 2) {
	            /* springRK4 must be passed the animation's duration. */
	            /* Note: If the springRK4 array contains non-numbers, generateSpringRK4() returns an easing
	               function generated with default tension and friction values. */
	            easing = generateSpringRK4.apply(null, value.concat([ duration ]));
	        } else if (Type.isArray(value) && value.length === 4) {
	            /* Note: If the bezier array contains non-numbers, generateBezier() returns false. */
	            easing = generateBezier.apply(null, value);
	        } else {
	            easing = false;
	        }

	        /* Revert to the Velocity-wide default easing type, or fall back to "swing" (which is also jQuery's default)
	           if the Velocity-wide default has been incorrectly modified. */
	        if (easing === false) {
	            if (Velocity.Easings[Velocity.defaults.easing]) {
	                easing = Velocity.defaults.easing;
	            } else {
	                easing = EASING_DEFAULT;
	            }
	        }

	        return easing;
	    }

	    /*****************
	        CSS Stack
	    *****************/

	    /* The CSS object is a highly condensed and performant CSS stack that fully replaces jQuery's.
	       It handles the validation, getting, and setting of both standard CSS properties and CSS property hooks. */
	    /* Note: A "CSS" shorthand is aliased so that our code is easier to read. */
	    var CSS = Velocity.CSS = {

	        /*************
	            RegEx
	        *************/

	        RegEx: {
	            isHex: /^#([A-f\d]{3}){1,2}$/i,
	            /* Unwrap a property value's surrounding text, e.g. "rgba(4, 3, 2, 1)" ==> "4, 3, 2, 1" and "rect(4px 3px 2px 1px)" ==> "4px 3px 2px 1px". */
	            valueUnwrap: /^[A-z]+\((.*)\)$/i,
	            wrappedValueAlreadyExtracted: /[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,
	            /* Split a multi-value property into an array of subvalues, e.g. "rgba(4, 3, 2, 1) 4px 3px 2px 1px" ==> [ "rgba(4, 3, 2, 1)", "4px", "3px", "2px", "1px" ]. */
	            valueSplit: /([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/ig
	        },

	        /************
	            Lists
	        ************/

	        Lists: {
	            colors: [ "fill", "stroke", "stopColor", "color", "backgroundColor", "borderColor", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor", "outlineColor" ],
	            transformsBase: [ "translateX", "translateY", "scale", "scaleX", "scaleY", "skewX", "skewY", "rotateZ" ],
	            transforms3D: [ "transformPerspective", "translateZ", "scaleZ", "rotateX", "rotateY" ]
	        },

	        /************
	            Hooks
	        ************/

	        /* Hooks allow a subproperty (e.g. "boxShadowBlur") of a compound-value CSS property
	           (e.g. "boxShadow: X Y Blur Spread Color") to be animated as if it were a discrete property. */
	        /* Note: Beyond enabling fine-grained property animation, hooking is necessary since Velocity only
	           tweens properties with single numeric values; unlike CSS transitions, Velocity does not interpolate compound-values. */
	        Hooks: {
	            /********************
	                Registration
	            ********************/

	            /* Templates are a concise way of indicating which subproperties must be individually registered for each compound-value CSS property. */
	            /* Each template consists of the compound-value's base name, its constituent subproperty names, and those subproperties' default values. */
	            templates: {
	                "textShadow": [ "Color X Y Blur", "black 0px 0px 0px" ],
	                "boxShadow": [ "Color X Y Blur Spread", "black 0px 0px 0px 0px" ],
	                "clip": [ "Top Right Bottom Left", "0px 0px 0px 0px" ],
	                "backgroundPosition": [ "X Y", "0% 0%" ],
	                "transformOrigin": [ "X Y Z", "50% 50% 0px" ],
	                "perspectiveOrigin": [ "X Y", "50% 50%" ]
	            },

	            /* A "registered" hook is one that has been converted from its template form into a live,
	               tweenable property. It contains data to associate it with its root property. */
	            registered: {
	                /* Note: A registered hook looks like this ==> textShadowBlur: [ "textShadow", 3 ],
	                   which consists of the subproperty's name, the associated root property's name,
	                   and the subproperty's position in the root's value. */
	            },
	            /* Convert the templates into individual hooks then append them to the registered object above. */
	            register: function () {
	                /* Color hooks registration: Colors are defaulted to white -- as opposed to black -- since colors that are
	                   currently set to "transparent" default to their respective template below when color-animated,
	                   and white is typically a closer match to transparent than black is. An exception is made for text ("color"),
	                   which is almost always set closer to black than white. */
	                for (var i = 0; i < CSS.Lists.colors.length; i++) {
	                    var rgbComponents = (CSS.Lists.colors[i] === "color") ? "0 0 0 1" : "255 255 255 1";
	                    CSS.Hooks.templates[CSS.Lists.colors[i]] = [ "Red Green Blue Alpha", rgbComponents ];
	                }

	                var rootProperty,
	                    hookTemplate,
	                    hookNames;

	                /* In IE, color values inside compound-value properties are positioned at the end the value instead of at the beginning.
	                   Thus, we re-arrange the templates accordingly. */
	                if (IE) {
	                    for (rootProperty in CSS.Hooks.templates) {
	                        hookTemplate = CSS.Hooks.templates[rootProperty];
	                        hookNames = hookTemplate[0].split(" ");

	                        var defaultValues = hookTemplate[1].match(CSS.RegEx.valueSplit);

	                        if (hookNames[0] === "Color") {
	                            /* Reposition both the hook's name and its default value to the end of their respective strings. */
	                            hookNames.push(hookNames.shift());
	                            defaultValues.push(defaultValues.shift());

	                            /* Replace the existing template for the hook's root property. */
	                            CSS.Hooks.templates[rootProperty] = [ hookNames.join(" "), defaultValues.join(" ") ];
	                        }
	                    }
	                }

	                /* Hook registration. */
	                for (rootProperty in CSS.Hooks.templates) {
	                    hookTemplate = CSS.Hooks.templates[rootProperty];
	                    hookNames = hookTemplate[0].split(" ");

	                    for (var i in hookNames) {
	                        var fullHookName = rootProperty + hookNames[i],
	                            hookPosition = i;

	                        /* For each hook, register its full name (e.g. textShadowBlur) with its root property (e.g. textShadow)
	                           and the hook's position in its template's default value string. */
	                        CSS.Hooks.registered[fullHookName] = [ rootProperty, hookPosition ];
	                    }
	                }
	            },

	            /*****************************
	               Injection and Extraction
	            *****************************/

	            /* Look up the root property associated with the hook (e.g. return "textShadow" for "textShadowBlur"). */
	            /* Since a hook cannot be set directly (the browser won't recognize it), style updating for hooks is routed through the hook's root property. */
	            getRoot: function (property) {
	                var hookData = CSS.Hooks.registered[property];

	                if (hookData) {
	                    return hookData[0];
	                } else {
	                    /* If there was no hook match, return the property name untouched. */
	                    return property;
	                }
	            },
	            /* Convert any rootPropertyValue, null or otherwise, into a space-delimited list of hook values so that
	               the targeted hook can be injected or extracted at its standard position. */
	            cleanRootPropertyValue: function(rootProperty, rootPropertyValue) {
	                /* If the rootPropertyValue is wrapped with "rgb()", "clip()", etc., remove the wrapping to normalize the value before manipulation. */
	                if (CSS.RegEx.valueUnwrap.test(rootPropertyValue)) {
	                    rootPropertyValue = rootPropertyValue.match(CSS.RegEx.valueUnwrap)[1];
	                }

	                /* If rootPropertyValue is a CSS null-value (from which there's inherently no hook value to extract),
	                   default to the root's default value as defined in CSS.Hooks.templates. */
	                /* Note: CSS null-values include "none", "auto", and "transparent". They must be converted into their
	                   zero-values (e.g. textShadow: "none" ==> textShadow: "0px 0px 0px black") for hook manipulation to proceed. */
	                if (CSS.Values.isCSSNullValue(rootPropertyValue)) {
	                    rootPropertyValue = CSS.Hooks.templates[rootProperty][1];
	                }

	                return rootPropertyValue;
	            },
	            /* Extracted the hook's value from its root property's value. This is used to get the starting value of an animating hook. */
	            extractValue: function (fullHookName, rootPropertyValue) {
	                var hookData = CSS.Hooks.registered[fullHookName];

	                if (hookData) {
	                    var hookRoot = hookData[0],
	                        hookPosition = hookData[1];

	                    rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue);

	                    /* Split rootPropertyValue into its constituent hook values then grab the desired hook at its standard position. */
	                    return rootPropertyValue.toString().match(CSS.RegEx.valueSplit)[hookPosition];
	                } else {
	                    /* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */
	                    return rootPropertyValue;
	                }
	            },
	            /* Inject the hook's value into its root property's value. This is used to piece back together the root property
	               once Velocity has updated one of its individually hooked values through tweening. */
	            injectValue: function (fullHookName, hookValue, rootPropertyValue) {
	                var hookData = CSS.Hooks.registered[fullHookName];

	                if (hookData) {
	                    var hookRoot = hookData[0],
	                        hookPosition = hookData[1],
	                        rootPropertyValueParts,
	                        rootPropertyValueUpdated;

	                    rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue);

	                    /* Split rootPropertyValue into its individual hook values, replace the targeted value with hookValue,
	                       then reconstruct the rootPropertyValue string. */
	                    rootPropertyValueParts = rootPropertyValue.toString().match(CSS.RegEx.valueSplit);
	                    rootPropertyValueParts[hookPosition] = hookValue;
	                    rootPropertyValueUpdated = rootPropertyValueParts.join(" ");

	                    return rootPropertyValueUpdated;
	                } else {
	                    /* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */
	                    return rootPropertyValue;
	                }
	            }
	        },

	        /*******************
	           Normalizations
	        *******************/

	        /* Normalizations standardize CSS property manipulation by pollyfilling browser-specific implementations (e.g. opacity)
	           and reformatting special properties (e.g. clip, rgba) to look like standard ones. */
	        Normalizations: {
	            /* Normalizations are passed a normalization target (either the property's name, its extracted value, or its injected value),
	               the targeted element (which may need to be queried), and the targeted property value. */
	            registered: {
	                clip: function (type, element, propertyValue) {
	                    switch (type) {
	                        case "name":
	                            return "clip";
	                        /* Clip needs to be unwrapped and stripped of its commas during extraction. */
	                        case "extract":
	                            var extracted;

	                            /* If Velocity also extracted this value, skip extraction. */
	                            if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) {
	                                extracted = propertyValue;
	                            } else {
	                                /* Remove the "rect()" wrapper. */
	                                extracted = propertyValue.toString().match(CSS.RegEx.valueUnwrap);

	                                /* Strip off commas. */
	                                extracted = extracted ? extracted[1].replace(/,(\s+)?/g, " ") : propertyValue;
	                            }

	                            return extracted;
	                        /* Clip needs to be re-wrapped during injection. */
	                        case "inject":
	                            return "rect(" + propertyValue + ")";
	                    }
	                },

	                blur: function(type, element, propertyValue) {
	                    switch (type) {
	                        case "name":
	                            return Velocity.State.isFirefox ? "filter" : "-webkit-filter";
	                        case "extract":
	                            var extracted = parseFloat(propertyValue);

	                            /* If extracted is NaN, meaning the value isn't already extracted. */
	                            if (!(extracted || extracted === 0)) {
	                                var blurComponent = propertyValue.toString().match(/blur\(([0-9]+[A-z]+)\)/i);

	                                /* If the filter string had a blur component, return just the blur value and unit type. */
	                                if (blurComponent) {
	                                    extracted = blurComponent[1];
	                                /* If the component doesn't exist, default blur to 0. */
	                                } else {
	                                    extracted = 0;
	                                }
	                            }

	                            return extracted;
	                        /* Blur needs to be re-wrapped during injection. */
	                        case "inject":
	                            /* For the blur effect to be fully de-applied, it needs to be set to "none" instead of 0. */
	                            if (!parseFloat(propertyValue)) {
	                                return "none";
	                            } else {
	                                return "blur(" + propertyValue + ")";
	                            }
	                    }
	                },

	                /* <=IE8 do not support the standard opacity property. They use filter:alpha(opacity=INT) instead. */
	                opacity: function (type, element, propertyValue) {
	                    if (IE <= 8) {
	                        switch (type) {
	                            case "name":
	                                return "filter";
	                            case "extract":
	                                /* <=IE8 return a "filter" value of "alpha(opacity=\d{1,3})".
	                                   Extract the value and convert it to a decimal value to match the standard CSS opacity property's formatting. */
	                                var extracted = propertyValue.toString().match(/alpha\(opacity=(.*)\)/i);

	                                if (extracted) {
	                                    /* Convert to decimal value. */
	                                    propertyValue = extracted[1] / 100;
	                                } else {
	                                    /* When extracting opacity, default to 1 since a null value means opacity hasn't been set. */
	                                    propertyValue = 1;
	                                }

	                                return propertyValue;
	                            case "inject":
	                                /* Opacified elements are required to have their zoom property set to a non-zero value. */
	                                element.style.zoom = 1;

	                                /* Setting the filter property on elements with certain font property combinations can result in a
	                                   highly unappealing ultra-bolding effect. There's no way to remedy this throughout a tween, but dropping the
	                                   value altogether (when opacity hits 1) at leasts ensures that the glitch is gone post-tweening. */
	                                if (parseFloat(propertyValue) >= 1) {
	                                    return "";
	                                } else {
	                                  /* As per the filter property's spec, convert the decimal value to a whole number and wrap the value. */
	                                  return "alpha(opacity=" + parseInt(parseFloat(propertyValue) * 100, 10) + ")";
	                                }
	                        }
	                    /* With all other browsers, normalization is not required; return the same values that were passed in. */
	                    } else {
	                        switch (type) {
	                            case "name":
	                                return "opacity";
	                            case "extract":
	                                return propertyValue;
	                            case "inject":
	                                return propertyValue;
	                        }
	                    }
	                }
	            },

	            /*****************************
	                Batched Registrations
	            *****************************/

	            /* Note: Batched normalizations extend the CSS.Normalizations.registered object. */
	            register: function () {

	                /*****************
	                    Transforms
	                *****************/

	                /* Transforms are the subproperties contained by the CSS "transform" property. Transforms must undergo normalization
	                   so that they can be referenced in a properties map by their individual names. */
	                /* Note: When transforms are "set", they are actually assigned to a per-element transformCache. When all transform
	                   setting is complete complete, CSS.flushTransformCache() must be manually called to flush the values to the DOM.
	                   Transform setting is batched in this way to improve performance: the transform style only needs to be updated
	                   once when multiple transform subproperties are being animated simultaneously. */
	                /* Note: IE9 and Android Gingerbread have support for 2D -- but not 3D -- transforms. Since animating unsupported
	                   transform properties results in the browser ignoring the *entire* transform string, we prevent these 3D values
	                   from being normalized for these browsers so that tweening skips these properties altogether
	                   (since it will ignore them as being unsupported by the browser.) */
	                if (!(IE <= 9) && !Velocity.State.isGingerbread) {
	                    /* Note: Since the standalone CSS "perspective" property and the CSS transform "perspective" subproperty
	                    share the same name, the latter is given a unique token within Velocity: "transformPerspective". */
	                    CSS.Lists.transformsBase = CSS.Lists.transformsBase.concat(CSS.Lists.transforms3D);
	                }

	                for (var i = 0; i < CSS.Lists.transformsBase.length; i++) {
	                    /* Wrap the dynamically generated normalization function in a new scope so that transformName's value is
	                    paired with its respective function. (Otherwise, all functions would take the final for loop's transformName.) */
	                    (function() {
	                        var transformName = CSS.Lists.transformsBase[i];

	                        CSS.Normalizations.registered[transformName] = function (type, element, propertyValue) {
	                            switch (type) {
	                                /* The normalized property name is the parent "transform" property -- the property that is actually set in CSS. */
	                                case "name":
	                                    return "transform";
	                                /* Transform values are cached onto a per-element transformCache object. */
	                                case "extract":
	                                    /* If this transform has yet to be assigned a value, return its null value. */
	                                    if (Data(element) === undefined || Data(element).transformCache[transformName] === undefined) {
	                                        /* Scale CSS.Lists.transformsBase default to 1 whereas all other transform properties default to 0. */
	                                        return /^scale/i.test(transformName) ? 1 : 0;
	                                    /* When transform values are set, they are wrapped in parentheses as per the CSS spec.
	                                       Thus, when extracting their values (for tween calculations), we strip off the parentheses. */
	                                    } else {
	                                        return Data(element).transformCache[transformName].replace(/[()]/g, "");
	                                    }
	                                case "inject":
	                                    var invalid = false;

	                                    /* If an individual transform property contains an unsupported unit type, the browser ignores the *entire* transform property.
	                                       Thus, protect users from themselves by skipping setting for transform values supplied with invalid unit types. */
	                                    /* Switch on the base transform type; ignore the axis by removing the last letter from the transform's name. */
	                                    switch (transformName.substr(0, transformName.length - 1)) {
	                                        /* Whitelist unit types for each transform. */
	                                        case "translate":
	                                            invalid = !/(%|px|em|rem|vw|vh|\d)$/i.test(propertyValue);
	                                            break;
	                                        /* Since an axis-free "scale" property is supported as well, a little hack is used here to detect it by chopping off its last letter. */
	                                        case "scal":
	                                        case "scale":
	                                            /* Chrome on Android has a bug in which scaled elements blur if their initial scale
	                                               value is below 1 (which can happen with forcefeeding). Thus, we detect a yet-unset scale property
	                                               and ensure that its first value is always 1. More info: http://stackoverflow.com/questions/10417890/css3-animations-with-transform-causes-blurred-elements-on-webkit/10417962#10417962 */
	                                            if (Velocity.State.isAndroid && Data(element).transformCache[transformName] === undefined && propertyValue < 1) {
	                                                propertyValue = 1;
	                                            }

	                                            invalid = !/(\d)$/i.test(propertyValue);
	                                            break;
	                                        case "skew":
	                                            invalid = !/(deg|\d)$/i.test(propertyValue);
	                                            break;
	                                        case "rotate":
	                                            invalid = !/(deg|\d)$/i.test(propertyValue);
	                                            break;
	                                    }

	                                    if (!invalid) {
	                                        /* As per the CSS spec, wrap the value in parentheses. */
	                                        Data(element).transformCache[transformName] = "(" + propertyValue + ")";
	                                    }

	                                    /* Although the value is set on the transformCache object, return the newly-updated value for the calling code to process as normal. */
	                                    return Data(element).transformCache[transformName];
	                            }
	                        };
	                    })();
	                }

	                /*************
	                    Colors
	                *************/

	                /* Since Velocity only animates a single numeric value per property, color animation is achieved by hooking the individual RGBA components of CSS color properties.
	                   Accordingly, color values must be normalized (e.g. "#ff0000", "red", and "rgb(255, 0, 0)" ==> "255 0 0 1") so that their components can be injected/extracted by CSS.Hooks logic. */
	                for (var i = 0; i < CSS.Lists.colors.length; i++) {
	                    /* Wrap the dynamically generated normalization function in a new scope so that colorName's value is paired with its respective function.
	                       (Otherwise, all functions would take the final for loop's colorName.) */
	                    (function () {
	                        var colorName = CSS.Lists.colors[i];

	                        /* Note: In IE<=8, which support rgb but not rgba, color properties are reverted to rgb by stripping off the alpha component. */
	                        CSS.Normalizations.registered[colorName] = function(type, element, propertyValue) {
	                            switch (type) {
	                                case "name":
	                                    return colorName;
	                                /* Convert all color values into the rgb format. (Old IE can return hex values and color names instead of rgb/rgba.) */
	                                case "extract":
	                                    var extracted;

	                                    /* If the color is already in its hookable form (e.g. "255 255 255 1") due to having been previously extracted, skip extraction. */
	                                    if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) {
	                                        extracted = propertyValue;
	                                    } else {
	                                        var converted,
	                                            colorNames = {
	                                                black: "rgb(0, 0, 0)",
	                                                blue: "rgb(0, 0, 255)",
	                                                gray: "rgb(128, 128, 128)",
	                                                green: "rgb(0, 128, 0)",
	                                                red: "rgb(255, 0, 0)",
	                                                white: "rgb(255, 255, 255)"
	                                            };

	                                        /* Convert color names to rgb. */
	                                        if (/^[A-z]+$/i.test(propertyValue)) {
	                                            if (colorNames[propertyValue] !== undefined) {
	                                                converted = colorNames[propertyValue]
	                                            } else {
	                                                /* If an unmatched color name is provided, default to black. */
	                                                converted = colorNames.black;
	                                            }
	                                        /* Convert hex values to rgb. */
	                                        } else if (CSS.RegEx.isHex.test(propertyValue)) {
	                                            converted = "rgb(" + CSS.Values.hexToRgb(propertyValue).join(" ") + ")";
	                                        /* If the provided color doesn't match any of the accepted color formats, default to black. */
	                                        } else if (!(/^rgba?\(/i.test(propertyValue))) {
	                                            converted = colorNames.black;
	                                        }

	                                        /* Remove the surrounding "rgb/rgba()" string then replace commas with spaces and strip
	                                           repeated spaces (in case the value included spaces to begin with). */
	                                        extracted = (converted || propertyValue).toString().match(CSS.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g, " ");
	                                    }

	                                    /* So long as this isn't <=IE8, add a fourth (alpha) component if it's missing and default it to 1 (visible). */
	                                    if (!(IE <= 8) && extracted.split(" ").length === 3) {
	                                        extracted += " 1";
	                                    }

	                                    return extracted;
	                                case "inject":
	                                    /* If this is IE<=8 and an alpha component exists, strip it off. */
	                                    if (IE <= 8) {
	                                        if (propertyValue.split(" ").length === 4) {
	                                            propertyValue = propertyValue.split(/\s+/).slice(0, 3).join(" ");
	                                        }
	                                    /* Otherwise, add a fourth (alpha) component if it's missing and default it to 1 (visible). */
	                                    } else if (propertyValue.split(" ").length === 3) {
	                                        propertyValue += " 1";
	                                    }

	                                    /* Re-insert the browser-appropriate wrapper("rgb/rgba()"), insert commas, and strip off decimal units
	                                       on all values but the fourth (R, G, and B only accept whole numbers). */
	                                    return (IE <= 8 ? "rgb" : "rgba") + "(" + propertyValue.replace(/\s+/g, ",").replace(/\.(\d)+(?=,)/g, "") + ")";
	                            }
	                        };
	                    })();
	                }
	            }
	        },

	        /************************
	           CSS Property Names
	        ************************/

	        Names: {
	            /* Camelcase a property name into its JavaScript notation (e.g. "background-color" ==> "backgroundColor").
	               Camelcasing is used to normalize property names between and across calls. */
	            camelCase: function (property) {
	                return property.replace(/-(\w)/g, function (match, subMatch) {
	                    return subMatch.toUpperCase();
	                });
	            },

	            /* For SVG elements, some properties (namely, dimensional ones) are GET/SET via the element's HTML attributes (instead of via CSS styles). */
	            SVGAttribute: function (property) {
	                var SVGAttributes = "width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";

	                /* Certain browsers require an SVG transform to be applied as an attribute. (Otherwise, application via CSS is preferable due to 3D support.) */
	                if (IE || (Velocity.State.isAndroid && !Velocity.State.isChrome)) {
	                    SVGAttributes += "|transform";
	                }

	                return new RegExp("^(" + SVGAttributes + ")$", "i").test(property);
	            },

	            /* Determine whether a property should be set with a vendor prefix. */
	            /* If a prefixed version of the property exists, return it. Otherwise, return the original property name.
	               If the property is not at all supported by the browser, return a false flag. */
	            prefixCheck: function (property) {
	                /* If this property has already been checked, return the cached value. */
	                if (Velocity.State.prefixMatches[property]) {
	                    return [ Velocity.State.prefixMatches[property], true ];
	                } else {
	                    var vendors = [ "", "Webkit", "Moz", "ms", "O" ];

	                    for (var i = 0, vendorsLength = vendors.length; i < vendorsLength; i++) {
	                        var propertyPrefixed;

	                        if (i === 0) {
	                            propertyPrefixed = property;
	                        } else {
	                            /* Capitalize the first letter of the property to conform to JavaScript vendor prefix notation (e.g. webkitFilter). */
	                            propertyPrefixed = vendors[i] + property.replace(/^\w/, function(match) { return match.toUpperCase(); });
	                        }

	                        /* Check if the browser supports this property as prefixed. */
	                        if (Type.isString(Velocity.State.prefixElement.style[propertyPrefixed])) {
	                            /* Cache the match. */
	                            Velocity.State.prefixMatches[property] = propertyPrefixed;

	                            return [ propertyPrefixed, true ];
	                        }
	                    }

	                    /* If the browser doesn't support this property in any form, include a false flag so that the caller can decide how to proceed. */
	                    return [ property, false ];
	                }
	            }
	        },

	        /************************
	           CSS Property Values
	        ************************/

	        Values: {
	            /* Hex to RGB conversion. Copyright Tim Down: http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb */
	            hexToRgb: function (hex) {
	                var shortformRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
	                    longformRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,
	                    rgbParts;

	                hex = hex.replace(shortformRegex, function (m, r, g, b) {
	                    return r + r + g + g + b + b;
	                });

	                rgbParts = longformRegex.exec(hex);

	                return rgbParts ? [ parseInt(rgbParts[1], 16), parseInt(rgbParts[2], 16), parseInt(rgbParts[3], 16) ] : [ 0, 0, 0 ];
	            },

	            isCSSNullValue: function (value) {
	                /* The browser defaults CSS values that have not been set to either 0 or one of several possible null-value strings.
	                   Thus, we check for both falsiness and these special strings. */
	                /* Null-value checking is performed to default the special strings to 0 (for the sake of tweening) or their hook
	                   templates as defined as CSS.Hooks (for the sake of hook injection/extraction). */
	                /* Note: Chrome returns "rgba(0, 0, 0, 0)" for an undefined color whereas IE returns "transparent". */
	                return (value == 0 || /^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(value));
	            },

	            /* Retrieve a property's default unit type. Used for assigning a unit type when one is not supplied by the user. */
	            getUnitType: function (property) {
	                if (/^(rotate|skew)/i.test(property)) {
	                    return "deg";
	                } else if (/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(property)) {
	                    /* The above properties are unitless. */
	                    return "";
	                } else {
	                    /* Default to px for all other properties. */
	                    return "px";
	                }
	            },

	            /* HTML elements default to an associated display type when they're not set to display:none. */
	            /* Note: This function is used for correctly setting the non-"none" display value in certain Velocity redirects, such as fadeIn/Out. */
	            getDisplayType: function (element) {
	                var tagName = element && element.tagName.toString().toLowerCase();

	                if (/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(tagName)) {
	                    return "inline";
	                } else if (/^(li)$/i.test(tagName)) {
	                    return "list-item";
	                } else if (/^(tr)$/i.test(tagName)) {
	                    return "table-row";
	                } else if (/^(table)$/i.test(tagName)) {
	                    return "table";
	                } else if (/^(tbody)$/i.test(tagName)) {
	                    return "table-row-group";
	                /* Default to "block" when no match is found. */
	                } else {
	                    return "block";
	                }
	            },

	            /* The class add/remove functions are used to temporarily apply a "velocity-animating" class to elements while they're animating. */
	            addClass: function (element, className) {
	                if (element.classList) {
	                    element.classList.add(className);
	                } else {
	                    element.className += (element.className.length ? " " : "") + className;
	                }
	            },

	            removeClass: function (element, className) {
	                if (element.classList) {
	                    element.classList.remove(className);
	                } else {
	                    element.className = element.className.toString().replace(new RegExp("(^|\\s)" + className.split(" ").join("|") + "(\\s|$)", "gi"), " ");
	                }
	            }
	        },

	        /****************************
	           Style Getting & Setting
	        ****************************/

	        /* The singular getPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */
	        getPropertyValue: function (element, property, rootPropertyValue, forceStyleLookup) {
	            /* Get an element's computed property value. */
	            /* Note: Retrieving the value of a CSS property cannot simply be performed by checking an element's
	               style attribute (which only reflects user-defined values). Instead, the browser must be queried for a property's
	               *computed* value. You can read more about getComputedStyle here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */
	            function computePropertyValue (element, property) {
	                /* When box-sizing isn't set to border-box, height and width style values are incorrectly computed when an
	                   element's scrollbars are visible (which expands the element's dimensions). Thus, we defer to the more accurate
	                   offsetHeight/Width property, which includes the total dimensions for interior, border, padding, and scrollbar.
	                   We subtract border and padding to get the sum of interior + scrollbar. */
	                var computedValue = 0;

	                /* IE<=8 doesn't support window.getComputedStyle, thus we defer to jQuery, which has an extensive array
	                   of hacks to accurately retrieve IE8 property values. Re-implementing that logic here is not worth bloating the
	                   codebase for a dying browser. The performance repercussions of using jQuery here are minimal since
	                   Velocity is optimized to rarely (and sometimes never) query the DOM. Further, the $.css() codepath isn't that slow. */
	                if (IE <= 8) {
	                    computedValue = $.css(element, property); /* GET */
	                /* All other browsers support getComputedStyle. The returned live object reference is cached onto its
	                   associated element so that it does not need to be refetched upon every GET. */
	                } else {
	                    /* Browsers do not return height and width values for elements that are set to display:"none". Thus, we temporarily
	                       toggle display to the element type's default value. */
	                    var toggleDisplay = false;

	                    if (/^(width|height)$/.test(property) && CSS.getPropertyValue(element, "display") === 0) {
	                        toggleDisplay = true;
	                        CSS.setPropertyValue(element, "display", CSS.Values.getDisplayType(element));
	                    }

	                    function revertDisplay () {
	                        if (toggleDisplay) {
	                            CSS.setPropertyValue(element, "display", "none");
	                        }
	                    }

	                    if (!forceStyleLookup) {
	                        if (property === "height" && CSS.getPropertyValue(element, "boxSizing").toString().toLowerCase() !== "border-box") {
	                            var contentBoxHeight = element.offsetHeight - (parseFloat(CSS.getPropertyValue(element, "borderTopWidth")) || 0) - (parseFloat(CSS.getPropertyValue(element, "borderBottomWidth")) || 0) - (parseFloat(CSS.getPropertyValue(element, "paddingTop")) || 0) - (parseFloat(CSS.getPropertyValue(element, "paddingBottom")) || 0);
	                            revertDisplay();

	                            return contentBoxHeight;
	                        } else if (property === "width" && CSS.getPropertyValue(element, "boxSizing").toString().toLowerCase() !== "border-box") {
	                            var contentBoxWidth = element.offsetWidth - (parseFloat(CSS.getPropertyValue(element, "borderLeftWidth")) || 0) - (parseFloat(CSS.getPropertyValue(element, "borderRightWidth")) || 0) - (parseFloat(CSS.getPropertyValue(element, "paddingLeft")) || 0) - (parseFloat(CSS.getPropertyValue(element, "paddingRight")) || 0);
	                            revertDisplay();

	                            return contentBoxWidth;
	                        }
	                    }

	                    var computedStyle;

	                    /* For elements that Velocity hasn't been called on directly (e.g. when Velocity queries the DOM on behalf
	                       of a parent of an element its animating), perform a direct getComputedStyle lookup since the object isn't cached. */
	                    if (Data(element) === undefined) {
	                        computedStyle = window.getComputedStyle(element, null); /* GET */
	                    /* If the computedStyle object has yet to be cached, do so now. */
	                    } else if (!Data(element).computedStyle) {
	                        computedStyle = Data(element).computedStyle = window.getComputedStyle(element, null); /* GET */
	                    /* If computedStyle is cached, use it. */
	                    } else {
	                        computedStyle = Data(element).computedStyle;
	                    }

	                    /* IE and Firefox do not return a value for the generic borderColor -- they only return individual values for each border side's color.
	                       Also, in all browsers, when border colors aren't all the same, a compound value is returned that Velocity isn't setup to parse.
	                       So, as a polyfill for querying individual border side colors, we just return the top border's color and animate all borders from that value. */
	                    if (property === "borderColor") {
	                        property = "borderTopColor";
	                    }

	                    /* IE9 has a bug in which the "filter" property must be accessed from computedStyle using the getPropertyValue method
	                       instead of a direct property lookup. The getPropertyValue method is slower than a direct lookup, which is why we avoid it by default. */
	                    if (IE === 9 && property === "filter") {
	                        computedValue = computedStyle.getPropertyValue(property); /* GET */
	                    } else {
	                        computedValue = computedStyle[property];
	                    }

	                    /* Fall back to the property's style value (if defined) when computedValue returns nothing,
	                       which can happen when the element hasn't been painted. */
	                    if (computedValue === "" || computedValue === null) {
	                        computedValue = element.style[property];
	                    }

	                    revertDisplay();
	                }

	                /* For top, right, bottom, and left (TRBL) values that are set to "auto" on elements of "fixed" or "absolute" position,
	                   defer to jQuery for converting "auto" to a numeric value. (For elements with a "static" or "relative" position, "auto" has the same
	                   effect as being set to 0, so no conversion is necessary.) */
	                /* An example of why numeric conversion is necessary: When an element with "position:absolute" has an untouched "left"
	                   property, which reverts to "auto", left's value is 0 relative to its parent element, but is often non-zero relative
	                   to its *containing* (not parent) element, which is the nearest "position:relative" ancestor or the viewport (and always the viewport in the case of "position:fixed"). */
	                if (computedValue === "auto" && /^(top|right|bottom|left)$/i.test(property)) {
	                    var position = computePropertyValue(element, "position"); /* GET */

	                    /* For absolute positioning, jQuery's $.position() only returns values for top and left;
	                       right and bottom will have their "auto" value reverted to 0. */
	                    /* Note: A jQuery object must be created here since jQuery doesn't have a low-level alias for $.position().
	                       Not a big deal since we're currently in a GET batch anyway. */
	                    if (position === "fixed" || (position === "absolute" && /top|left/i.test(property))) {
	                        /* Note: jQuery strips the pixel unit from its returned values; we re-add it here to conform with computePropertyValue's behavior. */
	                        computedValue = $(element).position()[property] + "px"; /* GET */
	                    }
	                }

	                return computedValue;
	            }

	            var propertyValue;

	            /* If this is a hooked property (e.g. "clipLeft" instead of the root property of "clip"),
	               extract the hook's value from a normalized rootPropertyValue using CSS.Hooks.extractValue(). */
	            if (CSS.Hooks.registered[property]) {
	                var hook = property,
	                    hookRoot = CSS.Hooks.getRoot(hook);

	                /* If a cached rootPropertyValue wasn't passed in (which Velocity always attempts to do in order to avoid requerying the DOM),
	                   query the DOM for the root property's value. */
	                if (rootPropertyValue === undefined) {
	                    /* Since the browser is now being directly queried, use the official post-prefixing property name for this lookup. */
	                    rootPropertyValue = CSS.getPropertyValue(element, CSS.Names.prefixCheck(hookRoot)[0]); /* GET */
	                }

	                /* If this root has a normalization registered, peform the associated normalization extraction. */
	                if (CSS.Normalizations.registered[hookRoot]) {
	                    rootPropertyValue = CSS.Normalizations.registered[hookRoot]("extract", element, rootPropertyValue);
	                }

	                /* Extract the hook's value. */
	                propertyValue = CSS.Hooks.extractValue(hook, rootPropertyValue);

	            /* If this is a normalized property (e.g. "opacity" becomes "filter" in <=IE8) or "translateX" becomes "transform"),
	               normalize the property's name and value, and handle the special case of transforms. */
	            /* Note: Normalizing a property is mutually exclusive from hooking a property since hook-extracted values are strictly
	               numerical and therefore do not require normalization extraction. */
	            } else if (CSS.Normalizations.registered[property]) {
	                var normalizedPropertyName,
	                    normalizedPropertyValue;

	                normalizedPropertyName = CSS.Normalizations.registered[property]("name", element);

	                /* Transform values are calculated via normalization extraction (see below), which checks against the element's transformCache.
	                   At no point do transform GETs ever actually query the DOM; initial stylesheet values are never processed.
	                   This is because parsing 3D transform matrices is not always accurate and would bloat our codebase;
	                   thus, normalization extraction defaults initial transform values to their zero-values (e.g. 1 for scaleX and 0 for translateX). */
	                if (normalizedPropertyName !== "transform") {
	                    normalizedPropertyValue = computePropertyValue(element, CSS.Names.prefixCheck(normalizedPropertyName)[0]); /* GET */

	                    /* If the value is a CSS null-value and this property has a hook template, use that zero-value template so that hooks can be extracted from it. */
	                    if (CSS.Values.isCSSNullValue(normalizedPropertyValue) && CSS.Hooks.templates[property]) {
	                        normalizedPropertyValue = CSS.Hooks.templates[property][1];
	                    }
	                }

	                propertyValue = CSS.Normalizations.registered[property]("extract", element, normalizedPropertyValue);
	            }

	            /* If a (numeric) value wasn't produced via hook extraction or normalization, query the DOM. */
	            if (!/^[\d-]/.test(propertyValue)) {
	                /* For SVG elements, dimensional properties (which SVGAttribute() detects) are tweened via
	                   their HTML attribute values instead of their CSS style values. */
	                if (Data(element) && Data(element).isSVG && CSS.Names.SVGAttribute(property)) {
	                    /* Since the height/width attribute values must be set manually, they don't reflect computed values.
	                       Thus, we use use getBBox() to ensure we always get values for elements with undefined height/width attributes. */
	                    if (/^(height|width)$/i.test(property)) {
	                        /* Firefox throws an error if .getBBox() is called on an SVG that isn't attached to the DOM. */
	                        try {
	                            propertyValue = element.getBBox()[property];
	                        } catch (error) {
	                            propertyValue = 0;
	                        }
	                    /* Otherwise, access the attribute value directly. */
	                    } else {
	                        propertyValue = element.getAttribute(property);
	                    }
	                } else {
	                    propertyValue = computePropertyValue(element, CSS.Names.prefixCheck(property)[0]); /* GET */
	                }
	            }

	            /* Since property lookups are for animation purposes (which entails computing the numeric delta between start and end values),
	               convert CSS null-values to an integer of value 0. */
	            if (CSS.Values.isCSSNullValue(propertyValue)) {
	                propertyValue = 0;
	            }

	            if (Velocity.debug >= 2) console.log("Get " + property + ": " + propertyValue);

	            return propertyValue;
	        },

	        /* The singular setPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */
	        setPropertyValue: function(element, property, propertyValue, rootPropertyValue, scrollData) {
	            var propertyName = property;

	            /* In order to be subjected to call options and element queueing, scroll animation is routed through Velocity as if it were a standard CSS property. */
	            if (property === "scroll") {
	                /* If a container option is present, scroll the container instead of the browser window. */
	                if (scrollData.container) {
	                    scrollData.container["scroll" + scrollData.direction] = propertyValue;
	                /* Otherwise, Velocity defaults to scrolling the browser window. */
	                } else {
	                    if (scrollData.direction === "Left") {
	                        window.scrollTo(propertyValue, scrollData.alternateValue);
	                    } else {
	                        window.scrollTo(scrollData.alternateValue, propertyValue);
	                    }
	                }
	            } else {
	                /* Transforms (translateX, rotateZ, etc.) are applied to a per-element transformCache object, which is manually flushed via flushTransformCache().
	                   Thus, for now, we merely cache transforms being SET. */
	                if (CSS.Normalizations.registered[property] && CSS.Normalizations.registered[property]("name", element) === "transform") {
	                    /* Perform a normalization injection. */
	                    /* Note: The normalization logic handles the transformCache updating. */
	                    CSS.Normalizations.registered[property]("inject", element, propertyValue);

	                    propertyName = "transform";
	                    propertyValue = Data(element).transformCache[property];
	                } else {
	                    /* Inject hooks. */
	                    if (CSS.Hooks.registered[property]) {
	                        var hookName = property,
	                            hookRoot = CSS.Hooks.getRoot(property);

	                        /* If a cached rootPropertyValue was not provided, query the DOM for the hookRoot's current value. */
	                        rootPropertyValue = rootPropertyValue || CSS.getPropertyValue(element, hookRoot); /* GET */

	                        propertyValue = CSS.Hooks.injectValue(hookName, propertyValue, rootPropertyValue);
	                        property = hookRoot;
	                    }

	                    /* Normalize names and values. */
	                    if (CSS.Normalizations.registered[property]) {
	                        propertyValue = CSS.Normalizations.registered[property]("inject", element, propertyValue);
	                        property = CSS.Normalizations.registered[property]("name", element);
	                    }

	                    /* Assign the appropriate vendor prefix before performing an official style update. */
	                    propertyName = CSS.Names.prefixCheck(property)[0];

	                    /* A try/catch is used for IE<=8, which throws an error when "invalid" CSS values are set, e.g. a negative width.
	                       Try/catch is avoided for other browsers since it incurs a performance overhead. */
	                    if (IE <= 8) {
	                        try {
	                            element.style[propertyName] = propertyValue;
	                        } catch (error) { if (Velocity.debug) console.log("Browser does not support [" + propertyValue + "] for [" + propertyName + "]"); }
	                    /* SVG elements have their dimensional properties (width, height, x, y, cx, etc.) applied directly as attributes instead of as styles. */
	                    /* Note: IE8 does not support SVG elements, so it's okay that we skip it for SVG animation. */
	                    } else if (Data(element) && Data(element).isSVG && CSS.Names.SVGAttribute(property)) {
	                        /* Note: For SVG attributes, vendor-prefixed property names are never used. */
	                        /* Note: Not all CSS properties can be animated via attributes, but the browser won't throw an error for unsupported properties. */
	                        element.setAttribute(property, propertyValue);
	                    } else {
	                        element.style[propertyName] = propertyValue;
	                    }

	                    if (Velocity.debug >= 2) console.log("Set " + property + " (" + propertyName + "): " + propertyValue);
	                }
	            }

	            /* Return the normalized property name and value in case the caller wants to know how these values were modified before being applied to the DOM. */
	            return [ propertyName, propertyValue ];
	        },

	        /* To increase performance by batching transform updates into a single SET, transforms are not directly applied to an element until flushTransformCache() is called. */
	        /* Note: Velocity applies transform properties in the same order that they are chronogically introduced to the element's CSS styles. */
	        flushTransformCache: function(element) {
	            var transformString = "";

	            /* Certain browsers require that SVG transforms be applied as an attribute. However, the SVG transform attribute takes a modified version of CSS's transform string
	               (units are dropped and, except for skewX/Y, subproperties are merged into their master property -- e.g. scaleX and scaleY are merged into scale(X Y). */
	            if ((IE || (Velocity.State.isAndroid && !Velocity.State.isChrome)) && Data(element).isSVG) {
	                /* Since transform values are stored in their parentheses-wrapped form, we use a helper function to strip out their numeric values.
	                   Further, SVG transform properties only take unitless (representing pixels) values, so it's okay that parseFloat() strips the unit suffixed to the float value. */
	                function getTransformFloat (transformProperty) {
	                    return parseFloat(CSS.getPropertyValue(element, transformProperty));
	                }

	                /* Create an object to organize all the transforms that we'll apply to the SVG element. To keep the logic simple,
	                   we process *all* transform properties -- even those that may not be explicitly applied (since they default to their zero-values anyway). */
	                var SVGTransforms = {
	                    translate: [ getTransformFloat("translateX"), getTransformFloat("translateY") ],
	                    skewX: [ getTransformFloat("skewX") ], skewY: [ getTransformFloat("skewY") ],
	                    /* If the scale property is set (non-1), use that value for the scaleX and scaleY values
	                       (this behavior mimics the result of animating all these properties at once on HTML elements). */
	                    scale: getTransformFloat("scale") !== 1 ? [ getTransformFloat("scale"), getTransformFloat("scale") ] : [ getTransformFloat("scaleX"), getTransformFloat("scaleY") ],
	                    /* Note: SVG's rotate transform takes three values: rotation degrees followed by the X and Y values
	                       defining the rotation's origin point. We ignore the origin values (default them to 0). */
	                    rotate: [ getTransformFloat("rotateZ"), 0, 0 ]
	                };

	                /* Iterate through the transform properties in the user-defined property map order.
	                   (This mimics the behavior of non-SVG transform animation.) */
	                $.each(Data(element).transformCache, function(transformName) {
	                    /* Except for with skewX/Y, revert the axis-specific transform subproperties to their axis-free master
	                       properties so that they match up with SVG's accepted transform properties. */
	                    if (/^translate/i.test(transformName)) {
	                        transformName = "translate";
	                    } else if (/^scale/i.test(transformName)) {
	                        transformName = "scale";
	                    } else if (/^rotate/i.test(transformName)) {
	                        transformName = "rotate";
	                    }

	                    /* Check that we haven't yet deleted the property from the SVGTransforms container. */
	                    if (SVGTransforms[transformName]) {
	                        /* Append the transform property in the SVG-supported transform format. As per the spec, surround the space-delimited values in parentheses. */
	                        transformString += transformName + "(" + SVGTransforms[transformName].join(" ") + ")" + " ";

	                        /* After processing an SVG transform property, delete it from the SVGTransforms container so we don't
	                           re-insert the same master property if we encounter another one of its axis-specific properties. */
	                        delete SVGTransforms[transformName];
	                    }
	                });
	            } else {
	                var transformValue,
	                    perspective;

	                /* Transform properties are stored as members of the transformCache object. Concatenate all the members into a string. */
	                $.each(Data(element).transformCache, function(transformName) {
	                    transformValue = Data(element).transformCache[transformName];

	                    /* Transform's perspective subproperty must be set first in order to take effect. Store it temporarily. */
	                    if (transformName === "transformPerspective") {
	                        perspective = transformValue;
	                        return true;
	                    }

	                    /* IE9 only supports one rotation type, rotateZ, which it refers to as "rotate". */
	                    if (IE === 9 && transformName === "rotateZ") {
	                        transformName = "rotate";
	                    }

	                    transformString += transformName + transformValue + " ";
	                });

	                /* If present, set the perspective subproperty first. */
	                if (perspective) {
	                    transformString = "perspective" + perspective + " " + transformString;
	                }
	            }

	            CSS.setPropertyValue(element, "transform", transformString);
	        }
	    };

	    /* Register hooks and normalizations. */
	    CSS.Hooks.register();
	    CSS.Normalizations.register();

	    /* Allow hook setting in the same fashion as jQuery's $.css(). */
	    Velocity.hook = function (elements, arg2, arg3) {
	        var value = undefined;

	        elements = sanitizeElements(elements);

	        $.each(elements, function(i, element) {
	            /* Initialize Velocity's per-element data cache if this element hasn't previously been animated. */
	            if (Data(element) === undefined) {
	                Velocity.init(element);
	            }

	            /* Get property value. If an element set was passed in, only return the value for the first element. */
	            if (arg3 === undefined) {
	                if (value === undefined) {
	                    value = Velocity.CSS.getPropertyValue(element, arg2);
	                }
	            /* Set property value. */
	            } else {
	                /* sPV returns an array of the normalized propertyName/propertyValue pair used to update the DOM. */
	                var adjustedSet = Velocity.CSS.setPropertyValue(element, arg2, arg3);

	                /* Transform properties don't automatically set. They have to be flushed to the DOM. */
	                if (adjustedSet[0] === "transform") {
	                    Velocity.CSS.flushTransformCache(element);
	                }

	                value = adjustedSet;
	            }
	        });

	        return value;
	    };

	    /*****************
	        Animation
	    *****************/

	    var animate = function() {

	        /******************
	            Call Chain
	        ******************/

	        /* Logic for determining what to return to the call stack when exiting out of Velocity. */
	        function getChain () {
	            /* If we are using the utility function, attempt to return this call's promise. If no promise library was detected,
	               default to null instead of returning the targeted elements so that utility function's return value is standardized. */
	            if (isUtility) {
	                return promiseData.promise || null;
	            /* Otherwise, if we're using $.fn, return the jQuery-/Zepto-wrapped element set. */
	            } else {
	                return elementsWrapped;
	            }
	        }

	        /*************************
	           Arguments Assignment
	        *************************/

	        /* To allow for expressive CoffeeScript code, Velocity supports an alternative syntax in which "elements" (or "e"), "properties" (or "p"), and "options" (or "o")
	           objects are defined on a container object that's passed in as Velocity's sole argument. */
	        /* Note: Some browsers automatically populate arguments with a "properties" object. We detect it by checking for its default "names" property. */
	        var syntacticSugar = (arguments[0] && (arguments[0].p || (($.isPlainObject(arguments[0].properties) && !arguments[0].properties.names) || Type.isString(arguments[0].properties)))),
	            /* Whether Velocity was called via the utility function (as opposed to on a jQuery/Zepto object). */
	            isUtility,
	            /* When Velocity is called via the utility function ($.Velocity()/Velocity()), elements are explicitly
	               passed in as the first parameter. Thus, argument positioning varies. We normalize them here. */
	            elementsWrapped,
	            argumentIndex;

	        var elements,
	            propertiesMap,
	            options;

	        /* Detect jQuery/Zepto elements being animated via the $.fn method. */
	        if (Type.isWrapped(this)) {
	            isUtility = false;

	            argumentIndex = 0;
	            elements = this;
	            elementsWrapped = this;
	        /* Otherwise, raw elements are being animated via the utility function. */
	        } else {
	            isUtility = true;

	            argumentIndex = 1;
	            elements = syntacticSugar ? (arguments[0].elements || arguments[0].e) : arguments[0];
	        }

	        elements = sanitizeElements(elements);

	        if (!elements) {
	            return;
	        }

	        if (syntacticSugar) {
	            propertiesMap = arguments[0].properties || arguments[0].p;
	            options = arguments[0].options || arguments[0].o;
	        } else {
	            propertiesMap = arguments[argumentIndex];
	            options = arguments[argumentIndex + 1];
	        }

	        /* The length of the element set (in the form of a nodeList or an array of elements) is defaulted to 1 in case a
	           single raw DOM element is passed in (which doesn't contain a length property). */
	        var elementsLength = elements.length,
	            elementsIndex = 0;

	        /***************************
	            Argument Overloading
	        ***************************/

	        /* Support is included for jQuery's argument overloading: $.animate(propertyMap [, duration] [, easing] [, complete]).
	           Overloading is detected by checking for the absence of an object being passed into options. */
	        /* Note: The stop and finish actions do not accept animation options, and are therefore excluded from this check. */
	        if (!/^(stop|finish|finishAll)$/i.test(propertiesMap) && !$.isPlainObject(options)) {
	            /* The utility function shifts all arguments one position to the right, so we adjust for that offset. */
	            var startingArgumentPosition = argumentIndex + 1;

	            options = {};

	            /* Iterate through all options arguments */
	            for (var i = startingArgumentPosition; i < arguments.length; i++) {
	                /* Treat a number as a duration. Parse it out. */
	                /* Note: The following RegEx will return true if passed an array with a number as its first item.
	                   Thus, arrays are skipped from this check. */
	                if (!Type.isArray(arguments[i]) && (/^(fast|normal|slow)$/i.test(arguments[i]) || /^\d/.test(arguments[i]))) {
	                    options.duration = arguments[i];
	                /* Treat strings and arrays as easings. */
	                } else if (Type.isString(arguments[i]) || Type.isArray(arguments[i])) {
	                    options.easing = arguments[i];
	                /* Treat a function as a complete callback. */
	                } else if (Type.isFunction(arguments[i])) {
	                    options.complete = arguments[i];
	                }
	            }
	        }

	        /***************
	            Promises
	        ***************/

	        var promiseData = {
	                promise: null,
	                resolver: null,
	                rejecter: null
	            };

	        /* If this call was made via the utility function (which is the default method of invocation when jQuery/Zepto are not being used), and if
	           promise support was detected, create a promise object for this call and store references to its resolver and rejecter methods. The resolve
	           method is used when a call completes naturally or is prematurely stopped by the user. In both cases, completeCall() handles the associated
	           call cleanup and promise resolving logic. The reject method is used when an invalid set of arguments is passed into a Velocity call. */
	        /* Note: Velocity employs a call-based queueing architecture, which means that stopping an animating element actually stops the full call that
	           triggered it -- not that one element exclusively. Similarly, there is one promise per call, and all elements targeted by a Velocity call are
	           grouped together for the purposes of resolving and rejecting a promise. */
	        if (isUtility && Velocity.Promise) {
	            promiseData.promise = new Velocity.Promise(function (resolve, reject) {
	                promiseData.resolver = resolve;
	                promiseData.rejecter = reject;
	            });
	        }

	        /*********************
	           Action Detection
	        *********************/

	        /* Velocity's behavior is categorized into "actions": Elements can either be specially scrolled into view,
	           or they can be started, stopped, or reversed. If a literal or referenced properties map is passed in as Velocity's
	           first argument, the associated action is "start". Alternatively, "scroll", "reverse", or "stop" can be passed in instead of a properties map. */
	        var action;

	        switch (propertiesMap) {
	            case "scroll":
	                action = "scroll";
	                break;

	            case "reverse":
	                action = "reverse";
	                break;

	            case "finish":
	            case "finishAll":
	            case "stop":
	                /*******************
	                    Action: Stop
	                *******************/

	                /* Clear the currently-active delay on each targeted element. */
	                $.each(elements, function(i, element) {
	                    if (Data(element) && Data(element).delayTimer) {
	                        /* Stop the timer from triggering its cached next() function. */
	                        clearTimeout(Data(element).delayTimer.setTimeout);

	                        /* Manually call the next() function so that the subsequent queue items can progress. */
	                        if (Data(element).delayTimer.next) {
	                            Data(element).delayTimer.next();
	                        }

	                        delete Data(element).delayTimer;
	                    }

	                    /* If we want to finish everything in the queue, we have to iterate through it
	                       and call each function. This will make them active calls below, which will
	                       cause them to be applied via the duration setting. */
	                    if (propertiesMap === "finishAll" && (options === true || Type.isString(options))) {
	                        /* Iterate through the items in the element's queue. */
	                        $.each($.queue(element, Type.isString(options) ? options : ""), function(_, item) {
	                            /* The queue array can contain an "inprogress" string, which we skip. */
	                            if (Type.isFunction(item)) {
	                                item();
	                            }
	                        });

	                        /* Clearing the $.queue() array is achieved by resetting it to []. */
	                        $.queue(element, Type.isString(options) ? options : "", []);
	                    }
	                });

	                var callsToStop = [];

	                /* When the stop action is triggered, the elements' currently active call is immediately stopped. The active call might have
	                   been applied to multiple elements, in which case all of the call's elements will be stopped. When an element
	                   is stopped, the next item in its animation queue is immediately triggered. */
	                /* An additional argument may be passed in to clear an element's remaining queued calls. Either true (which defaults to the "fx" queue)
	                   or a custom queue string can be passed in. */
	                /* Note: The stop command runs prior to Velocity's Queueing phase since its behavior is intended to take effect *immediately*,
	                   regardless of the element's current queue state. */

	                /* Iterate through every active call. */
	                $.each(Velocity.State.calls, function(i, activeCall) {
	                    /* Inactive calls are set to false by the logic inside completeCall(). Skip them. */
	                    if (activeCall) {
	                        /* Iterate through the active call's targeted elements. */
	                        $.each(activeCall[1], function(k, activeElement) {
	                            /* If true was passed in as a secondary argument, clear absolutely all calls on this element. Otherwise, only
	                               clear calls associated with the relevant queue. */
	                            /* Call stopping logic works as follows:
	                               - options === true --> stop current default queue calls (and queue:false calls), including remaining queued ones.
	                               - options === undefined --> stop current queue:"" call and all queue:false calls.
	                               - options === false --> stop only queue:false calls.
	                               - options === "custom" --> stop current queue:"custom" call, including remaining queued ones (there is no functionality to only clear the currently-running queue:"custom" call). */
	                            var queueName = (options === undefined) ? "" : options;

	                            if (queueName !== true && (activeCall[2].queue !== queueName) && !(options === undefined && activeCall[2].queue === false)) {
	                                return true;
	                            }

	                            /* Iterate through the calls targeted by the stop command. */
	                            $.each(elements, function(l, element) {
	                                /* Check that this call was applied to the target element. */
	                                if (element === activeElement) {
	                                    /* Optionally clear the remaining queued calls. If we're doing "finishAll" this won't find anything,
	                                       due to the queue-clearing above. */
	                                    if (options === true || Type.isString(options)) {
	                                        /* Iterate through the items in the element's queue. */
	                                        $.each($.queue(element, Type.isString(options) ? options : ""), function(_, item) {
	                                            /* The queue array can contain an "inprogress" string, which we skip. */
	                                            if (Type.isFunction(item)) {
	                                                /* Pass the item's callback a flag indicating that we want to abort from the queue call.
	                                                   (Specifically, the queue will resolve the call's associated promise then abort.)  */
	                                                item(null, true);
	                                            }
	                                        });

	                                        /* Clearing the $.queue() array is achieved by resetting it to []. */
	                                        $.queue(element, Type.isString(options) ? options : "", []);
	                                    }

	                                    if (propertiesMap === "stop") {
	                                        /* Since "reverse" uses cached start values (the previous call's endValues), these values must be
	                                           changed to reflect the final value that the elements were actually tweened to. */
	                                        /* Note: If only queue:false animations are currently running on an element, it won't have a tweensContainer
	                                           object. Also, queue:false animations can't be reversed. */
	                                        if (Data(element) && Data(element).tweensContainer && queueName !== false) {
	                                            $.each(Data(element).tweensContainer, function(m, activeTween) {
	                                                activeTween.endValue = activeTween.currentValue;
	                                            });
	                                        }

	                                        callsToStop.push(i);
	                                    } else if (propertiesMap === "finish" || propertiesMap === "finishAll") {
	                                        /* To get active tweens to finish immediately, we forcefully shorten their durations to 1ms so that
	                                        they finish upon the next rAf tick then proceed with normal call completion logic. */
	                                        activeCall[2].duration = 1;
	                                    }
	                                }
	                            });
	                        });
	                    }
	                });

	                /* Prematurely call completeCall() on each matched active call. Pass an additional flag for "stop" to indicate
	                   that the complete callback and display:none setting should be skipped since we're completing prematurely. */
	                if (propertiesMap === "stop") {
	                    $.each(callsToStop, function(i, j) {
	                        completeCall(j, true);
	                    });

	                    if (promiseData.promise) {
	                        /* Immediately resolve the promise associated with this stop call since stop runs synchronously. */
	                        promiseData.resolver(elements);
	                    }
	                }

	                /* Since we're stopping, and not proceeding with queueing, exit out of Velocity. */
	                return getChain();

	            default:
	                /* Treat a non-empty plain object as a literal properties map. */
	                if ($.isPlainObject(propertiesMap) && !Type.isEmptyObject(propertiesMap)) {
	                    action = "start";

	                /****************
	                    Redirects
	                ****************/

	                /* Check if a string matches a registered redirect (see Redirects above). */
	                } else if (Type.isString(propertiesMap) && Velocity.Redirects[propertiesMap]) {
	                    var opts = $.extend({}, options),
	                        durationOriginal = opts.duration,
	                        delayOriginal = opts.delay || 0;

	                    /* If the backwards option was passed in, reverse the element set so that elements animate from the last to the first. */
	                    if (opts.backwards === true) {
	                        elements = $.extend(true, [], elements).reverse();
	                    }

	                    /* Individually trigger the redirect for each element in the set to prevent users from having to handle iteration logic in their redirect. */
	                    $.each(elements, function(elementIndex, element) {
	                        /* If the stagger option was passed in, successively delay each element by the stagger value (in ms). Retain the original delay value. */
	                        if (parseFloat(opts.stagger)) {
	                            opts.delay = delayOriginal + (parseFloat(opts.stagger) * elementIndex);
	                        } else if (Type.isFunction(opts.stagger)) {
	                            opts.delay = delayOriginal + opts.stagger.call(element, elementIndex, elementsLength);
	                        }

	                        /* If the drag option was passed in, successively increase/decrease (depending on the presense of opts.backwards)
	                           the duration of each element's animation, using floors to prevent producing very short durations. */
	                        if (opts.drag) {
	                            /* Default the duration of UI pack effects (callouts and transitions) to 1000ms instead of the usual default duration of 400ms. */
	                            opts.duration = parseFloat(durationOriginal) || (/^(callout|transition)/.test(propertiesMap) ? 1000 : DURATION_DEFAULT);

	                            /* For each element, take the greater duration of: A) animation completion percentage relative to the original duration,
	                               B) 75% of the original duration, or C) a 200ms fallback (in case duration is already set to a low value).
	                               The end result is a baseline of 75% of the redirect's duration that increases/decreases as the end of the element set is approached. */
	                            opts.duration = Math.max(opts.duration * (opts.backwards ? 1 - elementIndex/elementsLength : (elementIndex + 1) / elementsLength), opts.duration * 0.75, 200);
	                        }

	                        /* Pass in the call's opts object so that the redirect can optionally extend it. It defaults to an empty object instead of null to
	                           reduce the opts checking logic required inside the redirect. */
	                        Velocity.Redirects[propertiesMap].call(element, element, opts || {}, elementIndex, elementsLength, elements, promiseData.promise ? promiseData : undefined);
	                    });

	                    /* Since the animation logic resides within the redirect's own code, abort the remainder of this call.
	                       (The performance overhead up to this point is virtually non-existant.) */
	                    /* Note: The jQuery call chain is kept intact by returning the complete element set. */
	                    return getChain();
	                } else {
	                    var abortError = "Velocity: First argument (" + propertiesMap + ") was not a property map, a known action, or a registered redirect. Aborting.";

	                    if (promiseData.promise) {
	                        promiseData.rejecter(new Error(abortError));
	                    } else {
	                        console.log(abortError);
	                    }

	                    return getChain();
	                }
	        }

	        /**************************
	            Call-Wide Variables
	        **************************/

	        /* A container for CSS unit conversion ratios (e.g. %, rem, and em ==> px) that is used to cache ratios across all elements
	           being animated in a single Velocity call. Calculating unit ratios necessitates DOM querying and updating, and is therefore
	           avoided (via caching) wherever possible. This container is call-wide instead of page-wide to avoid the risk of using stale
	           conversion metrics across Velocity animations that are not immediately consecutively chained. */
	        var callUnitConversionData = {
	                lastParent: null,
	                lastPosition: null,
	                lastFontSize: null,
	                lastPercentToPxWidth: null,
	                lastPercentToPxHeight: null,
	                lastEmToPx: null,
	                remToPx: null,
	                vwToPx: null,
	                vhToPx: null
	            };

	        /* A container for all the ensuing tween data and metadata associated with this call. This container gets pushed to the page-wide
	           Velocity.State.calls array that is processed during animation ticking. */
	        var call = [];

	        /************************
	           Element Processing
	        ************************/

	        /* Element processing consists of three parts -- data processing that cannot go stale and data processing that *can* go stale (i.e. third-party style modifications):
	           1) Pre-Queueing: Element-wide variables, including the element's data storage, are instantiated. Call options are prepared. If triggered, the Stop action is executed.
	           2) Queueing: The logic that runs once this call has reached its point of execution in the element's $.queue() stack. Most logic is placed here to avoid risking it becoming stale.
	           3) Pushing: Consolidation of the tween data followed by its push onto the global in-progress calls container.
	        */

	        function processElement () {

	            /*************************
	               Part I: Pre-Queueing
	            *************************/

	            /***************************
	               Element-Wide Variables
	            ***************************/

	            var element = this,
	                /* The runtime opts object is the extension of the current call's options and Velocity's page-wide option defaults. */
	                opts = $.extend({}, Velocity.defaults, options),
	                /* A container for the processed data associated with each property in the propertyMap.
	                   (Each property in the map produces its own "tween".) */
	                tweensContainer = {},
	                elementUnitConversionData;

	            /******************
	               Element Init
	            ******************/

	            if (Data(element) === undefined) {
	                Velocity.init(element);
	            }

	            /******************
	               Option: Delay
	            ******************/

	            /* Since queue:false doesn't respect the item's existing queue, we avoid injecting its delay here (it's set later on). */
	            /* Note: Velocity rolls its own delay function since jQuery doesn't have a utility alias for $.fn.delay()
	               (and thus requires jQuery element creation, which we avoid since its overhead includes DOM querying). */
	            if (parseFloat(opts.delay) && opts.queue !== false) {
	                $.queue(element, opts.queue, function(next) {
	                    /* This is a flag used to indicate to the upcoming completeCall() function that this queue entry was initiated by Velocity. See completeCall() for further details. */
	                    Velocity.velocityQueueEntryFlag = true;

	                    /* The ensuing queue item (which is assigned to the "next" argument that $.queue() automatically passes in) will be triggered after a setTimeout delay.
	                       The setTimeout is stored so that it can be subjected to clearTimeout() if this animation is prematurely stopped via Velocity's "stop" command. */
	                    Data(element).delayTimer = {
	                        setTimeout: setTimeout(next, parseFloat(opts.delay)),
	                        next: next
	                    };
	                });
	            }

	            /*********************
	               Option: Duration
	            *********************/

	            /* Support for jQuery's named durations. */
	            switch (opts.duration.toString().toLowerCase()) {
	                case "fast":
	                    opts.duration = 200;
	                    break;

	                case "normal":
	                    opts.duration = DURATION_DEFAULT;
	                    break;

	                case "slow":
	                    opts.duration = 600;
	                    break;

	                default:
	                    /* Remove the potential "ms" suffix and default to 1 if the user is attempting to set a duration of 0 (in order to produce an immediate style change). */
	                    opts.duration = parseFloat(opts.duration) || 1;
	            }

	            /************************
	               Global Option: Mock
	            ************************/

	            if (Velocity.mock !== false) {
	                /* In mock mode, all animations are forced to 1ms so that they occur immediately upon the next rAF tick.
	                   Alternatively, a multiplier can be passed in to time remap all delays and durations. */
	                if (Velocity.mock === true) {
	                    opts.duration = opts.delay = 1;
	                } else {
	                    opts.duration *= parseFloat(Velocity.mock) || 1;
	                    opts.delay *= parseFloat(Velocity.mock) || 1;
	                }
	            }

	            /*******************
	               Option: Easing
	            *******************/

	            opts.easing = getEasing(opts.easing, opts.duration);

	            /**********************
	               Option: Callbacks
	            **********************/

	            /* Callbacks must functions. Otherwise, default to null. */
	            if (opts.begin && !Type.isFunction(opts.begin)) {
	                opts.begin = null;
	            }

	            if (opts.progress && !Type.isFunction(opts.progress)) {
	                opts.progress = null;
	            }

	            if (opts.complete && !Type.isFunction(opts.complete)) {
	                opts.complete = null;
	            }

	            /*********************************
	               Option: Display & Visibility
	            *********************************/

	            /* Refer to Velocity's documentation (VelocityJS.org/#displayAndVisibility) for a description of the display and visibility options' behavior. */
	            /* Note: We strictly check for undefined instead of falsiness because display accepts an empty string value. */
	            if (opts.display !== undefined && opts.display !== null) {
	                opts.display = opts.display.toString().toLowerCase();

	                /* Users can pass in a special "auto" value to instruct Velocity to set the element to its default display value. */
	                if (opts.display === "auto") {
	                    opts.display = Velocity.CSS.Values.getDisplayType(element);
	                }
	            }

	            if (opts.visibility !== undefined && opts.visibility !== null) {
	                opts.visibility = opts.visibility.toString().toLowerCase();
	            }

	            /**********************
	               Option: mobileHA
	            **********************/

	            /* When set to true, and if this is a mobile device, mobileHA automatically enables hardware acceleration (via a null transform hack)
	               on animating elements. HA is removed from the element at the completion of its animation. */
	            /* Note: Android Gingerbread doesn't support HA. If a null transform hack (mobileHA) is in fact set, it will prevent other tranform subproperties from taking effect. */
	            /* Note: You can read more about the use of mobileHA in Velocity's documentation: VelocityJS.org/#mobileHA. */
	            opts.mobileHA = (opts.mobileHA && Velocity.State.isMobile && !Velocity.State.isGingerbread);

	            /***********************
	               Part II: Queueing
	            ***********************/

	            /* When a set of elements is targeted by a Velocity call, the set is broken up and each element has the current Velocity call individually queued onto it.
	               In this way, each element's existing queue is respected; some elements may already be animating and accordingly should not have this current Velocity call triggered immediately. */
	            /* In each queue, tween data is processed for each animating property then pushed onto the call-wide calls array. When the last element in the set has had its tweens processed,
	               the call array is pushed to Velocity.State.calls for live processing by the requestAnimationFrame tick. */
	            function buildQueue (next) {

	                /*******************
	                   Option: Begin
	                *******************/

	                /* The begin callback is fired once per call -- not once per elemenet -- and is passed the full raw DOM element set as both its context and its first argument. */
	                if (opts.begin && elementsIndex === 0) {
	                    /* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Velocity itself. */
	                    try {
	                        opts.begin.call(elements, elements);
	                    } catch (error) {
	                        setTimeout(function() { throw error; }, 1);
	                    }
	                }

	                /*****************************************
	                   Tween Data Construction (for Scroll)
	                *****************************************/

	                /* Note: In order to be subjected to chaining and animation options, scroll's tweening is routed through Velocity as if it were a standard CSS property animation. */
	                if (action === "scroll") {
	                    /* The scroll action uniquely takes an optional "offset" option -- specified in pixels -- that offsets the targeted scroll position. */
	                    var scrollDirection = (/^x$/i.test(opts.axis) ? "Left" : "Top"),
	                        scrollOffset = parseFloat(opts.offset) || 0,
	                        scrollPositionCurrent,
	                        scrollPositionCurrentAlternate,
	                        scrollPositionEnd;

	                    /* Scroll also uniquely takes an optional "container" option, which indicates the parent element that should be scrolled --
	                       as opposed to the browser window itself. This is useful for scrolling toward an element that's inside an overflowing parent element. */
	                    if (opts.container) {
	                        /* Ensure that either a jQuery object or a raw DOM element was passed in. */
	                        if (Type.isWrapped(opts.container) || Type.isNode(opts.container)) {
	                            /* Extract the raw DOM element from the jQuery wrapper. */
	                            opts.container = opts.container[0] || opts.container;
	                            /* Note: Unlike other properties in Velocity, the browser's scroll position is never cached since it so frequently changes
	                               (due to the user's natural interaction with the page). */
	                            scrollPositionCurrent = opts.container["scroll" + scrollDirection]; /* GET */

	                            /* $.position() values are relative to the container's currently viewable area (without taking into account the container's true dimensions
	                               -- say, for example, if the container was not overflowing). Thus, the scroll end value is the sum of the child element's position *and*
	                               the scroll container's current scroll position. */
	                            scrollPositionEnd = (scrollPositionCurrent + $(element).position()[scrollDirection.toLowerCase()]) + scrollOffset; /* GET */
	                        /* If a value other than a jQuery object or a raw DOM element was passed in, default to null so that this option is ignored. */
	                        } else {
	                            opts.container = null;
	                        }
	                    } else {
	                        /* If the window itself is being scrolled -- not a containing element -- perform a live scroll position lookup using
	                           the appropriate cached property names (which differ based on browser type). */
	                        scrollPositionCurrent = Velocity.State.scrollAnchor[Velocity.State["scrollProperty" + scrollDirection]]; /* GET */
	                        /* When scrolling the browser window, cache the alternate axis's current value since window.scrollTo() doesn't let us change only one value at a time. */
	                        scrollPositionCurrentAlternate = Velocity.State.scrollAnchor[Velocity.State["scrollProperty" + (scrollDirection === "Left" ? "Top" : "Left")]]; /* GET */

	                        /* Unlike $.position(), $.offset() values are relative to the browser window's true dimensions -- not merely its currently viewable area --
	                           and therefore end values do not need to be compounded onto current values. */
	                        scrollPositionEnd = $(element).offset()[scrollDirection.toLowerCase()] + scrollOffset; /* GET */
	                    }

	                    /* Since there's only one format that scroll's associated tweensContainer can take, we create it manually. */
	                    tweensContainer = {
	                        scroll: {
	                            rootPropertyValue: false,
	                            startValue: scrollPositionCurrent,
	                            currentValue: scrollPositionCurrent,
	                            endValue: scrollPositionEnd,
	                            unitType: "",
	                            easing: opts.easing,
	                            scrollData: {
	                                container: opts.container,
	                                direction: scrollDirection,
	                                alternateValue: scrollPositionCurrentAlternate
	                            }
	                        },
	                        element: element
	                    };

	                    if (Velocity.debug) console.log("tweensContainer (scroll): ", tweensContainer.scroll, element);

	                /******************************************
	                   Tween Data Construction (for Reverse)
	                ******************************************/

	                /* Reverse acts like a "start" action in that a property map is animated toward. The only difference is
	                   that the property map used for reverse is the inverse of the map used in the previous call. Thus, we manipulate
	                   the previous call to construct our new map: use the previous map's end values as our new map's start values. Copy over all other data. */
	                /* Note: Reverse can be directly called via the "reverse" parameter, or it can be indirectly triggered via the loop option. (Loops are composed of multiple reverses.) */
	                /* Note: Reverse calls do not need to be consecutively chained onto a currently-animating element in order to operate on cached values;
	                   there is no harm to reverse being called on a potentially stale data cache since reverse's behavior is simply defined
	                   as reverting to the element's values as they were prior to the previous *Velocity* call. */
	                } else if (action === "reverse") {
	                    /* Abort if there is no prior animation data to reverse to. */
	                    if (!Data(element).tweensContainer) {
	                        /* Dequeue the element so that this queue entry releases itself immediately, allowing subsequent queue entries to run. */
	                        $.dequeue(element, opts.queue);

	                        return;
	                    } else {
	                        /*********************
	                           Options Parsing
	                        *********************/

	                        /* If the element was hidden via the display option in the previous call,
	                           revert display to "auto" prior to reversal so that the element is visible again. */
	                        if (Data(element).opts.display === "none") {
	                            Data(element).opts.display = "auto";
	                        }

	                        if (Data(element).opts.visibility === "hidden") {
	                            Data(element).opts.visibility = "visible";
	                        }

	                        /* If the loop option was set in the previous call, disable it so that "reverse" calls aren't recursively generated.
	                           Further, remove the previous call's callback options; typically, users do not want these to be refired. */
	                        Data(element).opts.loop = false;
	                        Data(element).opts.begin = null;
	                        Data(element).opts.complete = null;

	                        /* Since we're extending an opts object that has already been extended with the defaults options object,
	                           we remove non-explicitly-defined properties that are auto-assigned values. */
	                        if (!options.easing) {
	                            delete opts.easing;
	                        }

	                        if (!options.duration) {
	                            delete opts.duration;
	                        }

	                        /* The opts object used for reversal is an extension of the options object optionally passed into this
	                           reverse call plus the options used in the previous Velocity call. */
	                        opts = $.extend({}, Data(element).opts, opts);

	                        /*************************************
	                           Tweens Container Reconstruction
	                        *************************************/

	                        /* Create a deepy copy (indicated via the true flag) of the previous call's tweensContainer. */
	                        var lastTweensContainer = $.extend(true, {}, Data(element).tweensContainer);

	                        /* Manipulate the previous tweensContainer by replacing its end values and currentValues with its start values. */
	                        for (var lastTween in lastTweensContainer) {
	                            /* In addition to tween data, tweensContainers contain an element property that we ignore here. */
	                            if (lastTween !== "element") {
	                                var lastStartValue = lastTweensContainer[lastTween].startValue;

	                                lastTweensContainer[lastTween].startValue = lastTweensContainer[lastTween].currentValue = lastTweensContainer[lastTween].endValue;
	                                lastTweensContainer[lastTween].endValue = lastStartValue;

	                                /* Easing is the only option that embeds into the individual tween data (since it can be defined on a per-property basis).
	                                   Accordingly, every property's easing value must be updated when an options object is passed in with a reverse call.
	                                   The side effect of this extensibility is that all per-property easing values are forcefully reset to the new value. */
	                                if (!Type.isEmptyObject(options)) {
	                                    lastTweensContainer[lastTween].easing = opts.easing;
	                                }

	                                if (Velocity.debug) console.log("reverse tweensContainer (" + lastTween + "): " + JSON.stringify(lastTweensContainer[lastTween]), element);
	                            }
	                        }

	                        tweensContainer = lastTweensContainer;
	                    }

	                /*****************************************
	                   Tween Data Construction (for Start)
	                *****************************************/

	                } else if (action === "start") {

	                    /*************************
	                        Value Transferring
	                    *************************/

	                    /* If this queue entry follows a previous Velocity-initiated queue entry *and* if this entry was created
	                       while the element was in the process of being animated by Velocity, then this current call is safe to use
	                       the end values from the prior call as its start values. Velocity attempts to perform this value transfer
	                       process whenever possible in order to avoid requerying the DOM. */
	                    /* If values aren't transferred from a prior call and start values were not forcefed by the user (more on this below),
	                       then the DOM is queried for the element's current values as a last resort. */
	                    /* Note: Conversely, animation reversal (and looping) *always* perform inter-call value transfers; they never requery the DOM. */
	                    var lastTweensContainer;

	                    /* The per-element isAnimating flag is used to indicate whether it's safe (i.e. the data isn't stale)
	                       to transfer over end values to use as start values. If it's set to true and there is a previous
	                       Velocity call to pull values from, do so. */
	                    if (Data(element).tweensContainer && Data(element).isAnimating === true) {
	                        lastTweensContainer = Data(element).tweensContainer;
	                    }

	                    /***************************
	                       Tween Data Calculation
	                    ***************************/

	                    /* This function parses property data and defaults endValue, easing, and startValue as appropriate. */
	                    /* Property map values can either take the form of 1) a single value representing the end value,
	                       or 2) an array in the form of [ endValue, [, easing] [, startValue] ].
	                       The optional third parameter is a forcefed startValue to be used instead of querying the DOM for
	                       the element's current value. Read Velocity's docmentation to learn more about forcefeeding: VelocityJS.org/#forcefeeding */
	                    function parsePropertyValue (valueData, skipResolvingEasing) {
	                        var endValue = undefined,
	                            easing = undefined,
	                            startValue = undefined;

	                        /* Handle the array format, which can be structured as one of three potential overloads:
	                           A) [ endValue, easing, startValue ], B) [ endValue, easing ], or C) [ endValue, startValue ] */
	                        if (Type.isArray(valueData)) {
	                            /* endValue is always the first item in the array. Don't bother validating endValue's value now
	                               since the ensuing property cycling logic does that. */
	                            endValue = valueData[0];

	                            /* Two-item array format: If the second item is a number, function, or hex string, treat it as a
	                               start value since easings can only be non-hex strings or arrays. */
	                            if ((!Type.isArray(valueData[1]) && /^[\d-]/.test(valueData[1])) || Type.isFunction(valueData[1]) || CSS.RegEx.isHex.test(valueData[1])) {
	                                startValue = valueData[1];
	                            /* Two or three-item array: If the second item is a non-hex string or an array, treat it as an easing. */
	                            } else if ((Type.isString(valueData[1]) && !CSS.RegEx.isHex.test(valueData[1])) || Type.isArray(valueData[1])) {
	                                easing = skipResolvingEasing ? valueData[1] : getEasing(valueData[1], opts.duration);

	                                /* Don't bother validating startValue's value now since the ensuing property cycling logic inherently does that. */
	                                if (valueData[2] !== undefined) {
	                                    startValue = valueData[2];
	                                }
	                            }
	                        /* Handle the single-value format. */
	                        } else {
	                            endValue = valueData;
	                        }

	                        /* Default to the call's easing if a per-property easing type was not defined. */
	                        if (!skipResolvingEasing) {
	                            easing = easing || opts.easing;
	                        }

	                        /* If functions were passed in as values, pass the function the current element as its context,
	                           plus the element's index and the element set's size as arguments. Then, assign the returned value. */
	                        if (Type.isFunction(endValue)) {
	                            endValue = endValue.call(element, elementsIndex, elementsLength);
	                        }

	                        if (Type.isFunction(startValue)) {
	                            startValue = startValue.call(element, elementsIndex, elementsLength);
	                        }

	                        /* Allow startValue to be left as undefined to indicate to the ensuing code that its value was not forcefed. */
	                        return [ endValue || 0, easing, startValue ];
	                    }

	                    /* Cycle through each property in the map, looking for shorthand color properties (e.g. "color" as opposed to "colorRed"). Inject the corresponding
	                       colorRed, colorGreen, and colorBlue RGB component tweens into the propertiesMap (which Velocity understands) and remove the shorthand property. */
	                    $.each(propertiesMap, function(property, value) {
	                        /* Find shorthand color properties that have been passed a hex string. */
	                        if (RegExp("^" + CSS.Lists.colors.join("$|^") + "$").test(property)) {
	                            /* Parse the value data for each shorthand. */
	                            var valueData = parsePropertyValue(value, true),
	                                endValue = valueData[0],
	                                easing = valueData[1],
	                                startValue = valueData[2];

	                            if (CSS.RegEx.isHex.test(endValue)) {
	                                /* Convert the hex strings into their RGB component arrays. */
	                                var colorComponents = [ "Red", "Green", "Blue" ],
	                                    endValueRGB = CSS.Values.hexToRgb(endValue),
	                                    startValueRGB = startValue ? CSS.Values.hexToRgb(startValue) : undefined;

	                                /* Inject the RGB component tweens into propertiesMap. */
	                                for (var i = 0; i < colorComponents.length; i++) {
	                                    var dataArray = [ endValueRGB[i] ];

	                                    if (easing) {
	                                        dataArray.push(easing);
	                                    }

	                                    if (startValueRGB !== undefined) {
	                                        dataArray.push(startValueRGB[i]);
	                                    }

	                                    propertiesMap[property + colorComponents[i]] = dataArray;
	                                }

	                                /* Remove the intermediary shorthand property entry now that we've processed it. */
	                                delete propertiesMap[property];
	                            }
	                        }
	                    });

	                    /* Create a tween out of each property, and append its associated data to tweensContainer. */
	                    for (var property in propertiesMap) {

	                        /**************************
	                           Start Value Sourcing
	                        **************************/

	                        /* Parse out endValue, easing, and startValue from the property's data. */
	                        var valueData = parsePropertyValue(propertiesMap[property]),
	                            endValue = valueData[0],
	                            easing = valueData[1],
	                            startValue = valueData[2];

	                        /* Now that the original property name's format has been used for the parsePropertyValue() lookup above,
	                           we force the property to its camelCase styling to normalize it for manipulation. */
	                        property = CSS.Names.camelCase(property);

	                        /* In case this property is a hook, there are circumstances where we will intend to work on the hook's root property and not the hooked subproperty. */
	                        var rootProperty = CSS.Hooks.getRoot(property),
	                            rootPropertyValue = false;

	                        /* Other than for the dummy tween property, properties that are not supported by the browser (and do not have an associated normalization) will
	                           inherently produce no style changes when set, so they are skipped in order to decrease animation tick overhead.
	                           Property support is determined via prefixCheck(), which returns a false flag when no supported is detected. */
	                        /* Note: Since SVG elements have some of their properties directly applied as HTML attributes,
	                           there is no way to check for their explicit browser support, and so we skip skip this check for them. */
	                        if (!Data(element).isSVG && rootProperty !== "tween" && CSS.Names.prefixCheck(rootProperty)[1] === false && CSS.Normalizations.registered[rootProperty] === undefined) {
	                            if (Velocity.debug) console.log("Skipping [" + rootProperty + "] due to a lack of browser support.");

	                            continue;
	                        }

	                        /* If the display option is being set to a non-"none" (e.g. "block") and opacity (filter on IE<=8) is being
	                           animated to an endValue of non-zero, the user's intention is to fade in from invisible, thus we forcefeed opacity
	                           a startValue of 0 if its startValue hasn't already been sourced by value transferring or prior forcefeeding. */
	                        if (((opts.display !== undefined && opts.display !== null && opts.display !== "none") || (opts.visibility !== undefined && opts.visibility !== "hidden")) && /opacity|filter/.test(property) && !startValue && endValue !== 0) {
	                            startValue = 0;
	                        }

	                        /* If values have been transferred from the previous Velocity call, extract the endValue and rootPropertyValue
	                           for all of the current call's properties that were *also* animated in the previous call. */
	                        /* Note: Value transferring can optionally be disabled by the user via the _cacheValues option. */
	                        if (opts._cacheValues && lastTweensContainer && lastTweensContainer[property]) {
	                            if (startValue === undefined) {
	                                startValue = lastTweensContainer[property].endValue + lastTweensContainer[property].unitType;
	                            }

	                            /* The previous call's rootPropertyValue is extracted from the element's data cache since that's the
	                               instance of rootPropertyValue that gets freshly updated by the tweening process, whereas the rootPropertyValue
	                               attached to the incoming lastTweensContainer is equal to the root property's value prior to any tweening. */
	                            rootPropertyValue = Data(element).rootPropertyValueCache[rootProperty];
	                        /* If values were not transferred from a previous Velocity call, query the DOM as needed. */
	                        } else {
	                            /* Handle hooked properties. */
	                            if (CSS.Hooks.registered[property]) {
	                               if (startValue === undefined) {
	                                    rootPropertyValue = CSS.getPropertyValue(element, rootProperty); /* GET */
	                                    /* Note: The following getPropertyValue() call does not actually trigger a DOM query;
	                                       getPropertyValue() will extract the hook from rootPropertyValue. */
	                                    startValue = CSS.getPropertyValue(element, property, rootPropertyValue);
	                                /* If startValue is already defined via forcefeeding, do not query the DOM for the root property's value;
	                                   just grab rootProperty's zero-value template from CSS.Hooks. This overwrites the element's actual
	                                   root property value (if one is set), but this is acceptable since the primary reason users forcefeed is
	                                   to avoid DOM queries, and thus we likewise avoid querying the DOM for the root property's value. */
	                                } else {
	                                    /* Grab this hook's zero-value template, e.g. "0px 0px 0px black". */
	                                    rootPropertyValue = CSS.Hooks.templates[rootProperty][1];
	                                }
	                            /* Handle non-hooked properties that haven't already been defined via forcefeeding. */
	                            } else if (startValue === undefined) {
	                                startValue = CSS.getPropertyValue(element, property); /* GET */
	                            }
	                        }

	                        /**************************
	                           Value Data Extraction
	                        **************************/

	                        var separatedValue,
	                            endValueUnitType,
	                            startValueUnitType,
	                            operator = false;

	                        /* Separates a property value into its numeric value and its unit type. */
	                        function separateValue (property, value) {
	                            var unitType,
	                                numericValue;

	                            numericValue = (value || "0")
	                                .toString()
	                                .toLowerCase()
	                                /* Match the unit type at the end of the value. */
	                                .replace(/[%A-z]+$/, function(match) {
	                                    /* Grab the unit type. */
	                                    unitType = match;

	                                    /* Strip the unit type off of value. */
	                                    return "";
	                                });

	                            /* If no unit type was supplied, assign one that is appropriate for this property (e.g. "deg" for rotateZ or "px" for width). */
	                            if (!unitType) {
	                                unitType = CSS.Values.getUnitType(property);
	                            }

	                            return [ numericValue, unitType ];
	                        }

	                        /* Separate startValue. */
	                        separatedValue = separateValue(property, startValue);
	                        startValue = separatedValue[0];
	                        startValueUnitType = separatedValue[1];

	                        /* Separate endValue, and extract a value operator (e.g. "+=", "-=") if one exists. */
	                        separatedValue = separateValue(property, endValue);
	                        endValue = separatedValue[0].replace(/^([+-\/*])=/, function(match, subMatch) {
	                            operator = subMatch;

	                            /* Strip the operator off of the value. */
	                            return "";
	                        });
	                        endValueUnitType = separatedValue[1];

	                        /* Parse float values from endValue and startValue. Default to 0 if NaN is returned. */
	                        startValue = parseFloat(startValue) || 0;
	                        endValue = parseFloat(endValue) || 0;

	                        /***************************************
	                           Property-Specific Value Conversion
	                        ***************************************/

	                        /* Custom support for properties that don't actually accept the % unit type, but where pollyfilling is trivial and relatively foolproof. */
	                        if (endValueUnitType === "%") {
	                            /* A %-value fontSize/lineHeight is relative to the parent's fontSize (as opposed to the parent's dimensions),
	                               which is identical to the em unit's behavior, so we piggyback off of that. */
	                            if (/^(fontSize|lineHeight)$/.test(property)) {
	                                /* Convert % into an em decimal value. */
	                                endValue = endValue / 100;
	                                endValueUnitType = "em";
	                            /* For scaleX and scaleY, convert the value into its decimal format and strip off the unit type. */
	                            } else if (/^scale/.test(property)) {
	                                endValue = endValue / 100;
	                                endValueUnitType = "";
	                            /* For RGB components, take the defined percentage of 255 and strip off the unit type. */
	                            } else if (/(Red|Green|Blue)$/i.test(property)) {
	                                endValue = (endValue / 100) * 255;
	                                endValueUnitType = "";
	                            }
	                        }

	                        /***************************
	                           Unit Ratio Calculation
	                        ***************************/

	                        /* When queried, the browser returns (most) CSS property values in pixels. Therefore, if an endValue with a unit type of
	                           %, em, or rem is animated toward, startValue must be converted from pixels into the same unit type as endValue in order
	                           for value manipulation logic (increment/decrement) to proceed. Further, if the startValue was forcefed or transferred
	                           from a previous call, startValue may also not be in pixels. Unit conversion logic therefore consists of two steps:
	                           1) Calculating the ratio of %/em/rem/vh/vw relative to pixels
	                           2) Converting startValue into the same unit of measurement as endValue based on these ratios. */
	                        /* Unit conversion ratios are calculated by inserting a sibling node next to the target node, copying over its position property,
	                           setting values with the target unit type then comparing the returned pixel value. */
	                        /* Note: Even if only one of these unit types is being animated, all unit ratios are calculated at once since the overhead
	                           of batching the SETs and GETs together upfront outweights the potential overhead
	                           of layout thrashing caused by re-querying for uncalculated ratios for subsequently-processed properties. */
	                        /* Todo: Shift this logic into the calls' first tick instance so that it's synced with RAF. */
	                        function calculateUnitRatios () {

	                            /************************
	                                Same Ratio Checks
	                            ************************/

	                            /* The properties below are used to determine whether the element differs sufficiently from this call's
	                               previously iterated element to also differ in its unit conversion ratios. If the properties match up with those
	                               of the prior element, the prior element's conversion ratios are used. Like most optimizations in Velocity,
	                               this is done to minimize DOM querying. */
	                            var sameRatioIndicators = {
	                                    myParent: element.parentNode || document.body, /* GET */
	                                    position: CSS.getPropertyValue(element, "position"), /* GET */
	                                    fontSize: CSS.getPropertyValue(element, "fontSize") /* GET */
	                                },
	                                /* Determine if the same % ratio can be used. % is based on the element's position value and its parent's width and height dimensions. */
	                                samePercentRatio = ((sameRatioIndicators.position === callUnitConversionData.lastPosition) && (sameRatioIndicators.myParent === callUnitConversionData.lastParent)),
	                                /* Determine if the same em ratio can be used. em is relative to the element's fontSize. */
	                                sameEmRatio = (sameRatioIndicators.fontSize === callUnitConversionData.lastFontSize);

	                            /* Store these ratio indicators call-wide for the next element to compare against. */
	                            callUnitConversionData.lastParent = sameRatioIndicators.myParent;
	                            callUnitConversionData.lastPosition = sameRatioIndicators.position;
	                            callUnitConversionData.lastFontSize = sameRatioIndicators.fontSize;

	                            /***************************
	                               Element-Specific Units
	                            ***************************/

	                            /* Note: IE8 rounds to the nearest pixel when returning CSS values, thus we perform conversions using a measurement
	                               of 100 (instead of 1) to give our ratios a precision of at least 2 decimal values. */
	                            var measurement = 100,
	                                unitRatios = {};

	                            if (!sameEmRatio || !samePercentRatio) {
	                                var dummy = Data(element).isSVG ? document.createElementNS("http://www.w3.org/2000/svg", "rect") : document.createElement("div");

	                                Velocity.init(dummy);
	                                sameRatioIndicators.myParent.appendChild(dummy);

	                                /* To accurately and consistently calculate conversion ratios, the element's cascaded overflow and box-sizing are stripped.
	                                   Similarly, since width/height can be artificially constrained by their min-/max- equivalents, these are controlled for as well. */
	                                /* Note: Overflow must be also be controlled for per-axis since the overflow property overwrites its per-axis values. */
	                                $.each([ "overflow", "overflowX", "overflowY" ], function(i, property) {
	                                    Velocity.CSS.setPropertyValue(dummy, property, "hidden");
	                                });
	                                Velocity.CSS.setPropertyValue(dummy, "position", sameRatioIndicators.position);
	                                Velocity.CSS.setPropertyValue(dummy, "fontSize", sameRatioIndicators.fontSize);
	                                Velocity.CSS.setPropertyValue(dummy, "boxSizing", "content-box");

	                                /* width and height act as our proxy properties for measuring the horizontal and vertical % ratios. */
	                                $.each([ "minWidth", "maxWidth", "width", "minHeight", "maxHeight", "height" ], function(i, property) {
	                                    Velocity.CSS.setPropertyValue(dummy, property, measurement + "%");
	                                });
	                                /* paddingLeft arbitrarily acts as our proxy property for the em ratio. */
	                                Velocity.CSS.setPropertyValue(dummy, "paddingLeft", measurement + "em");

	                                /* Divide the returned value by the measurement to get the ratio between 1% and 1px. Default to 1 since working with 0 can produce Infinite. */
	                                unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth = (parseFloat(CSS.getPropertyValue(dummy, "width", null, true)) || 1) / measurement; /* GET */
	                                unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight = (parseFloat(CSS.getPropertyValue(dummy, "height", null, true)) || 1) / measurement; /* GET */
	                                unitRatios.emToPx = callUnitConversionData.lastEmToPx = (parseFloat(CSS.getPropertyValue(dummy, "paddingLeft")) || 1) / measurement; /* GET */

	                                sameRatioIndicators.myParent.removeChild(dummy);
	                            } else {
	                                unitRatios.emToPx = callUnitConversionData.lastEmToPx;
	                                unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth;
	                                unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight;
	                            }

	                            /***************************
	                               Element-Agnostic Units
	                            ***************************/

	                            /* Whereas % and em ratios are determined on a per-element basis, the rem unit only needs to be checked
	                               once per call since it's exclusively dependant upon document.body's fontSize. If this is the first time
	                               that calculateUnitRatios() is being run during this call, remToPx will still be set to its default value of null,
	                               so we calculate it now. */
	                            if (callUnitConversionData.remToPx === null) {
	                                /* Default to browsers' default fontSize of 16px in the case of 0. */
	                                callUnitConversionData.remToPx = parseFloat(CSS.getPropertyValue(document.body, "fontSize")) || 16; /* GET */
	                            }

	                            /* Similarly, viewport units are %-relative to the window's inner dimensions. */
	                            if (callUnitConversionData.vwToPx === null) {
	                                callUnitConversionData.vwToPx = parseFloat(window.innerWidth) / 100; /* GET */
	                                callUnitConversionData.vhToPx = parseFloat(window.innerHeight) / 100; /* GET */
	                            }

	                            unitRatios.remToPx = callUnitConversionData.remToPx;
	                            unitRatios.vwToPx = callUnitConversionData.vwToPx;
	                            unitRatios.vhToPx = callUnitConversionData.vhToPx;

	                            if (Velocity.debug >= 1) console.log("Unit ratios: " + JSON.stringify(unitRatios), element);

	                            return unitRatios;
	                        }

	                        /********************
	                           Unit Conversion
	                        ********************/

	                        /* The * and / operators, which are not passed in with an associated unit, inherently use startValue's unit. Skip value and unit conversion. */
	                        if (/[\/*]/.test(operator)) {
	                            endValueUnitType = startValueUnitType;
	                        /* If startValue and endValue differ in unit type, convert startValue into the same unit type as endValue so that if endValueUnitType
	                           is a relative unit (%, em, rem), the values set during tweening will continue to be accurately relative even if the metrics they depend
	                           on are dynamically changing during the course of the animation. Conversely, if we always normalized into px and used px for setting values, the px ratio
	                           would become stale if the original unit being animated toward was relative and the underlying metrics change during the animation. */
	                        /* Since 0 is 0 in any unit type, no conversion is necessary when startValue is 0 -- we just start at 0 with endValueUnitType. */
	                        } else if ((startValueUnitType !== endValueUnitType) && startValue !== 0) {
	                            /* Unit conversion is also skipped when endValue is 0, but *startValueUnitType* must be used for tween values to remain accurate. */
	                            /* Note: Skipping unit conversion here means that if endValueUnitType was originally a relative unit, the animation won't relatively
	                               match the underlying metrics if they change, but this is acceptable since we're animating toward invisibility instead of toward visibility,
	                               which remains past the point of the animation's completion. */
	                            if (endValue === 0) {
	                                endValueUnitType = startValueUnitType;
	                            } else {
	                                /* By this point, we cannot avoid unit conversion (it's undesirable since it causes layout thrashing).
	                                   If we haven't already, we trigger calculateUnitRatios(), which runs once per element per call. */
	                                elementUnitConversionData = elementUnitConversionData || calculateUnitRatios();

	                                /* The following RegEx matches CSS properties that have their % values measured relative to the x-axis. */
	                                /* Note: W3C spec mandates that all of margin and padding's properties (even top and bottom) are %-relative to the *width* of the parent element. */
	                                var axis = (/margin|padding|left|right|width|text|word|letter/i.test(property) || /X$/.test(property) || property === "x") ? "x" : "y";

	                                /* In order to avoid generating n^2 bespoke conversion functions, unit conversion is a two-step process:
	                                   1) Convert startValue into pixels. 2) Convert this new pixel value into endValue's unit type. */
	                                switch (startValueUnitType) {
	                                    case "%":
	                                        /* Note: translateX and translateY are the only properties that are %-relative to an element's own dimensions -- not its parent's dimensions.
	                                           Velocity does not include a special conversion process to account for this behavior. Therefore, animating translateX/Y from a % value
	                                           to a non-% value will produce an incorrect start value. Fortunately, this sort of cross-unit conversion is rarely done by users in practice. */
	                                        startValue *= (axis === "x" ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight);
	                                        break;

	                                    case "px":
	                                        /* px acts as our midpoint in the unit conversion process; do nothing. */
	                                        break;

	                                    default:
	                                        startValue *= elementUnitConversionData[startValueUnitType + "ToPx"];
	                                }

	                                /* Invert the px ratios to convert into to the target unit. */
	                                switch (endValueUnitType) {
	                                    case "%":
	                                        startValue *= 1 / (axis === "x" ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight);
	                                        break;

	                                    case "px":
	                                        /* startValue is already in px, do nothing; we're done. */
	                                        break;

	                                    default:
	                                        startValue *= 1 / elementUnitConversionData[endValueUnitType + "ToPx"];
	                                }
	                            }
	                        }

	                        /*********************
	                           Relative Values
	                        *********************/

	                        /* Operator logic must be performed last since it requires unit-normalized start and end values. */
	                        /* Note: Relative *percent values* do not behave how most people think; while one would expect "+=50%"
	                           to increase the property 1.5x its current value, it in fact increases the percent units in absolute terms:
	                           50 points is added on top of the current % value. */
	                        switch (operator) {
	                            case "+":
	                                endValue = startValue + endValue;
	                                break;

	                            case "-":
	                                endValue = startValue - endValue;
	                                break;

	                            case "*":
	                                endValue = startValue * endValue;
	                                break;

	                            case "/":
	                                endValue = startValue / endValue;
	                                break;
	                        }

	                        /**************************
	                           tweensContainer Push
	                        **************************/

	                        /* Construct the per-property tween object, and push it to the element's tweensContainer. */
	                        tweensContainer[property] = {
	                            rootPropertyValue: rootPropertyValue,
	                            startValue: startValue,
	                            currentValue: startValue,
	                            endValue: endValue,
	                            unitType: endValueUnitType,
	                            easing: easing
	                        };

	                        if (Velocity.debug) console.log("tweensContainer (" + property + "): " + JSON.stringify(tweensContainer[property]), element);
	                    }

	                    /* Along with its property data, store a reference to the element itself onto tweensContainer. */
	                    tweensContainer.element = element;
	                }

	                /*****************
	                    Call Push
	                *****************/

	                /* Note: tweensContainer can be empty if all of the properties in this call's property map were skipped due to not
	                   being supported by the browser. The element property is used for checking that the tweensContainer has been appended to. */
	                if (tweensContainer.element) {
	                    /* Apply the "velocity-animating" indicator class. */
	                    CSS.Values.addClass(element, "velocity-animating");

	                    /* The call array houses the tweensContainers for each element being animated in the current call. */
	                    call.push(tweensContainer);

	                    /* Store the tweensContainer and options if we're working on the default effects queue, so that they can be used by the reverse command. */
	                    if (opts.queue === "") {
	                        Data(element).tweensContainer = tweensContainer;
	                        Data(element).opts = opts;
	                    }

	                    /* Switch on the element's animating flag. */
	                    Data(element).isAnimating = true;

	                    /* Once the final element in this call's element set has been processed, push the call array onto
	                       Velocity.State.calls for the animation tick to immediately begin processing. */
	                    if (elementsIndex === elementsLength - 1) {
	                        /* Add the current call plus its associated metadata (the element set and the call's options) onto the global call container.
	                           Anything on this call container is subjected to tick() processing. */
	                        Velocity.State.calls.push([ call, elements, opts, null, promiseData.resolver ]);

	                        /* If the animation tick isn't running, start it. (Velocity shuts it off when there are no active calls to process.) */
	                        if (Velocity.State.isTicking === false) {
	                            Velocity.State.isTicking = true;

	                            /* Start the tick loop. */
	                            tick();
	                        }
	                    } else {
	                        elementsIndex++;
	                    }
	                }
	            }

	            /* When the queue option is set to false, the call skips the element's queue and fires immediately. */
	            if (opts.queue === false) {
	                /* Since this buildQueue call doesn't respect the element's existing queue (which is where a delay option would have been appended),
	                   we manually inject the delay property here with an explicit setTimeout. */
	                if (opts.delay) {
	                    setTimeout(buildQueue, opts.delay);
	                } else {
	                    buildQueue();
	                }
	            /* Otherwise, the call undergoes element queueing as normal. */
	            /* Note: To interoperate with jQuery, Velocity uses jQuery's own $.queue() stack for queuing logic. */
	            } else {
	                $.queue(element, opts.queue, function(next, clearQueue) {
	                    /* If the clearQueue flag was passed in by the stop command, resolve this call's promise. (Promises can only be resolved once,
	                       so it's fine if this is repeatedly triggered for each element in the associated call.) */
	                    if (clearQueue === true) {
	                        if (promiseData.promise) {
	                            promiseData.resolver(elements);
	                        }

	                        /* Do not continue with animation queueing. */
	                        return true;
	                    }

	                    /* This flag indicates to the upcoming completeCall() function that this queue entry was initiated by Velocity.
	                       See completeCall() for further details. */
	                    Velocity.velocityQueueEntryFlag = true;

	                    buildQueue(next);
	                });
	            }

	            /*********************
	                Auto-Dequeuing
	            *********************/

	            /* As per jQuery's $.queue() behavior, to fire the first non-custom-queue entry on an element, the element
	               must be dequeued if its queue stack consists *solely* of the current call. (This can be determined by checking
	               for the "inprogress" item that jQuery prepends to active queue stack arrays.) Regardless, whenever the element's
	               queue is further appended with additional items -- including $.delay()'s or even $.animate() calls, the queue's
	               first entry is automatically fired. This behavior contrasts that of custom queues, which never auto-fire. */
	            /* Note: When an element set is being subjected to a non-parallel Velocity call, the animation will not begin until
	               each one of the elements in the set has reached the end of its individually pre-existing queue chain. */
	            /* Note: Unfortunately, most people don't fully grasp jQuery's powerful, yet quirky, $.queue() function.
	               Lean more here: http://stackoverflow.com/questions/1058158/can-somebody-explain-jquery-queue-to-me */
	            if ((opts.queue === "" || opts.queue === "fx") && $.queue(element)[0] !== "inprogress") {
	                $.dequeue(element);
	            }
	        }

	        /**************************
	           Element Set Iteration
	        **************************/

	        /* If the "nodeType" property exists on the elements variable, we're animating a single element.
	           Place it in an array so that $.each() can iterate over it. */
	        $.each(elements, function(i, element) {
	            /* Ensure each element in a set has a nodeType (is a real element) to avoid throwing errors. */
	            if (Type.isNode(element)) {
	                processElement.call(element);
	            }
	        });

	        /******************
	           Option: Loop
	        ******************/

	        /* The loop option accepts an integer indicating how many times the element should loop between the values in the
	           current call's properties map and the element's property values prior to this call. */
	        /* Note: The loop option's logic is performed here -- after element processing -- because the current call needs
	           to undergo its queue insertion prior to the loop option generating its series of constituent "reverse" calls,
	           which chain after the current call. Two reverse calls (two "alternations") constitute one loop. */
	        var opts = $.extend({}, Velocity.defaults, options),
	            reverseCallsCount;

	        opts.loop = parseInt(opts.loop);
	        reverseCallsCount = (opts.loop * 2) - 1;

	        if (opts.loop) {
	            /* Double the loop count to convert it into its appropriate number of "reverse" calls.
	               Subtract 1 from the resulting value since the current call is included in the total alternation count. */
	            for (var x = 0; x < reverseCallsCount; x++) {
	                /* Since the logic for the reverse action occurs inside Queueing and therefore this call's options object
	                   isn't parsed until then as well, the current call's delay option must be explicitly passed into the reverse
	                   call so that the delay logic that occurs inside *Pre-Queueing* can process it. */
	                var reverseOptions = {
	                    delay: opts.delay,
	                    progress: opts.progress
	                };

	                /* If a complete callback was passed into this call, transfer it to the loop redirect's final "reverse" call
	                   so that it's triggered when the entire redirect is complete (and not when the very first animation is complete). */
	                if (x === reverseCallsCount - 1) {
	                    reverseOptions.display = opts.display;
	                    reverseOptions.visibility = opts.visibility;
	                    reverseOptions.complete = opts.complete;
	                }

	                animate(elements, "reverse", reverseOptions);
	            }
	        }

	        /***************
	            Chaining
	        ***************/

	        /* Return the elements back to the call chain, with wrapped elements taking precedence in case Velocity was called via the $.fn. extension. */
	        return getChain();
	    };

	    /* Turn Velocity into the animation function, extended with the pre-existing Velocity object. */
	    Velocity = $.extend(animate, Velocity);
	    /* For legacy support, also expose the literal animate method. */
	    Velocity.animate = animate;

	    /**************
	        Timing
	    **************/

	    /* Ticker function. */
	    var ticker = window.requestAnimationFrame || rAFShim;

	    /* Inactive browser tabs pause rAF, which results in all active animations immediately sprinting to their completion states when the tab refocuses.
	       To get around this, we dynamically switch rAF to setTimeout (which the browser *doesn't* pause) when the tab loses focus. We skip this for mobile
	       devices to avoid wasting battery power on inactive tabs. */
	    /* Note: Tab focus detection doesn't work on older versions of IE, but that's okay since they don't support rAF to begin with. */
	    if (!Velocity.State.isMobile && document.hidden !== undefined) {
	        document.addEventListener("visibilitychange", function() {
	            /* Reassign the rAF function (which the global tick() function uses) based on the tab's focus state. */
	            if (document.hidden) {
	                ticker = function(callback) {
	                    /* The tick function needs a truthy first argument in order to pass its internal timestamp check. */
	                    return setTimeout(function() { callback(true) }, 16);
	                };

	                /* The rAF loop has been paused by the browser, so we manually restart the tick. */
	                tick();
	            } else {
	                ticker = window.requestAnimationFrame || rAFShim;
	            }
	        });
	    }

	    /************
	        Tick
	    ************/

	    /* Note: All calls to Velocity are pushed to the Velocity.State.calls array, which is fully iterated through upon each tick. */
	    function tick (timestamp) {
	        /* An empty timestamp argument indicates that this is the first tick occurence since ticking was turned on.
	           We leverage this metadata to fully ignore the first tick pass since RAF's initial pass is fired whenever
	           the browser's next tick sync time occurs, which results in the first elements subjected to Velocity
	           calls being animated out of sync with any elements animated immediately thereafter. In short, we ignore
	           the first RAF tick pass so that elements being immediately consecutively animated -- instead of simultaneously animated
	           by the same Velocity call -- are properly batched into the same initial RAF tick and consequently remain in sync thereafter. */
	        if (timestamp) {
	            /* We ignore RAF's high resolution timestamp since it can be significantly offset when the browser is
	               under high stress; we opt for choppiness over allowing the browser to drop huge chunks of frames. */
	            var timeCurrent = (new Date).getTime();

	            /********************
	               Call Iteration
	            ********************/

	            var callsLength = Velocity.State.calls.length;

	            /* To speed up iterating over this array, it is compacted (falsey items -- calls that have completed -- are removed)
	               when its length has ballooned to a point that can impact tick performance. This only becomes necessary when animation
	               has been continuous with many elements over a long period of time; whenever all active calls are completed, completeCall() clears Velocity.State.calls. */
	            if (callsLength > 10000) {
	                Velocity.State.calls = compactSparseArray(Velocity.State.calls);
	            }

	            /* Iterate through each active call. */
	            for (var i = 0; i < callsLength; i++) {
	                /* When a Velocity call is completed, its Velocity.State.calls entry is set to false. Continue on to the next call. */
	                if (!Velocity.State.calls[i]) {
	                    continue;
	                }

	                /************************
	                   Call-Wide Variables
	                ************************/

	                var callContainer = Velocity.State.calls[i],
	                    call = callContainer[0],
	                    opts = callContainer[2],
	                    timeStart = callContainer[3],
	                    firstTick = !!timeStart,
	                    tweenDummyValue = null;

	                /* If timeStart is undefined, then this is the first time that this call has been processed by tick().
	                   We assign timeStart now so that its value is as close to the real animation start time as possible.
	                   (Conversely, had timeStart been defined when this call was added to Velocity.State.calls, the delay
	                   between that time and now would cause the first few frames of the tween to be skipped since
	                   percentComplete is calculated relative to timeStart.) */
	                /* Further, subtract 16ms (the approximate resolution of RAF) from the current time value so that the
	                   first tick iteration isn't wasted by animating at 0% tween completion, which would produce the
	                   same style value as the element's current value. */
	                if (!timeStart) {
	                    timeStart = Velocity.State.calls[i][3] = timeCurrent - 16;
	                }

	                /* The tween's completion percentage is relative to the tween's start time, not the tween's start value
	                   (which would result in unpredictable tween durations since JavaScript's timers are not particularly accurate).
	                   Accordingly, we ensure that percentComplete does not exceed 1. */
	                var percentComplete = Math.min((timeCurrent - timeStart) / opts.duration, 1);

	                /**********************
	                   Element Iteration
	                **********************/

	                /* For every call, iterate through each of the elements in its set. */
	                for (var j = 0, callLength = call.length; j < callLength; j++) {
	                    var tweensContainer = call[j],
	                        element = tweensContainer.element;

	                    /* Check to see if this element has been deleted midway through the animation by checking for the
	                       continued existence of its data cache. If it's gone, skip animating this element. */
	                    if (!Data(element)) {
	                        continue;
	                    }

	                    var transformPropertyExists = false;

	                    /**********************************
	                       Display & Visibility Toggling
	                    **********************************/

	                    /* If the display option is set to non-"none", set it upfront so that the element can become visible before tweening begins.
	                       (Otherwise, display's "none" value is set in completeCall() once the animation has completed.) */
	                    if (opts.display !== undefined && opts.display !== null && opts.display !== "none") {
	                        if (opts.display === "flex") {
	                            var flexValues = [ "-webkit-box", "-moz-box", "-ms-flexbox", "-webkit-flex" ];

	                            $.each(flexValues, function(i, flexValue) {
	                                CSS.setPropertyValue(element, "display", flexValue);
	                            });
	                        }

	                        CSS.setPropertyValue(element, "display", opts.display);
	                    }

	                    /* Same goes with the visibility option, but its "none" equivalent is "hidden". */
	                    if (opts.visibility !== undefined && opts.visibility !== "hidden") {
	                        CSS.setPropertyValue(element, "visibility", opts.visibility);
	                    }

	                    /************************
	                       Property Iteration
	                    ************************/

	                    /* For every element, iterate through each property. */
	                    for (var property in tweensContainer) {
	                        /* Note: In addition to property tween data, tweensContainer contains a reference to its associated element. */
	                        if (property !== "element") {
	                            var tween = tweensContainer[property],
	                                currentValue,
	                                /* Easing can either be a pre-genereated function or a string that references a pre-registered easing
	                                   on the Velocity.Easings object. In either case, return the appropriate easing *function*. */
	                                easing = Type.isString(tween.easing) ? Velocity.Easings[tween.easing] : tween.easing;

	                            /******************************
	                               Current Value Calculation
	                            ******************************/

	                            /* If this is the last tick pass (if we've reached 100% completion for this tween),
	                               ensure that currentValue is explicitly set to its target endValue so that it's not subjected to any rounding. */
	                            if (percentComplete === 1) {
	                                currentValue = tween.endValue;
	                            /* Otherwise, calculate currentValue based on the current delta from startValue. */
	                            } else {
	                                var tweenDelta = tween.endValue - tween.startValue;
	                                currentValue = tween.startValue + (tweenDelta * easing(percentComplete, opts, tweenDelta));

	                                /* If no value change is occurring, don't proceed with DOM updating. */
	                                if (!firstTick && (currentValue === tween.currentValue)) {
	                                    continue;
	                                }
	                            }

	                            tween.currentValue = currentValue;

	                            /* If we're tweening a fake 'tween' property in order to log transition values, update the one-per-call variable so that
	                               it can be passed into the progress callback. */
	                            if (property === "tween") {
	                                tweenDummyValue = currentValue;
	                            } else {
	                                /******************
	                                   Hooks: Part I
	                                ******************/

	                                /* For hooked properties, the newly-updated rootPropertyValueCache is cached onto the element so that it can be used
	                                   for subsequent hooks in this call that are associated with the same root property. If we didn't cache the updated
	                                   rootPropertyValue, each subsequent update to the root property in this tick pass would reset the previous hook's
	                                   updates to rootPropertyValue prior to injection. A nice performance byproduct of rootPropertyValue caching is that
	                                   subsequently chained animations using the same hookRoot but a different hook can use this cached rootPropertyValue. */
	                                if (CSS.Hooks.registered[property]) {
	                                    var hookRoot = CSS.Hooks.getRoot(property),
	                                        rootPropertyValueCache = Data(element).rootPropertyValueCache[hookRoot];

	                                    if (rootPropertyValueCache) {
	                                        tween.rootPropertyValue = rootPropertyValueCache;
	                                    }
	                                }

	                                /*****************
	                                    DOM Update
	                                *****************/

	                                /* setPropertyValue() returns an array of the property name and property value post any normalization that may have been performed. */
	                                /* Note: To solve an IE<=8 positioning bug, the unit type is dropped when setting a property value of 0. */
	                                var adjustedSetData = CSS.setPropertyValue(element, /* SET */
	                                                                           property,
	                                                                           tween.currentValue + (parseFloat(currentValue) === 0 ? "" : tween.unitType),
	                                                                           tween.rootPropertyValue,
	                                                                           tween.scrollData);

	                                /*******************
	                                   Hooks: Part II
	                                *******************/

	                                /* Now that we have the hook's updated rootPropertyValue (the post-processed value provided by adjustedSetData), cache it onto the element. */
	                                if (CSS.Hooks.registered[property]) {
	                                    /* Since adjustedSetData contains normalized data ready for DOM updating, the rootPropertyValue needs to be re-extracted from its normalized form. ?? */
	                                    if (CSS.Normalizations.registered[hookRoot]) {
	                                        Data(element).rootPropertyValueCache[hookRoot] = CSS.Normalizations.registered[hookRoot]("extract", null, adjustedSetData[1]);
	                                    } else {
	                                        Data(element).rootPropertyValueCache[hookRoot] = adjustedSetData[1];
	                                    }
	                                }

	                                /***************
	                                   Transforms
	                                ***************/

	                                /* Flag whether a transform property is being animated so that flushTransformCache() can be triggered once this tick pass is complete. */
	                                if (adjustedSetData[0] === "transform") {
	                                    transformPropertyExists = true;
	                                }

	                            }
	                        }
	                    }

	                    /****************
	                        mobileHA
	                    ****************/

	                    /* If mobileHA is enabled, set the translate3d transform to null to force hardware acceleration.
	                       It's safe to override this property since Velocity doesn't actually support its animation (hooks are used in its place). */
	                    if (opts.mobileHA) {
	                        /* Don't set the null transform hack if we've already done so. */
	                        if (Data(element).transformCache.translate3d === undefined) {
	                            /* All entries on the transformCache object are later concatenated into a single transform string via flushTransformCache(). */
	                            Data(element).transformCache.translate3d = "(0px, 0px, 0px)";

	                            transformPropertyExists = true;
	                        }
	                    }

	                    if (transformPropertyExists) {
	                        CSS.flushTransformCache(element);
	                    }
	                }

	                /* The non-"none" display value is only applied to an element once -- when its associated call is first ticked through.
	                   Accordingly, it's set to false so that it isn't re-processed by this call in the next tick. */
	                if (opts.display !== undefined && opts.display !== "none") {
	                    Velocity.State.calls[i][2].display = false;
	                }
	                if (opts.visibility !== undefined && opts.visibility !== "hidden") {
	                    Velocity.State.calls[i][2].visibility = false;
	                }

	                /* Pass the elements and the timing data (percentComplete, msRemaining, timeStart, tweenDummyValue) into the progress callback. */
	                if (opts.progress) {
	                    opts.progress.call(callContainer[1],
	                                       callContainer[1],
	                                       percentComplete,
	                                       Math.max(0, (timeStart + opts.duration) - timeCurrent),
	                                       timeStart,
	                                       tweenDummyValue);
	                }

	                /* If this call has finished tweening, pass its index to completeCall() to handle call cleanup. */
	                if (percentComplete === 1) {
	                    completeCall(i);
	                }
	            }
	        }

	        /* Note: completeCall() sets the isTicking flag to false when the last call on Velocity.State.calls has completed. */
	        if (Velocity.State.isTicking) {
	            ticker(tick);
	        }
	    }

	    /**********************
	        Call Completion
	    **********************/

	    /* Note: Unlike tick(), which processes all active calls at once, call completion is handled on a per-call basis. */
	    function completeCall (callIndex, isStopped) {
	        /* Ensure the call exists. */
	        if (!Velocity.State.calls[callIndex]) {
	            return false;
	        }

	        /* Pull the metadata from the call. */
	        var call = Velocity.State.calls[callIndex][0],
	            elements = Velocity.State.calls[callIndex][1],
	            opts = Velocity.State.calls[callIndex][2],
	            resolver = Velocity.State.calls[callIndex][4];

	        var remainingCallsExist = false;

	        /*************************
	           Element Finalization
	        *************************/

	        for (var i = 0, callLength = call.length; i < callLength; i++) {
	            var element = call[i].element;

	            /* If the user set display to "none" (intending to hide the element), set it now that the animation has completed. */
	            /* Note: display:none isn't set when calls are manually stopped (via Velocity("stop"). */
	            /* Note: Display gets ignored with "reverse" calls and infinite loops, since this behavior would be undesirable. */
	            if (!isStopped && !opts.loop) {
	                if (opts.display === "none") {
	                    CSS.setPropertyValue(element, "display", opts.display);
	                }

	                if (opts.visibility === "hidden") {
	                    CSS.setPropertyValue(element, "visibility", opts.visibility);
	                }
	            }

	            /* If the element's queue is empty (if only the "inprogress" item is left at position 0) or if its queue is about to run
	               a non-Velocity-initiated entry, turn off the isAnimating flag. A non-Velocity-initiatied queue entry's logic might alter
	               an element's CSS values and thereby cause Velocity's cached value data to go stale. To detect if a queue entry was initiated by Velocity,
	               we check for the existence of our special Velocity.queueEntryFlag declaration, which minifiers won't rename since the flag
	               is assigned to jQuery's global $ object and thus exists out of Velocity's own scope. */
	            if (opts.loop !== true && ($.queue(element)[1] === undefined || !/\.velocityQueueEntryFlag/i.test($.queue(element)[1]))) {
	                /* The element may have been deleted. Ensure that its data cache still exists before acting on it. */
	                if (Data(element)) {
	                    Data(element).isAnimating = false;
	                    /* Clear the element's rootPropertyValueCache, which will become stale. */
	                    Data(element).rootPropertyValueCache = {};

	                    var transformHAPropertyExists = false;
	                    /* If any 3D transform subproperty is at its default value (regardless of unit type), remove it. */
	                    $.each(CSS.Lists.transforms3D, function(i, transformName) {
	                        var defaultValue = /^scale/.test(transformName) ? 1 : 0,
	                            currentValue = Data(element).transformCache[transformName];

	                        if (Data(element).transformCache[transformName] !== undefined && new RegExp("^\\(" + defaultValue + "[^.]").test(currentValue)) {
	                            transformHAPropertyExists = true;

	                            delete Data(element).transformCache[transformName];
	                        }
	                    });

	                    /* Mobile devices have hardware acceleration removed at the end of the animation in order to avoid hogging the GPU's memory. */
	                    if (opts.mobileHA) {
	                        transformHAPropertyExists = true;
	                        delete Data(element).transformCache.translate3d;
	                    }

	                    /* Flush the subproperty removals to the DOM. */
	                    if (transformHAPropertyExists) {
	                        CSS.flushTransformCache(element);
	                    }

	                    /* Remove the "velocity-animating" indicator class. */
	                    CSS.Values.removeClass(element, "velocity-animating");
	                }
	            }

	            /*********************
	               Option: Complete
	            *********************/

	            /* Complete is fired once per call (not once per element) and is passed the full raw DOM element set as both its context and its first argument. */
	            /* Note: Callbacks aren't fired when calls are manually stopped (via Velocity("stop"). */
	            if (!isStopped && opts.complete && !opts.loop && (i === callLength - 1)) {
	                /* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Velocity itself. */
	                try {
	                    opts.complete.call(elements, elements);
	                } catch (error) {
	                    setTimeout(function() { throw error; }, 1);
	                }
	            }

	            /**********************
	               Promise Resolving
	            **********************/

	            /* Note: Infinite loops don't return promises. */
	            if (resolver && opts.loop !== true) {
	                resolver(elements);
	            }

	            /****************************
	               Option: Loop (Infinite)
	            ****************************/

	            if (Data(element) && opts.loop === true && !isStopped) {
	                /* If a rotateX/Y/Z property is being animated to 360 deg with loop:true, swap tween start/end values to enable
	                   continuous iterative rotation looping. (Otherise, the element would just rotate back and forth.) */
	                $.each(Data(element).tweensContainer, function(propertyName, tweenContainer) {
	                    if (/^rotate/.test(propertyName) && parseFloat(tweenContainer.endValue) === 360) {
	                        tweenContainer.endValue = 0;
	                        tweenContainer.startValue = 360;
	                    }

	                    if (/^backgroundPosition/.test(propertyName) && parseFloat(tweenContainer.endValue) === 100 && tweenContainer.unitType === "%") {
	                        tweenContainer.endValue = 0;
	                        tweenContainer.startValue = 100;
	                    }
	                });

	                Velocity(element, "reverse", { loop: true, delay: opts.delay });
	            }

	            /***************
	               Dequeueing
	            ***************/

	            /* Fire the next call in the queue so long as this call's queue wasn't set to false (to trigger a parallel animation),
	               which would have already caused the next call to fire. Note: Even if the end of the animation queue has been reached,
	               $.dequeue() must still be called in order to completely clear jQuery's animation queue. */
	            if (opts.queue !== false) {
	                $.dequeue(element, opts.queue);
	            }
	        }

	        /************************
	           Calls Array Cleanup
	        ************************/

	        /* Since this call is complete, set it to false so that the rAF tick skips it. This array is later compacted via compactSparseArray().
	          (For performance reasons, the call is set to false instead of being deleted from the array: http://www.html5rocks.com/en/tutorials/speed/v8/) */
	        Velocity.State.calls[callIndex] = false;

	        /* Iterate through the calls array to determine if this was the final in-progress animation.
	           If so, set a flag to end ticking and clear the calls array. */
	        for (var j = 0, callsLength = Velocity.State.calls.length; j < callsLength; j++) {
	            if (Velocity.State.calls[j] !== false) {
	                remainingCallsExist = true;

	                break;
	            }
	        }

	        if (remainingCallsExist === false) {
	            /* tick() will detect this flag upon its next iteration and subsequently turn itself off. */
	            Velocity.State.isTicking = false;

	            /* Clear the calls array so that its length is reset. */
	            delete Velocity.State.calls;
	            Velocity.State.calls = [];
	        }
	    }

	    /******************
	        Frameworks
	    ******************/

	    /* Both jQuery and Zepto allow their $.fn object to be extended to allow wrapped elements to be subjected to plugin calls.
	       If either framework is loaded, register a "velocity" extension pointing to Velocity's core animate() method.  Velocity
	       also registers itself onto a global container (window.jQuery || window.Zepto || window) so that certain features are
	       accessible beyond just a per-element scope. This master object contains an .animate() method, which is later assigned to $.fn
	       (if jQuery or Zepto are present). Accordingly, Velocity can both act on wrapped DOM elements and stand alone for targeting raw DOM elements. */
	    global.Velocity = Velocity;

	    if (global !== window) {
	        /* Assign the element function to Velocity's core animate() method. */
	        global.fn.velocity = animate;
	        /* Assign the object function's defaults to Velocity's global defaults object. */
	        global.fn.velocity.defaults = Velocity.defaults;
	    }

	    /***********************
	       Packaged Redirects
	    ***********************/

	    /* slideUp, slideDown */
	    $.each([ "Down", "Up" ], function(i, direction) {
	        Velocity.Redirects["slide" + direction] = function (element, options, elementsIndex, elementsSize, elements, promiseData) {
	            var opts = $.extend({}, options),
	                begin = opts.begin,
	                complete = opts.complete,
	                computedValues = { height: "", marginTop: "", marginBottom: "", paddingTop: "", paddingBottom: "" },
	                inlineValues = {};

	            if (opts.display === undefined) {
	                /* Show the element before slideDown begins and hide the element after slideUp completes. */
	                /* Note: Inline elements cannot have dimensions animated, so they're reverted to inline-block. */
	                opts.display = (direction === "Down" ? (Velocity.CSS.Values.getDisplayType(element) === "inline" ? "inline-block" : "block") : "none");
	            }

	            opts.begin = function() {
	                /* If the user passed in a begin callback, fire it now. */
	                begin && begin.call(elements, elements);

	                /* Cache the elements' original vertical dimensional property values so that we can animate back to them. */
	                for (var property in computedValues) {
	                    inlineValues[property] = element.style[property];

	                    /* For slideDown, use forcefeeding to animate all vertical properties from 0. For slideUp,
	                       use forcefeeding to start from computed values and animate down to 0. */
	                    var propertyValue = Velocity.CSS.getPropertyValue(element, property);
	                    computedValues[property] = (direction === "Down") ? [ propertyValue, 0 ] : [ 0, propertyValue ];
	                }

	                /* Force vertical overflow content to clip so that sliding works as expected. */
	                inlineValues.overflow = element.style.overflow;
	                element.style.overflow = "hidden";
	            }

	            opts.complete = function() {
	                /* Reset element to its pre-slide inline values once its slide animation is complete. */
	                for (var property in inlineValues) {
	                    element.style[property] = inlineValues[property];
	                }

	                /* If the user passed in a complete callback, fire it now. */
	                complete && complete.call(elements, elements);
	                promiseData && promiseData.resolver(elements);
	            };

	            Velocity(element, computedValues, opts);
	        };
	    });

	    /* fadeIn, fadeOut */
	    $.each([ "In", "Out" ], function(i, direction) {
	        Velocity.Redirects["fade" + direction] = function (element, options, elementsIndex, elementsSize, elements, promiseData) {
	            var opts = $.extend({}, options),
	                propertiesMap = { opacity: (direction === "In") ? 1 : 0 },
	                originalComplete = opts.complete;

	            /* Since redirects are triggered individually for each element in the animated set, avoid repeatedly triggering
	               callbacks by firing them only when the final element has been reached. */
	            if (elementsIndex !== elementsSize - 1) {
	                opts.complete = opts.begin = null;
	            } else {
	                opts.complete = function() {
	                    if (originalComplete) {
	                        originalComplete.call(elements, elements);
	                    }

	                    promiseData && promiseData.resolver(elements);
	                }
	            }

	            /* If a display was passed in, use it. Otherwise, default to "none" for fadeOut or the element-specific default for fadeIn. */
	            /* Note: We allow users to pass in "null" to skip display setting altogether. */
	            if (opts.display === undefined) {
	                opts.display = (direction === "In" ? "auto" : "none");
	            }

	            Velocity(this, propertiesMap, opts);
	        };
	    });

	    return Velocity;
	}((window.jQuery || window.Zepto || window), window, document);
	}));

	/******************
	   Known Issues
	******************/

	/* The CSS spec mandates that the translateX/Y/Z transforms are %-relative to the element itself -- not its parent.
	Velocity, however, doesn't make this distinction. Thus, converting to or from the % unit with these subproperties
	will produce an inaccurate conversion value. The same issue exists with the cx/cy attributes of SVG circles and ellipses. */


/***/ },

/***/ 582:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * This shim replaces the default Backbone.Marionette communication library
	 * Backbone.Wreqr with Backbone.Radio ahead of time,
	 * since this libraries will be switched in Marionette 3.x anyway
	 *
	 * Courtesy of https://gist.github.com/jmeas/7992474cdb1c5672d88b
	 */

	(function (root, factory) {
	  var Marionette = __webpack_require__(571);
	  var Radio = __webpack_require__(572);
	  var _ = __webpack_require__(280);
	  if (true) {
	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(571), __webpack_require__(572), __webpack_require__(280)], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, Radio, _) {
	      return factory(Marionette, Radio, _);
	    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	  }
	  else if (typeof exports !== 'undefined') {
	    module.exports = factory(Marionette, Radio, _);
	  }
	  else {
	    factory(root.Backbone.Marionette, root.Backbone.Radio, root._);
	  }
	}(this, function (Marionette, Radio, _) {
	  'use strict';

	  var MarionetteApplication = Marionette.Application;
	  MarionetteApplication.prototype._initChannel = function () {
	    this.channelName = _.result(this, 'channelName') || 'global';
	    this.channel = _.result(this, 'channel') || Radio.channel(this.channelName);
	  };
	}));


/***/ },

/***/ 583:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(544),
	  __webpack_require__(571),
	  __webpack_require__(572)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Backbone, Marionette, BackboneRadio) {
	  var Radio = BackboneRadio;

	  var AppView = Marionette.View.extend({
	    el: '#mailpoet_editor',
	    regions: {
	      stylesRegion: '#mailpoet_editor_styles',
	      contentRegion: '#mailpoet_editor_content',
	      sidebarRegion: '#mailpoet_editor_sidebar',
	      bottomRegion: '#mailpoet_editor_bottom',
	      headingRegion: '#mailpoet_editor_heading'
	    }
	  });

	  var EditorApplication = Marionette.Application.extend({
	    region: '#mailpoet_editor',

	    onStart: function () {
	      this._appView = new AppView();
	      this.showView(this._appView);
	    },

	    getChannel: function (channel) {
	      if (channel === undefined) {
	        return Radio.channel('global');
	      }
	      return Radio.channel(channel);
	    }
	  });

	  var app = new EditorApplication();
	  window.EditorApplication = app;

	  return app;

	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 584:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(573)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, SuperModel) {

	  var Module = {};

	  Module.ConfigModel = SuperModel.extend({
	    defaults: {
	      availableStyles: {},
	      socialIcons: {},
	      blockDefaults: {},
	      sidepanelWidth: '331px',
	      validation: {},
	      urls: {}
	    }
	  });

	  // Global and available styles for access in blocks and their settings
	  Module._config = {};
	  Module.getConfig = function () { return Module._config; };
	  Module.setConfig = function (options) {
	    Module._config = new Module.ConfigModel(options, { parse: true });
	    return Module._config;
	  };

	  App.on('before:start', function (App, options) {
	    var Application = App;
	    // Expose config methods globally
	    Application.getConfig = Module.getConfig;
	    Application.setConfig = Module.setConfig;

	    Application.setConfig(options.config);
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 585:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(571),
	  __webpack_require__(573),
	  __webpack_require__(280)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, Marionette, SuperModel, _) {

	  'use strict';

	  var Module = {};

	  Module.StylesModel = SuperModel.extend({
	    defaults: {
	      text: {
	        fontColor: '#000000',
	        fontFamily: 'Arial',
	        fontSize: '16px'
	      },
	      h1: {
	        fontColor: '#111111',
	        fontFamily: 'Arial',
	        fontSize: '40px'
	      },
	      h2: {
	        fontColor: '#222222',
	        fontFamily: 'Tahoma',
	        fontSize: '32px'
	      },
	      h3: {
	        fontColor: '#333333',
	        fontFamily: 'Verdana',
	        fontSize: '24px'
	      },
	      link: {
	        fontColor: '#21759B',
	        textDecoration: 'underline'
	      },
	      wrapper: {
	        backgroundColor: '#ffffff'
	      },
	      body: {
	        backgroundColor: '#cccccc'
	      }
	    },
	    initialize: function () {
	      this.on('change', function () { App.getChannel().trigger('autoSave'); });
	    }
	  });

	  Module.StylesView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.styles; },
	    modelEvents: {
	      change: 'render'
	    },
	    serializeData: function () {
	      return this.model.toJSON();
	    }
	  });

	  Module._globalStyles = new SuperModel();
	  Module.getGlobalStyles = function () {
	    return Module._globalStyles;
	  };
	  Module.setGlobalStyles = function (options) {
	    Module._globalStyles = new Module.StylesModel(options);
	    return Module._globalStyles;
	  };
	  Module.getAvailableStyles = function () {
	    return App.getConfig().get('availableStyles');
	  };

	  App.on('before:start', function (App, options) {
	    var Application = App;
	    var body;
	    var globalStyles;
	    // Expose style methods to global application
	    Application.getGlobalStyles = Module.getGlobalStyles;
	    Application.setGlobalStyles = Module.setGlobalStyles;
	    Application.getAvailableStyles = Module.getAvailableStyles;

	    body = options.newsletter.body;
	    globalStyles = (_.has(body, 'globalStyles')) ? body.globalStyles : {};
	    this.setGlobalStyles(globalStyles);
	  });

	  App.on('start', function (App) {
	    var stylesView = new Module.StylesView({ model: App.getGlobalStyles() });
	    App._appView.showChildView('stylesRegion', stylesView);
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 586:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(587),
	  __webpack_require__(276),
	  __webpack_require__(544),
	  __webpack_require__(571),
	  __webpack_require__(573),
	  __webpack_require__(280),
	  __webpack_require__(275)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (
	  App,
	  CommunicationComponent,
	  MailPoet,
	  Backbone,
	  Marionette,
	  SuperModel,
	  _,
	  jQuery
	  ) {

	  'use strict';

	  var Module = {};
	  var SidebarView;
	  // Widget handlers for use to create new content blocks via drag&drop
	  Module._contentWidgets = new (Backbone.Collection.extend({
	    model: SuperModel.extend({
	      defaults: {
	        name: '',
	        priority: 100,
	        widgetView: undefined
	      }
	    }),
	    comparator: 'priority'
	  }))();
	  Module.registerWidget = function (widget) { return Module._contentWidgets.add(widget); };
	  Module.getWidgets = function () { return Module._contentWidgets; };

	  // Layout widget handlers for use to create new layout blocks via drag&drop
	  Module._layoutWidgets = new (Backbone.Collection.extend({
	    model: SuperModel.extend({
	      defaults: {
	        name: '',
	        priority: 100,
	        widgetView: undefined
	      }
	    }),
	    comparator: 'priority'
	  }))();
	  Module.registerLayoutWidget = function (widget) { return Module._layoutWidgets.add(widget); };
	  Module.getLayoutWidgets = function () { return Module._layoutWidgets; };

	  SidebarView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.sidebar; },
	    regions: {
	      contentRegion: '.mailpoet_content_region',
	      layoutRegion: '.mailpoet_layout_region',
	      stylesRegion: '.mailpoet_styles_region',
	      previewRegion: '.mailpoet_preview_region'
	    },
	    events: {
	      'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function (event) {
	        var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)');
	        var $targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');

	        $openRegion.find('.mailpoet_region_content').velocity(
	          'slideUp',
	          {
	            duration: 250,
	            easing: 'easeOut',
	            complete: function () {
	              $openRegion.addClass('closed');
	            }.bind(this)
	          }
	        );

	        if ($openRegion.get(0) !== $targetRegion.get(0)) {
	          $targetRegion.find('.mailpoet_region_content').velocity(
	            'slideDown',
	            {
	              duration: 250,
	              easing: 'easeIn',
	              complete: function () {
	                $targetRegion.removeClass('closed');
	              }
	            }
	          );
	        }
	      }
	    },
	    initialize: function () {
	      jQuery(window)
	        .on('resize', this.updateHorizontalScroll.bind(this))
	        .on('scroll', this.updateHorizontalScroll.bind(this));
	    },
	    onRender: function () {
	      this.showChildView('contentRegion', new Module.SidebarWidgetsView(
	        App.getWidgets()
	      ));
	      this.showChildView('layoutRegion', new Module.SidebarLayoutWidgetsView(
	        App.getLayoutWidgets()
	      ));
	      this.showChildView('stylesRegion', new Module.SidebarStylesView({
	        model: App.getGlobalStyles(),
	        availableStyles: App.getAvailableStyles()
	      }));
	      this.showChildView('previewRegion', new Module.SidebarPreviewView());
	    },
	    updateHorizontalScroll: function () {
	      // Fixes the sidebar so that on narrower screens the horizontal
	      // position of the sidebar would be scrollable and not fixed
	      // partially out of visible screen
	      this.$el.parent().each(function () {
	        var calculated_left;
	        var self = jQuery(this);

	        if (self.css('position') === 'fixed') {
	          calculated_left = self.parent().offset().left - jQuery(window).scrollLeft();
	          self.css('left', calculated_left + 'px');
	        } else {
	          self.css('left', '');
	        }
	      });
	    },
	    onDomRefresh: function () {
	      this.$el.parent().stick_in_parent({
	        offset_top: 32
	      });
	      this.$el.parent().on('sticky_kit:stick', this.updateHorizontalScroll.bind(this));
	      this.$el.parent().on('sticky_kit:unstick', this.updateHorizontalScroll.bind(this));
	      this.$el.parent().on('sticky_kit:bottom', this.updateHorizontalScroll.bind(this));
	      this.$el.parent().on('sticky_kit:unbottom', this.updateHorizontalScroll.bind(this));
	    }
	  });

	  /**
	   * Draggable widget collection view
	   */
	  Module.SidebarWidgetsCollectionView = Marionette.CollectionView.extend({
	    childView: function (item) { return item.get('widgetView'); }
	  });

	  /**
	   * Responsible for rendering draggable content widgets
	   */
	  Module.SidebarWidgetsView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.sidebarContent; },
	    regions: {
	      widgets: '.mailpoet_region_content'
	    },

	    initialize: function (widgets) {
	      this.widgets = widgets;
	    },

	    onRender: function () {
	      this.showChildView('widgets', new Module.SidebarWidgetsCollectionView({
	        collection: this.widgets
	      }));
	    }
	  });

	  /**
	   * Responsible for rendering draggable layout widgets
	   */
	  Module.SidebarLayoutWidgetsView = Module.SidebarWidgetsView.extend({
	    getTemplate: function () { return window.templates.sidebarLayout; }
	  });

	  /**
	   * Responsible for managing global styles
	   */
	  Module.SidebarStylesView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.sidebarStyles; },
	    behaviors: {
	      ColorPickerBehavior: {}
	    },
	    events: function () {
	      return {
	        'change #mailpoet_text_font_color': _.partial(this.changeColorField, 'text.fontColor'),
	        'change #mailpoet_text_font_family': function (event) {
	          this.model.set('text.fontFamily', event.target.value);
	        },
	        'change #mailpoet_text_font_size': function (event) {
	          this.model.set('text.fontSize', event.target.value);
	        },
	        'change #mailpoet_h1_font_color': _.partial(this.changeColorField, 'h1.fontColor'),
	        'change #mailpoet_h1_font_family': function (event) {
	          this.model.set('h1.fontFamily', event.target.value);
	        },
	        'change #mailpoet_h1_font_size': function (event) {
	          this.model.set('h1.fontSize', event.target.value);
	        },
	        'change #mailpoet_h2_font_color': _.partial(this.changeColorField, 'h2.fontColor'),
	        'change #mailpoet_h2_font_family': function (event) {
	          this.model.set('h2.fontFamily', event.target.value);
	        },
	        'change #mailpoet_h2_font_size': function (event) {
	          this.model.set('h2.fontSize', event.target.value);
	        },
	        'change #mailpoet_h3_font_color': _.partial(this.changeColorField, 'h3.fontColor'),
	        'change #mailpoet_h3_font_family': function (event) {
	          this.model.set('h3.fontFamily', event.target.value);
	        },
	        'change #mailpoet_h3_font_size': function (event) {
	          this.model.set('h3.fontSize', event.target.value);
	        },
	        'change #mailpoet_a_font_color': _.partial(this.changeColorField, 'link.fontColor'),
	        'change #mailpoet_a_font_underline': function (event) {
	          this.model.set('link.textDecoration', (event.target.checked) ? event.target.value : 'none');
	        },
	        'change #mailpoet_newsletter_background_color': _.partial(this.changeColorField, 'wrapper.backgroundColor'),
	        'change #mailpoet_background_color': _.partial(this.changeColorField, 'body.backgroundColor')
	      };
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON(),
	        availableStyles: this.availableStyles.toJSON()
	      };
	    },
	    initialize: function (options) {
	      this.availableStyles = options.availableStyles;
	    },
	    changeField: function (field, event) {
	      this.model.set(field, jQuery(event.target).val());
	    },
	    changeColorField: function (field, event) {
	      var value = jQuery(event.target).val();
	      if (value === '') {
	        value = 'transparent';
	      }
	      this.model.set(field, value);
	    }
	  });

	  Module.SidebarPreviewView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.sidebarPreview; },
	    events: {
	      'click .mailpoet_show_preview': 'showPreview',
	      'click #mailpoet_send_preview': 'sendPreview'
	    },
	    onBeforeDestroy: function () {
	      if (this.previewView) {
	        this.previewView.destroy();
	        this.previewView = null;
	      }
	    },
	    showPreview: function () {
	      var json = App.toJSON();

	      // Stringify to enable transmission of primitive non-string value types
	      if (!_.isUndefined(json.body)) {
	        json.body = JSON.stringify(json.body);
	      }

	      MailPoet.Modal.loading(true);

	      MailPoet.Ajax.post({
	        api_version: window.mailpoet_api_version,
	        endpoint: 'newsletters',
	        action: 'showPreview',
	        data: json
	      }).always(function () {
	        MailPoet.Modal.loading(false);
	      }).done(function (response) {
	        this.previewView = new Module.NewsletterPreviewView({
	          previewUrl: response.meta.preview_url
	        });

	        this.previewView.render();
	        this.previewView.$el.css('height', '100%');

	        MailPoet.Modal.popup({
	          template: '',
	          element: this.previewView.$el,
	          width: '95%',
	          height: '94%',
	          title: MailPoet.I18n.t('newsletterPreview'),
	          onCancel: function () {
	            this.previewView.destroy();
	            this.previewView = null;
	          }.bind(this)
	        });

	        MailPoet.trackEvent('Editor > Browser Preview', {
	          'MailPoet Free version': window.mailpoet_version
	        });
	      }.bind(this)).fail(function (response) {
	        if (response.errors.length > 0) {
	          MailPoet.Notice.error(
	            response.errors.map(function (error) { return error.message; }),
	            { scroll: true }
	          );
	        }
	      });
	    },
	    sendPreview: function () {
	      // get form data
	      var $emailField = this.$('#mailpoet_preview_to_email');
	      var data = {
	        subscriber: $emailField.val(),
	        id: App.getNewsletter().get('id')
	      };

	      if (data.subscriber.length <= 0) {
	        MailPoet.Notice.error(
	          MailPoet.I18n.t('newsletterPreviewEmailMissing'),
	          {
	            positionAfter: $emailField,
	            scroll: true
	          }
	        );
	        return false;
	      }

	      // send test email
	      MailPoet.Modal.loading(true);

	      // save before sending
	      App.getChannel().request('save').always(function () {
	        CommunicationComponent.previewNewsletter(data).always(function () {
	          MailPoet.Modal.loading(false);
	        }).done(function () {
	          MailPoet.Notice.success(
	            MailPoet.I18n.t('newsletterPreviewSent'),
	            { scroll: true }
	          );
	          MailPoet.trackEvent('Editor > Preview sent', {
	            'MailPoet Free version': window.mailpoet_version,
	            'Domain name': data.subscriber.substring(data.subscriber.indexOf('@') + 1)
	          });
	        }).fail(function (response) {
	          if (response.errors.length > 0) {
	            MailPoet.Notice.error(
	              response.errors.map(function (error) { return error.message; }),
	              { scroll: true, static: true }
	            );
	          }
	        });
	      });
	    }
	  });

	  Module.NewsletterPreviewView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.newsletterPreview; },
	    initialize: function (options) {
	      this.previewUrl = options.previewUrl;
	      this.width = '100%';
	      this.height = '100%';
	      // this.width = App.getConfig().get('newsletterPreview.width');
	      // this.height = App.getConfig().get('newsletterPreview.height')
	    },
	    templateContext: function () {
	      return {
	        previewUrl: this.previewUrl,
	        width: this.width,
	        height: this.height
	      };
	    }
	  });

	  App.on('before:start', function (App) {
	    var Application = App;
	    Application.registerWidget = Module.registerWidget;
	    Application.getWidgets = Module.getWidgets;
	    Application.registerLayoutWidget = Module.registerLayoutWidget;
	    Application.getLayoutWidgets = Module.getLayoutWidgets;
	  });

	  App.on('start', function (App) {
	    var sidebarView = new SidebarView();

	    App._appView.showChildView('sidebarRegion', sidebarView);

	    MailPoet.helpTooltip.show(document.getElementById('tooltip-send-preview'), {
	      tooltipId: 'tooltip-editor-send-preview',
	      tooltip: MailPoet.I18n.t('helpTooltipSendPreview')
	    });

	    MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-styles'), {
	      tooltipId: 'tooltip-editor-designer-styles',
	      tooltip: MailPoet.I18n.t('helpTooltipDesignerStyles')
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 275:
/***/ function(module, exports) {

	module.exports = jQuery;

/***/ },

/***/ 588:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(573),
	  __webpack_require__(280),
	  __webpack_require__(276)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, SuperModel, _, MailPoet) {
	  'use strict';

	  var Module = {};

	  // Holds newsletter entry fields, such as subject or creation datetime.
	  // Does not hold newsletter content nor newsletter styles, those are
	  // handled by other components.
	  Module.NewsletterModel = SuperModel.extend({
	    whitelisted: ['id', 'subject', 'preheader'],
	    initialize: function () {
	      this.on('change', function () {
	        App.getChannel().trigger('autoSave');
	      });
	    },
	    toJSON: function () {
	      // Use only whitelisted properties to ensure properties editor
	      // doesn't control don't change.
	      return _.pick(SuperModel.prototype.toJSON.call(this), this.whitelisted);
	    }
	  });

	  // Content block view and model handlers for different content types
	  Module._blockTypes = {};
	  Module.registerBlockType = function (type, data) {
	    Module._blockTypes[type] = data;
	  };
	  Module.getBlockTypeModel = function (type) {
	    if (type in Module._blockTypes) {
	      return Module._blockTypes[type].blockModel;
	    } else {
	      throw 'Block type not supported: ' + type;
	    }
	  };
	  Module.getBlockTypeView = function (type) {
	    if (type in Module._blockTypes) {
	      return Module._blockTypes[type].blockView;
	    } else {
	      throw 'Block type not supported: ' + type;
	    }
	  };

	  Module.getBody = function () {
	    return {
	      content: App._contentContainer.toJSON(),
	      globalStyles: App.getGlobalStyles().toJSON()
	    };
	  };

	  Module.toJSON = function () {
	    return _.extend({
	      body: Module.getBody()
	    }, App.getNewsletter().toJSON());
	  };

	  Module.getNewsletter = function () {
	    return Module.newsletter;
	  };

	  Module.findModels = function (predicate) {
	    var blocks = App._contentContainer.getChildren();
	    return _.filter(blocks, predicate);
	  };

	  App.on('before:start', function (Application, options) {
	    var App = Application;
	    // Expose block methods globally
	    App.registerBlockType = Module.registerBlockType;
	    App.getBlockTypeModel = Module.getBlockTypeModel;
	    App.getBlockTypeView = Module.getBlockTypeView;
	    App.toJSON = Module.toJSON;
	    App.getBody = Module.getBody;
	    App.getNewsletter = Module.getNewsletter;
	    App.findModels = Module.findModels;

	    Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body']));
	  });

	  App.on('start', function (Application, options) {
	    var App = Application;
	    var body = options.newsletter.body;
	    var content = (_.has(body, 'content')) ? body.content : {};

	    if (!_.has(options.newsletter, 'body') || !_.isObject(options.newsletter.body)) {
	      MailPoet.Notice.error(
	        MailPoet.I18n.t('newsletterBodyIsCorrupted'),
	        { static: true }
	      );
	    }

	    App._contentContainer = new (App.getBlockTypeModel('container'))(content, { parse: true });
	    App._contentContainerView = new (App.getBlockTypeView('container'))({
	      model: App._contentContainer,
	      renderOptions: { depth: 0 }
	    });

	    App._appView.showChildView('contentRegion', App._contentContainerView);
	  });


	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 589:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(544),
	  __webpack_require__(571),
	  __webpack_require__(280),
	  __webpack_require__(275),
	  __webpack_require__(276)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, Backbone, Marionette, _, jQuery, MailPoet) {

	  'use strict';

	  var Module = {};

	  Module.HeadingView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.heading; },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON()
	      };
	    },
	    events: function () {
	      return {
	        'keyup .mailpoet_input_title': _.partial(this.changeField, 'subject'),
	        'keyup .mailpoet_input_preheader': _.partial(this.changeField, 'preheader')
	      };
	    },
	    changeField: function (field, event) {
	      this.model.set(field, jQuery(event.target).val());
	    }
	  });

	  App.on('start', function (App) {
	    App._appView.showChildView('headingRegion', new Module.HeadingView({ model: App.getNewsletter() }));
	    MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-subject-line'), {
	      tooltipId: 'tooltip-designer-subject-line-ti',
	      tooltip: MailPoet.I18n.t('helpTooltipDesignerSubjectLine'),
	      place: 'right'
	    });
	    MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-preheader'), {
	      tooltipId: 'tooltip-designer-preheader-ti',
	      tooltip: MailPoet.I18n.t('helpTooltipDesignerPreheader')
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 590:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(587),
	  __webpack_require__(276),
	  __webpack_require__(565),
	  __webpack_require__(544),
	  __webpack_require__(571),
	  __webpack_require__(275),
	  __webpack_require__(577),
	  __webpack_require__(578),
	  __webpack_require__(591),
	  __webpack_require__(280),
	  __webpack_require__(275)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (
	  App,
	  CommunicationComponent,
	  MailPoet,
	  Notice,
	  Backbone,
	  Marionette,
	  jQuery,
	  Blob,
	  FileSaver,
	  html2canvas,
	  _,
	  $
	) {

	  'use strict';

	  var Module = {};
	  var saveTimeout;

	  // Save editor contents to server
	  Module.save = function () {

	    var json = App.toJSON();

	    // Stringify to enable transmission of primitive non-string value types
	    if (!_.isUndefined(json.body)) {
	      json.body = JSON.stringify(json.body);
	    }

	    App.getChannel().trigger('beforeEditorSave', json);

	    // save newsletter
	    return CommunicationComponent.saveNewsletter(json).done(function (response) {
	      if (response.success !== undefined && response.success === true) {
	        // TODO: Handle translations
	        // MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
	      } else if (response.error !== undefined) {
	        if (response.error.length === 0) {
	          MailPoet.Notice.error(
	            MailPoet.I18n.t('templateSaveFailed'),
	            {
	              scroll: true
	            }
	          );
	        } else {
	          $(response.error).each(function (i, error) {
	            MailPoet.Notice.error(error, { scroll: true });
	          });
	        }
	      }
	      App.getChannel().trigger('afterEditorSave', json, response);
	    }).fail(function (response) {
	      // TODO: Handle saving errors
	      App.getChannel().trigger('afterEditorSave', {}, response);
	    });
	  };

	  Module.getThumbnail = function (element, options) {
	    var promise = html2canvas(element, options || {});

	    return promise.then(function (oldCanvas) {
	      // Temporary workaround for html2canvas-alpha2.
	      // Removes 1px left transparent border from resulting canvas.

	      var newCanvas = document.createElement('canvas');
	      var newContext = newCanvas.getContext('2d');
	      var leftBorderWidth = 1;

	      newCanvas.width = oldCanvas.width;
	      newCanvas.height = oldCanvas.height;

	      newContext.drawImage(
	        oldCanvas,
	        leftBorderWidth, 0, oldCanvas.width - leftBorderWidth, oldCanvas.height,
	        0, 0, oldCanvas.width, oldCanvas.height
	      );

	      return newCanvas;
	    });
	  };

	  Module.saveTemplate = function (options) {
	    var promise = jQuery.Deferred();

	    promise.then(function (thumbnail) {
	      var data = _.extend(options || {}, {
	        thumbnail: thumbnail.toDataURL('image/jpeg'),
	        body: JSON.stringify(App.getBody())
	      });

	      return MailPoet.Ajax.post({
	        api_version: window.mailpoet_api_version,
	        endpoint: 'newsletterTemplates',
	        action: 'save',
	        data: data
	      });
	    });

	    Module.getThumbnail(
	      jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
	    ).then(function (thumbnail) {
	      promise.resolve(thumbnail);
	    });

	    return promise;
	  };

	  Module.exportTemplate = function (options) {
	    return Module.getThumbnail(
	      jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
	    ).then(function (thumbnail) {
	      var data = _.extend(options || {}, {
	        thumbnail: thumbnail.toDataURL('image/jpeg'),
	        body: App.getBody()
	      });
	      var blob = new Blob(
	        [JSON.stringify(data)],
	        { type: 'application/json;charset=utf-8' }
	      );

	      FileSaver.saveAs(blob, 'template.json');
	      MailPoet.trackEvent('Editor > Template exported', {
	        'MailPoet Free version': window.mailpoet_version
	      });
	    });
	  };

	  Module.SaveView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.save; },
	    events: {
	      'click .mailpoet_save_button': 'save',
	      'click .mailpoet_save_show_options': 'toggleSaveOptions',
	      'click .mailpoet_save_next': 'next',
	      /* Save as template */
	      'click .mailpoet_save_template': 'toggleSaveAsTemplate',
	      'click .mailpoet_save_as_template': 'saveAsTemplate',
	      /* Export template */
	      'click .mailpoet_save_export': 'toggleExportTemplate',
	      'click .mailpoet_export_template': 'exportTemplate'
	    },
	    initialize: function () {
	      App.getChannel().on('beforeEditorSave', this.beforeSave, this);
	      App.getChannel().on('afterEditorSave', this.afterSave, this);
	    },
	    onRender: function () {
	      this.validateNewsletter(App.toJSON());
	    },
	    save: function () {
	      this.hideOptionContents();
	      App.getChannel().request('save');
	    },
	    beforeSave: function () {
	      // TODO: Add a loading animation instead
	      this.$('.mailpoet_autosaved_at').text(MailPoet.I18n.t('saving'));
	    },
	    afterSave: function (json) {
	      this.validateNewsletter(json);
	      // Update 'Last saved timer'
	      this.$('.mailpoet_editor_last_saved').removeClass('mailpoet_hidden');
	      this.$('.mailpoet_autosaved_at').text('');
	    },
	    toggleSaveOptions: function () {
	      this.$('.mailpoet_save_options').toggleClass('mailpoet_hidden');
	      this.$('.mailpoet_save_show_options').toggleClass('mailpoet_save_show_options_active');
	    },
	    toggleSaveAsTemplate: function () {
	      this.$('.mailpoet_save_as_template_container').toggleClass('mailpoet_hidden');
	      this.toggleSaveOptions();
	    },
	    showSaveAsTemplate: function () {
	      this.$('.mailpoet_save_as_template_container').removeClass('mailpoet_hidden');
	      this.toggleSaveOptions();
	    },
	    hideSaveAsTemplate: function () {
	      this.$('.mailpoet_save_as_template_container').addClass('mailpoet_hidden');
	    },
	    saveAsTemplate: function () {
	      var templateName = this.$('.mailpoet_save_as_template_name').val();
	      var templateDescription = this.$('.mailpoet_save_as_template_description').val();
	      var that = this;

	      if (templateName === '') {
	        MailPoet.Notice.error(
	          MailPoet.I18n.t('templateNameMissing'),
	          {
	            positionAfter: that.$el,
	            scroll: true
	          }
	        );
	      } else if (templateDescription === '') {
	        MailPoet.Notice.error(
	          MailPoet.I18n.t('templateDescriptionMissing'),
	          {
	            positionAfter: that.$el,
	            scroll: true
	          }
	        );
	      } else {
	        Module.saveTemplate({
	          name: templateName,
	          description: templateDescription
	        }).done(function () {
	          MailPoet.Notice.success(
	            MailPoet.I18n.t('templateSaved'),
	            {
	              positionAfter: that.$el,
	              scroll: true
	            }
	          );
	          MailPoet.trackEvent('Editor > Template saved', {
	            'MailPoet Free version': window.mailpoet_version
	          });
	        }).fail(function () {
	          MailPoet.Notice.error(
	            MailPoet.I18n.t('templateSaveFailed'),
	            {
	              positionAfter: that.$el,
	              scroll: true
	            }
	          );
	        });
	        this.hideOptionContents();
	      }

	    },
	    toggleExportTemplate: function () {
	      this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
	      this.toggleSaveOptions();
	    },
	    hideExportTemplate: function () {
	      this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden');
	    },
	    exportTemplate: function () {
	      var templateName = this.$('.mailpoet_export_template_name').val();
	      var templateDescription = this.$('.mailpoet_export_template_description').val();
	      var that = this;

	      if (templateName === '') {
	        MailPoet.Notice.error(
	          MailPoet.I18n.t('templateNameMissing'),
	          {
	            positionAfter: that.$el,
	            scroll: true
	          }
	        );
	      } else if (templateDescription === '') {
	        MailPoet.Notice.error(
	          MailPoet.I18n.t('templateDescriptionMissing'),
	          {
	            positionAfter: that.$el,
	            scroll: true
	          }
	        );
	      } else {
	        Module.exportTemplate({
	          name: templateName,
	          description: templateDescription
	        });
	        this.hideExportTemplate();
	      }
	    },
	    hideOptionContents: function () {
	      this.hideSaveAsTemplate();
	      this.hideExportTemplate();
	      this.$('.mailpoet_save_options').addClass('mailpoet_hidden');
	    },
	    next: function () {
	      this.hideOptionContents();
	      if (!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
	        Module._cancelAutosave();
	        Module.save().done(function () {
	          window.location.href = App.getConfig().get('urls.send');
	        });
	      }
	    },
	    validateNewsletter: function (jsonObject) {
	      var contents;
	      if (!App._contentContainer.isValid()) {
	        this.showValidationError(App._contentContainer.validationError);
	        return;
	      }

	      contents = JSON.stringify(jsonObject);
	      if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
	          contents.indexOf('[link:subscription_unsubscribe_url]') < 0 &&
	          contents.indexOf('[link:subscription_unsubscribe]') < 0) {
	        this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
	        return;
	      }

	      this.hideValidationError();
	    },
	    showValidationError: function (message) {
	      var $el = this.$('.mailpoet_save_error');
	      $el.text(message);
	      $el.removeClass('mailpoet_hidden');

	      this.$('.mailpoet_save_next').addClass('button-disabled');
	    },
	    hideValidationError: function () {
	      this.$('.mailpoet_save_error').addClass('mailpoet_hidden');
	      this.$('.mailpoet_save_next').removeClass('button-disabled');
	    }
	  });

	  Module.autoSave = function () {
	    // Delay in saving editor contents, during which a new autosave
	    // may be requested
	    var AUTOSAVE_DELAY_DURATION = 1000;

	    Module._cancelAutosave();
	    saveTimeout = setTimeout(function () {
	      App.getChannel().request('save').always(function () {
	        Module._cancelAutosave();
	      });
	    }, AUTOSAVE_DELAY_DURATION);
	  };

	  Module._cancelAutosave = function () {
	    if (!saveTimeout) return;

	    clearTimeout(saveTimeout);
	    saveTimeout = undefined;
	  };

	  Module.beforeExitWithUnsavedChanges = function (e) {
	    var message;
	    var event;
	    if (saveTimeout) {
	      message = MailPoet.I18n.t('unsavedChangesWillBeLost');
	      event = e || window.event;

	      if (event) {
	        event.returnValue = message;
	      }

	      return message;
	    }
	  };

	  App.on('before:start', function (App) {
	    var Application = App;
	    Application.save = Module.save;
	    Application.getChannel().on('autoSave', Module.autoSave);

	    window.onbeforeunload = Module.beforeExitWithUnsavedChanges;

	    Application.getChannel().reply('save', Application.save);
	  });

	  App.on('start', function (App) {
	    var saveView = new Module.SaveView();
	    App._appView.showChildView('bottomRegion', saveView);
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 591:
/***/ function(module, exports, __webpack_require__) {

	/* WEBPACK VAR INJECTION */(function(global) {module.exports = global["html2canvas"] = __webpack_require__(592);
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },

/***/ 592:
/***/ function(module, exports, __webpack_require__) {

	var require;var require;/* WEBPACK VAR INJECTION */(function(global) {/*
	  html2canvas 0.5.0-alpha2 <http://html2canvas.hertzen.com>
	  Copyright (c) 2015 Niklas von Hertzen

	  Released under MIT License
	*/

	!function(e){if(true)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.html2canvas=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return require(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
	(function (process,global){
	/*!
	 * @overview es6-promise - a tiny implementation of Promises/A+.
	 * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
	 * @license   Licensed under MIT license
	 *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
	 * @version   2.0.1
	 */

	(function() {
	    "use strict";

	    function $$utils$$objectOrFunction(x) {
	      return typeof x === 'function' || (typeof x === 'object' && x !== null);
	    }

	    function $$utils$$isFunction(x) {
	      return typeof x === 'function';
	    }

	    function $$utils$$isMaybeThenable(x) {
	      return typeof x === 'object' && x !== null;
	    }

	    var $$utils$$_isArray;

	    if (!Array.isArray) {
	      $$utils$$_isArray = function (x) {
	        return Object.prototype.toString.call(x) === '[object Array]';
	      };
	    } else {
	      $$utils$$_isArray = Array.isArray;
	    }

	    var $$utils$$isArray = $$utils$$_isArray;
	    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
	    function $$utils$$F() { }

	    var $$utils$$o_create = (Object.create || function (o) {
	      if (arguments.length > 1) {
	        throw new Error('Second argument not supported');
	      }
	      if (typeof o !== 'object') {
	        throw new TypeError('Argument must be an object');
	      }
	      $$utils$$F.prototype = o;
	      return new $$utils$$F();
	    });

	    var $$asap$$len = 0;

	    var $$asap$$default = function asap(callback, arg) {
	      $$asap$$queue[$$asap$$len] = callback;
	      $$asap$$queue[$$asap$$len + 1] = arg;
	      $$asap$$len += 2;
	      if ($$asap$$len === 2) {
	        // If len is 1, that means that we need to schedule an async flush.
	        // If additional callbacks are queued before the queue is flushed, they
	        // will be processed by this flush that we are scheduling.
	        $$asap$$scheduleFlush();
	      }
	    };

	    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
	    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;

	    // test for web worker but not in IE10
	    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
	      typeof importScripts !== 'undefined' &&
	      typeof MessageChannel !== 'undefined';

	    // node
	    function $$asap$$useNextTick() {
	      return function() {
	        process.nextTick($$asap$$flush);
	      };
	    }

	    function $$asap$$useMutationObserver() {
	      var iterations = 0;
	      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
	      var node = document.createTextNode('');
	      observer.observe(node, { characterData: true });

	      return function() {
	        node.data = (iterations = ++iterations % 2);
	      };
	    }

	    // web worker
	    function $$asap$$useMessageChannel() {
	      var channel = new MessageChannel();
	      channel.port1.onmessage = $$asap$$flush;
	      return function () {
	        channel.port2.postMessage(0);
	      };
	    }

	    function $$asap$$useSetTimeout() {
	      return function() {
	        setTimeout($$asap$$flush, 1);
	      };
	    }

	    var $$asap$$queue = new Array(1000);

	    function $$asap$$flush() {
	      for (var i = 0; i < $$asap$$len; i+=2) {
	        var callback = $$asap$$queue[i];
	        var arg = $$asap$$queue[i+1];

	        callback(arg);

	        $$asap$$queue[i] = undefined;
	        $$asap$$queue[i+1] = undefined;
	      }

	      $$asap$$len = 0;
	    }

	    var $$asap$$scheduleFlush;

	    // Decide what async method to use to triggering processing of queued callbacks:
	    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
	      $$asap$$scheduleFlush = $$asap$$useNextTick();
	    } else if ($$asap$$BrowserMutationObserver) {
	      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
	    } else if ($$asap$$isWorker) {
	      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
	    } else {
	      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
	    }

	    function $$$internal$$noop() {}
	    var $$$internal$$PENDING   = void 0;
	    var $$$internal$$FULFILLED = 1;
	    var $$$internal$$REJECTED  = 2;
	    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();

	    function $$$internal$$selfFullfillment() {
	      return new TypeError("You cannot resolve a promise with itself");
	    }

	    function $$$internal$$cannotReturnOwn() {
	      return new TypeError('A promises callback cannot return that same promise.')
	    }

	    function $$$internal$$getThen(promise) {
	      try {
	        return promise.then;
	      } catch(error) {
	        $$$internal$$GET_THEN_ERROR.error = error;
	        return $$$internal$$GET_THEN_ERROR;
	      }
	    }

	    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
	      try {
	        then.call(value, fulfillmentHandler, rejectionHandler);
	      } catch(e) {
	        return e;
	      }
	    }

	    function $$$internal$$handleForeignThenable(promise, thenable, then) {
	       $$asap$$default(function(promise) {
	        var sealed = false;
	        var error = $$$internal$$tryThen(then, thenable, function(value) {
	          if (sealed) { return; }
	          sealed = true;
	          if (thenable !== value) {
	            $$$internal$$resolve(promise, value);
	          } else {
	            $$$internal$$fulfill(promise, value);
	          }
	        }, function(reason) {
	          if (sealed) { return; }
	          sealed = true;

	          $$$internal$$reject(promise, reason);
	        }, 'Settle: ' + (promise._label || ' unknown promise'));

	        if (!sealed && error) {
	          sealed = true;
	          $$$internal$$reject(promise, error);
	        }
	      }, promise);
	    }

	    function $$$internal$$handleOwnThenable(promise, thenable) {
	      if (thenable._state === $$$internal$$FULFILLED) {
	        $$$internal$$fulfill(promise, thenable._result);
	      } else if (promise._state === $$$internal$$REJECTED) {
	        $$$internal$$reject(promise, thenable._result);
	      } else {
	        $$$internal$$subscribe(thenable, undefined, function(value) {
	          $$$internal$$resolve(promise, value);
	        }, function(reason) {
	          $$$internal$$reject(promise, reason);
	        });
	      }
	    }

	    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
	      if (maybeThenable.constructor === promise.constructor) {
	        $$$internal$$handleOwnThenable(promise, maybeThenable);
	      } else {
	        var then = $$$internal$$getThen(maybeThenable);

	        if (then === $$$internal$$GET_THEN_ERROR) {
	          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
	        } else if (then === undefined) {
	          $$$internal$$fulfill(promise, maybeThenable);
	        } else if ($$utils$$isFunction(then)) {
	          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
	        } else {
	          $$$internal$$fulfill(promise, maybeThenable);
	        }
	      }
	    }

	    function $$$internal$$resolve(promise, value) {
	      if (promise === value) {
	        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
	      } else if ($$utils$$objectOrFunction(value)) {
	        $$$internal$$handleMaybeThenable(promise, value);
	      } else {
	        $$$internal$$fulfill(promise, value);
	      }
	    }

	    function $$$internal$$publishRejection(promise) {
	      if (promise._onerror) {
	        promise._onerror(promise._result);
	      }

	      $$$internal$$publish(promise);
	    }

	    function $$$internal$$fulfill(promise, value) {
	      if (promise._state !== $$$internal$$PENDING) { return; }

	      promise._result = value;
	      promise._state = $$$internal$$FULFILLED;

	      if (promise._subscribers.length === 0) {
	      } else {
	        $$asap$$default($$$internal$$publish, promise);
	      }
	    }

	    function $$$internal$$reject(promise, reason) {
	      if (promise._state !== $$$internal$$PENDING) { return; }
	      promise._state = $$$internal$$REJECTED;
	      promise._result = reason;

	      $$asap$$default($$$internal$$publishRejection, promise);
	    }

	    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
	      var subscribers = parent._subscribers;
	      var length = subscribers.length;

	      parent._onerror = null;

	      subscribers[length] = child;
	      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
	      subscribers[length + $$$internal$$REJECTED]  = onRejection;

	      if (length === 0 && parent._state) {
	        $$asap$$default($$$internal$$publish, parent);
	      }
	    }

	    function $$$internal$$publish(promise) {
	      var subscribers = promise._subscribers;
	      var settled = promise._state;

	      if (subscribers.length === 0) { return; }

	      var child, callback, detail = promise._result;

	      for (var i = 0; i < subscribers.length; i += 3) {
	        child = subscribers[i];
	        callback = subscribers[i + settled];

	        if (child) {
	          $$$internal$$invokeCallback(settled, child, callback, detail);
	        } else {
	          callback(detail);
	        }
	      }

	      promise._subscribers.length = 0;
	    }

	    function $$$internal$$ErrorObject() {
	      this.error = null;
	    }

	    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();

	    function $$$internal$$tryCatch(callback, detail) {
	      try {
	        return callback(detail);
	      } catch(e) {
	        $$$internal$$TRY_CATCH_ERROR.error = e;
	        return $$$internal$$TRY_CATCH_ERROR;
	      }
	    }

	    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
	      var hasCallback = $$utils$$isFunction(callback),
	          value, error, succeeded, failed;

	      if (hasCallback) {
	        value = $$$internal$$tryCatch(callback, detail);

	        if (value === $$$internal$$TRY_CATCH_ERROR) {
	          failed = true;
	          error = value.error;
	          value = null;
	        } else {
	          succeeded = true;
	        }

	        if (promise === value) {
	          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
	          return;
	        }

	      } else {
	        value = detail;
	        succeeded = true;
	      }

	      if (promise._state !== $$$internal$$PENDING) {
	        // noop
	      } else if (hasCallback && succeeded) {
	        $$$internal$$resolve(promise, value);
	      } else if (failed) {
	        $$$internal$$reject(promise, error);
	      } else if (settled === $$$internal$$FULFILLED) {
	        $$$internal$$fulfill(promise, value);
	      } else if (settled === $$$internal$$REJECTED) {
	        $$$internal$$reject(promise, value);
	      }
	    }

	    function $$$internal$$initializePromise(promise, resolver) {
	      try {
	        resolver(function resolvePromise(value){
	          $$$internal$$resolve(promise, value);
	        }, function rejectPromise(reason) {
	          $$$internal$$reject(promise, reason);
	        });
	      } catch(e) {
	        $$$internal$$reject(promise, e);
	      }
	    }

	    function $$$enumerator$$makeSettledResult(state, position, value) {
	      if (state === $$$internal$$FULFILLED) {
	        return {
	          state: 'fulfilled',
	          value: value
	        };
	      } else {
	        return {
	          state: 'rejected',
	          reason: value
	        };
	      }
	    }

	    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
	      this._instanceConstructor = Constructor;
	      this.promise = new Constructor($$$internal$$noop, label);
	      this._abortOnReject = abortOnReject;

	      if (this._validateInput(input)) {
	        this._input     = input;
	        this.length     = input.length;
	        this._remaining = input.length;

	        this._init();

	        if (this.length === 0) {
	          $$$internal$$fulfill(this.promise, this._result);
	        } else {
	          this.length = this.length || 0;
	          this._enumerate();
	          if (this._remaining === 0) {
	            $$$internal$$fulfill(this.promise, this._result);
	          }
	        }
	      } else {
	        $$$internal$$reject(this.promise, this._validationError());
	      }
	    }

	    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
	      return $$utils$$isArray(input);
	    };

	    $$$enumerator$$Enumerator.prototype._validationError = function() {
	      return new Error('Array Methods must be provided an Array');
	    };

	    $$$enumerator$$Enumerator.prototype._init = function() {
	      this._result = new Array(this.length);
	    };

	    var $$$enumerator$$default = $$$enumerator$$Enumerator;

	    $$$enumerator$$Enumerator.prototype._enumerate = function() {
	      var length  = this.length;
	      var promise = this.promise;
	      var input   = this._input;

	      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
	        this._eachEntry(input[i], i);
	      }
	    };

	    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
	      var c = this._instanceConstructor;
	      if ($$utils$$isMaybeThenable(entry)) {
	        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
	          entry._onerror = null;
	          this._settledAt(entry._state, i, entry._result);
	        } else {
	          this._willSettleAt(c.resolve(entry), i);
	        }
	      } else {
	        this._remaining--;
	        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
	      }
	    };

	    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
	      var promise = this.promise;

	      if (promise._state === $$$internal$$PENDING) {
	        this._remaining--;

	        if (this._abortOnReject && state === $$$internal$$REJECTED) {
	          $$$internal$$reject(promise, value);
	        } else {
	          this._result[i] = this._makeResult(state, i, value);
	        }
	      }

	      if (this._remaining === 0) {
	        $$$internal$$fulfill(promise, this._result);
	      }
	    };

	    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
	      return value;
	    };

	    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
	      var enumerator = this;

	      $$$internal$$subscribe(promise, undefined, function(value) {
	        enumerator._settledAt($$$internal$$FULFILLED, i, value);
	      }, function(reason) {
	        enumerator._settledAt($$$internal$$REJECTED, i, reason);
	      });
	    };

	    var $$promise$all$$default = function all(entries, label) {
	      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
	    };

	    var $$promise$race$$default = function race(entries, label) {
	      /*jshint validthis:true */
	      var Constructor = this;

	      var promise = new Constructor($$$internal$$noop, label);

	      if (!$$utils$$isArray(entries)) {
	        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
	        return promise;
	      }

	      var length = entries.length;

	      function onFulfillment(value) {
	        $$$internal$$resolve(promise, value);
	      }

	      function onRejection(reason) {
	        $$$internal$$reject(promise, reason);
	      }

	      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
	        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
	      }

	      return promise;
	    };

	    var $$promise$resolve$$default = function resolve(object, label) {
	      /*jshint validthis:true */
	      var Constructor = this;

	      if (object && typeof object === 'object' && object.constructor === Constructor) {
	        return object;
	      }

	      var promise = new Constructor($$$internal$$noop, label);
	      $$$internal$$resolve(promise, object);
	      return promise;
	    };

	    var $$promise$reject$$default = function reject(reason, label) {
	      /*jshint validthis:true */
	      var Constructor = this;
	      var promise = new Constructor($$$internal$$noop, label);
	      $$$internal$$reject(promise, reason);
	      return promise;
	    };

	    var $$es6$promise$promise$$counter = 0;

	    function $$es6$promise$promise$$needsResolver() {
	      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
	    }

	    function $$es6$promise$promise$$needsNew() {
	      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
	    }

	    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;

	    /**
	      Promise objects represent the eventual result of an asynchronous operation. The
	      primary way of interacting with a promise is through its `then` method, which
	      registers callbacks to receive either a promise’s eventual value or the reason
	      why the promise cannot be fulfilled.

	      Terminology
	      -----------

	      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
	      - `thenable` is an object or function that defines a `then` method.
	      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
	      - `exception` is a value that is thrown using the throw statement.
	      - `reason` is a value that indicates why a promise was rejected.
	      - `settled` the final resting state of a promise, fulfilled or rejected.

	      A promise can be in one of three states: pending, fulfilled, or rejected.

	      Promises that are fulfilled have a fulfillment value and are in the fulfilled
	      state.  Promises that are rejected have a rejection reason and are in the
	      rejected state.  A fulfillment value is never a thenable.

	      Promises can also be said to *resolve* a value.  If this value is also a
	      promise, then the original promise's settled state will match the value's
	      settled state.  So a promise that *resolves* a promise that rejects will
	      itself reject, and a promise that *resolves* a promise that fulfills will
	      itself fulfill.


	      Basic Usage:
	      ------------

	      ```js
	      var promise = new Promise(function(resolve, reject) {
	        // on success
	        resolve(value);

	        // on failure
	        reject(reason);
	      });

	      promise.then(function(value) {
	        // on fulfillment
	      }, function(reason) {
	        // on rejection
	      });
	      ```

	      Advanced Usage:
	      ---------------

	      Promises shine when abstracting away asynchronous interactions such as
	      `XMLHttpRequest`s.

	      ```js
	      function getJSON(url) {
	        return new Promise(function(resolve, reject){
	          var xhr = new XMLHttpRequest();

	          xhr.open('GET', url);
	          xhr.onreadystatechange = handler;
	          xhr.responseType = 'json';
	          xhr.setRequestHeader('Accept', 'application/json');
	          xhr.send();

	          function handler() {
	            if (this.readyState === this.DONE) {
	              if (this.status === 200) {
	                resolve(this.response);
	              } else {
	                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
	              }
	            }
	          };
	        });
	      }

	      getJSON('/posts.json').then(function(json) {
	        // on fulfillment
	      }, function(reason) {
	        // on rejection
	      });
	      ```

	      Unlike callbacks, promises are great composable primitives.

	      ```js
	      Promise.all([
	        getJSON('/posts'),
	        getJSON('/comments')
	      ]).then(function(values){
	        values[0] // => postsJSON
	        values[1] // => commentsJSON

	        return values;
	      });
	      ```

	      @class Promise
	      @param {function} resolver
	      Useful for tooling.
	      @constructor
	    */
	    function $$es6$promise$promise$$Promise(resolver) {
	      this._id = $$es6$promise$promise$$counter++;
	      this._state = undefined;
	      this._result = undefined;
	      this._subscribers = [];

	      if ($$$internal$$noop !== resolver) {
	        if (!$$utils$$isFunction(resolver)) {
	          $$es6$promise$promise$$needsResolver();
	        }

	        if (!(this instanceof $$es6$promise$promise$$Promise)) {
	          $$es6$promise$promise$$needsNew();
	        }

	        $$$internal$$initializePromise(this, resolver);
	      }
	    }

	    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
	    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
	    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
	    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;

	    $$es6$promise$promise$$Promise.prototype = {
	      constructor: $$es6$promise$promise$$Promise,

	    /**
	      The primary way of interacting with a promise is through its `then` method,
	      which registers callbacks to receive either a promise's eventual value or the
	      reason why the promise cannot be fulfilled.

	      ```js
	      findUser().then(function(user){
	        // user is available
	      }, function(reason){
	        // user is unavailable, and you are given the reason why
	      });
	      ```

	      Chaining
	      --------

	      The return value of `then` is itself a promise.  This second, 'downstream'
	      promise is resolved with the return value of the first promise's fulfillment
	      or rejection handler, or rejected if the handler throws an exception.

	      ```js
	      findUser().then(function (user) {
	        return user.name;
	      }, function (reason) {
	        return 'default name';
	      }).then(function (userName) {
	        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
	        // will be `'default name'`
	      });

	      findUser().then(function (user) {
	        throw new Error('Found user, but still unhappy');
	      }, function (reason) {
	        throw new Error('`findUser` rejected and we're unhappy');
	      }).then(function (value) {
	        // never reached
	      }, function (reason) {
	        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
	        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
	      });
	      ```
	      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.

	      ```js
	      findUser().then(function (user) {
	        throw new PedagogicalException('Upstream error');
	      }).then(function (value) {
	        // never reached
	      }).then(function (value) {
	        // never reached
	      }, function (reason) {
	        // The `PedgagocialException` is propagated all the way down to here
	      });
	      ```

	      Assimilation
	      ------------

	      Sometimes the value you want to propagate to a downstream promise can only be
	      retrieved asynchronously. This can be achieved by returning a promise in the
	      fulfillment or rejection handler. The downstream promise will then be pending
	      until the returned promise is settled. This is called *assimilation*.

	      ```js
	      findUser().then(function (user) {
	        return findCommentsByAuthor(user);
	      }).then(function (comments) {
	        // The user's comments are now available
	      });
	      ```

	      If the assimliated promise rejects, then the downstream promise will also reject.

	      ```js
	      findUser().then(function (user) {
	        return findCommentsByAuthor(user);
	      }).then(function (comments) {
	        // If `findCommentsByAuthor` fulfills, we'll have the value here
	      }, function (reason) {
	        // If `findCommentsByAuthor` rejects, we'll have the reason here
	      });
	      ```

	      Simple Example
	      --------------

	      Synchronous Example

	      ```javascript
	      var result;

	      try {
	        result = findResult();
	        // success
	      } catch(reason) {
	        // failure
	      }
	      ```

	      Errback Example

	      ```js
	      findResult(function(result, err){
	        if (err) {
	          // failure
	        } else {
	          // success
	        }
	      });
	      ```

	      Promise Example;

	      ```javascript
	      findResult().then(function(result){
	        // success
	      }, function(reason){
	        // failure
	      });
	      ```

	      Advanced Example
	      --------------

	      Synchronous Example

	      ```javascript
	      var author, books;

	      try {
	        author = findAuthor();
	        books  = findBooksByAuthor(author);
	        // success
	      } catch(reason) {
	        // failure
	      }
	      ```

	      Errback Example

	      ```js

	      function foundBooks(books) {

	      }

	      function failure(reason) {

	      }

	      findAuthor(function(author, err){
	        if (err) {
	          failure(err);
	          // failure
	        } else {
	          try {
	            findBoooksByAuthor(author, function(books, err) {
	              if (err) {
	                failure(err);
	              } else {
	                try {
	                  foundBooks(books);
	                } catch(reason) {
	                  failure(reason);
	                }
	              }
	            });
	          } catch(error) {
	            failure(err);
	          }
	          // success
	        }
	      });
	      ```

	      Promise Example;

	      ```javascript
	      findAuthor().
	        then(findBooksByAuthor).
	        then(function(books){
	          // found books
	      }).catch(function(reason){
	        // something went wrong
	      });
	      ```

	      @method then
	      @param {Function} onFulfilled
	      @param {Function} onRejected
	      Useful for tooling.
	      @return {Promise}
	    */
	      then: function(onFulfillment, onRejection) {
	        var parent = this;
	        var state = parent._state;

	        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
	          return this;
	        }

	        var child = new this.constructor($$$internal$$noop);
	        var result = parent._result;

	        if (state) {
	          var callback = arguments[state - 1];
	          $$asap$$default(function(){
	            $$$internal$$invokeCallback(state, child, callback, result);
	          });
	        } else {
	          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
	        }

	        return child;
	      },

	    /**
	      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
	      as the catch block of a try/catch statement.

	      ```js
	      function findAuthor(){
	        throw new Error('couldn't find that author');
	      }

	      // synchronous
	      try {
	        findAuthor();
	      } catch(reason) {
	        // something went wrong
	      }

	      // async with promises
	      findAuthor().catch(function(reason){
	        // something went wrong
	      });
	      ```

	      @method catch
	      @param {Function} onRejection
	      Useful for tooling.
	      @return {Promise}
	    */
	      'catch': function(onRejection) {
	        return this.then(null, onRejection);
	      }
	    };

	    var $$es6$promise$polyfill$$default = function polyfill() {
	      var local;

	      if (typeof global !== 'undefined') {
	        local = global;
	      } else if (typeof window !== 'undefined' && window.document) {
	        local = window;
	      } else {
	        local = self;
	      }

	      var es6PromiseSupport =
	        "Promise" in local &&
	        // Some of these methods are missing from
	        // Firefox/Chrome experimental implementations
	        "resolve" in local.Promise &&
	        "reject" in local.Promise &&
	        "all" in local.Promise &&
	        "race" in local.Promise &&
	        // Older version of the spec had a resolver object
	        // as the arg rather than a function
	        (function() {
	          var resolve;
	          new local.Promise(function(r) { resolve = r; });
	          return $$utils$$isFunction(resolve);
	        }());

	      if (!es6PromiseSupport) {
	        local.Promise = $$es6$promise$promise$$default;
	      }
	    };

	    var es6$promise$umd$$ES6Promise = {
	      'Promise': $$es6$promise$promise$$default,
	      'polyfill': $$es6$promise$polyfill$$default
	    };

	    /* global define:true module:true window: true */
	    if (typeof define === 'function' && define['amd']) {
	      define(function() { return es6$promise$umd$$ES6Promise; });
	    } else if (typeof module !== 'undefined' && module['exports']) {
	      module['exports'] = es6$promise$umd$$ES6Promise;
	    } else if (typeof this !== 'undefined') {
	      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
	    }
	}).call(this);
	}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
	},{"_process":2}],2:[function(require,module,exports){
	// shim for using process in browser

	var process = module.exports = {};
	var queue = [];
	var draining = false;

	function drainQueue() {
	    if (draining) {
	        return;
	    }
	    draining = true;
	    var currentQueue;
	    var len = queue.length;
	    while(len) {
	        currentQueue = queue;
	        queue = [];
	        var i = -1;
	        while (++i < len) {
	            currentQueue[i]();
	        }
	        len = queue.length;
	    }
	    draining = false;
	}
	process.nextTick = function (fun) {
	    queue.push(fun);
	    if (!draining) {
	        setTimeout(drainQueue, 0);
	    }
	};

	process.title = 'browser';
	process.browser = true;
	process.env = {};
	process.argv = [];
	process.version = ''; // empty string to avoid regexp issues

	function noop() {}

	process.on = noop;
	process.addListener = noop;
	process.once = noop;
	process.off = noop;
	process.removeListener = noop;
	process.removeAllListeners = noop;
	process.emit = noop;

	process.binding = function (name) {
	    throw new Error('process.binding is not supported');
	};

	// TODO(shtylman)
	process.cwd = function () { return '/' };
	process.chdir = function (dir) {
	    throw new Error('process.chdir is not supported');
	};
	process.umask = function() { return 0; };

	},{}],3:[function(require,module,exports){
	(function (global){
	/*! http://mths.be/punycode v1.2.4 by @mathias */
	;(function(root) {

		/** Detect free variables */
		var freeExports = typeof exports == 'object' && exports;
		var freeModule = typeof module == 'object' && module &&
			module.exports == freeExports && module;
		var freeGlobal = typeof global == 'object' && global;
		if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
			root = freeGlobal;
		}

		/**
		 * The `punycode` object.
		 * @name punycode
		 * @type Object
		 */
		var punycode,

		/** Highest positive signed 32-bit float value */
		maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1

		/** Bootstring parameters */
		base = 36,
		tMin = 1,
		tMax = 26,
		skew = 38,
		damp = 700,
		initialBias = 72,
		initialN = 128, // 0x80
		delimiter = '-', // '\x2D'

		/** Regular expressions */
		regexPunycode = /^xn--/,
		regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
		regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators

		/** Error messages */
		errors = {
			'overflow': 'Overflow: input needs wider integers to process',
			'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
			'invalid-input': 'Invalid input'
		},

		/** Convenience shortcuts */
		baseMinusTMin = base - tMin,
		floor = Math.floor,
		stringFromCharCode = String.fromCharCode,

		/** Temporary variable */
		key;

		/*--------------------------------------------------------------------------*/

		/**
		 * A generic error utility function.
		 * @private
		 * @param {String} type The error type.
		 * @returns {Error} Throws a `RangeError` with the applicable error message.
		 */
		function error(type) {
			throw RangeError(errors[type]);
		}

		/**
		 * A generic `Array#map` utility function.
		 * @private
		 * @param {Array} array The array to iterate over.
		 * @param {Function} callback The function that gets called for every array
		 * item.
		 * @returns {Array} A new array of values returned by the callback function.
		 */
		function map(array, fn) {
			var length = array.length;
			while (length--) {
				array[length] = fn(array[length]);
			}
			return array;
		}

		/**
		 * A simple `Array#map`-like wrapper to work with domain name strings.
		 * @private
		 * @param {String} domain The domain name.
		 * @param {Function} callback The function that gets called for every
		 * character.
		 * @returns {Array} A new string of characters returned by the callback
		 * function.
		 */
		function mapDomain(string, fn) {
			return map(string.split(regexSeparators), fn).join('.');
		}

		/**
		 * Creates an array containing the numeric code points of each Unicode
		 * character in the string. While JavaScript uses UCS-2 internally,
		 * this function will convert a pair of surrogate halves (each of which
		 * UCS-2 exposes as separate characters) into a single code point,
		 * matching UTF-16.
		 * @see `punycode.ucs2.encode`
		 * @see <http://mathiasbynens.be/notes/javascript-encoding>
		 * @memberOf punycode.ucs2
		 * @name decode
		 * @param {String} string The Unicode input string (UCS-2).
		 * @returns {Array} The new array of code points.
		 */
		function ucs2decode(string) {
			var output = [],
			    counter = 0,
			    length = string.length,
			    value,
			    extra;
			while (counter < length) {
				value = string.charCodeAt(counter++);
				if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
					// high surrogate, and there is a next character
					extra = string.charCodeAt(counter++);
					if ((extra & 0xFC00) == 0xDC00) { // low surrogate
						output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
					} else {
						// unmatched surrogate; only append this code unit, in case the next
						// code unit is the high surrogate of a surrogate pair
						output.push(value);
						counter--;
					}
				} else {
					output.push(value);
				}
			}
			return output;
		}

		/**
		 * Creates a string based on an array of numeric code points.
		 * @see `punycode.ucs2.decode`
		 * @memberOf punycode.ucs2
		 * @name encode
		 * @param {Array} codePoints The array of numeric code points.
		 * @returns {String} The new Unicode string (UCS-2).
		 */
		function ucs2encode(array) {
			return map(array, function(value) {
				var output = '';
				if (value > 0xFFFF) {
					value -= 0x10000;
					output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
					value = 0xDC00 | value & 0x3FF;
				}
				output += stringFromCharCode(value);
				return output;
			}).join('');
		}

		/**
		 * Converts a basic code point into a digit/integer.
		 * @see `digitToBasic()`
		 * @private
		 * @param {Number} codePoint The basic numeric code point value.
		 * @returns {Number} The numeric value of a basic code point (for use in
		 * representing integers) in the range `0` to `base - 1`, or `base` if
		 * the code point does not represent a value.
		 */
		function basicToDigit(codePoint) {
			if (codePoint - 48 < 10) {
				return codePoint - 22;
			}
			if (codePoint - 65 < 26) {
				return codePoint - 65;
			}
			if (codePoint - 97 < 26) {
				return codePoint - 97;
			}
			return base;
		}

		/**
		 * Converts a digit/integer into a basic code point.
		 * @see `basicToDigit()`
		 * @private
		 * @param {Number} digit The numeric value of a basic code point.
		 * @returns {Number} The basic code point whose value (when used for
		 * representing integers) is `digit`, which needs to be in the range
		 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
		 * used; else, the lowercase form is used. The behavior is undefined
		 * if `flag` is non-zero and `digit` has no uppercase form.
		 */
		function digitToBasic(digit, flag) {
			//  0..25 map to ASCII a..z or A..Z
			// 26..35 map to ASCII 0..9
			return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
		}

		/**
		 * Bias adaptation function as per section 3.4 of RFC 3492.
		 * http://tools.ietf.org/html/rfc3492#section-3.4
		 * @private
		 */
		function adapt(delta, numPoints, firstTime) {
			var k = 0;
			delta = firstTime ? floor(delta / damp) : delta >> 1;
			delta += floor(delta / numPoints);
			for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
				delta = floor(delta / baseMinusTMin);
			}
			return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
		}

		/**
		 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
		 * symbols.
		 * @memberOf punycode
		 * @param {String} input The Punycode string of ASCII-only symbols.
		 * @returns {String} The resulting string of Unicode symbols.
		 */
		function decode(input) {
			// Don't use UCS-2
			var output = [],
			    inputLength = input.length,
			    out,
			    i = 0,
			    n = initialN,
			    bias = initialBias,
			    basic,
			    j,
			    index,
			    oldi,
			    w,
			    k,
			    digit,
			    t,
			    /** Cached calculation results */
			    baseMinusT;

			// Handle the basic code points: let `basic` be the number of input code
			// points before the last delimiter, or `0` if there is none, then copy
			// the first basic code points to the output.

			basic = input.lastIndexOf(delimiter);
			if (basic < 0) {
				basic = 0;
			}

			for (j = 0; j < basic; ++j) {
				// if it's not a basic code point
				if (input.charCodeAt(j) >= 0x80) {
					error('not-basic');
				}
				output.push(input.charCodeAt(j));
			}

			// Main decoding loop: start just after the last delimiter if any basic code
			// points were copied; start at the beginning otherwise.

			for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {

				// `index` is the index of the next character to be consumed.
				// Decode a generalized variable-length integer into `delta`,
				// which gets added to `i`. The overflow checking is easier
				// if we increase `i` as we go, then subtract off its starting
				// value at the end to obtain `delta`.
				for (oldi = i, w = 1, k = base; /* no condition */; k += base) {

					if (index >= inputLength) {
						error('invalid-input');
					}

					digit = basicToDigit(input.charCodeAt(index++));

					if (digit >= base || digit > floor((maxInt - i) / w)) {
						error('overflow');
					}

					i += digit * w;
					t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);

					if (digit < t) {
						break;
					}

					baseMinusT = base - t;
					if (w > floor(maxInt / baseMinusT)) {
						error('overflow');
					}

					w *= baseMinusT;

				}

				out = output.length + 1;
				bias = adapt(i - oldi, out, oldi == 0);

				// `i` was supposed to wrap around from `out` to `0`,
				// incrementing `n` each time, so we'll fix that now:
				if (floor(i / out) > maxInt - n) {
					error('overflow');
				}

				n += floor(i / out);
				i %= out;

				// Insert `n` at position `i` of the output
				output.splice(i++, 0, n);

			}

			return ucs2encode(output);
		}

		/**
		 * Converts a string of Unicode symbols to a Punycode string of ASCII-only
		 * symbols.
		 * @memberOf punycode
		 * @param {String} input The string of Unicode symbols.
		 * @returns {String} The resulting Punycode string of ASCII-only symbols.
		 */
		function encode(input) {
			var n,
			    delta,
			    handledCPCount,
			    basicLength,
			    bias,
			    j,
			    m,
			    q,
			    k,
			    t,
			    currentValue,
			    output = [],
			    /** `inputLength` will hold the number of code points in `input`. */
			    inputLength,
			    /** Cached calculation results */
			    handledCPCountPlusOne,
			    baseMinusT,
			    qMinusT;

			// Convert the input in UCS-2 to Unicode
			input = ucs2decode(input);

			// Cache the length
			inputLength = input.length;

			// Initialize the state
			n = initialN;
			delta = 0;
			bias = initialBias;

			// Handle the basic code points
			for (j = 0; j < inputLength; ++j) {
				currentValue = input[j];
				if (currentValue < 0x80) {
					output.push(stringFromCharCode(currentValue));
				}
			}

			handledCPCount = basicLength = output.length;

			// `handledCPCount` is the number of code points that have been handled;
			// `basicLength` is the number of basic code points.

			// Finish the basic string - if it is not empty - with a delimiter
			if (basicLength) {
				output.push(delimiter);
			}

			// Main encoding loop:
			while (handledCPCount < inputLength) {

				// All non-basic code points < n have been handled already. Find the next
				// larger one:
				for (m = maxInt, j = 0; j < inputLength; ++j) {
					currentValue = input[j];
					if (currentValue >= n && currentValue < m) {
						m = currentValue;
					}
				}

				// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
				// but guard against overflow
				handledCPCountPlusOne = handledCPCount + 1;
				if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
					error('overflow');
				}

				delta += (m - n) * handledCPCountPlusOne;
				n = m;

				for (j = 0; j < inputLength; ++j) {
					currentValue = input[j];

					if (currentValue < n && ++delta > maxInt) {
						error('overflow');
					}

					if (currentValue == n) {
						// Represent delta as a generalized variable-length integer
						for (q = delta, k = base; /* no condition */; k += base) {
							t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
							if (q < t) {
								break;
							}
							qMinusT = q - t;
							baseMinusT = base - t;
							output.push(
								stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
							);
							q = floor(qMinusT / baseMinusT);
						}

						output.push(stringFromCharCode(digitToBasic(q, 0)));
						bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
						delta = 0;
						++handledCPCount;
					}
				}

				++delta;
				++n;

			}
			return output.join('');
		}

		/**
		 * Converts a Punycode string representing a domain name to Unicode. Only the
		 * Punycoded parts of the domain name will be converted, i.e. it doesn't
		 * matter if you call it on a string that has already been converted to
		 * Unicode.
		 * @memberOf punycode
		 * @param {String} domain The Punycode domain name to convert to Unicode.
		 * @returns {String} The Unicode representation of the given Punycode
		 * string.
		 */
		function toUnicode(domain) {
			return mapDomain(domain, function(string) {
				return regexPunycode.test(string)
					? decode(string.slice(4).toLowerCase())
					: string;
			});
		}

		/**
		 * Converts a Unicode string representing a domain name to Punycode. Only the
		 * non-ASCII parts of the domain name will be converted, i.e. it doesn't
		 * matter if you call it with a domain that's already in ASCII.
		 * @memberOf punycode
		 * @param {String} domain The domain name to convert, as a Unicode string.
		 * @returns {String} The Punycode representation of the given domain name.
		 */
		function toASCII(domain) {
			return mapDomain(domain, function(string) {
				return regexNonASCII.test(string)
					? 'xn--' + encode(string)
					: string;
			});
		}

		/*--------------------------------------------------------------------------*/

		/** Define the public API */
		punycode = {
			/**
			 * A string representing the current Punycode.js version number.
			 * @memberOf punycode
			 * @type String
			 */
			'version': '1.2.4',
			/**
			 * An object of methods to convert from JavaScript's internal character
			 * representation (UCS-2) to Unicode code points, and back.
			 * @see <http://mathiasbynens.be/notes/javascript-encoding>
			 * @memberOf punycode
			 * @type Object
			 */
			'ucs2': {
				'decode': ucs2decode,
				'encode': ucs2encode
			},
			'decode': decode,
			'encode': encode,
			'toASCII': toASCII,
			'toUnicode': toUnicode
		};

		/** Expose `punycode` */
		// Some AMD build optimizers, like r.js, check for specific condition patterns
		// like the following:
		if (
			typeof define == 'function' &&
			typeof define.amd == 'object' &&
			define.amd
		) {
			define('punycode', function() {
				return punycode;
			});
		} else if (freeExports && !freeExports.nodeType) {
			if (freeModule) { // in Node.js or RingoJS v0.8.0+
				freeModule.exports = punycode;
			} else { // in Narwhal or RingoJS v0.7.0-
				for (key in punycode) {
					punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
				}
			}
		} else { // in Rhino or a web browser
			root.punycode = punycode;
		}

	}(this));

	}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
	},{}],4:[function(require,module,exports){
	var log = require('./log');
	var Promise = require('./promise');

	var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone";
	var html2canvasCanvasCloneIndex = 0;

	function cloneNodeValues(document, clone, nodeName) {
	    var originalNodes = document.getElementsByTagName(nodeName);
	    var clonedNodes = clone.getElementsByTagName(nodeName);
	    var count = originalNodes.length;
	    for (var i = 0; i < count; i++) {
	        clonedNodes[i].value = originalNodes[i].value;
	    }
	}

	function restoreOwnerScroll(ownerDocument, x, y) {
	    if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) {
	        ownerDocument.defaultView.scrollTo(x, y);
	    }
	}

	function labelCanvasElements(ownerDocument) {
	    [].slice.call(ownerDocument.querySelectorAll("canvas"), 0).forEach(function(canvas) {
	        canvas.setAttribute(html2canvasCanvasCloneAttribute, "canvas-" + html2canvasCanvasCloneIndex++);
	    });
	}

	function cloneCanvasContents(ownerDocument, documentClone) {
	    [].slice.call(ownerDocument.querySelectorAll("[" + html2canvasCanvasCloneAttribute + "]"), 0).forEach(function(canvas) {
	        try {
	            var clonedCanvas = documentClone.querySelector('[' + html2canvasCanvasCloneAttribute + '="' + canvas.getAttribute(html2canvasCanvasCloneAttribute) + '"]');
	            if (clonedCanvas) {
	                clonedCanvas.width = canvas.width;
	                clonedCanvas.height = canvas.height;
	                clonedCanvas.getContext("2d").putImageData(canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height), 0, 0);
	            }
	        } catch(e) {
	            log("Unable to copy canvas content from", canvas, e);
	        }
	        canvas.removeAttribute(html2canvasCanvasCloneAttribute);
	    });
	}

	function removeScriptNodes(parent) {
	    [].slice.call(parent.childNodes, 0).filter(isElementNode).forEach(function(node) {
	        if (node.tagName === "SCRIPT") {
	            parent.removeChild(node);
	        } else {
	            removeScriptNodes(node);
	        }
	    });
	    return parent;
	}

	function isIE9() {
	    return document.documentMode && document.documentMode <= 9;
	}

	// https://github.com/niklasvh/html2canvas/issues/503
	function cloneNodeIE9(node, javascriptEnabled) {
	    var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);

	    var child = node.firstChild;
	    while(child) {
	        if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
	            clone.appendChild(cloneNodeIE9(child, javascriptEnabled));
	        }
	        child = child.nextSibling;
	    }

	    return clone;
	}



	function isElementNode(node) {
	    return node.nodeType === Node.ELEMENT_NODE;
	}

	module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) {
	    labelCanvasElements(ownerDocument);
	    var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true);
	    var container = containerDocument.createElement("iframe");

	    container.className = "html2canvas-container";
	    container.style.visibility = "hidden";
	    container.style.position = "fixed";
	    container.style.left = "-10000px";
	    container.style.top = "0px";
	    container.style.border = "0";
	    container.width = width;
	    container.height = height;
	    container.scrolling = "no"; // ios won't scroll without it
	    containerDocument.body.appendChild(container);

	    return new Promise(function(resolve) {
	        var documentClone = container.contentWindow.document;

	        cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea");
	        cloneNodeValues(ownerDocument.documentElement, documentElement, "select");

	        /* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
	         if window url is about:blank, we can assign the url to current by writing onto the document
	         */
	        container.contentWindow.onload = container.onload = function() {
	            var interval = setInterval(function() {
	                if (documentClone.body.childNodes.length > 0) {
	                    cloneCanvasContents(ownerDocument, documentClone);
	                    clearInterval(interval);
	                    if (options.type === "view") {
	                        container.contentWindow.scrollTo(x, y);
	                    }
	                    resolve(container);
	                }
	            }, 50);
	        };

	        documentClone.open();
	        documentClone.write("<!DOCTYPE html><html></html>");
	        // Chrome scrolls the parent document for some reason after the write to the cloned window???
	        restoreOwnerScroll(ownerDocument, x, y);
	        documentClone.replaceChild(options.javascriptEnabled === true ? documentClone.adoptNode(documentElement) : removeScriptNodes(documentClone.adoptNode(documentElement)), documentClone.documentElement);
	        documentClone.close();
	    });
	};

	},{"./log":15,"./promise":18}],5:[function(require,module,exports){
	// http://dev.w3.org/csswg/css-color/

	function Color(value) {
	    this.r = 0;
	    this.g = 0;
	    this.b = 0;
	    this.a = null;
	    var result = this.fromArray(value) ||
	        this.namedColor(value) ||
	        this.rgb(value) ||
	        this.rgba(value) ||
	        this.hex6(value) ||
	        this.hex3(value);
	}

	Color.prototype.darken = function(amount) {
	    var a = 1 - amount;
	    return  new Color([
	        Math.round(this.r * a),
	        Math.round(this.g * a),
	        Math.round(this.b * a),
	        this.a
	    ]);
	};

	Color.prototype.isTransparent = function() {
	    return this.a === 0;
	};

	Color.prototype.isBlack = function() {
	    return this.r === 0 && this.g === 0 && this.b === 0;
	};

	Color.prototype.fromArray = function(array) {
	    if (Array.isArray(array)) {
	        this.r = Math.min(array[0], 255);
	        this.g = Math.min(array[1], 255);
	        this.b = Math.min(array[2], 255);
	        if (array.length > 3) {
	            this.a = array[3];
	        }
	    }

	    return (Array.isArray(array));
	};

	var _hex3 = /^#([a-f0-9]{3})$/i;

	Color.prototype.hex3 = function(value) {
	    var match = null;
	    if ((match = value.match(_hex3)) !== null) {
	        this.r = parseInt(match[1][0] + match[1][0], 16);
	        this.g = parseInt(match[1][1] + match[1][1], 16);
	        this.b = parseInt(match[1][2] + match[1][2], 16);
	    }
	    return match !== null;
	};

	var _hex6 = /^#([a-f0-9]{6})$/i;

	Color.prototype.hex6 = function(value) {
	    var match = null;
	    if ((match = value.match(_hex6)) !== null) {
	        this.r = parseInt(match[1].substring(0, 2), 16);
	        this.g = parseInt(match[1].substring(2, 4), 16);
	        this.b = parseInt(match[1].substring(4, 6), 16);
	    }
	    return match !== null;
	};


	var _rgb = /^rgb\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3})\)$/;

	Color.prototype.rgb = function(value) {
	    var match = null;
	    if ((match = value.match(_rgb)) !== null) {
	        this.r = Number(match[1]);
	        this.g = Number(match[2]);
	        this.b = Number(match[3]);
	    }
	    return match !== null;
	};

	var _rgba = /^rgba\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *, *(\d+\.?\d*)\)$/;

	Color.prototype.rgba = function(value) {
	    var match = null;
	    if ((match = value.match(_rgba)) !== null) {
	        this.r = Number(match[1]);
	        this.g = Number(match[2]);
	        this.b = Number(match[3]);
	        this.a = Number(match[4]);
	    }
	    return match !== null;
	};

	Color.prototype.toString = function() {
	    return this.a !== null && this.a !== 1 ?
	    "rgba(" + [this.r, this.g, this.b, this.a].join(",") + ")" :
	    "rgb(" + [this.r, this.g, this.b].join(",") + ")";
	};

	Color.prototype.namedColor = function(value) {
	    var color = colors[value.toLowerCase()];
	    if (color) {
	        this.r = color[0];
	        this.g = color[1];
	        this.b = color[2];
	    } else if (value.toLowerCase() === "transparent") {
	        this.r = this.g = this.b = this.a = 0;
	        return true;
	    }

	    return !!color;
	};

	Color.prototype.isColor = true;

	// JSON.stringify([].slice.call($$('.named-color-table tr'), 1).map(function(row) { return [row.childNodes[3].textContent, row.childNodes[5].textContent.trim().split(",").map(Number)] }).reduce(function(data, row) {data[row[0]] = row[1]; return data}, {}))
	var colors = {
	    "aliceblue": [240, 248, 255],
	    "antiquewhite": [250, 235, 215],
	    "aqua": [0, 255, 255],
	    "aquamarine": [127, 255, 212],
	    "azure": [240, 255, 255],
	    "beige": [245, 245, 220],
	    "bisque": [255, 228, 196],
	    "black": [0, 0, 0],
	    "blanchedalmond": [255, 235, 205],
	    "blue": [0, 0, 255],
	    "blueviolet": [138, 43, 226],
	    "brown": [165, 42, 42],
	    "burlywood": [222, 184, 135],
	    "cadetblue": [95, 158, 160],
	    "chartreuse": [127, 255, 0],
	    "chocolate": [210, 105, 30],
	    "coral": [255, 127, 80],
	    "cornflowerblue": [100, 149, 237],
	    "cornsilk": [255, 248, 220],
	    "crimson": [220, 20, 60],
	    "cyan": [0, 255, 255],
	    "darkblue": [0, 0, 139],
	    "darkcyan": [0, 139, 139],
	    "darkgoldenrod": [184, 134, 11],
	    "darkgray": [169, 169, 169],
	    "darkgreen": [0, 100, 0],
	    "darkgrey": [169, 169, 169],
	    "darkkhaki": [189, 183, 107],
	    "darkmagenta": [139, 0, 139],
	    "darkolivegreen": [85, 107, 47],
	    "darkorange": [255, 140, 0],
	    "darkorchid": [153, 50, 204],
	    "darkred": [139, 0, 0],
	    "darksalmon": [233, 150, 122],
	    "darkseagreen": [143, 188, 143],
	    "darkslateblue": [72, 61, 139],
	    "darkslategray": [47, 79, 79],
	    "darkslategrey": [47, 79, 79],
	    "darkturquoise": [0, 206, 209],
	    "darkviolet": [148, 0, 211],
	    "deeppink": [255, 20, 147],
	    "deepskyblue": [0, 191, 255],
	    "dimgray": [105, 105, 105],
	    "dimgrey": [105, 105, 105],
	    "dodgerblue": [30, 144, 255],
	    "firebrick": [178, 34, 34],
	    "floralwhite": [255, 250, 240],
	    "forestgreen": [34, 139, 34],
	    "fuchsia": [255, 0, 255],
	    "gainsboro": [220, 220, 220],
	    "ghostwhite": [248, 248, 255],
	    "gold": [255, 215, 0],
	    "goldenrod": [218, 165, 32],
	    "gray": [128, 128, 128],
	    "green": [0, 128, 0],
	    "greenyellow": [173, 255, 47],
	    "grey": [128, 128, 128],
	    "honeydew": [240, 255, 240],
	    "hotpink": [255, 105, 180],
	    "indianred": [205, 92, 92],
	    "indigo": [75, 0, 130],
	    "ivory": [255, 255, 240],
	    "khaki": [240, 230, 140],
	    "lavender": [230, 230, 250],
	    "lavenderblush": [255, 240, 245],
	    "lawngreen": [124, 252, 0],
	    "lemonchiffon": [255, 250, 205],
	    "lightblue": [173, 216, 230],
	    "lightcoral": [240, 128, 128],
	    "lightcyan": [224, 255, 255],
	    "lightgoldenrodyellow": [250, 250, 210],
	    "lightgray": [211, 211, 211],
	    "lightgreen": [144, 238, 144],
	    "lightgrey": [211, 211, 211],
	    "lightpink": [255, 182, 193],
	    "lightsalmon": [255, 160, 122],
	    "lightseagreen": [32, 178, 170],
	    "lightskyblue": [135, 206, 250],
	    "lightslategray": [119, 136, 153],
	    "lightslategrey": [119, 136, 153],
	    "lightsteelblue": [176, 196, 222],
	    "lightyellow": [255, 255, 224],
	    "lime": [0, 255, 0],
	    "limegreen": [50, 205, 50],
	    "linen": [250, 240, 230],
	    "magenta": [255, 0, 255],
	    "maroon": [128, 0, 0],
	    "mediumaquamarine": [102, 205, 170],
	    "mediumblue": [0, 0, 205],
	    "mediumorchid": [186, 85, 211],
	    "mediumpurple": [147, 112, 219],
	    "mediumseagreen": [60, 179, 113],
	    "mediumslateblue": [123, 104, 238],
	    "mediumspringgreen": [0, 250, 154],
	    "mediumturquoise": [72, 209, 204],
	    "mediumvioletred": [199, 21, 133],
	    "midnightblue": [25, 25, 112],
	    "mintcream": [245, 255, 250],
	    "mistyrose": [255, 228, 225],
	    "moccasin": [255, 228, 181],
	    "navajowhite": [255, 222, 173],
	    "navy": [0, 0, 128],
	    "oldlace": [253, 245, 230],
	    "olive": [128, 128, 0],
	    "olivedrab": [107, 142, 35],
	    "orange": [255, 165, 0],
	    "orangered": [255, 69, 0],
	    "orchid": [218, 112, 214],
	    "palegoldenrod": [238, 232, 170],
	    "palegreen": [152, 251, 152],
	    "paleturquoise": [175, 238, 238],
	    "palevioletred": [219, 112, 147],
	    "papayawhip": [255, 239, 213],
	    "peachpuff": [255, 218, 185],
	    "peru": [205, 133, 63],
	    "pink": [255, 192, 203],
	    "plum": [221, 160, 221],
	    "powderblue": [176, 224, 230],
	    "purple": [128, 0, 128],
	    "rebeccapurple": [102, 51, 153],
	    "red": [255, 0, 0],
	    "rosybrown": [188, 143, 143],
	    "royalblue": [65, 105, 225],
	    "saddlebrown": [139, 69, 19],
	    "salmon": [250, 128, 114],
	    "sandybrown": [244, 164, 96],
	    "seagreen": [46, 139, 87],
	    "seashell": [255, 245, 238],
	    "sienna": [160, 82, 45],
	    "silver": [192, 192, 192],
	    "skyblue": [135, 206, 235],
	    "slateblue": [106, 90, 205],
	    "slategray": [112, 128, 144],
	    "slategrey": [112, 128, 144],
	    "snow": [255, 250, 250],
	    "springgreen": [0, 255, 127],
	    "steelblue": [70, 130, 180],
	    "tan": [210, 180, 140],
	    "teal": [0, 128, 128],
	    "thistle": [216, 191, 216],
	    "tomato": [255, 99, 71],
	    "turquoise": [64, 224, 208],
	    "violet": [238, 130, 238],
	    "wheat": [245, 222, 179],
	    "white": [255, 255, 255],
	    "whitesmoke": [245, 245, 245],
	    "yellow": [255, 255, 0],
	    "yellowgreen": [154, 205, 50]
	};

	module.exports = Color;

	},{}],6:[function(require,module,exports){
	var Promise = require('./promise');
	var Support = require('./support');
	var CanvasRenderer = require('./renderers/canvas');
	var ImageLoader = require('./imageloader');
	var NodeParser = require('./nodeparser');
	var NodeContainer = require('./nodecontainer');
	var log = require('./log');
	var utils = require('./utils');
	var createWindowClone = require('./clone');
	var loadUrlDocument = require('./proxy').loadUrlDocument;
	var getBounds = utils.getBounds;

	var html2canvasNodeAttribute = "data-html2canvas-node";
	var html2canvasCloneIndex = 0;

	function html2canvas(nodeList, options) {
	    var index = html2canvasCloneIndex++;
	    options = options || {};
	    if (options.logging) {
	        window.html2canvas.logging = true;
	        window.html2canvas.start = Date.now();
	    }

	    options.async = typeof(options.async) === "undefined" ? true : options.async;
	    options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
	    options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
	    options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
	    options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
	    options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer;
	    options.strict = !!options.strict;

	    if (typeof(nodeList) === "string") {
	        if (typeof(options.proxy) !== "string") {
	            return Promise.reject("Proxy must be used when rendering url");
	        }
	        var width = options.width != null ? options.width : window.innerWidth;
	        var height = options.height != null ? options.height : window.innerHeight;
	        return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) {
	            return renderWindow(container.contentWindow.document.documentElement, container, options, width, height);
	        });
	    }

	    var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
	    node.setAttribute(html2canvasNodeAttribute + index, index);
	    return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
	        if (typeof(options.onrendered) === "function") {
	            log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
	            options.onrendered(canvas);
	        }
	        return canvas;
	    });
	}

	html2canvas.Promise = Promise;
	html2canvas.CanvasRenderer = CanvasRenderer;
	html2canvas.NodeContainer = NodeContainer;
	html2canvas.log = log;
	html2canvas.utils = utils;

	module.exports = (typeof(document) === "undefined" || typeof(Object.create) !== "function" || typeof(document.createElement("canvas").getContext) !== "function") ? function() {
	    return Promise.reject("No canvas support");
	} : html2canvas;

	function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) {
	    return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) {
	        log("Document cloned");
	        var attributeName = html2canvasNodeAttribute + html2canvasIndex;
	        var selector = "[" + attributeName + "='" + html2canvasIndex + "']";
	        document.querySelector(selector).removeAttribute(attributeName);
	        var clonedWindow = container.contentWindow;
	        var node = clonedWindow.document.querySelector(selector);
	        var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true);
	        return oncloneHandler.then(function() {
	            return renderWindow(node, container, options, windowWidth, windowHeight);
	        });
	    });
	}

	function renderWindow(node, container, options, windowWidth, windowHeight) {
	    var clonedWindow = container.contentWindow;
	    var support = new Support(clonedWindow.document);
	    var imageLoader = new ImageLoader(options, support);
	    var bounds = getBounds(node);
	    var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document);
	    var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document);
	    var renderer = new options.renderer(width, height, imageLoader, options, document);
	    var parser = new NodeParser(node, renderer, support, imageLoader, options);
	    return parser.ready.then(function() {
	        log("Finished rendering");
	        var canvas;

	        if (options.type === "view") {
	            canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
	        } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
	            canvas = renderer.canvas;
	        } else {
	            canvas = crop(renderer.canvas, {width:  options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: clonedWindow.pageXOffset, y: clonedWindow.pageYOffset});
	        }

	        cleanupContainer(container, options);
	        return canvas;
	    });
	}

	function cleanupContainer(container, options) {
	    if (options.removeContainer) {
	        container.parentNode.removeChild(container);
	        log("Cleaned up container");
	    }
	}

	function crop(canvas, bounds) {
	    var croppedCanvas = document.createElement("canvas");
	    var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left));
	    var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width));
	    var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top));
	    var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height));
	    croppedCanvas.width = bounds.width;
	    croppedCanvas.height =  bounds.height;
	    log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", (x2-x1), "height:", (y2-y1));
	    log("Resulting crop with width", bounds.width, "and height", bounds.height, " with x", x1, "and y", y1);
	    croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, x2-x1, y2-y1, bounds.x, bounds.y, x2-x1, y2-y1);
	    return croppedCanvas;
	}

	function documentWidth (doc) {
	    return Math.max(
	        Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
	        Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
	        Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
	    );
	}

	function documentHeight (doc) {
	    return Math.max(
	        Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
	        Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
	        Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
	    );
	}

	function absoluteUrl(url) {
	    var link = document.createElement("a");
	    link.href = url;
	    link.href = link.href;
	    return link;
	}

	},{"./clone":4,"./imageloader":13,"./log":15,"./nodecontainer":16,"./nodeparser":17,"./promise":18,"./proxy":19,"./renderers/canvas":23,"./support":25,"./utils":29}],7:[function(require,module,exports){
	var Promise = require('./promise');
	var log = require('./log');
	var smallImage = require('./utils').smallImage;

	function DummyImageContainer(src) {
	    this.src = src;
	    log("DummyImageContainer for", src);
	    if (!this.promise || !this.image) {
	        log("Initiating DummyImageContainer");
	        DummyImageContainer.prototype.image = new Image();
	        var image = this.image;
	        DummyImageContainer.prototype.promise = new Promise(function(resolve, reject) {
	            image.onload = resolve;
	            image.onerror = reject;
	            image.src = smallImage();
	            if (image.complete === true) {
	                resolve(image);
	            }
	        });
	    }
	}

	module.exports = DummyImageContainer;

	},{"./log":15,"./promise":18,"./utils":29}],8:[function(require,module,exports){
	var smallImage = require('./utils').smallImage;

	function Font(family, size) {
	    var container = document.createElement('div'),
	        img = document.createElement('img'),
	        span = document.createElement('span'),
	        sampleText = 'Hidden Text',
	        baseline,
	        middle;

	    container.style.visibility = "hidden";
	    container.style.fontFamily = family;
	    container.style.fontSize = size;
	    container.style.margin = 0;
	    container.style.padding = 0;

	    document.body.appendChild(container);

	    img.src = smallImage();
	    img.width = 1;
	    img.height = 1;

	    img.style.margin = 0;
	    img.style.padding = 0;
	    img.style.verticalAlign = "baseline";

	    span.style.fontFamily = family;
	    span.style.fontSize = size;
	    span.style.margin = 0;
	    span.style.padding = 0;

	    span.appendChild(document.createTextNode(sampleText));
	    container.appendChild(span);
	    container.appendChild(img);
	    baseline = (img.offsetTop - span.offsetTop) + 1;

	    container.removeChild(span);
	    container.appendChild(document.createTextNode(sampleText));

	    container.style.lineHeight = "normal";
	    img.style.verticalAlign = "super";

	    middle = (img.offsetTop-container.offsetTop) + 1;

	    document.body.removeChild(container);

	    this.baseline = baseline;
	    this.lineWidth = 1;
	    this.middle = middle;
	}

	module.exports = Font;

	},{"./utils":29}],9:[function(require,module,exports){
	var Font = require('./font');

	function FontMetrics() {
	    this.data = {};
	}

	FontMetrics.prototype.getMetrics = function(family, size) {
	    if (this.data[family + "-" + size] === undefined) {
	        this.data[family + "-" + size] = new Font(family, size);
	    }
	    return this.data[family + "-" + size];
	};

	module.exports = FontMetrics;

	},{"./font":8}],10:[function(require,module,exports){
	var utils = require('./utils');
	var Promise = require('./promise');
	var getBounds = utils.getBounds;
	var loadUrlDocument = require('./proxy').loadUrlDocument;

	function FrameContainer(container, sameOrigin, options) {
	    this.image = null;
	    this.src = container;
	    var self = this;
	    var bounds = getBounds(container);
	    this.promise = (!sameOrigin ? this.proxyLoad(options.proxy, bounds, options) : new Promise(function(resolve) {
	        if (container.contentWindow.document.URL === "about:blank" || container.contentWindow.document.documentElement == null) {
	            container.contentWindow.onload = container.onload = function() {
	                resolve(container);
	            };
	        } else {
	            resolve(container);
	        }
	    })).then(function(container) {
	        var html2canvas = require('./core');
	        return html2canvas(container.contentWindow.document.documentElement, {type: 'view', width: container.width, height: container.height, proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer, allowTaint: options.allowTaint, imageTimeout: options.imageTimeout / 2});
	    }).then(function(canvas) {
	        return self.image = canvas;
	    });
	}

	FrameContainer.prototype.proxyLoad = function(proxy, bounds, options) {
	    var container = this.src;
	    return loadUrlDocument(container.src, proxy, container.ownerDocument, bounds.width, bounds.height, options);
	};

	module.exports = FrameContainer;

	},{"./core":6,"./promise":18,"./proxy":19,"./utils":29}],11:[function(require,module,exports){
	var Promise = require('./promise');

	function GradientContainer(imageData) {
	    this.src = imageData.value;
	    this.colorStops = [];
	    this.type = null;
	    this.x0 = 0.5;
	    this.y0 = 0.5;
	    this.x1 = 0.5;
	    this.y1 = 0.5;
	    this.promise = Promise.resolve(true);
	}

	GradientContainer.prototype.TYPES = {
	    LINEAR: 1,
	    RADIAL: 2
	};

	module.exports = GradientContainer;

	},{"./promise":18}],12:[function(require,module,exports){
	var Promise = require('./promise');

	function ImageContainer(src, cors) {
	    this.src = src;
	    this.image = new Image();
	    var self = this;
	    this.tainted = null;
	    this.promise = new Promise(function(resolve, reject) {
	        self.image.onload = resolve;
	        self.image.onerror = reject;
	        if (cors) {
	            self.image.crossOrigin = "anonymous";
	        }
	        self.image.src = src;
	        if (self.image.complete === true) {
	            resolve(self.image);
	        }
	    });
	}

	module.exports = ImageContainer;

	},{"./promise":18}],13:[function(require,module,exports){
	var Promise = require('./promise');
	var log = require('./log');
	var ImageContainer = require('./imagecontainer');
	var DummyImageContainer = require('./dummyimagecontainer');
	var ProxyImageContainer = require('./proxyimagecontainer');
	var FrameContainer = require('./framecontainer');
	var SVGContainer = require('./svgcontainer');
	var SVGNodeContainer = require('./svgnodecontainer');
	var LinearGradientContainer = require('./lineargradientcontainer');
	var WebkitGradientContainer = require('./webkitgradientcontainer');
	var bind = require('./utils').bind;

	function ImageLoader(options, support) {
	    this.link = null;
	    this.options = options;
	    this.support = support;
	    this.origin = this.getOrigin(window.location.href);
	}

	ImageLoader.prototype.findImages = function(nodes) {
	    var images = [];
	    nodes.reduce(function(imageNodes, container) {
	        switch(container.node.nodeName) {
	        case "IMG":
	            return imageNodes.concat([{
	                args: [container.node.src],
	                method: "url"
	            }]);
	        case "svg":
	        case "IFRAME":
	            return imageNodes.concat([{
	                args: [container.node],
	                method: container.node.nodeName
	            }]);
	        }
	        return imageNodes;
	    }, []).forEach(this.addImage(images, this.loadImage), this);
	    return images;
	};

	ImageLoader.prototype.findBackgroundImage = function(images, container) {
	    container.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(images, this.loadImage), this);
	    return images;
	};

	ImageLoader.prototype.addImage = function(images, callback) {
	    return function(newImage) {
	        newImage.args.forEach(function(image) {
	            if (!this.imageExists(images, image)) {
	                images.splice(0, 0, callback.call(this, newImage));
	                log('Added image #' + (images.length), typeof(image) === "string" ? image.substring(0, 100) : image);
	            }
	        }, this);
	    };
	};

	ImageLoader.prototype.hasImageBackground = function(imageData) {
	    return imageData.method !== "none";
	};

	ImageLoader.prototype.loadImage = function(imageData) {
	    if (imageData.method === "url") {
	        var src = imageData.args[0];
	        if (this.isSVG(src) && !this.support.svg && !this.options.allowTaint) {
	            return new SVGContainer(src);
	        } else if (src.match(/data:image\/.*;base64,/i)) {
	            return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false);
	        } else if (this.isSameOrigin(src) || this.options.allowTaint === true || this.isSVG(src)) {
	            return new ImageContainer(src, false);
	        } else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {
	            return new ImageContainer(src, true);
	        } else if (this.options.proxy) {
	            return new ProxyImageContainer(src, this.options.proxy);
	        } else {
	            return new DummyImageContainer(src);
	        }
	    } else if (imageData.method === "linear-gradient") {
	        return new LinearGradientContainer(imageData);
	    } else if (imageData.method === "gradient") {
	        return new WebkitGradientContainer(imageData);
	    } else if (imageData.method === "svg") {
	        return new SVGNodeContainer(imageData.args[0], this.support.svg);
	    } else if (imageData.method === "IFRAME") {
	        return new FrameContainer(imageData.args[0], this.isSameOrigin(imageData.args[0].src), this.options);
	    } else {
	        return new DummyImageContainer(imageData);
	    }
	};

	ImageLoader.prototype.isSVG = function(src) {
	    return src.substring(src.length - 3).toLowerCase() === "svg" || SVGContainer.prototype.isInline(src);
	};

	ImageLoader.prototype.imageExists = function(images, src) {
	    return images.some(function(image) {
	        return image.src === src;
	    });
	};

	ImageLoader.prototype.isSameOrigin = function(url) {
	    return (this.getOrigin(url) === this.origin);
	};

	ImageLoader.prototype.getOrigin = function(url) {
	    var link = this.link || (this.link = document.createElement("a"));
	    link.href = url;
	    link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
	    return link.protocol + link.hostname + link.port;
	};

	ImageLoader.prototype.getPromise = function(container) {
	    return this.timeout(container, this.options.imageTimeout)['catch'](function() {
	        var dummy = new DummyImageContainer(container.src);
	        return dummy.promise.then(function(image) {
	            container.image = image;
	        });
	    });
	};

	ImageLoader.prototype.get = function(src) {
	    var found = null;
	    return this.images.some(function(img) {
	        return (found = img).src === src;
	    }) ? found : null;
	};

	ImageLoader.prototype.fetch = function(nodes) {
	    this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
	    this.images.forEach(function(image, index) {
	        image.promise.then(function() {
	            log("Succesfully loaded image #"+ (index+1), image);
	        }, function(e) {
	            log("Failed loading image #"+ (index+1), image, e);
	        });
	    });
	    this.ready = Promise.all(this.images.map(this.getPromise, this));
	    log("Finished searching images");
	    return this;
	};

	ImageLoader.prototype.timeout = function(container, timeout) {
	    var timer;
	    var promise = Promise.race([container.promise, new Promise(function(res, reject) {
	        timer = setTimeout(function() {
	            log("Timed out loading image", container);
	            reject(container);
	        }, timeout);
	    })]).then(function(container) {
	        clearTimeout(timer);
	        return container;
	    });
	    promise['catch'](function() {
	        clearTimeout(timer);
	    });
	    return promise;
	};

	module.exports = ImageLoader;

	},{"./dummyimagecontainer":7,"./framecontainer":10,"./imagecontainer":12,"./lineargradientcontainer":14,"./log":15,"./promise":18,"./proxyimagecontainer":20,"./svgcontainer":26,"./svgnodecontainer":27,"./utils":29,"./webkitgradientcontainer":30}],14:[function(require,module,exports){
	var GradientContainer = require('./gradientcontainer');
	var Color = require('./color');

	function LinearGradientContainer(imageData) {
	    GradientContainer.apply(this, arguments);
	    this.type = this.TYPES.LINEAR;

	    var hasDirection = imageData.args[0].match(this.stepRegExp) === null;

	    if (hasDirection) {
	        imageData.args[0].split(" ").reverse().forEach(function(position) {
	            switch(position) {
	            case "left":
	                this.x0 = 0;
	                this.x1 = 1;
	                break;
	            case "top":
	                this.y0 = 0;
	                this.y1 = 1;
	                break;
	            case "right":
	                this.x0 = 1;
	                this.x1 = 0;
	                break;
	            case "bottom":
	                this.y0 = 1;
	                this.y1 = 0;
	                break;
	            case "to":
	                var y0 = this.y0;
	                var x0 = this.x0;
	                this.y0 = this.y1;
	                this.x0 = this.x1;
	                this.x1 = x0;
	                this.y1 = y0;
	                break;
	            }
	        }, this);
	    } else {
	        this.y0 = 0;
	        this.y1 = 1;
	    }

	    this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) {
	        var colorStopMatch = colorStop.match(this.stepRegExp);
	        return {
	            color: new Color(colorStopMatch[1]),
	            stop: colorStopMatch[3] === "%" ? colorStopMatch[2] / 100 : null
	        };
	    }, this);

	    if (this.colorStops[0].stop === null) {
	        this.colorStops[0].stop = 0;
	    }

	    if (this.colorStops[this.colorStops.length - 1].stop === null) {
	        this.colorStops[this.colorStops.length - 1].stop = 1;
	    }

	    this.colorStops.forEach(function(colorStop, index) {
	        if (colorStop.stop === null) {
	            this.colorStops.slice(index).some(function(find, count) {
	                if (find.stop !== null) {
	                    colorStop.stop = ((find.stop - this.colorStops[index - 1].stop) / (count + 1)) + this.colorStops[index - 1].stop;
	                    return true;
	                } else {
	                    return false;
	                }
	            }, this);
	        }
	    }, this);
	}

	LinearGradientContainer.prototype = Object.create(GradientContainer.prototype);

	LinearGradientContainer.prototype.stepRegExp = /((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/;

	module.exports = LinearGradientContainer;

	},{"./color":5,"./gradientcontainer":11}],15:[function(require,module,exports){
	module.exports = function() {
	    if (window.html2canvas.logging && window.console && window.console.log) {
	        Function.prototype.bind.call(window.console.log, (window.console)).apply(window.console, [(Date.now() - window.html2canvas.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0)));
	    }
	};

	},{}],16:[function(require,module,exports){
	var Color = require('./color');
	var utils = require('./utils');
	var getBounds = utils.getBounds;
	var parseBackgrounds = utils.parseBackgrounds;
	var offsetBounds = utils.offsetBounds;

	function NodeContainer(node, parent) {
	    this.node = node;
	    this.parent = parent;
	    this.stack = null;
	    this.bounds = null;
	    this.borders = null;
	    this.clip = [];
	    this.backgroundClip = [];
	    this.offsetBounds = null;
	    this.visible = null;
	    this.computedStyles = null;
	    this.colors = {};
	    this.styles = {};
	    this.backgroundImages = null;
	    this.transformData = null;
	    this.transformMatrix = null;
	    this.isPseudoElement = false;
	    this.opacity = null;
	}

	NodeContainer.prototype.cloneTo = function(stack) {
	    stack.visible = this.visible;
	    stack.borders = this.borders;
	    stack.bounds = this.bounds;
	    stack.clip = this.clip;
	    stack.backgroundClip = this.backgroundClip;
	    stack.computedStyles = this.computedStyles;
	    stack.styles = this.styles;
	    stack.backgroundImages = this.backgroundImages;
	    stack.opacity = this.opacity;
	};

	NodeContainer.prototype.getOpacity = function() {
	    return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity;
	};

	NodeContainer.prototype.assignStack = function(stack) {
	    this.stack = stack;
	    stack.children.push(this);
	};

	NodeContainer.prototype.isElementVisible = function() {
	    return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (
	        this.css('display') !== "none" &&
	        this.css('visibility') !== "hidden" &&
	        !this.node.hasAttribute("data-html2canvas-ignore") &&
	        (this.node.nodeName !== "INPUT" || this.node.getAttribute("type") !== "hidden")
	    );
	};

	NodeContainer.prototype.css = function(attribute) {
	    if (!this.computedStyles) {
	        this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? ":before" : ":after") : this.computedStyle(null);
	    }

	    return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
	};

	NodeContainer.prototype.prefixedCss = function(attribute) {
	    var prefixes = ["webkit", "moz", "ms", "o"];
	    var value = this.css(attribute);
	    if (value === undefined) {
	        prefixes.some(function(prefix) {
	            value = this.css(prefix + attribute.substr(0, 1).toUpperCase() + attribute.substr(1));
	            return value !== undefined;
	        }, this);
	    }
	    return value === undefined ? null : value;
	};

	NodeContainer.prototype.computedStyle = function(type) {
	    return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);
	};

	NodeContainer.prototype.cssInt = function(attribute) {
	    var value = parseInt(this.css(attribute), 10);
	    return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
	};

	NodeContainer.prototype.color = function(attribute) {
	    return this.colors[attribute] || (this.colors[attribute] = new Color(this.css(attribute)));
	};

	NodeContainer.prototype.cssFloat = function(attribute) {
	    var value = parseFloat(this.css(attribute));
	    return (isNaN(value)) ? 0 : value;
	};

	NodeContainer.prototype.fontWeight = function() {
	    var weight = this.css("fontWeight");
	    switch(parseInt(weight, 10)){
	    case 401:
	        weight = "bold";
	        break;
	    case 400:
	        weight = "normal";
	        break;
	    }
	    return weight;
	};

	NodeContainer.prototype.parseClip = function() {
	    var matches = this.css('clip').match(this.CLIP);
	    if (matches) {
	        return {
	            top: parseInt(matches[1], 10),
	            right: parseInt(matches[2], 10),
	            bottom: parseInt(matches[3], 10),
	            left: parseInt(matches[4], 10)
	        };
	    }
	    return null;
	};

	NodeContainer.prototype.parseBackgroundImages = function() {
	    return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage")));
	};

	NodeContainer.prototype.cssList = function(property, index) {
	    var value = (this.css(property) || '').split(',');
	    value = value[index || 0] || value[0] || 'auto';
	    value = value.trim().split(' ');
	    if (value.length === 1) {
	        value = [value[0], isPercentage(value[0]) ? 'auto' : value[0]];
	    }
	    return value;
	};

	NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
	    var size = this.cssList("backgroundSize", index);
	    var width, height;

	    if (isPercentage(size[0])) {
	        width = bounds.width * parseFloat(size[0]) / 100;
	    } else if (/contain|cover/.test(size[0])) {
	        var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
	        return (targetRatio < currentRatio ^ size[0] === 'contain') ?  {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
	    } else {
	        width = parseInt(size[0], 10);
	    }

	    if (size[0] === 'auto' && size[1] === 'auto') {
	        height = image.height;
	    } else if (size[1] === 'auto') {
	        height = width / image.width * image.height;
	    } else if (isPercentage(size[1])) {
	        height =  bounds.height * parseFloat(size[1]) / 100;
	    } else {
	        height = parseInt(size[1], 10);
	    }

	    if (size[0] === 'auto') {
	        width = height / image.height * image.width;
	    }

	    return {width: width, height: height};
	};

	NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
	    var position = this.cssList('backgroundPosition', index);
	    var left, top;

	    if (isPercentage(position[0])){
	        left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
	    } else {
	        left = parseInt(position[0], 10);
	    }

	    if (position[1] === 'auto') {
	        top = left / image.width * image.height;
	    } else if (isPercentage(position[1])){
	        top =  (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
	    } else {
	        top = parseInt(position[1], 10);
	    }

	    if (position[0] === 'auto') {
	        left = top / image.height * image.width;
	    }

	    return {left: left, top: top};
	};

	NodeContainer.prototype.parseBackgroundRepeat = function(index) {
	    return this.cssList("backgroundRepeat", index)[0];
	};

	NodeContainer.prototype.parseTextShadows = function() {
	    var textShadow = this.css("textShadow");
	    var results = [];

	    if (textShadow && textShadow !== 'none') {
	        var shadows = textShadow.match(this.TEXT_SHADOW_PROPERTY);
	        for (var i = 0; shadows && (i < shadows.length); i++) {
	            var s = shadows[i].match(this.TEXT_SHADOW_VALUES);
	            results.push({
	                color: new Color(s[0]),
	                offsetX: s[1] ? parseFloat(s[1].replace('px', '')) : 0,
	                offsetY: s[2] ? parseFloat(s[2].replace('px', '')) : 0,
	                blur: s[3] ? s[3].replace('px', '') : 0
	            });
	        }
	    }
	    return results;
	};

	NodeContainer.prototype.parseTransform = function() {
	    if (!this.transformData) {
	        if (this.hasTransform()) {
	            var offset = this.parseBounds();
	            var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat);
	            origin[0] += offset.left;
	            origin[1] += offset.top;
	            this.transformData = {
	                origin: origin,
	                matrix: this.parseTransformMatrix()
	            };
	        } else {
	            this.transformData = {
	                origin: [0, 0],
	                matrix: [1, 0, 0, 1, 0, 0]
	            };
	        }
	    }
	    return this.transformData;
	};

	NodeContainer.prototype.parseTransformMatrix = function() {
	    if (!this.transformMatrix) {
	        var transform = this.prefixedCss("transform");
	        var matrix = transform ? parseMatrix(transform.match(this.MATRIX_PROPERTY)) : null;
	        this.transformMatrix = matrix ? matrix : [1, 0, 0, 1, 0, 0];
	    }
	    return this.transformMatrix;
	};

	NodeContainer.prototype.parseBounds = function() {
	    return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
	};

	NodeContainer.prototype.hasTransform = function() {
	    return this.parseTransformMatrix().join(",") !== "1,0,0,1,0,0" || (this.parent && this.parent.hasTransform());
	};

	NodeContainer.prototype.getValue = function() {
	    var value = this.node.value || "";
	    if (this.node.tagName === "SELECT") {
	        value = selectionValue(this.node);
	    } else if (this.node.type === "password") {
	        value = Array(value.length + 1).join('\u2022'); // jshint ignore:line
	    }
	    return value.length === 0 ? (this.node.placeholder || "") : value;
	};

	NodeContainer.prototype.MATRIX_PROPERTY = /(matrix)\((.+)\)/;
	NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
	NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
	NodeContainer.prototype.CLIP = /^rect\((\d+)px,? (\d+)px,? (\d+)px,? (\d+)px\)$/;

	function selectionValue(node) {
	    var option = node.options[node.selectedIndex || 0];
	    return option ? (option.text || "") : "";
	}

	function parseMatrix(match) {
	    if (match && match[1] === "matrix") {
	        return match[2].split(",").map(function(s) {
	            return parseFloat(s.trim());
	        });
	    }
	}

	function isPercentage(value) {
	    return value.toString().indexOf("%") !== -1;
	}

	function removePx(str) {
	    return str.replace("px", "");
	}

	function asFloat(str) {
	    return parseFloat(str);
	}

	module.exports = NodeContainer;

	},{"./color":5,"./utils":29}],17:[function(require,module,exports){
	var log = require('./log');
	var punycode = require('punycode');
	var NodeContainer = require('./nodecontainer');
	var TextContainer = require('./textcontainer');
	var PseudoElementContainer = require('./pseudoelementcontainer');
	var FontMetrics = require('./fontmetrics');
	var Color = require('./color');
	var Promise = require('./promise');
	var StackingContext = require('./stackingcontext');
	var utils = require('./utils');
	var bind = utils.bind;
	var getBounds = utils.getBounds;
	var parseBackgrounds = utils.parseBackgrounds;
	var offsetBounds = utils.offsetBounds;

	function NodeParser(element, renderer, support, imageLoader, options) {
	    log("Starting NodeParser");
	    this.renderer = renderer;
	    this.options = options;
	    this.range = null;
	    this.support = support;
	    this.renderQueue = [];
	    this.stack = new StackingContext(true, 1, element.ownerDocument, null);
	    var parent = new NodeContainer(element, null);
	    if (options.background) {
	        renderer.rectangle(0, 0, renderer.width, renderer.height, new Color(options.background));
	    }
	    if (element === element.ownerDocument.documentElement) {
	        // http://www.w3.org/TR/css3-background/#special-backgrounds
	        var canvasBackground = new NodeContainer(parent.color('backgroundColor').isTransparent() ? element.ownerDocument.body : element.ownerDocument.documentElement, null);
	        renderer.rectangle(0, 0, renderer.width, renderer.height, canvasBackground.color('backgroundColor'));
	    }
	    parent.visibile = parent.isElementVisible();
	    this.createPseudoHideStyles(element.ownerDocument);
	    this.disableAnimations(element.ownerDocument);
	    this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) {
	        return container.visible = container.isElementVisible();
	    }).map(this.getPseudoElements, this));
	    this.fontMetrics = new FontMetrics();
	    log("Fetched nodes, total:", this.nodes.length);
	    log("Calculate overflow clips");
	    this.calculateOverflowClips();
	    log("Start fetching images");
	    this.images = imageLoader.fetch(this.nodes.filter(isElement));
	    this.ready = this.images.ready.then(bind(function() {
	        log("Images loaded, starting parsing");
	        log("Creating stacking contexts");
	        this.createStackingContexts();
	        log("Sorting stacking contexts");
	        this.sortStackingContexts(this.stack);
	        this.parse(this.stack);
	        log("Render queue created with " + this.renderQueue.length + " items");
	        return new Promise(bind(function(resolve) {
	            if (!options.async) {
	                this.renderQueue.forEach(this.paint, this);
	                resolve();
	            } else if (typeof(options.async) === "function") {
	                options.async.call(this, this.renderQueue, resolve);
	            } else if (this.renderQueue.length > 0){
	                this.renderIndex = 0;
	                this.asyncRenderer(this.renderQueue, resolve);
	            } else {
	                resolve();
	            }
	        }, this));
	    }, this));
	}

	NodeParser.prototype.calculateOverflowClips = function() {
	    this.nodes.forEach(function(container) {
	        if (isElement(container)) {
	            if (isPseudoElement(container)) {
	                container.appendToDOM();
	            }
	            container.borders = this.parseBorders(container);
	            var clip = (container.css('overflow') === "hidden") ? [container.borders.clip] : [];
	            var cssClip = container.parseClip();
	            if (cssClip && ["absolute", "fixed"].indexOf(container.css('position')) !== -1) {
	                clip.push([["rect",
	                        container.bounds.left + cssClip.left,
	                        container.bounds.top + cssClip.top,
	                        cssClip.right - cssClip.left,
	                        cssClip.bottom - cssClip.top
	                ]]);
	            }
	            container.clip = hasParentClip(container) ? container.parent.clip.concat(clip) : clip;
	            container.backgroundClip = (container.css('overflow') !== "hidden") ? container.clip.concat([container.borders.clip]) : container.clip;
	            if (isPseudoElement(container)) {
	                container.cleanDOM();
	            }
	        } else if (isTextNode(container)) {
	            container.clip = hasParentClip(container) ? container.parent.clip : [];
	        }
	        if (!isPseudoElement(container)) {
	            container.bounds = null;
	        }
	    }, this);
	};

	function hasParentClip(container) {
	    return container.parent && container.parent.clip.length;
	}

	NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
	    asyncTimer = asyncTimer || Date.now();
	    this.paint(queue[this.renderIndex++]);
	    if (queue.length === this.renderIndex) {
	        resolve();
	    } else if (asyncTimer + 20 > Date.now()) {
	        this.asyncRenderer(queue, resolve, asyncTimer);
	    } else {
	        setTimeout(bind(function() {
	            this.asyncRenderer(queue, resolve);
	        }, this), 0);
	    }
	};

	NodeParser.prototype.createPseudoHideStyles = function(document) {
	    this.createStyles(document, '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ':before { content: "" !important; display: none !important; }' +
	        '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER + ':after { content: "" !important; display: none !important; }');
	};

	NodeParser.prototype.disableAnimations = function(document) {
	    this.createStyles(document, '* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; ' +
	        '-webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}');
	};

	NodeParser.prototype.createStyles = function(document, styles) {
	    var hidePseudoElements = document.createElement('style');
	    hidePseudoElements.innerHTML = styles;
	    document.body.appendChild(hidePseudoElements);
	};

	NodeParser.prototype.getPseudoElements = function(container) {
	    var nodes = [[container]];
	    if (container.node.nodeType === Node.ELEMENT_NODE) {
	        var before = this.getPseudoElement(container, ":before");
	        var after = this.getPseudoElement(container, ":after");

	        if (before) {
	            nodes.push(before);
	        }

	        if (after) {
	            nodes.push(after);
	        }
	    }
	    return flatten(nodes);
	};

	function toCamelCase(str) {
	    return str.replace(/(\-[a-z])/g, function(match){
	        return match.toUpperCase().replace('-','');
	    });
	}

	NodeParser.prototype.getPseudoElement = function(container, type) {
	    var style = container.computedStyle(type);
	    if(!style || !style.content || style.content === "none" || style.content === "-moz-alt-content" || style.display === "none") {
	        return null;
	    }

	    var content = stripQuotes(style.content);
	    var isImage = content.substr(0, 3) === 'url';
	    var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement');
	    var pseudoContainer = new PseudoElementContainer(pseudoNode, container, type);

	    for (var i = style.length-1; i >= 0; i--) {
	        var property = toCamelCase(style.item(i));
	        pseudoNode.style[property] = style[property];
	    }

	    pseudoNode.className = PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + " " + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER;

	    if (isImage) {
	        pseudoNode.src = parseBackgrounds(content)[0].args[0];
	        return [pseudoContainer];
	    } else {
	        var text = document.createTextNode(content);
	        pseudoNode.appendChild(text);
	        return [pseudoContainer, new TextContainer(text, pseudoContainer)];
	    }
	};


	NodeParser.prototype.getChildren = function(parentContainer) {
	    return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) {
	        var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
	        return node.nodeType === Node.ELEMENT_NODE && container.length && node.tagName !== "TEXTAREA" ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container;
	    }, this));
	};

	NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
	    var stack = new StackingContext(hasOwnStacking, container.getOpacity(), container.node, container.parent);
	    container.cloneTo(stack);
	    var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
	    parentStack.contexts.push(stack);
	    container.stack = stack;
	};

	NodeParser.prototype.createStackingContexts = function() {
	    this.nodes.forEach(function(container) {
	        if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container) || container.hasTransform())) {
	            this.newStackingContext(container, true);
	        } else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) {
	            this.newStackingContext(container, false);
	        } else {
	            container.assignStack(container.parent.stack);
	        }
	    }, this);
	};

	NodeParser.prototype.isBodyWithTransparentRoot = function(container) {
	    return container.node.nodeName === "BODY" && container.parent.color('backgroundColor').isTransparent();
	};

	NodeParser.prototype.isRootElement = function(container) {
	    return container.parent === null;
	};

	NodeParser.prototype.sortStackingContexts = function(stack) {
	    stack.contexts.sort(zIndexSort(stack.contexts.slice(0)));
	    stack.contexts.forEach(this.sortStackingContexts, this);
	};

	NodeParser.prototype.parseTextBounds = function(container) {
	    return function(text, index, textList) {
	        if (container.parent.css("textDecoration").substr(0, 4) !== "none" || text.trim().length !== 0) {
	            if (this.support.rangeBounds && !container.parent.hasTransform()) {
	                var offset = textList.slice(0, index).join("").length;
	                return this.getRangeBounds(container.node, offset, text.length);
	            } else if (container.node && typeof(container.node.data) === "string") {
	                var replacementNode = container.node.splitText(text.length);
	                var bounds = this.getWrapperBounds(container.node, container.parent.hasTransform());
	                container.node = replacementNode;
	                return bounds;
	            }
	        } else if(!this.support.rangeBounds || container.parent.hasTransform()){
	            container.node = container.node.splitText(text.length);
	        }
	        return {};
	    };
	};

	NodeParser.prototype.getWrapperBounds = function(node, transform) {
	    var wrapper = node.ownerDocument.createElement('html2canvaswrapper');
	    var parent = node.parentNode,
	        backupText = node.cloneNode(true);

	    wrapper.appendChild(node.cloneNode(true));
	    parent.replaceChild(wrapper, node);
	    var bounds = transform ? offsetBounds(wrapper) : getBounds(wrapper);
	    parent.replaceChild(backupText, wrapper);
	    return bounds;
	};

	NodeParser.prototype.getRangeBounds = function(node, offset, length) {
	    var range = this.range || (this.range = node.ownerDocument.createRange());
	    range.setStart(node, offset);
	    range.setEnd(node, offset + length);
	    return range.getBoundingClientRect();
	};

	function ClearTransform() {}

	NodeParser.prototype.parse = function(stack) {
	    // http://www.w3.org/TR/CSS21/visuren.html#z-index
	    var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).
	    var descendantElements = stack.children.filter(isElement);
	    var descendantNonFloats = descendantElements.filter(not(isFloating));
	    var nonInlineNonPositionedDescendants = descendantNonFloats.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.
	    var nonPositionedFloats = descendantElements.filter(not(isPositioned)).filter(isFloating); // 4. the non-positioned floats.
	    var inFlow = descendantNonFloats.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
	    var stackLevel0 = stack.contexts.concat(descendantNonFloats.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
	    var text = stack.children.filter(isTextNode).filter(hasText);
	    var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).
	    negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats)
	        .concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {
	            this.renderQueue.push(container);
	            if (isStackingContext(container)) {
	                this.parse(container);
	                this.renderQueue.push(new ClearTransform());
	            }
	        }, this);
	};

	NodeParser.prototype.paint = function(container) {
	    try {
	        if (container instanceof ClearTransform) {
	            this.renderer.ctx.restore();
	        } else if (isTextNode(container)) {
	            if (isPseudoElement(container.parent)) {
	                container.parent.appendToDOM();
	            }
	            this.paintText(container);
	            if (isPseudoElement(container.parent)) {
	                container.parent.cleanDOM();
	            }
	        } else {
	            this.paintNode(container);
	        }
	    } catch(e) {
	        log(e);
	        if (this.options.strict) {
	            throw e;
	        }
	    }
	};

	NodeParser.prototype.paintNode = function(container) {
	    if (isStackingContext(container)) {
	        this.renderer.setOpacity(container.opacity);
	        this.renderer.ctx.save();
	        if (container.hasTransform()) {
	            this.renderer.setTransform(container.parseTransform());
	        }
	    }

	    if (container.node.nodeName === "INPUT" && container.node.type === "checkbox") {
	        this.paintCheckbox(container);
	    } else if (container.node.nodeName === "INPUT" && container.node.type === "radio") {
	        this.paintRadio(container);
	    } else {
	        this.paintElement(container);
	    }
	};

	NodeParser.prototype.paintElement = function(container) {
	    var bounds = container.parseBounds();
	    this.renderer.clip(container.backgroundClip, function() {
	        this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
	    }, this);

	    this.renderer.clip(container.clip, function() {
	        this.renderer.renderBorders(container.borders.borders);
	    }, this);

	    this.renderer.clip(container.backgroundClip, function() {
	        switch (container.node.nodeName) {
	        case "svg":
	        case "IFRAME":
	            var imgContainer = this.images.get(container.node);
	            if (imgContainer) {
	                this.renderer.renderImage(container, bounds, container.borders, imgContainer);
	            } else {
	                log("Error loading <" + container.node.nodeName + ">", container.node);
	            }
	            break;
	        case "IMG":
	            var imageContainer = this.images.get(container.node.src);
	            if (imageContainer) {
	                this.renderer.renderImage(container, bounds, container.borders, imageContainer);
	            } else {
	                log("Error loading <img>", container.node.src);
	            }
	            break;
	        case "CANVAS":
	            this.renderer.renderImage(container, bounds, container.borders, {image: container.node});
	            break;
	        case "SELECT":
	        case "INPUT":
	        case "TEXTAREA":
	            this.paintFormValue(container);
	            break;
	        }
	    }, this);
	};

	NodeParser.prototype.paintCheckbox = function(container) {
	    var b = container.parseBounds();

	    var size = Math.min(b.width, b.height);
	    var bounds = {width: size - 1, height: size - 1, top: b.top, left: b.left};
	    var r = [3, 3];
	    var radius = [r, r, r, r];
	    var borders = [1,1,1,1].map(function(w) {
	        return {color: new Color('#A5A5A5'), width: w};
	    });

	    var borderPoints = calculateCurvePoints(bounds, radius, borders);

	    this.renderer.clip(container.backgroundClip, function() {
	        this.renderer.rectangle(bounds.left + 1, bounds.top + 1, bounds.width - 2, bounds.height - 2, new Color("#DEDEDE"));
	        this.renderer.renderBorders(calculateBorders(borders, bounds, borderPoints, radius));
	        if (container.node.checked) {
	            this.renderer.font(new Color('#424242'), 'normal', 'normal', 'bold', (size - 3) + "px", 'arial');
	            this.renderer.text("\u2714", bounds.left + size / 6, bounds.top + size - 1);
	        }
	    }, this);
	};

	NodeParser.prototype.paintRadio = function(container) {
	    var bounds = container.parseBounds();

	    var size = Math.min(bounds.width, bounds.height) - 2;

	    this.renderer.clip(container.backgroundClip, function() {
	        this.renderer.circleStroke(bounds.left + 1, bounds.top + 1, size, new Color('#DEDEDE'), 1, new Color('#A5A5A5'));
	        if (container.node.checked) {
	            this.renderer.circle(Math.ceil(bounds.left + size / 4) + 1, Math.ceil(bounds.top + size / 4) + 1, Math.floor(size / 2), new Color('#424242'));
	        }
	    }, this);
	};

	NodeParser.prototype.paintFormValue = function(container) {
	    var value = container.getValue();
	    if (value.length > 0) {
	        var document = container.node.ownerDocument;
	        var wrapper = document.createElement('html2canvaswrapper');
	        var properties = ['lineHeight', 'textAlign', 'fontFamily', 'fontWeight', 'fontSize', 'color',
	            'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom',
	            'width', 'height', 'borderLeftStyle', 'borderTopStyle', 'borderLeftWidth', 'borderTopWidth',
	            'boxSizing', 'whiteSpace', 'wordWrap'];

	        properties.forEach(function(property) {
	            try {
	                wrapper.style[property] = container.css(property);
	            } catch(e) {
	                // Older IE has issues with "border"
	                log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
	            }
	        });
	        var bounds = container.parseBounds();
	        wrapper.style.position = "fixed";
	        wrapper.style.left = bounds.left + "px";
	        wrapper.style.top = bounds.top + "px";
	        wrapper.textContent = value;
	        document.body.appendChild(wrapper);
	        this.paintText(new TextContainer(wrapper.firstChild, container));
	        document.body.removeChild(wrapper);
	    }
	};

	NodeParser.prototype.paintText = function(container) {
	    container.applyTextTransform();
	    var characters = punycode.ucs2.decode(container.node.data);
	    var textList = (!this.options.letterRendering || noLetterSpacing(container)) && !hasUnicode(container.node.data) ? getWords(characters) : characters.map(function(character) {
	        return punycode.ucs2.encode([character]);
	    });

	    var weight = container.parent.fontWeight();
	    var size = container.parent.css('fontSize');
	    var family = container.parent.css('fontFamily');
	    var shadows = container.parent.parseTextShadows();

	    this.renderer.font(container.parent.color('color'), container.parent.css('fontStyle'), container.parent.css('fontVariant'), weight, size, family);
	    if (shadows.length) {
	        // TODO: support multiple text shadows
	        this.renderer.fontShadow(shadows[0].color, shadows[0].offsetX, shadows[0].offsetY, shadows[0].blur);
	    } else {
	        this.renderer.clearShadow();
	    }

	    this.renderer.clip(container.parent.clip, function() {
	        textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
	            if (bounds) {
	                this.renderer.text(textList[index], bounds.left, bounds.bottom);
	                this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
	            }
	        }, this);
	    }, this);
	};

	NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
	    switch(container.css("textDecoration").split(" ")[0]) {
	    case "underline":
	        // Draws a line at the baseline of the font
	        // TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
	        this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.color("color"));
	        break;
	    case "overline":
	        this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.color("color"));
	        break;
	    case "line-through":
	        // TODO try and find exact position for line-through
	        this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.color("color"));
	        break;
	    }
	};

	var borderColorTransforms = {
	    inset: [
	        ["darken", 0.60],
	        ["darken", 0.10],
	        ["darken", 0.10],
	        ["darken", 0.60]
	    ]
	};

	NodeParser.prototype.parseBorders = function(container) {
	    var nodeBounds = container.parseBounds();
	    var radius = getBorderRadiusData(container);
	    var borders = ["Top", "Right", "Bottom", "Left"].map(function(side, index) {
	        var style = container.css('border' + side + 'Style');
	        var color = container.color('border' + side + 'Color');
	        if (style === "inset" && color.isBlack()) {
	            color = new Color([255, 255, 255, color.a]); // this is wrong, but
	        }
	        var colorTransform = borderColorTransforms[style] ? borderColorTransforms[style][index] : null;
	        return {
	            width: container.cssInt('border' + side + 'Width'),
	            color: colorTransform ? color[colorTransform[0]](colorTransform[1]) : color,
	            args: null
	        };
	    });
	    var borderPoints = calculateCurvePoints(nodeBounds, radius, borders);

	    return {
	        clip: this.parseBackgroundClip(container, borderPoints, borders, radius, nodeBounds),
	        borders: calculateBorders(borders, nodeBounds, borderPoints, radius)
	    };
	};

	function calculateBorders(borders, nodeBounds, borderPoints, radius) {
	    return borders.map(function(border, borderSide) {
	        if (border.width > 0) {
	            var bx = nodeBounds.left;
	            var by = nodeBounds.top;
	            var bw = nodeBounds.width;
	            var bh = nodeBounds.height - (borders[2].width);

	            switch(borderSide) {
	            case 0:
	                // top border
	                bh = borders[0].width;
	                border.args = drawSide({
	                        c1: [bx, by],
	                        c2: [bx + bw, by],
	                        c3: [bx + bw - borders[1].width, by + bh],
	                        c4: [bx + borders[3].width, by + bh]
	                    }, radius[0], radius[1],
	                    borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
	                break;
	            case 1:
	                // right border
	                bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
	                bw = borders[1].width;

	                border.args = drawSide({
	                        c1: [bx + bw, by],
	                        c2: [bx + bw, by + bh + borders[2].width],
	                        c3: [bx, by + bh],
	                        c4: [bx, by + borders[0].width]
	                    }, radius[1], radius[2],
	                    borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
	                break;
	            case 2:
	                // bottom border
	                by = (by + nodeBounds.height) - (borders[2].width);
	                bh = borders[2].width;
	                border.args = drawSide({
	                        c1: [bx + bw, by + bh],
	                        c2: [bx, by + bh],
	                        c3: [bx + borders[3].width, by],
	                        c4: [bx + bw - borders[3].width, by]
	                    }, radius[2], radius[3],
	                    borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
	                break;
	            case 3:
	                // left border
	                bw = borders[3].width;
	                border.args = drawSide({
	                        c1: [bx, by + bh + borders[2].width],
	                        c2: [bx, by],
	                        c3: [bx + bw, by + borders[0].width],
	                        c4: [bx + bw, by + bh]
	                    }, radius[3], radius[0],
	                    borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
	                break;
	            }
	        }
	        return border;
	    });
	}

	NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, borders, radius, bounds) {
	    var backgroundClip = container.css('backgroundClip'),
	        borderArgs = [];

	    switch(backgroundClip) {
	    case "content-box":
	    case "padding-box":
	        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
	        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
	        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
	        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
	        break;

	    default:
	        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
	        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
	        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
	        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
	        break;
	    }

	    return borderArgs;
	};

	function getCurvePoints(x, y, r1, r2) {
	    var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
	    var ox = (r1) * kappa, // control point offset horizontal
	        oy = (r2) * kappa, // control point offset vertical
	        xm = x + r1, // x-middle
	        ym = y + r2; // y-middle
	    return {
	        topLeft: bezierCurve({x: x, y: ym}, {x: x, y: ym - oy}, {x: xm - ox, y: y}, {x: xm, y: y}),
	        topRight: bezierCurve({x: x, y: y}, {x: x + ox,y: y}, {x: xm, y: ym - oy}, {x: xm, y: ym}),
	        bottomRight: bezierCurve({x: xm, y: y}, {x: xm, y: y + oy}, {x: x + ox, y: ym}, {x: x, y: ym}),
	        bottomLeft: bezierCurve({x: xm, y: ym}, {x: xm - ox, y: ym}, {x: x, y: y + oy}, {x: x, y:y})
	    };
	}

	function calculateCurvePoints(bounds, borderRadius, borders) {
	    var x = bounds.left,
	        y = bounds.top,
	        width = bounds.width,
	        height = bounds.height,

	        tlh = borderRadius[0][0],
	        tlv = borderRadius[0][1],
	        trh = borderRadius[1][0],
	        trv = borderRadius[1][1],
	        brh = borderRadius[2][0],
	        brv = borderRadius[2][1],
	        blh = borderRadius[3][0],
	        blv = borderRadius[3][1];

	    var topWidth = width - trh,
	        rightHeight = height - brv,
	        bottomWidth = width - brh,
	        leftHeight = height - blv;

	    return {
	        topLeftOuter: getCurvePoints(x, y, tlh, tlv).topLeft.subdivide(0.5),
	        topLeftInner: getCurvePoints(x + borders[3].width, y + borders[0].width, Math.max(0, tlh - borders[3].width), Math.max(0, tlv - borders[0].width)).topLeft.subdivide(0.5),
	        topRightOuter: getCurvePoints(x + topWidth, y, trh, trv).topRight.subdivide(0.5),
	        topRightInner: getCurvePoints(x + Math.min(topWidth, width + borders[3].width), y + borders[0].width, (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width, trv - borders[0].width).topRight.subdivide(0.5),
	        bottomRightOuter: getCurvePoints(x + bottomWidth, y + rightHeight, brh, brv).bottomRight.subdivide(0.5),
	        bottomRightInner: getCurvePoints(x + Math.min(bottomWidth, width - borders[3].width), y + Math.min(rightHeight, height + borders[0].width), Math.max(0, brh - borders[1].width),  brv - borders[2].width).bottomRight.subdivide(0.5),
	        bottomLeftOuter: getCurvePoints(x, y + leftHeight, blh, blv).bottomLeft.subdivide(0.5),
	        bottomLeftInner: getCurvePoints(x + borders[3].width, y + leftHeight, Math.max(0, blh - borders[3].width), blv - borders[2].width).bottomLeft.subdivide(0.5)
	    };
	}

	function bezierCurve(start, startControl, endControl, end) {
	    var lerp = function (a, b, t) {
	        return {
	            x: a.x + (b.x - a.x) * t,
	            y: a.y + (b.y - a.y) * t
	        };
	    };

	    return {
	        start: start,
	        startControl: startControl,
	        endControl: endControl,
	        end: end,
	        subdivide: function(t) {
	            var ab = lerp(start, startControl, t),
	                bc = lerp(startControl, endControl, t),
	                cd = lerp(endControl, end, t),
	                abbc = lerp(ab, bc, t),
	                bccd = lerp(bc, cd, t),
	                dest = lerp(abbc, bccd, t);
	            return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
	        },
	        curveTo: function(borderArgs) {
	            borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
	        },
	        curveToReversed: function(borderArgs) {
	            borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
	        }
	    };
	}

	function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
	    var borderArgs = [];

	    if (radius1[0] > 0 || radius1[1] > 0) {
	        borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
	        outer1[1].curveTo(borderArgs);
	    } else {
	        borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
	    }

	    if (radius2[0] > 0 || radius2[1] > 0) {
	        borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
	        outer2[0].curveTo(borderArgs);
	        borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
	        inner2[0].curveToReversed(borderArgs);
	    } else {
	        borderArgs.push(["line", borderData.c2[0], borderData.c2[1]]);
	        borderArgs.push(["line", borderData.c3[0], borderData.c3[1]]);
	    }

	    if (radius1[0] > 0 || radius1[1] > 0) {
	        borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
	        inner1[1].curveToReversed(borderArgs);
	    } else {
	        borderArgs.push(["line", borderData.c4[0], borderData.c4[1]]);
	    }

	    return borderArgs;
	}

	function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
	    if (radius1[0] > 0 || radius1[1] > 0) {
	        borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
	        corner1[0].curveTo(borderArgs);
	        corner1[1].curveTo(borderArgs);
	    } else {
	        borderArgs.push(["line", x, y]);
	    }

	    if (radius2[0] > 0 || radius2[1] > 0) {
	        borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
	    }
	}

	function negativeZIndex(container) {
	    return container.cssInt("zIndex") < 0;
	}

	function positiveZIndex(container) {
	    return container.cssInt("zIndex") > 0;
	}

	function zIndex0(container) {
	    return container.cssInt("zIndex") === 0;
	}

	function inlineLevel(container) {
	    return ["inline", "inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
	}

	function isStackingContext(container) {
	    return (container instanceof StackingContext);
	}

	function hasText(container) {
	    return container.node.data.trim().length > 0;
	}

	function noLetterSpacing(container) {
	    return (/^(normal|none|0px)$/.test(container.parent.css("letterSpacing")));
	}

	function getBorderRadiusData(container) {
	    return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
	        var value = container.css('border' + side + 'Radius');
	        var arr = value.split(" ");
	        if (arr.length <= 1) {
	            arr[1] = arr[0];
	        }
	        return arr.map(asInt);
	    });
	}

	function renderableNode(node) {
	    return (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE);
	}

	function isPositionedForStacking(container) {
	    var position = container.css("position");
	    var zIndex = (["absolute", "relative", "fixed"].indexOf(position) !== -1) ? container.css("zIndex") : "auto";
	    return zIndex !== "auto";
	}

	function isPositioned(container) {
	    return container.css("position") !== "static";
	}

	function isFloating(container) {
	    return container.css("float") !== "none";
	}

	function isInlineBlock(container) {
	    return ["inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
	}

	function not(callback) {
	    var context = this;
	    return function() {
	        return !callback.apply(context, arguments);
	    };
	}

	function isElement(container) {
	    return container.node.nodeType === Node.ELEMENT_NODE;
	}

	function isPseudoElement(container) {
	    return container.isPseudoElement === true;
	}

	function isTextNode(container) {
	    return container.node.nodeType === Node.TEXT_NODE;
	}

	function zIndexSort(contexts) {
	    return function(a, b) {
	        return (a.cssInt("zIndex") + (contexts.indexOf(a) / contexts.length)) - (b.cssInt("zIndex") + (contexts.indexOf(b) / contexts.length));
	    };
	}

	function hasOpacity(container) {
	    return container.getOpacity() < 1;
	}

	function asInt(value) {
	    return parseInt(value, 10);
	}

	function getWidth(border) {
	    return border.width;
	}

	function nonIgnoredElement(nodeContainer) {
	    return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || ["SCRIPT", "HEAD", "TITLE", "OBJECT", "BR", "OPTION"].indexOf(nodeContainer.node.nodeName) === -1);
	}

	function flatten(arrays) {
	    return [].concat.apply([], arrays);
	}

	function stripQuotes(content) {
	    var first = content.substr(0, 1);
	    return (first === content.substr(content.length - 1) && first.match(/'|"/)) ? content.substr(1, content.length - 2) : content;
	}

	function getWords(characters) {
	    var words = [], i = 0, onWordBoundary = false, word;
	    while(characters.length) {
	        if (isWordBoundary(characters[i]) === onWordBoundary) {
	            word = characters.splice(0, i);
	            if (word.length) {
	                words.push(punycode.ucs2.encode(word));
	            }
	            onWordBoundary =! onWordBoundary;
	            i = 0;
	        } else {
	            i++;
	        }

	        if (i >= characters.length) {
	            word = characters.splice(0, i);
	            if (word.length) {
	                words.push(punycode.ucs2.encode(word));
	            }
	        }
	    }
	    return words;
	}

	function isWordBoundary(characterCode) {
	    return [
	        32, // <space>
	        13, // \r
	        10, // \n
	        9, // \t
	        45 // -
	    ].indexOf(characterCode) !== -1;
	}

	function hasUnicode(string) {
	    return (/[^\u0000-\u00ff]/).test(string);
	}

	module.exports = NodeParser;

	},{"./color":5,"./fontmetrics":9,"./log":15,"./nodecontainer":16,"./promise":18,"./pseudoelementcontainer":21,"./stackingcontext":24,"./textcontainer":28,"./utils":29,"punycode":3}],18:[function(require,module,exports){
	module.exports = require('es6-promise').Promise;

	},{"es6-promise":1}],19:[function(require,module,exports){
	var Promise = require('./promise');
	var XHR = require('./xhr');
	var utils = require('./utils');
	var log = require('./log');
	var createWindowClone = require('./clone');
	var decode64 = utils.decode64;

	function Proxy(src, proxyUrl, document) {
	    var supportsCORS = ('withCredentials' in new XMLHttpRequest());
	    if (!proxyUrl) {
	        return Promise.reject("No proxy configured");
	    }
	    var callback = createCallback(supportsCORS);
	    var url = createProxyUrl(proxyUrl, src, callback);

	    return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) {
	        return decode64(response.content);
	    }));
	}
	var proxyCount = 0;

	function ProxyURL(src, proxyUrl, document) {
	    var supportsCORSImage = ('crossOrigin' in new Image());
	    var callback = createCallback(supportsCORSImage);
	    var url = createProxyUrl(proxyUrl, src, callback);
	    return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) {
	        return "data:" + response.type + ";base64," + response.content;
	    }));
	}

	function jsonp(document, url, callback) {
	    return new Promise(function(resolve, reject) {
	        var s = document.createElement("script");
	        var cleanup = function() {
	            delete window.html2canvas.proxy[callback];
	            document.body.removeChild(s);
	        };
	        window.html2canvas.proxy[callback] = function(response) {
	            cleanup();
	            resolve(response);
	        };
	        s.src = url;
	        s.onerror = function(e) {
	            cleanup();
	            reject(e);
	        };
	        document.body.appendChild(s);
	    });
	}

	function createCallback(useCORS) {
	    return !useCORS ? "html2canvas_" + Date.now() + "_" + (++proxyCount) + "_" + Math.round(Math.random() * 100000) : "";
	}

	function createProxyUrl(proxyUrl, src, callback) {
	    return proxyUrl + "?url=" + encodeURIComponent(src) + (callback.length ? "&callback=html2canvas.proxy." + callback : "");
	}

	function documentFromHTML(src) {
	    return function(html) {
	        var parser = new DOMParser(), doc;
	        try {
	            doc = parser.parseFromString(html, "text/html");
	        } catch(e) {
	            log("DOMParser not supported, falling back to createHTMLDocument");
	            doc = document.implementation.createHTMLDocument("");
	            try {
	                doc.open();
	                doc.write(html);
	                doc.close();
	            } catch(ee) {
	                log("createHTMLDocument write not supported, falling back to document.body.innerHTML");
	                doc.body.innerHTML = html; // ie9 doesnt support writing to documentElement
	            }
	        }

	        var b = doc.querySelector("base");
	        if (!b || !b.href.host) {
	            var base = doc.createElement("base");
	            base.href = src;
	            doc.head.insertBefore(base, doc.head.firstChild);
	        }

	        return doc;
	    };
	}

	function loadUrlDocument(src, proxy, document, width, height, options) {
	    return new Proxy(src, proxy, window.document).then(documentFromHTML(src)).then(function(doc) {
	        return createWindowClone(doc, document, width, height, options, 0, 0);
	    });
	}

	exports.Proxy = Proxy;
	exports.ProxyURL = ProxyURL;
	exports.loadUrlDocument = loadUrlDocument;

	},{"./clone":4,"./log":15,"./promise":18,"./utils":29,"./xhr":31}],20:[function(require,module,exports){
	var ProxyURL = require('./proxy').ProxyURL;
	var Promise = require('./promise');

	function ProxyImageContainer(src, proxy) {
	    var link = document.createElement("a");
	    link.href = src;
	    src = link.href;
	    this.src = src;
	    this.image = new Image();
	    var self = this;
	    this.promise = new Promise(function(resolve, reject) {
	        self.image.crossOrigin = "Anonymous";
	        self.image.onload = resolve;
	        self.image.onerror = reject;

	        new ProxyURL(src, proxy, document).then(function(url) {
	            self.image.src = url;
	        })['catch'](reject);
	    });
	}

	module.exports = ProxyImageContainer;

	},{"./promise":18,"./proxy":19}],21:[function(require,module,exports){
	var NodeContainer = require('./nodecontainer');

	function PseudoElementContainer(node, parent, type) {
	    NodeContainer.call(this, node, parent);
	    this.isPseudoElement = true;
	    this.before = type === ":before";
	}

	PseudoElementContainer.prototype.cloneTo = function(stack) {
	    PseudoElementContainer.prototype.cloneTo.call(this, stack);
	    stack.isPseudoElement = true;
	    stack.before = this.before;
	};

	PseudoElementContainer.prototype = Object.create(NodeContainer.prototype);

	PseudoElementContainer.prototype.appendToDOM = function() {
	    if (this.before) {
	        this.parent.node.insertBefore(this.node, this.parent.node.firstChild);
	    } else {
	        this.parent.node.appendChild(this.node);
	    }
	    this.parent.node.className += " " + this.getHideClass();
	};

	PseudoElementContainer.prototype.cleanDOM = function() {
	    this.node.parentNode.removeChild(this.node);
	    this.parent.node.className = this.parent.node.className.replace(this.getHideClass(), "");
	};

	PseudoElementContainer.prototype.getHideClass = function() {
	    return this["PSEUDO_HIDE_ELEMENT_CLASS_" + (this.before ? "BEFORE" : "AFTER")];
	};

	PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = "___html2canvas___pseudoelement_before";
	PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER = "___html2canvas___pseudoelement_after";

	module.exports = PseudoElementContainer;

	},{"./nodecontainer":16}],22:[function(require,module,exports){
	var log = require('./log');

	function Renderer(width, height, images, options, document) {
	    this.width = width;
	    this.height = height;
	    this.images = images;
	    this.options = options;
	    this.document = document;
	}

	Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) {
	    var paddingLeft = container.cssInt('paddingLeft'),
	        paddingTop = container.cssInt('paddingTop'),
	        paddingRight = container.cssInt('paddingRight'),
	        paddingBottom = container.cssInt('paddingBottom'),
	        borders = borderData.borders;

	    var width = bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight);
	    var height = bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom);
	    this.drawImage(
	        imageContainer,
	        0,
	        0,
	        imageContainer.image.width || width,
	        imageContainer.image.height || height,
	        bounds.left + paddingLeft + borders[3].width,
	        bounds.top + paddingTop + borders[0].width,
	        width,
	        height
	    );
	};

	Renderer.prototype.renderBackground = function(container, bounds, borderData) {
	    if (bounds.height > 0 && bounds.width > 0) {
	        this.renderBackgroundColor(container, bounds);
	        this.renderBackgroundImage(container, bounds, borderData);
	    }
	};

	Renderer.prototype.renderBackgroundColor = function(container, bounds) {
	    var color = container.color("backgroundColor");
	    if (!color.isTransparent()) {
	        this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, color);
	    }
	};

	Renderer.prototype.renderBorders = function(borders) {
	    borders.forEach(this.renderBorder, this);
	};

	Renderer.prototype.renderBorder = function(data) {
	    if (!data.color.isTransparent() && data.args !== null) {
	        this.drawShape(data.args, data.color);
	    }
	};

	Renderer.prototype.renderBackgroundImage = function(container, bounds, borderData) {
	    var backgroundImages = container.parseBackgroundImages();
	    backgroundImages.reverse().forEach(function(backgroundImage, index, arr) {
	        switch(backgroundImage.method) {
	        case "url":
	            var image = this.images.get(backgroundImage.args[0]);
	            if (image) {
	                this.renderBackgroundRepeating(container, bounds, image, arr.length - (index+1), borderData);
	            } else {
	                log("Error loading background-image", backgroundImage.args[0]);
	            }
	            break;
	        case "linear-gradient":
	        case "gradient":
	            var gradientImage = this.images.get(backgroundImage.value);
	            if (gradientImage) {
	                this.renderBackgroundGradient(gradientImage, bounds, borderData);
	            } else {
	                log("Error loading background-image", backgroundImage.args[0]);
	            }
	            break;
	        case "none":
	            break;
	        default:
	            log("Unknown background-image type", backgroundImage.args[0]);
	        }
	    }, this);
	};

	Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index, borderData) {
	    var size = container.parseBackgroundSize(bounds, imageContainer.image, index);
	    var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);
	    var repeat = container.parseBackgroundRepeat(index);
	    switch (repeat) {
	    case "repeat-x":
	    case "repeat no-repeat":
	        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + borderData[3], bounds.top + position.top + borderData[0], 99999, size.height, borderData);
	        break;
	    case "repeat-y":
	    case "no-repeat repeat":
	        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + borderData[0], size.width, 99999, borderData);
	        break;
	    case "no-repeat":
	        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + position.top + borderData[0], size.width, size.height, borderData);
	        break;
	    default:
	        this.renderBackgroundRepeat(imageContainer, position, size, {top: bounds.top, left: bounds.left}, borderData[3], borderData[0]);
	        break;
	    }
	};

	module.exports = Renderer;

	},{"./log":15}],23:[function(require,module,exports){
	var Renderer = require('../renderer');
	var LinearGradientContainer = require('../lineargradientcontainer');
	var log = require('../log');

	function CanvasRenderer(width, height) {
	    Renderer.apply(this, arguments);
	    this.canvas = this.options.canvas || this.document.createElement("canvas");
	    if (!this.options.canvas) {
	        this.canvas.width = width;
	        this.canvas.height = height;
	    }
	    this.ctx = this.canvas.getContext("2d");
	    this.taintCtx = this.document.createElement("canvas").getContext("2d");
	    this.ctx.textBaseline = "bottom";
	    this.variables = {};
	    log("Initialized CanvasRenderer with size", width, "x", height);
	}

	CanvasRenderer.prototype = Object.create(Renderer.prototype);

	CanvasRenderer.prototype.setFillStyle = function(fillStyle) {
	    this.ctx.fillStyle = typeof(fillStyle) === "object" && !!fillStyle.isColor ? fillStyle.toString() : fillStyle;
	    return this.ctx;
	};

	CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) {
	    this.setFillStyle(color).fillRect(left, top, width, height);
	};

	CanvasRenderer.prototype.circle = function(left, top, size, color) {
	    this.setFillStyle(color);
	    this.ctx.beginPath();
	    this.ctx.arc(left + size / 2, top + size / 2, size / 2, 0, Math.PI*2, true);
	    this.ctx.closePath();
	    this.ctx.fill();
	};

	CanvasRenderer.prototype.circleStroke = function(left, top, size, color, stroke, strokeColor) {
	    this.circle(left, top, size, color);
	    this.ctx.strokeStyle = strokeColor.toString();
	    this.ctx.stroke();
	};

	CanvasRenderer.prototype.drawShape = function(shape, color) {
	    this.shape(shape);
	    this.setFillStyle(color).fill();
	};

	CanvasRenderer.prototype.taints = function(imageContainer) {
	    if (imageContainer.tainted === null) {
	        this.taintCtx.drawImage(imageContainer.image, 0, 0);
	        try {
	            this.taintCtx.getImageData(0, 0, 1, 1);
	            imageContainer.tainted = false;
	        } catch(e) {
	            this.taintCtx = document.createElement("canvas").getContext("2d");
	            imageContainer.tainted = true;
	        }
	    }

	    return imageContainer.tainted;
	};

	CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) {
	    if (!this.taints(imageContainer) || this.options.allowTaint) {
	        this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh);
	    }
	};

	CanvasRenderer.prototype.clip = function(shapes, callback, context) {
	    this.ctx.save();
	    shapes.filter(hasEntries).forEach(function(shape) {
	        this.shape(shape).clip();
	    }, this);
	    callback.call(context);
	    this.ctx.restore();
	};

	CanvasRenderer.prototype.shape = function(shape) {
	    this.ctx.beginPath();
	    shape.forEach(function(point, index) {
	        if (point[0] === "rect") {
	            this.ctx.rect.apply(this.ctx, point.slice(1));
	        } else {
	            this.ctx[(index === 0) ? "moveTo" : point[0] + "To" ].apply(this.ctx, point.slice(1));
	        }
	    }, this);
	    this.ctx.closePath();
	    return this.ctx;
	};

	CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) {
	    this.setFillStyle(color).font = [style, variant, weight, size, family].join(" ").split(",")[0];
	};

	CanvasRenderer.prototype.fontShadow = function(color, offsetX, offsetY, blur) {
	    this.setVariable("shadowColor", color.toString())
	        .setVariable("shadowOffsetY", offsetX)
	        .setVariable("shadowOffsetX", offsetY)
	        .setVariable("shadowBlur", blur);
	};

	CanvasRenderer.prototype.clearShadow = function() {
	    this.setVariable("shadowColor", "rgba(0,0,0,0)");
	};

	CanvasRenderer.prototype.setOpacity = function(opacity) {
	    this.ctx.globalAlpha = opacity;
	};

	CanvasRenderer.prototype.setTransform = function(transform) {
	    this.ctx.translate(transform.origin[0], transform.origin[1]);
	    this.ctx.transform.apply(this.ctx, transform.matrix);
	    this.ctx.translate(-transform.origin[0], -transform.origin[1]);
	};

	CanvasRenderer.prototype.setVariable = function(property, value) {
	    if (this.variables[property] !== value) {
	        this.variables[property] = this.ctx[property] = value;
	    }

	    return this;
	};

	CanvasRenderer.prototype.text = function(text, left, bottom) {
	    this.ctx.fillText(text, left, bottom);
	};

	CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, size, bounds, left, top, width, height, borderData) {
	    var shape = [
	        ["line", Math.round(left), Math.round(top)],
	        ["line", Math.round(left + width), Math.round(top)],
	        ["line", Math.round(left + width), Math.round(height + top)],
	        ["line", Math.round(left), Math.round(height + top)]
	    ];
	    this.clip([shape], function() {
	        this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
	    }, this);
	};

	CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, size, bounds, borderLeft, borderTop) {
	    var offsetX = Math.round(bounds.left + backgroundPosition.left + borderLeft), offsetY = Math.round(bounds.top + backgroundPosition.top + borderTop);
	    this.setFillStyle(this.ctx.createPattern(this.resizeImage(imageContainer, size), "repeat"));
	    this.ctx.translate(offsetX, offsetY);
	    this.ctx.fill();
	    this.ctx.translate(-offsetX, -offsetY);
	};

	CanvasRenderer.prototype.renderBackgroundGradient = function(gradientImage, bounds) {
	    if (gradientImage instanceof LinearGradientContainer) {
	        var gradient = this.ctx.createLinearGradient(
	            bounds.left + bounds.width * gradientImage.x0,
	            bounds.top + bounds.height * gradientImage.y0,
	            bounds.left +  bounds.width * gradientImage.x1,
	            bounds.top +  bounds.height * gradientImage.y1);
	        gradientImage.colorStops.forEach(function(colorStop) {
	            gradient.addColorStop(colorStop.stop, colorStop.color.toString());
	        });
	        this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, gradient);
	    }
	};

	CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
	    var image = imageContainer.image;
	    if(image.width === size.width && image.height === size.height) {
	        return image;
	    }

	    var ctx, canvas = document.createElement('canvas');
	    canvas.width = size.width;
	    canvas.height = size.height;
	    ctx = canvas.getContext("2d");
	    ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );
	    return canvas;
	};

	function hasEntries(array) {
	    return array.length > 0;
	}

	module.exports = CanvasRenderer;

	},{"../lineargradientcontainer":14,"../log":15,"../renderer":22}],24:[function(require,module,exports){
	var NodeContainer = require('./nodecontainer');

	function StackingContext(hasOwnStacking, opacity, element, parent) {
	    NodeContainer.call(this, element, parent);
	    this.ownStacking = hasOwnStacking;
	    this.contexts = [];
	    this.children = [];
	    this.opacity = (this.parent ? this.parent.stack.opacity : 1) * opacity;
	}

	StackingContext.prototype = Object.create(NodeContainer.prototype);

	StackingContext.prototype.getParentStack = function(context) {
	    var parentStack = (this.parent) ? this.parent.stack : null;
	    return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;
	};

	module.exports = StackingContext;

	},{"./nodecontainer":16}],25:[function(require,module,exports){
	function Support(document) {
	    this.rangeBounds = this.testRangeBounds(document);
	    this.cors = this.testCORS();
	    this.svg = this.testSVG();
	}

	Support.prototype.testRangeBounds = function(document) {
	    var range, testElement, rangeBounds, rangeHeight, support = false;

	    if (document.createRange) {
	        range = document.createRange();
	        if (range.getBoundingClientRect) {
	            testElement = document.createElement('boundtest');
	            testElement.style.height = "123px";
	            testElement.style.display = "block";
	            document.body.appendChild(testElement);

	            range.selectNode(testElement);
	            rangeBounds = range.getBoundingClientRect();
	            rangeHeight = rangeBounds.height;

	            if (rangeHeight === 123) {
	                support = true;
	            }
	            document.body.removeChild(testElement);
	        }
	    }

	    return support;
	};

	Support.prototype.testCORS = function() {
	    return typeof((new Image()).crossOrigin) !== "undefined";
	};

	Support.prototype.testSVG = function() {
	    var img = new Image();
	    var canvas = document.createElement("canvas");
	    var ctx =  canvas.getContext("2d");
	    img.src = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>";

	    try {
	        ctx.drawImage(img, 0, 0);
	        canvas.toDataURL();
	    } catch(e) {
	        return false;
	    }
	    return true;
	};

	module.exports = Support;

	},{}],26:[function(require,module,exports){
	var Promise = require('./promise');
	var XHR = require('./xhr');
	var decode64 = require('./utils').decode64;

	function SVGContainer(src) {
	    this.src = src;
	    this.image = null;
	    var self = this;

	    this.promise = this.hasFabric().then(function() {
	        return (self.isInline(src) ? Promise.resolve(self.inlineFormatting(src)) : XHR(src));
	    }).then(function(svg) {
	        return new Promise(function(resolve) {
	            window.html2canvas.svg.fabric.loadSVGFromString(svg, self.createCanvas.call(self, resolve));
	        });
	    });
	}

	SVGContainer.prototype.hasFabric = function() {
	    return !window.html2canvas.svg || !window.html2canvas.svg.fabric ? Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg")) : Promise.resolve();
	};

	SVGContainer.prototype.inlineFormatting = function(src) {
	    return (/^data:image\/svg\+xml;base64,/.test(src)) ? this.decode64(this.removeContentType(src)) : this.removeContentType(src);
	};

	SVGContainer.prototype.removeContentType = function(src) {
	    return src.replace(/^data:image\/svg\+xml(;base64)?,/,'');
	};

	SVGContainer.prototype.isInline = function(src) {
	    return (/^data:image\/svg\+xml/i.test(src));
	};

	SVGContainer.prototype.createCanvas = function(resolve) {
	    var self = this;
	    return function (objects, options) {
	        var canvas = new window.html2canvas.svg.fabric.StaticCanvas('c');
	        self.image = canvas.lowerCanvasEl;
	        canvas
	            .setWidth(options.width)
	            .setHeight(options.height)
	            .add(window.html2canvas.svg.fabric.util.groupSVGElements(objects, options))
	            .renderAll();
	        resolve(canvas.lowerCanvasEl);
	    };
	};

	SVGContainer.prototype.decode64 = function(str) {
	    return (typeof(window.atob) === "function") ? window.atob(str) : decode64(str);
	};

	module.exports = SVGContainer;

	},{"./promise":18,"./utils":29,"./xhr":31}],27:[function(require,module,exports){
	var SVGContainer = require('./svgcontainer');
	var Promise = require('./promise');

	function SVGNodeContainer(node, _native) {
	    this.src = node;
	    this.image = null;
	    var self = this;

	    this.promise = _native ? new Promise(function(resolve, reject) {
	        self.image = new Image();
	        self.image.onload = resolve;
	        self.image.onerror = reject;
	        self.image.src = "data:image/svg+xml," + (new XMLSerializer()).serializeToString(node);
	        if (self.image.complete === true) {
	            resolve(self.image);
	        }
	    }) : this.hasFabric().then(function() {
	        return new Promise(function(resolve) {
	            window.html2canvas.svg.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve));
	        });
	    });
	}

	SVGNodeContainer.prototype = Object.create(SVGContainer.prototype);

	module.exports = SVGNodeContainer;

	},{"./promise":18,"./svgcontainer":26}],28:[function(require,module,exports){
	var NodeContainer = require('./nodecontainer');

	function TextContainer(node, parent) {
	    NodeContainer.call(this, node, parent);
	}

	TextContainer.prototype = Object.create(NodeContainer.prototype);

	TextContainer.prototype.applyTextTransform = function() {
	    this.node.data = this.transform(this.parent.css("textTransform"));
	};

	TextContainer.prototype.transform = function(transform) {
	    var text = this.node.data;
	    switch(transform){
	        case "lowercase":
	            return text.toLowerCase();
	        case "capitalize":
	            return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
	        case "uppercase":
	            return text.toUpperCase();
	        default:
	            return text;
	    }
	};

	function capitalize(m, p1, p2) {
	    if (m.length > 0) {
	        return p1 + p2.toUpperCase();
	    }
	}

	module.exports = TextContainer;

	},{"./nodecontainer":16}],29:[function(require,module,exports){
	exports.smallImage = function smallImage() {
	    return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
	};

	exports.bind = function(callback, context) {
	    return function() {
	        return callback.apply(context, arguments);
	    };
	};

	/*
	 * base64-arraybuffer
	 * https://github.com/niklasvh/base64-arraybuffer
	 *
	 * Copyright (c) 2012 Niklas von Hertzen
	 * Licensed under the MIT license.
	 */

	exports.decode64 = function(base64) {
	    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	    var len = base64.length, i, encoded1, encoded2, encoded3, encoded4, byte1, byte2, byte3;

	    var output = "";

	    for (i = 0; i < len; i+=4) {
	        encoded1 = chars.indexOf(base64[i]);
	        encoded2 = chars.indexOf(base64[i+1]);
	        encoded3 = chars.indexOf(base64[i+2]);
	        encoded4 = chars.indexOf(base64[i+3]);

	        byte1 = (encoded1 << 2) | (encoded2 >> 4);
	        byte2 = ((encoded2 & 15) << 4) | (encoded3 >> 2);
	        byte3 = ((encoded3 & 3) << 6) | encoded4;
	        if (encoded3 === 64) {
	            output += String.fromCharCode(byte1);
	        } else if (encoded4 === 64 || encoded4 === -1) {
	            output += String.fromCharCode(byte1, byte2);
	        } else{
	            output += String.fromCharCode(byte1, byte2, byte3);
	        }
	    }

	    return output;
	};

	exports.getBounds = function(node) {
	    if (node.getBoundingClientRect) {
	        var clientRect = node.getBoundingClientRect();
	        var width = node.offsetWidth == null ? clientRect.width : node.offsetWidth;
	        return {
	            top: clientRect.top,
	            bottom: clientRect.bottom || (clientRect.top + clientRect.height),
	            right: clientRect.left + width,
	            left: clientRect.left,
	            width:  width,
	            height: node.offsetHeight == null ? clientRect.height : node.offsetHeight
	        };
	    }
	    return {};
	};

	exports.offsetBounds = function(node) {
	    var parent = node.offsetParent ? exports.offsetBounds(node.offsetParent) : {top: 0, left: 0};

	    return {
	        top: node.offsetTop + parent.top,
	        bottom: node.offsetTop + node.offsetHeight + parent.top,
	        right: node.offsetLeft + parent.left + node.offsetWidth,
	        left: node.offsetLeft + parent.left,
	        width: node.offsetWidth,
	        height: node.offsetHeight
	    };
	};

	exports.parseBackgrounds = function(backgroundImage) {
	    var whitespace = ' \r\n\t',
	        method, definition, prefix, prefix_i, block, results = [],
	        mode = 0, numParen = 0, quote, args;
	    var appendResult = function() {
	        if(method) {
	            if (definition.substr(0, 1) === '"') {
	                definition = definition.substr(1, definition.length - 2);
	            }
	            if (definition) {
	                args.push(definition);
	            }
	            if (method.substr(0, 1) === '-' && (prefix_i = method.indexOf('-', 1 ) + 1) > 0) {
	                prefix = method.substr(0, prefix_i);
	                method = method.substr(prefix_i);
	            }
	            results.push({
	                prefix: prefix,
	                method: method.toLowerCase(),
	                value: block,
	                args: args,
	                image: null
	            });
	        }
	        args = [];
	        method = prefix = definition = block = '';
	    };
	    args = [];
	    method = prefix = definition = block = '';
	    backgroundImage.split("").forEach(function(c) {
	        if (mode === 0 && whitespace.indexOf(c) > -1) {
	            return;
	        }
	        switch(c) {
	        case '"':
	            if(!quote) {
	                quote = c;
	            } else if(quote === c) {
	                quote = null;
	            }
	            break;
	        case '(':
	            if(quote) {
	                break;
	            } else if(mode === 0) {
	                mode = 1;
	                block += c;
	                return;
	            } else {
	                numParen++;
	            }
	            break;
	        case ')':
	            if (quote) {
	                break;
	            } else if(mode === 1) {
	                if(numParen === 0) {
	                    mode = 0;
	                    block += c;
	                    appendResult();
	                    return;
	                } else {
	                    numParen--;
	                }
	            }
	            break;

	        case ',':
	            if (quote) {
	                break;
	            } else if(mode === 0) {
	                appendResult();
	                return;
	            } else if (mode === 1) {
	                if (numParen === 0 && !method.match(/^url$/i)) {
	                    args.push(definition);
	                    definition = '';
	                    block += c;
	                    return;
	                }
	            }
	            break;
	        }

	        block += c;
	        if (mode === 0) {
	            method += c;
	        } else {
	            definition += c;
	        }
	    });

	    appendResult();
	    return results;
	};

	},{}],30:[function(require,module,exports){
	var GradientContainer = require('./gradientcontainer');

	function WebkitGradientContainer(imageData) {
	    GradientContainer.apply(this, arguments);
	    this.type = (imageData.args[0] === "linear") ? this.TYPES.LINEAR : this.TYPES.RADIAL;
	}

	WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype);

	module.exports = WebkitGradientContainer;

	},{"./gradientcontainer":11}],31:[function(require,module,exports){
	var Promise = require('./promise');

	function XHR(url) {
	    return new Promise(function(resolve, reject) {
	        var xhr = new XMLHttpRequest();
	        xhr.open('GET', url);

	        xhr.onload = function() {
	            if (xhr.status === 200) {
	                resolve(xhr.responseText);
	            } else {
	                reject(new Error(xhr.statusText));
	            }
	        };

	        xhr.onerror = function() {
	            reject(new Error("Network Error"));
	        };

	        xhr.send();
	    });
	}

	module.exports = XHR;

	},{"./promise":18}]},{},[6])(6)
	});
	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

/***/ },

/***/ 593:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * BehaviorsLookup holds all behaviors and is used by Marionette to glue
	 * Behavior instances to Views
	 *
	 * For more check: http://marionettejs.com/docs/marionette.behaviors.html#behaviorslookup
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (BackboneMarionette) {
	  var Marionette = BackboneMarionette;
	  var BehaviorsLookup = {};
	  Marionette.Behaviors.behaviorsLookup = function () {
	    return BehaviorsLookup;
	  };

	  window.BehaviorsLookup = BehaviorsLookup;

	  return BehaviorsLookup;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 594:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Color Picker behavior
	 *
	 * Adds a color picker integration with the view
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(593),
	  __webpack_require__(276),
	  __webpack_require__(575)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, BehaviorsLookup, MailPoet) {
	  var BL = BehaviorsLookup;

	  BL.ColorPickerBehavior = Marionette.Behavior.extend({
	    onRender: function () {
	      var that = this;
	      var preferredFormat = 'hex6';
	      this.view.$('.mailpoet_color').each(function () {
	        var $input = that.view.$(this);
	        var updateColorInput = function (color) {
	          if (color && color.getAlpha() > 0) {
	            $input.val(color.toString(preferredFormat));
	          } else {
	            $input.val('');
	          }
	          $input.trigger('change');
	        };
	        $input.spectrum({
	          clickoutFiresChange: true,
	          showInput: true,
	          showInitial: true,
	          showPalette: true,
	          showSelectionPalette: true,
	          palette: [],
	          localStorageKey: 'newsletter_editor.spectrum.palette',
	          preferredFormat: preferredFormat,
	          allowEmpty: true,
	          chooseText: MailPoet.I18n.t('selectColor'),
	          cancelText: MailPoet.I18n.t('cancelColorSelection'),
	          change: updateColorInput,
	          move: updateColorInput,
	          hide: updateColorInput
	        });
	      });
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 595:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;
	/**
	 * ContainerDropZoneBehavior
	 *
	 * A receiving behavior for drag&drop.
	 * Allows CollectionView instances that use this behavior to act as drop zones and
	 * accept droppables
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(280),
	  __webpack_require__(275),
	  __webpack_require__(593),
	  __webpack_require__(574)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, _, jQuery, BL, interact) {
	  var BehaviorsLookup = BL;

	  BehaviorsLookup.ContainerDropZoneBehavior = Marionette.Behavior.extend({
	    defaults: {
	      columnLimit: 3
	    },
	    onRender: function () {
	      var dragAndDropDisabled = _.isObject(this.view.options.renderOptions) && this.view.options.renderOptions.disableDragAndDrop === true;
	      if (!dragAndDropDisabled) {
	        this.addDropZone();
	      }
	    },
	    addDropZone: function () {
	      var that = this;
	      var view = this.view;
	      var domElement = that.$el.get(0);
	      var acceptableElementSelector;

	      // TODO: Extract this limitation code to be controlled from containers
	      if (this.view.renderOptions.depth === 0) {
	        // Root level accept. Allow only layouts
	        acceptableElementSelector = '.mailpoet_droppable_block.mailpoet_droppable_layout_block';
	      } else if (this.view.renderOptions.depth === 2) {
	        // Column level accept. Disallow layouts, allow only content blocks
	        acceptableElementSelector = '.mailpoet_droppable_block:not(.mailpoet_droppable_layout_block)';
	      } else {
	        // Layout section container level. Allow nothing.
	        return;
	      }

	      // TODO: Simplify target block identification, remove special insertion support
	      interact(domElement).dropzone({
	        accept: acceptableElementSelector,
	        overlap: 'pointer', // Mouse pointer denotes location of a droppable
	        ondragenter: function () {
	          // 1. Visually mark block as active for dropping
	          view.$el.addClass('mailpoet_drop_active');
	        },
	        ondragleave: function () {
	          // 1. Remove visual markings of active dropping container
	          // 2. Remove visual markings of drop position visualization
	          that.cleanup();
	        },
	        ondropmove: function (event) {
	          // 1. Compute actual location of the mouse within the container
	          // 2. Check if insertion is regular (between blocks) or special (with container insertion)
	          // 3a. If insertion is regular, compute position where insertion should happen
	          // 3b. If insertion is special, compute position (which side) and which cell the insertion belongs to
	          // 4. If insertion at that position is not visualized, display position visualization there, remove other visualizations from this container
	          var dropPosition = that.getDropPosition(
	            event.dragmove.pageX,
	            event.dragmove.pageY,
	            view.$el,
	            view.model.get('orientation'),
	            view.model.get('blocks').length
	          );
	          var element = view.$el;
	          var markerWidth = '';
	          var markerHeight = '';
	          var containerOffset = element.offset();
	          var viewCollection = that.getCollection();
	          var marker;
	          var targetModel;
	          var targetView;
	          var targetElement;
	          var topOffset;
	          var leftOffset;
	          var isLastBlockInsertion;
	          var $targetBlock;
	          var margin;

	          if (dropPosition === undefined) return;

	          element.find('.mailpoet_drop_marker').remove();

	          // Allow empty collections to handle their own drop marking
	          if (viewCollection.isEmpty()) return;

	          if (viewCollection.length === 0) {
	            targetElement = element.find(view.childViewContainer);
	            topOffset = targetElement.offset().top - element.offset().top;
	            leftOffset = targetElement.offset().left - element.offset().left;
	            markerWidth = targetElement.width();
	            markerHeight = targetElement.height();
	          } else {
	            isLastBlockInsertion = that.getCollection().length === dropPosition.index;
	            targetModel = isLastBlockInsertion ? viewCollection.at(dropPosition.index - 1) : viewCollection.at(dropPosition.index);

	            targetView = that.getChildren().findByModel(targetModel);
	            targetElement = targetView.$el;

	            topOffset = targetElement.offset().top - containerOffset.top;
	            leftOffset = targetElement.offset().left - containerOffset.left;
	            if (dropPosition.insertionType === 'normal') {
	              if (dropPosition.position === 'after') {
	                // Move the marker to the opposite side of the block
	                if (view.model.get('orientation') === 'vertical') {
	                  topOffset += targetElement.outerHeight(true);
	                } else {
	                  leftOffset += targetElement.outerWidth();
	                }
	              }

	              if (view.model.get('orientation') === 'vertical') {
	                markerWidth = targetElement.outerWidth();
	              } else {
	                markerHeight = targetElement.outerHeight();
	              }
	            } else {
	              if (dropPosition.position === 'after') {
	                // Move the marker to the opposite side of the block
	                if (view.model.get('orientation') === 'vertical') {
	                  leftOffset += targetElement.outerWidth();
	                } else {
	                  topOffset += targetElement.outerHeight();
	                }
	              }

	              if (view.model.get('orientation') === 'vertical') {
	                markerHeight = targetElement.outerHeight(true);
	              } else {
	                markerWidth = targetElement.outerWidth(true);
	              }
	            }
	          }

	          marker = jQuery('<div class="mailpoet_drop_marker"></div>');
	          // Add apropriate CSS classes for position refinement with CSS
	          if (dropPosition.index === 0) {
	            marker.addClass('mailpoet_drop_marker_first');
	          }
	          if (viewCollection.length - 1 === dropPosition.index) {
	            marker.addClass('mailpoet_drop_marker_last');
	          }
	          if (dropPosition.index > 0 && viewCollection.length - 1 > dropPosition.index) {
	            marker.addClass('mailpoet_drop_marker_middle');
	          }
	          marker.addClass('mailpoet_drop_marker_' + dropPosition.position);

	          // Compute margin (optional for each block) that needs to be
	          // compensated for to position marker right in the middle of two
	          // blocks
	          if (dropPosition.position === 'before') {
	            $targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index - 1)).$el;
	          } else {
	            $targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index)).$el;
	          }
	          margin = $targetBlock.outerHeight(true) - $targetBlock.outerHeight();

	          marker.css('top', topOffset - (margin / 2));
	          marker.css('left', leftOffset);
	          marker.css('width', markerWidth);
	          marker.css('height', markerHeight);

	          element.append(marker);
	        },
	        ondrop: function (event) {
	          // 1. Compute actual location of the mouse
	          // 2. Check if insertion is regular (between blocks) or special (with container insertion)
	          // 3a. If insertion is regular
	          //     3a1. compute position where insertion should happen
	          //     3a2. insert the drop model there
	          // 3b. If insertion is special
	          //     3b1. compute position (which side) and which cell the insertion belongs to
	          //     3b2. remove element at that position from the collection
	          //     3b3. create a new collection, insert the removed element to it
	          //     3b4. insert the droppable model at the start or end of the new collection, depending on 3b1. position
	          //     3b5. insert the new collection into the old collection to cell from 3b1.
	          // 4. Perform cleanup actions

	          var dropPosition = that.getDropPosition(
	            event.dragEvent.pageX,
	            event.dragEvent.pageY,
	            view.$el,
	            view.model.get('orientation'),
	            view.model.get('blocks').length
	          );
	          var droppableModel = event.draggable.getDropModel();
	          var viewCollection = that.getCollection();
	          var droppedView;
	          var index;
	          var tempCollection;
	          var tempCollection2;
	          var tempModel;

	          if (dropPosition === undefined) return;

	          if (dropPosition.insertionType === 'normal') {
	            // Normal insertion of dropModel into existing collection
	            index = (dropPosition.position === 'after') ? dropPosition.index + 1 : dropPosition.index;

	            if (view.model.get('orientation') === 'horizontal' && droppableModel.get('type') !== 'container') {
	              // Regular blocks always need to be inserted into columns - vertical containers

	              tempCollection = new (window.EditorApplication.getBlockTypeModel('container'))({
	                orientation: 'vertical'
	              });
	              tempCollection.get('blocks').add(droppableModel);
	              viewCollection.add(tempCollection, { at: index });
	            } else {
	              viewCollection.add(droppableModel, { at: index });
	            }

	            droppedView = that.getChildren().findByModel(droppableModel);
	          } else {
	            // Special insertion by replacing target block with collection
	            // and inserting dropModel into that
	            tempModel = viewCollection.at(dropPosition.index);

	            tempCollection = new (window.EditorApplication.getBlockTypeModel('container'))({
	              orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical'
	            });

	            viewCollection.remove(tempModel);

	            if (tempCollection.get('orientation') === 'horizontal') {
	              if (dropPosition.position === 'before') {
	                tempCollection2 = new (window.EditorApplication.getBlockTypeModel('container'))({
	                  orientation: 'vertical'
	                });
	                tempCollection2.get('blocks').add(droppableModel);
	                tempCollection.get('blocks').add(tempCollection2);
	              }
	              tempCollection2 = new (window.EditorApplication.getBlockTypeModel('container'))({
	                orientation: 'vertical'
	              });
	              tempCollection2.get('blocks').add(tempModel);
	              tempCollection.get('blocks').add(tempCollection2);
	              if (dropPosition.position === 'after') {
	                tempCollection2 = new (window.EditorApplication.getBlockTypeModel('container'))({
	                  orientation: 'vertical'
	                });
	                tempCollection2.get('blocks').add(droppableModel);
	                tempCollection.get('blocks').add(tempCollection2);
	              }
	            } else {
	              if (dropPosition.position === 'before') {
	                tempCollection.get('blocks').add(droppableModel);
	              }
	              tempCollection.get('blocks').add(tempModel);
	              if (dropPosition.position === 'after') {
	                tempCollection.get('blocks').add(droppableModel);
	              }
	            }
	            viewCollection.add(tempCollection, { at: dropPosition.index });

	            // Call post add actions
	            droppedView = that.getChildren().findByModel(tempCollection).children.findByModel(droppableModel);
	          }

	          // Call post add actions
	          event.draggable.onDrop({
	            dropBehavior: that,
	            droppedModel: droppableModel,
	            droppedView: droppedView
	          });

	          that.cleanup();
	        }
	      });
	    },
	    cleanup: function () {
	      // 1. Remove visual markings of active dropping container
	      this.view.$el.removeClass('mailpoet_drop_active');

	      // 2. Remove visual markings of drop position visualization
	      this.view.$('.mailpoet_drop_marker').remove();
	    },
	    getDropPosition: function (eventX, eventY, is_unsafe) {
	      var SPECIAL_AREA_INSERTION_WIDTH = 0.00; // Disable special insertion. Default: 0.3

	      var element = this.view.$el;
	      var orientation = this.view.model.get('orientation');

	      var elementOffset = element.offset();
	      var elementPageX = elementOffset.left;
	      var elementPageY = elementOffset.top;
	      var elementWidth = element.outerWidth(true);
	      var elementHeight = element.outerHeight(true);

	      var relativeX = eventX - elementPageX;
	      var relativeY = eventY - elementPageY;

	      var relativeOffset;
	      var elementLength;

	      var canAcceptNormalInsertion = this._canAcceptNormalInsertion();
	      var canAcceptSpecialInsertion = this._canAcceptSpecialInsertion();

	      var insertionType;
	      var index;
	      var position;
	      var indexAndPosition;

	      var unsafe = !!is_unsafe;

	      if (this.getCollection().length === 0) {
	        return {
	          insertionType: 'normal',
	          index: 0,
	          position: 'inside'
	        };
	      }

	      if (orientation === 'vertical') {
	        relativeOffset = relativeX;
	        elementLength = elementWidth;
	      } else {
	        relativeOffset = relativeY;
	        elementLength = elementHeight;
	      }

	      if (canAcceptSpecialInsertion && !canAcceptNormalInsertion) {
	        // If normal insertion is not available, dedicate whole element area
	        // to special insertion
	        SPECIAL_AREA_INSERTION_WIDTH = 0.5;
	      }

	      if (relativeOffset <= elementLength * SPECIAL_AREA_INSERTION_WIDTH && (unsafe || canAcceptSpecialInsertion)) {
	        insertionType = 'special';
	        position = 'before';
	        index = this._computeSpecialIndex(eventX, eventY);
	      } else if (relativeOffset > elementLength * (1 - SPECIAL_AREA_INSERTION_WIDTH) && (unsafe || canAcceptSpecialInsertion)) {
	        insertionType = 'special';
	        position = 'after';
	        index = this._computeSpecialIndex(eventX, eventY);
	      } else {
	        indexAndPosition = this._computeNormalIndex(eventX, eventY);
	        insertionType = 'normal';
	        position = indexAndPosition.position;
	        index = indexAndPosition.index;
	      }

	      if (!unsafe && orientation === 'vertical' && insertionType === 'special' && this.getCollection().at(index).get('orientation') === 'horizontal') {
	        // Prevent placing horizontal container in another horizontal container,
	        // which would allow breaking the column limit.
	        // Switch that to normal insertion
	        indexAndPosition = this._computeNormalIndex(eventX, eventY);
	        insertionType = 'normal';
	        position = indexAndPosition.position;
	        index = indexAndPosition.index;
	      }

	      if (orientation === 'horizontal' && insertionType === 'special') {
	        // Disable special insertion for horizontal containers
	        return;
	      }

	      return {
	        insertionType: insertionType, // 'normal'|'special'
	        index: index,
	        position: position // 'inside'|'before'|'after'
	      };
	    },
	    _computeNormalIndex: function (eventX, eventY) {
	      // Normal insertion inserts dropModel before target element if
	      // event happens on the first half of the element and after the
	      // target element if event happens on the second half of the element.
	      // Halves depend on orientation.

	      var index = this._computeCellIndex(eventX, eventY);
	      // TODO: Handle case when there are no children, container is empty
	      var targetView = this.getChildren().findByModel(this.getCollection().at(index));
	      var orientation = this.view.model.get('orientation');
	      var element = targetView.$el;
	      var eventOffset;
	      var closeOffset;
	      var elementDimension;

	      if (orientation === 'vertical') {
	        eventOffset = eventY;
	        closeOffset = element.offset().top;
	        elementDimension = element.outerHeight(true);
	      } else {
	        eventOffset = eventX;
	        closeOffset = element.offset().left;
	        elementDimension = element.outerWidth(true);
	      }

	      if (eventOffset <= closeOffset + elementDimension / 2) {
	        // First half of the element
	        return {
	          index: index,
	          position: 'before'
	        };
	      } else {
	        // Second half of the element
	        return {
	          index: index,
	          position: 'after'
	        };
	      }
	    },
	    _computeSpecialIndex: function (eventX, eventY) {
	      return this._computeCellIndex(eventX, eventY);
	    },
	    _computeCellIndex: function (eventX, eventY) {
	      var orientation = this.view.model.get('orientation');
	      var eventOffset = (orientation === 'vertical') ? eventY : eventX;
	      var resultView = this.getChildren().find(function (view) {
	        var element = view.$el;
	        var closeOffset;
	        var farOffset;

	        if (orientation === 'vertical') {
	          closeOffset = element.offset().top;
	          farOffset = element.outerHeight(true);
	        } else {
	          closeOffset = element.offset().left;
	          farOffset = element.outerWidth(true);
	        }
	        farOffset += closeOffset;

	        return closeOffset <= eventOffset && eventOffset <= farOffset;
	      });

	      var index = (typeof resultView === 'object') ? resultView._index : 0;

	      return index;
	    },
	    _canAcceptNormalInsertion: function () {
	      var orientation = this.view.model.get('orientation');
	      var depth = this.view.renderOptions.depth;
	      var childCount = this.getChildren().length;
	      // Note that depth is zero indexed. Root container has depth=0
	      return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit);
	    },
	    _canAcceptSpecialInsertion: function () {
	      var orientation = this.view.model.get('orientation');
	      var depth = this.view.renderOptions.depth;
	      var childCount = this.getChildren().length;
	      return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit);
	    },
	    getCollectionView: function () {
	      return this.view.getChildView('blocks');
	    },
	    getChildren: function () {
	      return this.getCollectionView().children;
	    },
	    getCollection: function () {
	      return this.getCollectionView().collection;
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 596:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * DraggableBehavior
	 *
	 * Allows View instances to be draggable.
	 * Part of the drag&drop behavior.
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(280),
	  __webpack_require__(275),
	  __webpack_require__(593),
	  __webpack_require__(574)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, _, jQuery, BehaviorsLookup, interact) {
	  var BL = BehaviorsLookup;

	  BL.DraggableBehavior = Marionette.Behavior.extend({
	    defaults: {
	      cloneOriginal: false,
	      hideOriginal: false,
	      ignoreSelector: '.mailpoet_ignore_drag, .mailpoet_ignore_drag *',
	      onDragSubstituteBy: undefined,
	      /**
	       * Constructs a model that will be passed to the receiver on drop
	       *
	       * @return Backbone.Model A model that will be passed to the receiver
	       */
	      getDropModel: function () {
	        throw "Missing 'drop' function for DraggableBehavior";
	      },

	      onDrop: function () {},
	      testAttachToInstance: function () { return true; }
	    },
	    onRender: function () {
	      var that = this;
	      var interactable;

	      // Give instances more control over whether Draggable should be applied
	      if (!this.options.testAttachToInstance(this.view.model, this.view)) return;

	      interactable = interact(this.$el.get(0), {
	        ignoreFrom: this.options.ignoreSelector
	      }).draggable({
	        // allow dragging of multple elements at the same time
	        max: Infinity,

	        // Scroll when dragging near edges of a window
	        autoScroll: true,

	        onstart: function (startEvent) {
	          var event = startEvent;
	          var centerXOffset;
	          var centerYOffset;
	          var tempClone;
	          var clone;
	          var $clone;

	          if (that.options.cloneOriginal === true) {
	            // Use substitution instead of a clone
	            tempClone = (_.isFunction(that.options.onDragSubstituteBy)) ? that.options.onDragSubstituteBy(that) : undefined;
	            // Or use a clone
	            clone = tempClone || event.target.cloneNode(true);
	            jQuery(event.target);
	            $clone = jQuery(clone);

	            $clone.addClass('mailpoet_droppable_active');
	            $clone.css('position', 'absolute');
	            $clone.css('top', 0);
	            $clone.css('left', 0);
	            document.body.appendChild(clone);

	            // Position the clone over the target element with a slight
	            // offset to center the clone under the mouse cursor.
	            // Accurate dimensions can only be taken after insertion to document
	            centerXOffset = $clone.width() / 2;
	            centerYOffset = $clone.height() / 2;
	            $clone.css('top', event.pageY - centerYOffset);
	            $clone.css('left', event.pageX - centerXOffset);

	            event.interaction.element = clone;


	            if (that.options.hideOriginal === true) {
	              that.view.$el.addClass('mailpoet_hidden');
	            }
	          }

	        },
	        // call this function on every dragmove event
	        onmove: function (event) {
	          var target = event.target;
	          // keep the dragged position in the data-x/data-y attributes
	          var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
	          var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

	          // translate the element
	          target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
	          target.style.webkitTransform = target.style.transform;

	          // update the posiion attributes
	          target.setAttribute('data-x', x);
	          target.setAttribute('data-y', y);
	        },
	        onend: function (event) {
	          var target = event.target;
	          target.style.transform = '';
	          target.style.webkitTransform = target.style.transform;
	          target.removeAttribute('data-x');
	          target.removeAttribute('data-y');
	          jQuery(event.interaction.element).addClass('mailpoet_droppable_active');

	          if (that.options.cloneOriginal === true) {
	            jQuery(target).remove();

	            if (that.options.hideOriginal === true) {
	              that.view.$el.removeClass('mailpoet_hidden');
	            }
	          }
	        }
	      })
	      .preventDefault('auto')
	      .styleCursor(false)
	      .actionChecker(function (pointer, event, action) {
	        // Disable dragging with right click
	        if (event.button !== 0) {
	          return null;
	        }

	        return action;
	      });

	      if (this.options.drop !== undefined) {
	        interactable.getDropModel = this.options.drop;
	      } else {
	        interactable.getDropModel = this.view.getDropFunc();
	      }
	      interactable.onDrop = function (opts) {
	        var options = opts;
	        if (_.isObject(options)) {
	          // Inject Draggable behavior if possible
	          options.dragBehavior = that;
	        }
	        // Delegate to view's event handler
	        that.options.onDrop.apply(that, [options]);
	      };
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 597:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Highlight Container Behavior
	 *
	 * Highlights a container block when hovering over its tools
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(593)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, BehaviorsLookup) {
	  var BL = BehaviorsLookup;

	  BL.HighlightContainerBehavior = Marionette.Behavior.extend({
	    events: {
	      'mouseenter @ui.tools': 'enableHighlight',
	      'mouseleave @ui.tools': 'disableHighlight'
	    },
	    enableHighlight: function () {
	      this.$el.addClass('mailpoet_highlight');
	    },
	    disableHighlight: function () {
	      if (!this.view._isBeingEdited) {
	        this.$el.removeClass('mailpoet_highlight');
	      }
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 598:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Highlight Editing Behavior
	 *
	 * Highlights a block that is being edited
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(593)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, BehaviorsLookup) {
	  var BL = BehaviorsLookup;

	  BL.HighlightEditingBehavior = Marionette.Behavior.extend({
	    modelEvents: {
	      startEditing: 'enableHighlight',
	      stopEditing: 'disableHighlight'
	    },
	    enableHighlight: function () {
	      this.view._isBeingEdited = true;
	      this.$el.addClass('mailpoet_highlight');
	    },
	    disableHighlight: function () {
	      this.view._isBeingEdited = false;
	      this.$el.removeClass('mailpoet_highlight');
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 599:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * ResizableBehavior
	 *
	 * Allows resizing elements within a block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(593),
	  __webpack_require__(574)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, BehaviorsLookup, interact) {
	  var BL = BehaviorsLookup;

	  BL.ResizableBehavior = Marionette.Behavior.extend({
	    defaults: {
	      elementSelector: null,
	      resizeHandleSelector: true, // true will use edges of the element itself
	      transformationFunction: function (y) { return y; }, // for blocks that use the default onResize function
	      minLength: 0,
	      maxLength: Infinity,
	      modelField: 'styles.block.height',
	      onResize: function (event) {
	        var currentLength = parseFloat(this.view.model.get(this.options.modelField));
	        var newLength = currentLength + this.options.transformationFunction(event.dy);
	        newLength = Math.min(this.options.maxLength, Math.max(this.options.minLength, newLength));
	        this.view.model.set(this.options.modelField, newLength + 'px');
	      }
	    },
	    events: {
	      mouseenter: 'showResizeHandle',
	      mouseleave: 'hideResizeHandle'
	    },
	    onRender: function () {
	      this.attachResize();

	      if (this.isBeingResized !== true) {
	        this.hideResizeHandle();
	      }
	    },
	    attachResize: function () {
	      var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0);
	      var that = this;
	      interact(domElement).resizable({
	        // axis: 'y',
	        edges: {
	          top: false,
	          left: false,
	          right: false,
	          bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector
	        }
	      })
	      .on('resizestart', function () {
	        that.isBeingResized = true;
	        that.$el.addClass('mailpoet_resize_active');
	      }).on('resizemove', function (event) {
	        var onResize = that.options.onResize.bind(that);
	        return onResize(event);
	      })
	      .on('resizeend', function () {
	        that.isBeingResized = null;
	        that.$el.removeClass('mailpoet_resize_active');
	      });
	    },
	    showResizeHandle: function () {
	      if (typeof this.options.resizeHandleSelector === 'string') {
	        this.view.$(this.options.resizeHandleSelector).removeClass('mailpoet_hidden');
	      }
	    },
	    hideResizeHandle: function () {
	      if (typeof this.options.resizeHandleSelector === 'string') {
	        this.view.$(this.options.resizeHandleSelector).addClass('mailpoet_hidden');
	      }
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 600:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * SortableBehavior
	 *
	 * Allows sorting elements within a collection
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(280),
	  __webpack_require__(593)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, _, BehaviorsLookup) {
	  var BL = BehaviorsLookup;

	  BL.SortableBehavior = Marionette.Behavior.extend({
	    onRender: function () {
	      var collection = this.view.collection;

	      if (_.isFunction(this.$el.sortable)) {
	        this.$el.sortable({
	          cursor: 'move',
	          start: function (event, ui) {
	            ui.item.data('previousIndex', ui.item.index());
	          },
	          end: function (event, ui) {
	            ui.item.removeData('previousIndex');
	          },
	          update: function (event, ui) {
	            var previousIndex = ui.item.data('previousIndex');
	            var newIndex = ui.item.index();
	            var model = collection.at(previousIndex);

	            // Replicate DOM changes. Move target model to a new position
	            // within the collection
	            collection.remove(model);
	            collection.add(model, { at: newIndex });
	          },
	          items: this.options.items
	        });
	      }
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 601:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Show Settings Behavior
	 *
	 * Opens up settings of a BlockView if contents are clicked upon
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(275),
	  __webpack_require__(593)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, jQuery, BehaviorsLookup) {
	  var BL = BehaviorsLookup;

	  BL.ShowSettingsBehavior = Marionette.Behavior.extend({
	    defaults: {
	      ignoreFrom: '' // selector
	    },
	    events: {
	      'click .mailpoet_content': 'showSettings'
	    },
	    showSettings: function (event) {
	      if (!this.isIgnoredElement(event.target)) {
	        this.view.triggerMethod('showSettings');
	      }
	    },
	    isIgnoredElement: function (element) {
	      return this.options.ignoreFrom
	        && this.options.ignoreFrom.length > 0
	        && jQuery(element).is(this.options.ignoreFrom);
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));



/***/ },

/***/ 602:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Text Editor Behavior
	 *
	 * Adds TinyMCE text editing capabilities to a view
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(571),
	  __webpack_require__(280),
	  __webpack_require__(593)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Marionette, _, BehaviorsLookup) {
	  var BL = BehaviorsLookup;

	  BL.TextEditorBehavior = Marionette.Behavior.extend({
	    defaults: {
	      selector: '.mailpoet_content',
	      toolbar1: 'bold italic link unlink forecolor mailpoet_shortcodes',
	      toolbar2: '',
	      validElements: 'p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br',
	      invalidElements: 'script',
	      blockFormats: 'Paragraph=p',
	      plugins: 'link textcolor colorpicker mailpoet_shortcodes',
	      configurationFilter: function (originalConfig) { return originalConfig; }
	    },
	    onDomRefresh: function () {
	      var that = this;
	      if (this.view.disableTextEditor === true) {
	        return;
	      }

	      this.$(this.options.selector).tinymce(this.options.configurationFilter({
	        inline: true,

	        menubar: false,
	        toolbar1: this.options.toolbar1,
	        toolbar2: this.options.toolbar2,

	        browser_spellcheck: true,

	        valid_elements: this.options.validElements,
	        invalid_elements: this.options.invalidElements,
	        block_formats: this.options.blockFormats,
	        relative_urls: false,
	        remove_script_host: false,
	        convert_urls: true,
	        urlconverter_callback: function (url) {
	          if (url.match(/\[.+\]/g)) {
	            // Do not convert URLs with shortcodes
	            return url;
	          }

	          return this.documentBaseURI.toAbsolute(
	            url,
	            this.settings.remove_script_host
	          );
	        },

	        plugins: this.options.plugins,

	        setup: function (editor) {
	          editor.on('change', function () {
	            that.view.triggerMethod('text:editor:change', editor.getContent());
	          });

	          editor.on('click', function (e) {
	            editor.focus();
	            if (that._isActivationClick) {
	              editor.selection.setRng(
	                  window.tinymce.dom.RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc())
	              );
	              that._isActivationClick = false;
	            }
	          });

	          editor.on('focus', function () {
	            that.view.triggerMethod('text:editor:focus');
	            that._isActivationClick = true;
	          });

	          editor.on('blur', function () {
	            that.view.triggerMethod('text:editor:blur');
	          });
	        }
	      }));
	    }
	  });
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 603:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Defines base classes for actual content blocks to extend.
	 * Extending content block modules need to at least extend
	 * a BlockModel and a BlockView.
	 * BlockToolsView, BlockSettingsView and BlockWidgetView are optional.
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(571),
	  __webpack_require__(573),
	  __webpack_require__(280),
	  __webpack_require__(275),
	  __webpack_require__(276),
	  __webpack_require__(564)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, Marionette, SuperModel, _, jQuery, MailPoet) {

	  'use strict';

	  var Module = {};
	  var AugmentedView = Marionette.View.extend({});

	  Module.BlockModel = SuperModel.extend({
	    stale: [], // Attributes to be removed upon saving
	    initialize: function () {
	      this.on('change', function () {
	        App.getChannel().trigger('autoSave');
	      });
	    },
	    _getDefaults: function (blockDefaults, configDefaults) {
	      var defaults = (_.isObject(configDefaults) && _.isFunction(configDefaults.toJSON)) ? configDefaults.toJSON() : configDefaults;

	      // Patch the resulting JSON object and fix it's constructors to be Object.
	      // Otherwise SuperModel interprets it not as a simpleObject
	      // and misbehaves
	      // TODO: Investigate for a better solution
	      return JSON.parse(JSON.stringify(jQuery.extend(blockDefaults, defaults || {})));
	    },
	    toJSON: function () {
	      // Remove stale attributes from resulting JSON object
	      return _.omit(SuperModel.prototype.toJSON.call(this), this.stale);
	    },
	    getChildren: function () {
	      return [];
	    }
	  });

	  Module.BlockView = AugmentedView.extend({
	    regions: {
	      toolsRegion: '> .mailpoet_tools'
	    },
	    modelEvents: {
	      change: 'render',
	      delete: 'deleteBlock',
	      duplicate: 'duplicateBlock'
	    },
	    events: {
	      mouseenter: 'showTools',
	      mouseleave: 'hideTools'
	    },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        hideOriginal: true,
	        onDrop: function (options) {
	          // After a clone of model has been dropped, cleanup
	          // and destroy self
	          options.dragBehavior.view.model.destroy();
	        },
	        onDragSubstituteBy: function (behavior) {
	          var WidgetView;
	          var node;
	          // When block is being dragged, display the widget icon instead.
	          // This will create an instance of block's widget view and
	          // use it's rendered DOM element instead of the content block
	          if (_.isFunction(behavior.view.onDragSubstituteBy)) {
	            WidgetView = new (behavior.view.onDragSubstituteBy())();
	            WidgetView.render();
	            node = WidgetView.$el.get(0).cloneNode(true);
	            WidgetView.destroy();
	            return node;
	          }
	        }
	      },
	      HighlightEditingBehavior: {}
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON(),
	        viewCid: this.cid
	      };
	    },
	    constructor: function () {
	      AugmentedView.apply(this, arguments);
	      this.$el.addClass('mailpoet_editor_view_' + this.cid);
	    },
	    initialize: function () {
	      this.on('showSettings', this.showSettings, this);
	      this.on('dom:refresh', this.showBlock, this);
	      this._isFirstRender = true;
	    },
	    showTools: function () {
	      if (!this.showingToolsDisabled) {
	        this.$('> .mailpoet_tools').addClass('mailpoet_display_tools');
	        this.toolsView.triggerMethod('showTools');
	      }
	    },
	    hideTools: function () {
	      this.$('> .mailpoet_tools').removeClass('mailpoet_display_tools');
	      this.toolsView.triggerMethod('hideTools');
	    },
	    enableShowingTools: function () {
	      this.showingToolsDisabled = false;
	    },
	    disableShowingTools: function () {
	      this.showingToolsDisabled = true;
	      this.hideTools();
	    },
	    showSettings: function (options) {
	      this.toolsView.triggerMethod('showSettings', options);
	    },
	    /**
	     * Defines drop behavior of BlockView instance
	     */
	    getDropFunc: function () {
	      return function () {
	        return this.model.clone();
	      }.bind(this);
	    },
	    disableDragging: function () {
	      this.$el.addClass('mailpoet_ignore_drag');
	    },
	    enableDragging: function () {
	      this.$el.removeClass('mailpoet_ignore_drag');
	    },
	    showBlock: function () {
	      if (this._isFirstRender) {
	        this.transitionIn();
	        this._isFirstRender = false;
	      }
	    },
	    deleteBlock: function () {
	      this.transitionOut().then(function () {
	        this.model.destroy();
	      }.bind(this));
	    },
	    duplicateBlock: function () {
	      this.model.collection.add(this.model.toJSON(), { at: this.model.collection.findIndex(this.model) });
	    },
	    transitionIn: function () {
	      return this._transition('slideDown', 'fadeIn', 'easeOut');
	    },
	    transitionOut: function () {
	      return this._transition('slideUp', 'fadeOut', 'easeIn');
	    },
	    _transition: function (slideDirection, fadeDirection, easing) {
	      var promise = jQuery.Deferred();

	      this.$el.velocity(
	        slideDirection,
	        {
	          duration: 250,
	          easing: easing,
	          complete: function () {
	            promise.resolve();
	          }.bind(this)
	        }
	      ).velocity(
	        fadeDirection,
	        {
	          duration: 250,
	          easing: easing,
	          queue: false // Do not enqueue, trigger animation in parallel
	        }
	      );

	      return promise;
	    }
	  });

	  Module.BlockToolsView = AugmentedView.extend({
	    getTemplate: function () { return window.templates.genericBlockTools; },
	    events: {
	      'click .mailpoet_edit_block': 'changeSettings',
	      'click .mailpoet_delete_block_activate': 'showDeletionConfirmation',
	      'click .mailpoet_delete_block_cancel': 'hideDeletionConfirmation',
	      'click .mailpoet_delete_block_confirm': 'deleteBlock',
	      'click .mailpoet_duplicate_block': 'duplicateBlock'
	    },
	    // Markers of whether these particular tools will be used for this instance
	    tools: {
	      settings: true,
	      delete: true,
	      duplicate: true,
	      move: true
	    },
	    getSettingsView: function () { return Module.BlockSettingsView; },
	    initialize: function (opts) {
	      var options = opts || {};
	      if (!_.isUndefined(options.tools)) {
	        // Make a new block specific tool config object
	        this.tools = jQuery.extend({}, this.tools, options.tools || {});
	      }

	      // Automatically cancel deletion
	      this.on('hideTools', this.hideDeletionConfirmation, this);
	      this.on('showSettings', this.changeSettings);
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON(),
	        viewCid: this.cid,
	        tools: this.tools
	      };
	    },
	    changeSettings: function (options) {
	      var ViewType = this.getSettingsView();
	      (new ViewType(_.extend({ model: this.model }, options || {}))).render();
	    },
	    showDeletionConfirmation: function () {
	      this.$('.mailpoet_delete_block').addClass('mailpoet_delete_block_activated');
	    },
	    hideDeletionConfirmation: function () {
	      this.$('.mailpoet_delete_block').removeClass('mailpoet_delete_block_activated');
	    },
	    deleteBlock: function (event) {
	      event.preventDefault();
	      this.model.trigger('delete');
	      return false;
	    },
	    duplicateBlock: function (event) {
	      event.preventDefault();
	      this.model.trigger('duplicate');
	      return false;
	    }
	  });

	  Module.BlockSettingsView = Marionette.View.extend({
	    className: 'mailpoet_editor_settings',
	    behaviors: {
	      ColorPickerBehavior: {}
	    },
	    initialize: function (params) {
	      var panelParams;
	      this.model.trigger('startEditing');
	      panelParams = {
	        element: this.$el,
	        template: '',
	        position: 'right',
	        width: App.getConfig().get('sidepanelWidth'),
	        onCancel: function () {
	          this.destroy();
	        }.bind(this)
	      };
	      this.renderOptions = params.renderOptions || {};
	      if (this.renderOptions.displayFormat === 'subpanel') {
	        MailPoet.Modal.subpanel(panelParams);
	      } else {
	        MailPoet.Modal.panel(panelParams);
	      }
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON()
	      };
	    },
	    close: function () {
	      this.destroy();
	    },
	    changeField: function (field, event) {
	      this.model.set(field, jQuery(event.target).val());
	    },
	    changePixelField: function (field, event) {
	      this.changeFieldWithSuffix(field, event, 'px');
	    },
	    changeFieldWithSuffix: function (field, event, suffix) {
	      this.model.set(field, jQuery(event.target).val() + suffix);
	    },
	    changeBoolField: function (field, event) {
	      this.model.set(field, (jQuery(event.target).val() === 'true'));
	    },
	    changeBoolCheckboxField: function (field, event) {
	      this.model.set(field, (!!jQuery(event.target).prop('checked')));
	    },
	    changeColorField: function (field, event) {
	      var value = jQuery(event.target).val();
	      if (value === '') {
	        value = 'transparent';
	      }
	      this.model.set(field, value);
	    },
	    onBeforeDestroy: function () {
	      MailPoet.Modal.close();
	      this.model.trigger('stopEditing');
	    }
	  });

	  Module.WidgetView = Marionette.View.extend({
	    className: 'mailpoet_widget mailpoet_droppable_block mailpoet_droppable_widget',
	    behaviors: {
	      DraggableBehavior: {
	        drop: function () {
	          throw 'Unsupported operation';
	        }
	      }
	    }
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 604:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Container content block.
	 * This is a special kind of block, as it can contain content blocks, as well
	 * as other containers.
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(544),
	  __webpack_require__(571),
	  __webpack_require__(280),
	  __webpack_require__(275),
	  __webpack_require__(583),
	  __webpack_require__(603)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (Backbone, Marionette, _, jQuery, App, BaseBlock) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;
	  var BlockCollection;

	  BlockCollection = Backbone.Collection.extend({
	    model: base.BlockModel,
	    initialize: function () {
	      this.on('add change remove', function () { App.getChannel().trigger('autoSave'); });
	    },
	    parse: function (response) {
	      return _.map(response, function (block) {
	        var Type = App.getBlockTypeModel(block.type);
	        // TODO: If type has no registered model, use a backup one
	        return new Type(block, { parse: true });
	      });
	    }
	  });

	  Module.ContainerBlockModel = base.BlockModel.extend({
	    relations: {
	      blocks: BlockCollection
	    },
	    defaults: function () {
	      return this._getDefaults({
	        type: 'container',
	        orientation: 'vertical',
	        styles: {
	          block: {
	            backgroundColor: 'transparent'
	          }
	        },
	        blocks: new BlockCollection()
	      }, App.getConfig().get('blockDefaults.container'));
	    },
	    validate: function () {
	      // Recursively propagate validation checks to blocks in the tree
	      var invalidBlock = this.get('blocks').find(function (block) { return !block.isValid(); });
	      if (invalidBlock) {
	        return invalidBlock.validationError;
	      }
	    },
	    parse: function (response) {
	      // If container has any blocks - add them to a collection
	      if (response.type === 'container' && _.has(response, 'blocks')) {
	        response.blocks = new BlockCollection(response.blocks, {
	          parse: true
	        });
	      }
	      return response;
	    },
	    getChildren: function () {
	      var models = this.get('blocks').map(function (model) {
	        return [model, model.getChildren()];
	      });

	      return _.flatten(models);
	    }
	  });

	  Module.ContainerBlocksView = Marionette.CollectionView.extend({
	    className: 'mailpoet_container',
	    childView: function (model) {
	      return App.getBlockTypeView(model.get('type'));
	    },
	    childViewOptions: function () {
	      var newRenderOptions = _.clone(this.renderOptions);
	      if (newRenderOptions.depth !== undefined) {
	        newRenderOptions.depth += 1;
	      }
	      return {
	        renderOptions: newRenderOptions
	      };
	    },
	    emptyView: function () { return Module.ContainerBlockEmptyView; },
	    emptyViewOptions: function () { return { renderOptions: this.renderOptions }; },
	    initialize: function (options) {
	      this.renderOptions = options.renderOptions;
	    }
	  });

	  Module.ContainerBlockView = base.BlockView.extend({
	    regions: _.extend({}, base.BlockView.prototype.regions, {
	      blocks: {
	        el: '> .mailpoet_container',
	        replaceElement: true
	      }
	    }),
	    className: 'mailpoet_block mailpoet_container_block mailpoet_droppable_block mailpoet_droppable_layout_block',
	    getTemplate: function () { return window.templates.containerBlock; },
	    events: _.extend({}, base.BlockView.prototype.events, {
	      'click .mailpoet_newsletter_layer_selector': 'toggleEditingLayer'
	    }),
	    ui: {
	      tools: '> .mailpoet_tools'
	    },
	    behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
	      ContainerDropZoneBehavior: {},
	      DraggableBehavior: {
	        cloneOriginal: true,
	        hideOriginal: true,
	        onDrop: function (options) {
	          // After a clone of model has been dropped, cleanup
	          // and destroy self
	          options.dragBehavior.view.model.destroy();
	        },
	        onDragSubstituteBy: function (behavior) {
	          var WidgetView;
	          var node;
	          // When block is being dragged, display the widget icon instead.
	          // This will create an instance of block's widget view and
	          // use it's rendered DOM element instead of the content block
	          if (_.isFunction(behavior.view.onDragSubstituteBy)) {
	            WidgetView = new (behavior.view.onDragSubstituteBy())();
	            WidgetView.render();
	            node = WidgetView.$el.get(0).cloneNode(true);
	            WidgetView.destroy();
	            return node;
	          }
	        },
	        testAttachToInstance: function (model, view) {
	          // Attach Draggable only to layout containers and disable it
	          // for root and column containers.
	          return view.renderOptions.depth === 1;
	        }
	      },
	      HighlightContainerBehavior: {}
	    }),
	    onDragSubstituteBy: function () {
	      // For two and three column layouts display their respective widgets,
	      // otherwise always default to one column layout widget
	      if (this.renderOptions.depth === 1) {
	        if (this.model.get('blocks').length === 3) return Module.ThreeColumnContainerWidgetView;
	        if (this.model.get('blocks').length === 2) return Module.TwoColumnContainerWidgetView;
	      }
	      return Module.OneColumnContainerWidgetView;

	    },
	    initialize: function (options) {
	      base.BlockView.prototype.initialize.apply(this, arguments);

	      this.renderOptions = _.defaults(options.renderOptions || {}, {});
	    },
	    onRender: function () {
	      this.toolsView = new Module.ContainerBlockToolsView({
	        model: this.model,
	        tools: {
	          settings: this.renderOptions.depth === 1,
	          delete: this.renderOptions.depth === 1,
	          duplicate: true,
	          move: this.renderOptions.depth === 1,
	          layerSelector: false
	        }
	      });
	      this.showChildView('toolsRegion', this.toolsView);
	      this.showChildView('blocks', new Module.ContainerBlocksView({
	        collection: this.model.get('blocks'),
	        renderOptions: this.renderOptions
	      }));

	      // TODO: Look for a better way to do this than here
	      // Sets child container orientation HTML class here, as child CollectionView won't have access to model and will overwrite existing region element instead
	      this.$('> .mailpoet_container').attr('class', 'mailpoet_container mailpoet_container_' + this.model.get('orientation'));
	    },
	    showTools: function () {
	      if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
	        this.$(this.ui.tools).addClass('mailpoet_display_tools');
	        this.toolsView.triggerMethod('showTools');
	      }
	    },
	    hideTools: function () {
	      if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
	        this.$(this.ui.tools).removeClass('mailpoet_display_tools');
	        this.toolsView.triggerMethod('hideTools');
	      }
	    },
	    toggleEditingLayer: function (event) {
	      var that = this;
	      var $toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector');
	      var $overlay = jQuery('.mailpoet_layer_overlay');
	      var $container = this.$('> .mailpoet_container');
	      var enableContainerLayer = function () {
	        that.$el.addClass('mailpoet_container_layer_active');
	        $toggleButton.addClass('mailpoet_container_layer_active');
	        $container.addClass('mailpoet_layer_highlight');
	        $overlay.click(disableContainerLayer);
	        $overlay.show();
	      };
	      var disableContainerLayer = function () {
	        that.$el.removeClass('mailpoet_container_layer_active');
	        $toggleButton.removeClass('mailpoet_container_layer_active');
	        $container.removeClass('mailpoet_layer_highlight');
	        $overlay.hide();
	        $overlay.off('click');
	      };
	      if ($toggleButton.hasClass('mailpoet_container_layer_active')) {
	        disableContainerLayer();
	      } else {
	        enableContainerLayer();
	      }
	      event.stopPropagation();
	    }
	  });

	  Module.ContainerBlockEmptyView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.containerEmpty; },
	    initialize: function (options) {
	      this.renderOptions = _.defaults(options.renderOptions || {}, {});
	    },
	    templateContext: function () {
	      return {
	        isRoot: this.renderOptions.depth === 0,
	        emptyContainerMessage: this.renderOptions.emptyContainerMessage || ''
	      };
	    }
	  });

	  Module.ContainerBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.ContainerBlockSettingsView; }
	  });

	  Module.ContainerBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.containerBlockSettings; },
	    events: function () {
	      return {
	        'change .mailpoet_field_container_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
	        'click .mailpoet_done_editing': 'close'
	      };
	    },
	    regions: {
	      columnsSettingsRegion: '.mailpoet_container_columns_settings'
	    },
	    initialize: function () {
	      base.BlockSettingsView.prototype.initialize.apply(this, arguments);

	      this._columnsSettingsView = new (Module.ContainerBlockColumnsSettingsView)({
	        collection: this.model.get('blocks')
	      });
	    },
	    onRender: function () {
	      this.showChildView('columnsSettingsRegion', this._columnsSettingsView);
	    }
	  });

	  Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
	    childView: function () { return Module.ContainerBlockColumnSettingsView; },
	    childViewOptions: function (model, index) {
	      return {
	        columnIndex: index
	      };
	    }
	  });

	  Module.ContainerBlockColumnSettingsView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.containerBlockColumnSettings; },
	    initialize: function (options) {
	      this.columnNumber = (options.columnIndex || 0) + 1;
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON(),
	        columnNumber: this.columnNumber
	      };
	    }
	  });

	  Module.OneColumnContainerWidgetView = base.WidgetView.extend({
	    className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
	    getTemplate: function () { return window.templates.oneColumnLayoutInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.ContainerBlockModel({
	            orientation: 'horizontal',
	            blocks: [
	              new Module.ContainerBlockModel()
	            ]
	          });
	        }
	      }
	    }
	  });

	  Module.TwoColumnContainerWidgetView = base.WidgetView.extend({
	    className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
	    getTemplate: function () { return window.templates.twoColumnLayoutInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.ContainerBlockModel({
	            orientation: 'horizontal',
	            blocks: [
	              new Module.ContainerBlockModel(),
	              new Module.ContainerBlockModel()
	            ]
	          });
	        }
	      }
	    }
	  });

	  Module.ThreeColumnContainerWidgetView = base.WidgetView.extend({
	    className: base.WidgetView.prototype.className + ' mailpoet_droppable_layout_block',
	    getTemplate: function () { return window.templates.threeColumnLayoutInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.ContainerBlockModel({
	            orientation: 'horizontal',
	            blocks: [
	              new Module.ContainerBlockModel(),
	              new Module.ContainerBlockModel(),
	              new Module.ContainerBlockModel()
	            ]
	          });
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('container', {
	      blockModel: Module.ContainerBlockModel,
	      blockView: Module.ContainerBlockView
	    });

	    App.registerLayoutWidget({
	      name: 'oneColumnLayout',
	      priority: 100,
	      widgetView: Module.OneColumnContainerWidgetView
	    });

	    App.registerLayoutWidget({
	      name: 'twoColumnLayout',
	      priority: 100,
	      widgetView: Module.TwoColumnContainerWidgetView
	    });

	    App.registerLayoutWidget({
	      name: 'threeColumnLayout',
	      priority: 100,
	      widgetView: Module.ThreeColumnContainerWidgetView
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 605:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Button content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(276),
	  __webpack_require__(280),
	  __webpack_require__(275)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, MailPoet, _, jQuery) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;

	  Module.ButtonBlockModel = base.BlockModel.extend({
	    defaults: function () {
	      return this._getDefaults({
	        type: 'button',
	        text: 'Button',
	        url: '',
	        styles: {
	          block: {
	            backgroundColor: '#ff0000',
	            borderColor: '#cccccc',
	            borderWidth: '1px',
	            borderRadius: '4px',
	            borderStyle: 'solid',
	            width: '200px',
	            lineHeight: '40px',
	            fontColor: '#000000',
	            fontFamily: 'Arial',
	            fontSize: '16px',
	            fontWeight: 'normal', // 'normal'|'bold'
	            textAlign: 'center'
	          }
	        }
	      }, App.getConfig().get('blockDefaults.button'));
	    }
	  });

	  Module.ButtonBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_button_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.buttonBlock; },
	    onDragSubstituteBy: function () { return Module.ButtonWidgetView; },
	    behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
	      ShowSettingsBehavior: {}
	    }),
	    initialize: function () {
	      base.BlockView.prototype.initialize.apply(this, arguments);

	      // Listen for attempts to change all dividers in one go
	      this._replaceButtonStylesHandler = function (data) { this.model.set(data); }.bind(this);
	      App.getChannel().on('replaceAllButtonStyles', this._replaceButtonStylesHandler);
	    },
	    onRender: function () {
	      this.toolsView = new Module.ButtonBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	    }
	  });

	  Module.ButtonBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.ButtonBlockSettingsView; }
	  });

	  Module.ButtonBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.buttonBlockSettings; },
	    events: function () {
	      return {
	        'input .mailpoet_field_button_text': _.partial(this.changeField, 'text'),
	        'input .mailpoet_field_button_url': _.partial(this.changeField, 'url'),
	        'change .mailpoet_field_button_alignment': _.partial(this.changeField, 'styles.block.textAlign'),
	        'change .mailpoet_field_button_font_color': _.partial(this.changeColorField, 'styles.block.fontColor'),
	        'change .mailpoet_field_button_font_family': _.partial(this.changeField, 'styles.block.fontFamily'),
	        'change .mailpoet_field_button_font_size': _.partial(this.changeField, 'styles.block.fontSize'),
	        'change .mailpoet_field_button_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
	        'change .mailpoet_field_button_border_color': _.partial(this.changeColorField, 'styles.block.borderColor'),
	        'change .mailpoet_field_button_font_weight': 'changeFontWeight',

	        'input .mailpoet_field_button_border_width': _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, 'styles.block.borderWidth').bind(this)),
	        'change .mailpoet_field_button_border_width': _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, 'styles.block.borderWidth').bind(this)),
	        'input .mailpoet_field_button_border_width_input': _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width', _.partial(this.changePixelField, 'styles.block.borderWidth').bind(this)),

	        'input .mailpoet_field_button_border_radius': _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius_input', _.partial(this.changePixelField, 'styles.block.borderRadius').bind(this)),
	        'change .mailpoet_field_button_border_radius': _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius_input', _.partial(this.changePixelField, 'styles.block.borderRadius').bind(this)),
	        'input .mailpoet_field_button_border_radius_input': _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius', _.partial(this.changePixelField, 'styles.block.borderRadius').bind(this)),

	        'input .mailpoet_field_button_width': _.partial(this.updateValueAndCall, '.mailpoet_field_button_width_input', _.partial(this.changePixelField, 'styles.block.width').bind(this)),
	        'change .mailpoet_field_button_width': _.partial(this.updateValueAndCall, '.mailpoet_field_button_width_input', _.partial(this.changePixelField, 'styles.block.width').bind(this)),
	        'input .mailpoet_field_button_width_input': _.partial(this.updateValueAndCall, '.mailpoet_field_button_width', _.partial(this.changePixelField, 'styles.block.width').bind(this)),

	        'input .mailpoet_field_button_line_height': _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height_input', _.partial(this.changePixelField, 'styles.block.lineHeight').bind(this)),
	        'change .mailpoet_field_button_line_height': _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height_input', _.partial(this.changePixelField, 'styles.block.lineHeight').bind(this)),
	        'input .mailpoet_field_button_line_height_input': _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, 'styles.block.lineHeight').bind(this)),

	        'click .mailpoet_field_button_replace_all_styles': 'applyToAll',
	        'click .mailpoet_done_editing': 'close'
	      };
	    },
	    templateContext: function () {
	      return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
	        availableStyles: App.getAvailableStyles().toJSON(),
	        renderOptions: this.renderOptions
	      });
	    },
	    applyToAll: function () {
	      App.getChannel().trigger('replaceAllButtonStyles', _.pick(this.model.toJSON(), 'styles', 'type'));
	    },
	    updateValueAndCall: function (fieldToUpdate, callable, event) {
	      this.$(fieldToUpdate).val(jQuery(event.target).val());
	      callable(event);
	    },
	    changeFontWeight: function (event) {
	      var checked = !!jQuery(event.target).prop('checked');
	      this.model.set(
	        'styles.block.fontWeight',
	        (checked) ? jQuery(event.target).val() : 'normal'
	      );
	    }
	  });

	  Module.ButtonWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.buttonInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.ButtonBlockModel();
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('button', {
	      blockModel: Module.ButtonBlockModel,
	      blockView: Module.ButtonBlockView
	    });

	    App.registerWidget({
	      name: 'button',
	      widgetView: Module.ButtonWidgetView,
	      priority: 92
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 606:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Image content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(280),
	  __webpack_require__(276),
	  __webpack_require__(275)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, _, MailPoet, jQuery) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;
	  var ImageWidgetView;

	  Module.ImageBlockModel = base.BlockModel.extend({
	    defaults: function () {
	      return this._getDefaults({
	        type: 'image',
	        link: '',
	        src: '',
	        alt: 'An image of...',
	        fullWidth: true, // true | false
	        width: '64px',
	        height: '64px',
	        styles: {
	          block: {
	            textAlign: 'center'
	          }
	        }
	      }, App.getConfig().get('blockDefaults.image'));
	    }
	  });

	  Module.ImageBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_image_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.imageBlock; },
	    onDragSubstituteBy: function () { return Module.ImageWidgetView; },
	    templateContext: function () {
	      return _.extend({
	        imageMissingSrc: App.getConfig().get('urls.imageMissing')
	      }, base.BlockView.prototype.templateContext.apply(this));
	    },
	    behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
	      ResizableBehavior: {
	        elementSelector: '.mailpoet_image',
	        resizeHandleSelector: '.mailpoet_image_resize_handle',
	        onResize: function (event) {
	          var corner = this.$('.mailpoet_image').offset();
	          var width = event.pageX - corner.left;
	          this.view.model.set('width', width + 'px');
	        }
	      },
	      ShowSettingsBehavior: {
	        ignoreFrom: '.mailpoet_image_resize_handle'
	      }
	    }),
	    onRender: function () {
	      this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	      if (this.model.get('fullWidth')) {
	        this.$el.addClass('mailpoet_full_image');
	      } else {
	        this.$el.removeClass('mailpoet_full_image');
	      }
	      this.$('.mailpoet_content').css('width', this.model.get('width'));
	    }
	  });

	  Module.ImageBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.ImageBlockSettingsView; }
	  });

	  Module.ImageBlockSettingsView = base.BlockSettingsView.extend({
	    onRender: function () {
	      MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-full-width'), {
	        tooltipId: 'tooltip-editor-full-width',
	        tooltip: MailPoet.I18n.t('helpTooltipDesignerFullWidth')
	      });
	      MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-ideal-width'), {
	        tooltipId: 'tooltip-editor-ideal-width',
	        tooltip: MailPoet.I18n.t('helpTooltipDesignerIdealWidth')
	      });
	    },
	    getTemplate: function () { return window.templates.imageBlockSettings; },
	    events: function () {
	      return {
	        'input .mailpoet_field_image_link': _.partial(this.changeField, 'link'),
	        'input .mailpoet_field_image_address': 'changeAddress',
	        'input .mailpoet_field_image_alt_text': _.partial(this.changeField, 'alt'),
	        'change .mailpoet_field_image_full_width': _.partial(this.changeBoolCheckboxField, 'fullWidth'),
	        'change .mailpoet_field_image_alignment': _.partial(this.changeField, 'styles.block.textAlign'),
	        'click .mailpoet_field_image_select_another_image': 'showMediaManager',
	        'click .mailpoet_done_editing': 'close',
	        'input .mailpoet_field_image_width': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width_input', _.partial(this.changePixelField, 'width').bind(this)),
	        'change .mailpoet_field_image_width': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width_input', _.partial(this.changePixelField, 'width').bind(this)),
	        'input .mailpoet_field_image_width_input': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width', _.partial(this.changePixelField, 'width').bind(this))
	      };
	    },
	    modelEvents: function () {
	      return {
	        'change:maxWidth': 'updateMaxWidth',
	        'change:width': 'updateWidth'
	      };
	    },
	    updateValueAndCall: function (fieldToUpdate, callable, event) {
	      this.$(fieldToUpdate).val(jQuery(event.target).val());
	      callable(event);
	    },
	    updateMaxWidth: function () {
	      var maxWidth = parseInt(this.model.get('maxWidth'));
	      this.$('.mailpoet_field_image_width').attr('max', maxWidth);
	      this.$('.mailpoet_field_image_width_input').attr('max', maxWidth);
	    },
	    updateWidth: function () {
	      var width = parseInt(this.model.get('width'));
	      this.$('.mailpoet_field_image_width').val(width);
	      this.$('.mailpoet_field_image_width_input').val(width);
	    },
	    initialize: function (options) {
	      base.BlockSettingsView.prototype.initialize.apply(this, arguments);

	      if (options.showImageManager) {
	        this.showMediaManager();
	      }
	    },
	    showMediaManager: function () {
	      var that = this;
	      var MediaManager;
	      var theFrame;
	      if (this._mediaManager) {
	        this._mediaManager.resetSelections();
	        this._mediaManager.open();
	        return;
	      }

	      MediaManager = window.wp.media.view.MediaFrame.Select.extend({

	        initialize: function () {
	          window.wp.media.view.MediaFrame.prototype.initialize.apply(this, arguments);

	          _.defaults(this.options, {
	            multiple: true,
	            editing: false,
	            state: 'insert'
	          });

	          this.createSelection();
	          this.createStates();
	          this.bindHandlers();
	          this.createIframeStates();

	          // Hide title
	          this.$el.addClass('hide-title');
	        },

	        resetSelections: function () {
	          this.state().get('selection').reset();
	        },

	        createQuery: function (options) {
	          var query = window.wp.media.query(options);
	          return query;
	        },

	        createStates: function () {
	          var options = this.options;

	          // Add the default states.
	          this.states.add([
	            // Main states.
	            new window.wp.media.controller.Library({
	              id: 'insert',
	              title: 'Add images',
	              priority: 20,
	              toolbar: 'main-insert',
	              filterable: 'image',
	              library: this.createQuery(options.library),
	              multiple: options.multiple ? 'reset' : false,
	              editable: false,

	              // If the user isn't allowed to edit fields,
	              // can they still edit it locally?
	              allowLocalEdits: false,

	              // Show the attachment display settings.
	              displaySettings: false,
	              // Update user settings when users adjust the
	              // attachment display settings.
	              displayUserSettings: false
	            })
	          ]);

	          if (window.wp.media.view.settings.post.featuredImageId) {
	            this.states.add(new window.wp.media.controller.FeaturedImage());
	          }
	        },

	        bindHandlers: function () {
	          var handlers;
	          // from Select
	          this.on('router:create:browse', this.createRouter, this);
	          this.on('router:render:browse', this.browseRouter, this);
	          this.on('content:create:browse', this.browseContent, this);
	          this.on('content:render:upload', this.uploadContent, this);
	          this.on('toolbar:create:select', this.createSelectToolbar, this);

	          this.on('menu:create:gallery', this.createMenu, this);
	          this.on('toolbar:create:main-insert', this.createToolbar, this);
	          this.on('toolbar:create:main-gallery', this.createToolbar, this);
	          this.on('toolbar:create:main-embed', this.mainEmbedToolbar, this);

	          this.on('updateExcluded', this.browseContent, this);

	          handlers = {
	            content: {
	              embed: 'embedContent',
	              'edit-selection': 'editSelectionContent'
	            },
	            toolbar: {
	              'main-insert': 'mainInsertToolbar'
	            }
	          };

	          _.each(handlers, function (regionHandlers, region) {
	            _.each(regionHandlers, function (callback, handler) {
	              this.on(region + ':render:' + handler, this[callback], this);
	            }, this);
	          }, this);
	        },

	        uploadContent: function () {
	          window.wp.media.view.MediaFrame.Select.prototype.uploadContent.apply(this, arguments);
	          this.$el.addClass('hide-toolbar');
	        },

	        // Content
	        embedContent: function () {
	          var view = new window.wp.media.view.Embed({
	            controller: this,
	            model: this.state()
	          }).render();

	          this.content.set(view);
	          view.url.focus();
	        },

	        editSelectionContent: function () {
	          var state = this.state();
	          var selection = state.get('selection');
	          var view;

	          view = new window.wp.media.view.AttachmentsBrowser({
	            controller: this,
	            collection: selection,
	            selection: selection,
	            model: state,
	            sortable: true,
	            search: false,
	            dragInfo: true,

	            AttachmentView: window.wp.media.view.Attachment.EditSelection
	          }).render();

	          view.toolbar.set('backToLibrary', {
	            text: 'Return to library',
	            priority: -100,

	            click: function () {
	              this.controller.content.mode('browse');
	            }
	          });

	          // Browse our library of attachments.
	          this.content.set(view);
	        },

	        // Toolbars
	        selectionStatusToolbar: function (view) {
	          var editable = this.state().get('editable');

	          view.set('selection', new window.wp.media.view.Selection({
	            controller: this,
	            collection: this.state().get('selection'),
	            priority: -40,

	            // If the selection is editable, pass the callback to
	            // switch the content mode.
	            editable: editable && function () {
	              this.controller.content.mode('edit-selection');
	            }
	          }).render());
	        },

	        mainInsertToolbar: function (view) {
	          var controller = this;

	          this.selectionStatusToolbar(view);

	          view.set('insert', {
	            style: 'primary',
	            priority: 80,
	            text: 'Select Image',
	            requires: { selection: true },

	            click: function () {
	              var state = controller.state();
	              var selection = state.get('selection');

	              controller.close();
	              state.trigger('insert', selection).reset();
	            }
	          });
	        },

	        mainEmbedToolbar: function (toolbar) {
	          var tbar = toolbar;
	          tbar.view = new window.wp.media.view.Toolbar.Embed({
	            controller: this,
	            text: 'Add images'
	          });
	        }

	      });

	      theFrame = new MediaManager({
	        id: 'mailpoet-media-manager',
	        frame: 'select',
	        title: 'Select image',
	        editing: false,
	        multiple: false,
	        library: {
	          type: 'image'
	        },
	        displaySettings: false,
	        button: {
	          text: 'Select'
	        }
	      });
	      this._mediaManager = theFrame;

	      this._mediaManager.on('insert', function () {
	        // Append media manager image selections to Images tab
	        var selection = theFrame.state().get('selection');
	        selection.each(function (attachment) {
	          var sizes = attachment.get('sizes');
	          // Following advice from Becs, the target width should
	          // be a double of one column width to render well on
	          // retina screen devices
	          var targetImageWidth = 1320;

	          // Pick the width that is closest to target width
	          var increasingByWidthDifference = _.sortBy(
	            _.keys(sizes),
	            function (size) { return Math.abs(targetImageWidth - sizes[size].width); }
	          );
	          var bestWidth = sizes[_.first(increasingByWidthDifference)].width;
	          var imagesOfBestWidth = _.filter(_.values(sizes), function (size) { return size.width === bestWidth; });

	          // Maximize the height if there are multiple images with same width
	          var mainSize = _.max(imagesOfBestWidth, function (size) { return size.height; });

	          that.model.set({
	            height: mainSize.height + 'px',
	            width: mainSize.width + 'px',
	            src: mainSize.url,
	            alt: (attachment.get('alt') !== '' && attachment.get('alt') !== undefined) ? attachment.get('alt') : attachment.get('title')
	          });
	          // Rerender settings view due to changes from outside of settings view
	          that.render();
	        });
	      });

	      this._mediaManager.open();
	    },
	    changeAddress: function (event) {
	      var src = jQuery(event.target).val();
	      var image = new Image();

	      image.onload = function () {
	        this.model.set({
	          src: src,
	          width: image.naturalWidth + 'px',
	          height: image.naturalHeight + 'px'
	        });
	      }.bind(this);

	      image.src = src;
	    },
	    onBeforeDestroy: function () {
	      base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
	      if (typeof this._mediaManager === 'object') {
	        this._mediaManager.remove();
	      }
	    }
	  });

	  ImageWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.imageInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.ImageBlockModel();
	        },
	        onDrop: function (options) {
	          options.droppedView.triggerMethod('showSettings', { showImageManager: true });
	        }
	      }
	    }
	  });
	  Module.ImageWidgetView = ImageWidgetView;

	  App.on('before:start', function (App) {
	    App.registerBlockType('image', {
	      blockModel: Module.ImageBlockModel,
	      blockView: Module.ImageBlockView
	    });

	    App.registerWidget({
	      name: 'image',
	      widgetView: Module.ImageWidgetView,
	      priority: 91
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 607:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Divider content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(280),
	  __webpack_require__(275)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, _, jQuery) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;

	  Module.DividerBlockModel = base.BlockModel.extend({
	    defaults: function () {
	      return this._getDefaults({
	        type: 'divider',
	        styles: {
	          block: {
	            backgroundColor: 'transparent',
	            padding: '12px',
	            borderStyle: 'solid',
	            borderWidth: '1px',
	            borderColor: '#000000'
	          }
	        }
	      }, App.getConfig().get('blockDefaults.divider'));
	    }
	  });

	  Module.DividerBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_divider_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.dividerBlock; },
	    modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'),
	    behaviors: _.defaults({
	      ResizableBehavior: {
	        elementSelector: '.mailpoet_content',
	        resizeHandleSelector: '.mailpoet_resize_handle',
	        transformationFunction: function (y) { return y / 2; },
	        minLength: 0, // TODO: Move this number to editor configuration
	        modelField: 'styles.block.padding'
	      },
	      ShowSettingsBehavior: {
	        ignoreFrom: '.mailpoet_resize_handle'
	      }
	    }, base.BlockView.prototype.behaviors),
	    onDragSubstituteBy: function () { return Module.DividerWidgetView; },
	    initialize: function () {
	      var that = this;
	      base.BlockView.prototype.initialize.apply(this, arguments);

	      // Listen for attempts to change all dividers in one go
	      this._replaceDividerHandler = function (data) { that.model.set(data); that.model.trigger('applyToAll'); };
	      App.getChannel().on('replaceAllDividers', this._replaceDividerHandler);

	      this.listenTo(this.model, 'change:src change:styles.block.backgroundColor change:styles.block.borderStyle change:styles.block.borderWidth change:styles.block.borderColor applyToAll', this.render);
	      this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
	    },
	    templateContext: function () {
	      return _.extend({
	        totalHeight: parseInt(this.model.get('styles.block.padding'), 10) * 2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px'
	      }, base.BlockView.prototype.templateContext.apply(this));
	    },
	    onRender: function () {
	      this.toolsView = new Module.DividerBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	    },
	    onBeforeDestroy: function () {
	      App.getChannel().off('replaceAllDividers', this._replaceDividerHandler);
	      this.stopListening(this.model);
	    },
	    changePadding: function () {
	      this.$('.mailpoet_content').css('padding-top', this.model.get('styles.block.padding'));
	      this.$('.mailpoet_content').css('padding-bottom', this.model.get('styles.block.padding'));
	      this.$('.mailpoet_resize_handle_text').text(parseInt(this.model.get('styles.block.padding'), 10) * 2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px');
	    }
	  });

	  Module.DividerBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.DividerBlockSettingsView; }
	  });

	  Module.DividerBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.dividerBlockSettings; },
	    events: function () {
	      return {
	        'click .mailpoet_field_divider_style': 'changeStyle',

	        'input .mailpoet_field_divider_border_width': _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width_input', _.partial(this.changePixelField, 'styles.block.borderWidth').bind(this)),
	        'change .mailpoet_field_divider_border_width': _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width_input', _.partial(this.changePixelField, 'styles.block.borderWidth').bind(this)),
	        'input .mailpoet_field_divider_border_width_input': _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width', _.partial(this.changePixelField, 'styles.block.borderWidth').bind(this)),

	        'change .mailpoet_field_divider_border_color': _.partial(this.changeColorField, 'styles.block.borderColor'),
	        'change .mailpoet_field_divider_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
	        'click .mailpoet_button_divider_apply_to_all': 'applyToAll',
	        'click .mailpoet_done_editing': 'close'
	      };
	    },
	    modelEvents: function () {
	      return {
	        'change:styles.block.borderColor': 'repaintDividerStyleOptions'
	      };
	    },
	    templateContext: function () {
	      return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
	        availableStyles: App.getAvailableStyles().toJSON(),
	        renderOptions: this.renderOptions
	      });
	    },
	    changeStyle: function (event) {
	      var style = jQuery(event.currentTarget).data('style');
	      this.model.set('styles.block.borderStyle', style);
	      this.$('.mailpoet_field_divider_style').removeClass('mailpoet_active_divider_style');
	      this.$('.mailpoet_field_divider_style[data-style="' + style + '"]').addClass('mailpoet_active_divider_style');
	    },
	    repaintDividerStyleOptions: function () {
	      this.$('.mailpoet_field_divider_style > div').css('border-top-color', this.model.get('styles.block.borderColor'));
	    },
	    applyToAll: function () {
	      App.getChannel().trigger('replaceAllDividers', this.model.toJSON());
	    },
	    updateValueAndCall: function (fieldToUpdate, callable, event) {
	      this.$(fieldToUpdate).val(jQuery(event.target).val());
	      callable(event);
	    }
	  });

	  Module.DividerWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.dividerInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.DividerBlockModel();
	        }
	      }
	    }
	  });
	  App.on('before:start', function (App) {
	    App.registerBlockType('divider', {
	      blockModel: Module.DividerBlockModel,
	      blockView: Module.DividerBlockView
	    });

	    App.registerWidget({
	      name: 'divider',
	      widgetView: Module.DividerWidgetView,
	      priority: 93
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 608:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Text content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(280),
	  __webpack_require__(276)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, _, MailPoet) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;

	  Module.TextBlockModel = base.BlockModel.extend({
	    defaults: function () {
	      return this._getDefaults({
	        type: 'text',
	        text: 'Edit this to insert text'
	      }, App.getConfig().get('blockDefaults.text'));
	    }
	  });

	  Module.TextBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_text_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.textBlock; },
	    modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing
	    behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
	      TextEditorBehavior: {
	        toolbar1: 'formatselect bold italic forecolor | link unlink',
	        toolbar2: 'alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_shortcodes',
	        validElements: 'p[class|style],span[class|style],a[href|class|title|target|style],h1[class|style],h2[class|style],h3[class|style],ol[class|style],ul[class|style],li[class|style],strong[class|style],em[class|style],strike,br,blockquote[class|style],table[class|style],tr[class|style],th[class|style],td[class|style]',
	        invalidElements: 'script',
	        blockFormats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
	        plugins: 'link lists code textcolor colorpicker mailpoet_shortcodes paste',
	        configurationFilter: function (originalSettings) {
	          return _.extend({}, originalSettings, {
	            mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
	            mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
	          });
	        }
	      }
	    }),
	    initialize: function (options) {
	      base.BlockView.prototype.initialize.apply(this, arguments);

	      this.renderOptions = _.defaults(options.renderOptions || {}, {
	        disableTextEditor: false
	      });

	      this.disableTextEditor = this.renderOptions.disableTextEditor;
	    },
	    onDragSubstituteBy: function () { return Module.TextWidgetView; },
	    onRender: function () {
	      this.toolsView = new Module.TextBlockToolsView({
	        model: this.model,
	        tools: {
	          settings: false
	        }
	      });
	      this.showChildView('toolsRegion', this.toolsView);
	    },
	    onTextEditorChange: function (newContent) {
	      this.model.set('text', newContent);
	    },
	    onTextEditorFocus: function () {
	      this.disableDragging();
	      this.disableShowingTools();
	    },
	    onTextEditorBlur: function () {
	      this.enableDragging();
	      this.enableShowingTools();
	    }
	  });

	  Module.TextBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.TextBlockSettingsView; }
	  });

	  Module.TextBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.textBlockSettings; }
	  });

	  Module.TextWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.textInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.TextBlockModel();
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('text', {
	      blockModel: Module.TextBlockModel,
	      blockView: Module.TextBlockView
	    });

	    App.registerWidget({
	      name: 'text',
	      widgetView: Module.TextWidgetView,
	      priority: 90
	    });
	  });


	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 609:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Spacer content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(280)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, _) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;

	  Module.SpacerBlockModel = base.BlockModel.extend({
	    defaults: function () {
	      return this._getDefaults({
	        type: 'spacer',
	        styles: {
	          block: {
	            backgroundColor: 'transparent',
	            height: '40px'
	          }
	        }
	      }, App.getConfig().get('blockDefaults.spacer'));
	    }
	  });

	  Module.SpacerBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_spacer_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.spacerBlock; },
	    behaviors: _.defaults({
	      ResizableBehavior: {
	        elementSelector: '.mailpoet_spacer',
	        resizeHandleSelector: '.mailpoet_resize_handle',
	        minLength: 20, // TODO: Move this number to editor configuration
	        modelField: 'styles.block.height'
	      },
	      ShowSettingsBehavior: {
	        ignoreFrom: '.mailpoet_resize_handle'
	      }
	    }, base.BlockView.prototype.behaviors),
	    modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'),
	    onDragSubstituteBy: function () { return Module.SpacerWidgetView; },
	    initialize: function () {
	      base.BlockView.prototype.initialize.apply(this, arguments);

	      this.listenTo(this.model, 'change:styles.block.backgroundColor', this.render);
	      this.listenTo(this.model, 'change:styles.block.height', this.changeHeight);
	    },
	    onRender: function () {
	      this.toolsView = new Module.SpacerBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	    },
	    changeHeight: function () {
	      this.$('.mailpoet_spacer').css('height', this.model.get('styles.block.height'));
	      this.$('.mailpoet_resize_handle_text').text(this.model.get('styles.block.height'));
	    },
	    onBeforeDestroy: function () {
	      this.stopListening(this.model);
	    }
	  });

	  Module.SpacerBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.SpacerBlockSettingsView; }
	  });

	  Module.SpacerBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.spacerBlockSettings; },
	    events: function () {
	      return {
	        'change .mailpoet_field_spacer_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
	        'click .mailpoet_done_editing': 'close'
	      };
	    }
	  });

	  Module.SpacerWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.spacerInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.SpacerBlockModel();
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('spacer', {
	      blockModel: Module.SpacerBlockModel,
	      blockView: Module.SpacerBlockView
	    });

	    App.registerWidget({
	      name: 'spacer',
	      widgetView: Module.SpacerWidgetView,
	      priority: 94
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 610:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Footer content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(280),
	  __webpack_require__(276)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, _, MailPoet) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;

	  Module.FooterBlockModel = base.BlockModel.extend({
	    defaults: function () {
	      return this._getDefaults({
	        type: 'footer',
	        text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
	        styles: {
	          block: {
	            backgroundColor: 'transparent'
	          },
	          text: {
	            fontColor: '#000000',
	            fontFamily: 'Arial',
	            fontSize: '12px',
	            textAlign: 'center'
	          },
	          link: {
	            fontColor: '#0000ff',
	            textDecoration: 'none'
	          }
	        }
	      }, App.getConfig().get('blockDefaults.footer'));
	    }
	  });

	  Module.FooterBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_footer_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.footerBlock; },
	    modelEvents: _.extend({
	      'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render'
	    }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
	    behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
	      TextEditorBehavior: {
	        configurationFilter: function (originalSettings) {
	          return _.extend({}, originalSettings, {
	            mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
	            mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
	          });
	        }
	      }
	    }),
	    onDragSubstituteBy: function () { return Module.FooterWidgetView; },
	    onRender: function () {
	      this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	    },
	    onTextEditorChange: function (newContent) {
	      this.model.set('text', newContent);
	    },
	    onTextEditorFocus: function () {
	      this.disableDragging();
	      this.disableShowingTools();
	    },
	    onTextEditorBlur: function () {
	      this.enableDragging();
	      this.enableShowingTools();
	    }
	  });

	  Module.FooterBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.FooterBlockSettingsView; }
	  });

	  Module.FooterBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.footerBlockSettings; },
	    events: function () {
	      return {
	        'change .mailpoet_field_footer_text_color': _.partial(this.changeColorField, 'styles.text.fontColor'),
	        'change .mailpoet_field_footer_text_font_family': _.partial(this.changeField, 'styles.text.fontFamily'),
	        'change .mailpoet_field_footer_text_size': _.partial(this.changeField, 'styles.text.fontSize'),
	        'change #mailpoet_field_footer_link_color': _.partial(this.changeColorField, 'styles.link.fontColor'),
	        'change #mailpoet_field_footer_link_underline': function (event) {
	          this.model.set('styles.link.textDecoration', (event.target.checked) ? event.target.value : 'none');
	        },
	        'change .mailpoet_field_footer_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
	        'change .mailpoet_field_footer_alignment': _.partial(this.changeField, 'styles.text.textAlign'),
	        'click .mailpoet_done_editing': 'close'
	      };
	    },
	    templateContext: function () {
	      return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
	        availableStyles: App.getAvailableStyles().toJSON()
	      });
	    }
	  });

	  Module.FooterWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.footerInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.FooterBlockModel();
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('footer', {
	      blockModel: Module.FooterBlockModel,
	      blockView: Module.FooterBlockView
	    });

	    App.registerWidget({
	      name: 'footer',
	      widgetView: Module.FooterWidgetView,
	      priority: 99
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 611:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Header content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(280),
	  __webpack_require__(276)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, _, MailPoet) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;

	  Module.HeaderBlockModel = base.BlockModel.extend({
	    defaults: function () {
	      return this._getDefaults({
	        type: 'header',
	        text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
	        styles: {
	          block: {
	            backgroundColor: 'transparent'
	          },
	          text: {
	            fontColor: '#000000',
	            fontFamily: 'Arial',
	            fontSize: '12px',
	            textAlign: 'center'
	          },
	          link: {
	            fontColor: '#0000ff',
	            textDecoration: 'underline'
	          }
	        }
	      }, App.getConfig().get('blockDefaults.header'));
	    }
	  });

	  Module.HeaderBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_header_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.headerBlock; },
	    modelEvents: _.extend({
	      'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render'
	    }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
	    behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
	      TextEditorBehavior: {
	        configurationFilter: function (originalSettings) {
	          return _.extend({}, originalSettings, {
	            mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
	            mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle')
	          });
	        }
	      }
	    }),
	    onDragSubstituteBy: function () { return Module.HeaderWidgetView; },
	    onRender: function () {
	      this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	    },
	    onTextEditorChange: function (newContent) {
	      this.model.set('text', newContent);
	    },
	    onTextEditorFocus: function () {
	      this.disableDragging();
	      this.disableShowingTools();
	    },
	    onTextEditorBlur: function () {
	      this.enableDragging();
	      this.enableShowingTools();
	    }
	  });

	  Module.HeaderBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.HeaderBlockSettingsView; }
	  });

	  Module.HeaderBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.headerBlockSettings; },
	    events: function () {
	      return {
	        'change .mailpoet_field_header_text_color': _.partial(this.changeColorField, 'styles.text.fontColor'),
	        'change .mailpoet_field_header_text_font_family': _.partial(this.changeField, 'styles.text.fontFamily'),
	        'change .mailpoet_field_header_text_size': _.partial(this.changeField, 'styles.text.fontSize'),
	        'change #mailpoet_field_header_link_color': _.partial(this.changeColorField, 'styles.link.fontColor'),
	        'change #mailpoet_field_header_link_underline': function (event) {
	          this.model.set('styles.link.textDecoration', (event.target.checked) ? event.target.value : 'none');
	        },
	        'change .mailpoet_field_header_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
	        'change .mailpoet_field_header_alignment': _.partial(this.changeField, 'styles.text.textAlign'),
	        'click .mailpoet_done_editing': 'close'
	      };
	    },
	    templateContext: function () {
	      return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
	        availableStyles: App.getAvailableStyles().toJSON()
	      });
	    }
	  });

	  Module.HeaderWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.headerInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.HeaderBlockModel();
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('header', {
	      blockModel: Module.HeaderBlockModel,
	      blockView: Module.HeaderBlockView
	    });

	    App.registerWidget({
	      name: 'header',
	      widgetView: Module.HeaderWidgetView,
	      priority: 98
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 612:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Automated latest content block.
	 * Only query parameters can be modified by the user. Posts pulled by this
	 * block will change as more posts get published.
	 *
	 * This block depends on blocks.button and blocks.divider for block model and
	 * block settings view.
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(605),
	  __webpack_require__(607),
	  __webpack_require__(587),
	  __webpack_require__(276),
	  __webpack_require__(573),
	  __webpack_require__(280),
	  __webpack_require__(275)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (
	  App,
	  BaseBlock,
	  ButtonBlock,
	  DividerBlock,
	  CommunicationComponent,
	  MailPoet,
	  SuperModel,
	  _,
	  jQuery
	  ) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;

	  Module.ALCSupervisor = SuperModel.extend({
	    initialize: function () {
	      var DELAY_REFRESH_FOR_MS = 500;
	      this.listenTo(
	        App.getChannel(),
	        'automatedLatestContentRefresh',
	        _.debounce(this.refresh, DELAY_REFRESH_FOR_MS)
	      );
	    },
	    refresh: function () {
	      var blocks;
	      var models = App.findModels(function (model) {
	        return model.get('type') === 'automatedLatestContent';
	      }) || [];

	      if (models.length === 0) return;
	      blocks = _.map(models, function (model) {
	        return model.toJSON();
	      });

	      CommunicationComponent.getBulkTransformedPosts({
	        blocks: blocks
	      }).then(_.partial(this.refreshBlocks, models));
	    },
	    refreshBlocks: function (models, renderedBlocks) {
	      _.each(
	        _.zip(models, renderedBlocks),
	        function (args) {
	          var model = args[0];
	          var contents = args[1];
	          model.trigger('refreshPosts', contents);
	        }
	      );
	    }
	  });

	  Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({
	    stale: ['_container'],
	    defaults: function () {
	      return this._getDefaults({
	        type: 'automatedLatestContent',
	        amount: '5',
	        contentType: 'post', // 'post'|'page'|'mailpoet_page'
	        terms: [], // List of category and tag objects
	        inclusionType: 'include', // 'include'|'exclude'
	        displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
	        titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
	        titleAlignment: 'left', // 'left'|'center'|'right'
	        titleIsLink: false, // false|true
	        imageFullWidth: false, // true|false
	        featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
	        // imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
	        showAuthor: 'no', // 'no'|'aboveText'|'belowText'
	        authorPrecededBy: 'Author:',
	        showCategories: 'no', // 'no'|'aboveText'|'belowText'
	        categoriesPrecededBy: 'Categories:',
	        readMoreType: 'button', // 'link'|'button'
	        readMoreText: 'Read more', // 'link'|'button'
	        readMoreButton: {
	          text: 'Read more',
	          url: '[postLink]'
	        },
	        sortBy: 'newest', // 'newest'|'oldest',
	        showDivider: true, // true|false
	        divider: {},
	        _container: new (App.getBlockTypeModel('container'))()
	      }, App.getConfig().get('blockDefaults.automatedLatestContent'));
	    },
	    relations: function () {
	      return {
	        readMoreButton: App.getBlockTypeModel('button'),
	        divider: App.getBlockTypeModel('divider'),
	        _container: App.getBlockTypeModel('container')
	      };
	    },
	    initialize: function () {
	      base.BlockView.prototype.initialize.apply(this, arguments);
	      this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
	      this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
	      this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
	      this.on('add remove update reset', this._scheduleFetchPosts);
	      this.on('refreshPosts', this.updatePosts, this);
	    },
	    updatePosts: function (posts) {
	      this.get('_container.blocks').reset(posts, { parse: true });
	    },
	    /**
	     * Batch more changes during a specific time, instead of fetching
	     * ALC posts on each model change
	     */
	    _scheduleFetchPosts: function () {
	      App.getChannel().trigger('automatedLatestContentRefresh');
	    }
	  });

	  Module.AutomatedLatestContentBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_automated_latest_content_block mailpoet_droppable_block',
	    initialize: function () {
	      function replaceButtonStylesHandler(data) {
	        this.model.set({ readMoreButton: data });
	      }
	      App.getChannel().on('replaceAllButtonStyles', replaceButtonStylesHandler.bind(this));
	    },
	    getTemplate: function () { return window.templates.automatedLatestContentBlock; },
	    regions: {
	      toolsRegion: '.mailpoet_tools',
	      postsRegion: '.mailpoet_automated_latest_content_block_posts'
	    },
	    modelEvents: _.extend(
	      _.omit(base.BlockView.prototype.modelEvents, 'change'),
	      {
	        postsChanged: 'render'
	      }),
	    events: _.extend(base.BlockView.prototype.events, {
	      'click .mailpoet_automated_latest_content_block_overlay': 'showSettings'
	    }),
	    onDragSubstituteBy: function () { return Module.AutomatedLatestContentWidgetView; },
	    onRender: function () {
	      var ContainerView = App.getBlockTypeView('container');
	      var renderOptions = {
	        disableTextEditor: true,
	        disableDragAndDrop: true,
	        emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
	      };
	      this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	      this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions }));
	    }
	  });

	  Module.AutomatedLatestContentBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.AutomatedLatestContentBlockSettingsView; }
	  });

	  // Sidebar view container
	  Module.AutomatedLatestContentBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.automatedLatestContentBlockSettings; },
	    events: function () {
	      return {
	        'click .mailpoet_automated_latest_content_hide_display_options': 'toggleDisplayOptions',
	        'click .mailpoet_automated_latest_content_show_display_options': 'toggleDisplayOptions',
	        'click .mailpoet_automated_latest_content_select_button': 'showButtonSettings',
	        'click .mailpoet_automated_latest_content_select_divider': 'showDividerSettings',
	        'change .mailpoet_automated_latest_content_read_more_type': 'changeReadMoreType',
	        'change .mailpoet_automated_latest_content_display_type': 'changeDisplayType',
	        'change .mailpoet_automated_latest_content_title_format': 'changeTitleFormat',
	        'change .mailpoet_automated_latest_content_title_as_links': _.partial(this.changeBoolField, 'titleIsLink'),
	        'change .mailpoet_automated_latest_content_show_divider': _.partial(this.changeBoolField, 'showDivider'),
	        'input .mailpoet_automated_latest_content_show_amount': _.partial(this.changeField, 'amount'),
	        'change .mailpoet_automated_latest_content_content_type': _.partial(this.changeField, 'contentType'),
	        'change .mailpoet_automated_latest_content_include_or_exclude': _.partial(this.changeField, 'inclusionType'),
	        'change .mailpoet_automated_latest_content_title_alignment': _.partial(this.changeField, 'titleAlignment'),
	        'change .mailpoet_automated_latest_content_image_full_width': _.partial(this.changeBoolField, 'imageFullWidth'),
	        'change .mailpoet_automated_latest_content_featured_image_position': _.partial(this.changeField, 'featuredImagePosition'),
	        'change .mailpoet_automated_latest_content_show_author': _.partial(this.changeField, 'showAuthor'),
	        'input .mailpoet_automated_latest_content_author_preceded_by': _.partial(this.changeField, 'authorPrecededBy'),
	        'change .mailpoet_automated_latest_content_show_categories': _.partial(this.changeField, 'showCategories'),
	        'input .mailpoet_automated_latest_content_categories': _.partial(this.changeField, 'categoriesPrecededBy'),
	        'input .mailpoet_automated_latest_content_read_more_text': _.partial(this.changeField, 'readMoreText'),
	        'change .mailpoet_automated_latest_content_sort_by': _.partial(this.changeField, 'sortBy'),
	        'click .mailpoet_done_editing': 'close'
	      };
	    },
	    onRender: function () {
	      var that = this;

	      // Dynamically update available post types
	      CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));

	      this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
	        multiple: true,
	        allowClear: true,
	        placeholder: MailPoet.I18n.t('categoriesAndTags'),
	        ajax: {
	          data: function (params) {
	            return {
	              term: params.term
	            };
	          },
	          transport: function (options, success, failure) {
	            var taxonomies;
	            var termsPromise;
	            var promise = CommunicationComponent.getTaxonomies(
	              that.model.get('contentType')
	            ).then(function (tax) {
	              taxonomies = tax;
	              // Fetch available terms based on the list of taxonomies already fetched
	              termsPromise = CommunicationComponent.getTerms({
	                search: options.data.term,
	                taxonomies: _.keys(taxonomies)
	              }).then(function (terms) {
	                return {
	                  taxonomies: taxonomies,
	                  terms: terms
	                };
	              });
	              return termsPromise;
	            });

	            promise.then(success);
	            promise.fail(failure);
	            return promise;
	          },
	          processResults: function (data) {
	            // Transform taxonomies and terms into select2 compatible format
	            return {
	              results: _.map(
	                data.terms,
	                function (item) {
	                  return _.defaults({
	                    text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
	                    id: item.term_id
	                  }, item);
	                }
	              )
	            };
	          }
	        }
	      }).on({
	        'select2:select': function (event) {
	          var terms = that.model.get('terms');
	          terms.add(event.params.data);
	          // Reset whole model in order for change events to propagate properly
	          that.model.set('terms', terms.toJSON());
	        },
	        'select2:unselect': function (event) {
	          var terms = that.model.get('terms');
	          terms.remove(event.params.data);
	          // Reset whole model in order for change events to propagate properly
	          that.model.set('terms', terms.toJSON());
	        }
	      }).trigger('change');
	    },
	    toggleDisplayOptions: function () {
	      var el = this.$('.mailpoet_automated_latest_content_display_options');
	      var showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
	      if (el.hasClass('mailpoet_closed')) {
	        el.removeClass('mailpoet_closed');
	        showControl.addClass('mailpoet_hidden');
	      } else {
	        el.addClass('mailpoet_closed');
	        showControl.removeClass('mailpoet_hidden');
	      }
	    },
	    showButtonSettings: function () {
	      var buttonModule = ButtonBlock;
	      (new buttonModule.ButtonBlockSettingsView({
	        model: this.model.get('readMoreButton'),
	        renderOptions: {
	          displayFormat: 'subpanel',
	          hideLink: true,
	          hideApplyToAll: true
	        }
	      })).render();
	    },
	    showDividerSettings: function () {
	      var dividerModule = DividerBlock;
	      (new dividerModule.DividerBlockSettingsView({
	        model: this.model.get('divider'),
	        renderOptions: {
	          displayFormat: 'subpanel',
	          hideApplyToAll: true
	        }
	      })).render();
	    },
	    changeReadMoreType: function (event) {
	      var value = jQuery(event.target).val();
	      if (value == 'link') {
	        this.$('.mailpoet_automated_latest_content_read_more_text').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_select_button').addClass('mailpoet_hidden');
	      } else if (value == 'button') {
	        this.$('.mailpoet_automated_latest_content_read_more_text').addClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_select_button').removeClass('mailpoet_hidden');
	      }
	      this.changeField('readMoreType', event);
	    },
	    changeDisplayType: function (event) {
	      var value = jQuery(event.target).val();

	      if (value == 'titleOnly') {
	        this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_image_separator').addClass('mailpoet_hidden');
	      } else {
	        this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_image_separator').removeClass('mailpoet_hidden');

	        // Reset titleFormat if it was set to List when switching away from displayType=titleOnly
	        if (this.model.get('titleFormat') === 'ul') {
	          this.model.set('titleFormat', 'h1');
	          this.$('.mailpoet_automated_latest_content_title_format').val(['h1']);
	          this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden');
	        }
	      }

	      if (value === 'excerpt') {
	        this.$('.mailpoet_automated_latest_content_featured_image_position_container').removeClass('mailpoet_hidden');
	      } else {
	        this.$('.mailpoet_automated_latest_content_featured_image_position_container').addClass('mailpoet_hidden');
	      }
	      this.changeField('displayType', event);
	    },
	    changeTitleFormat: function (event) {
	      var value = jQuery(event.target).val();
	      if (value == 'ul') {
	        this.$('.mailpoet_automated_latest_content_non_title_list_options').addClass('mailpoet_hidden');

	        this.model.set('titleIsLink', true);
	        this.$('.mailpoet_automated_latest_content_title_as_link').addClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_title_as_links').val(['true']);
	      } else {
	        this.$('.mailpoet_automated_latest_content_non_title_list_options').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden');
	      }
	      this.changeField('titleFormat', event);
	    },
	    _updateContentTypes: function (postTypes) {
	      var select = this.$('.mailpoet_automated_latest_content_content_type');
	      var selectedValue = this.model.get('contentType');

	      select.find('option').remove();
	      _.each(postTypes, function (type) {
	        select.append(jQuery('<option>', {
	          value: type.name,
	          text: type.label
	        }));
	      });
	      select.val(selectedValue);
	    }
	  });

	  Module.AutomatedLatestContentWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.automatedLatestContentInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.AutomatedLatestContentBlockModel({}, { parse: true });
	        },
	        onDrop: function (options) {
	          options.droppedView.triggerMethod('showSettings');
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('automatedLatestContent', {
	      blockModel: Module.AutomatedLatestContentBlockModel,
	      blockView: Module.AutomatedLatestContentBlockView
	    });

	    App.registerWidget({
	      name: 'automatedLatestContent',
	      widgetView: Module.AutomatedLatestContentWidgetView,
	      priority: 97
	    });
	  });

	  App.on('start', function (App) {
	    var Application = App;
	    Application._ALCSupervisor = new Module.ALCSupervisor();
	    Application._ALCSupervisor.refresh();
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 613:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Posts block.
	 *
	 * This block works slightly differently compared to any other block.
	 * The difference is that once the user changes settings of this block,
	 * it will be removed and replaced with other blocks. So this block will
	 * not appear in the final JSON structure, it serves only as a placeholder
	 * for posts, that will be comprised of container, image, button and text blocks
	 *
	 * This block depends on blocks.button and blocks.divider for block model and
	 * block settings view.
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(544),
	  __webpack_require__(571),
	  __webpack_require__(572),
	  __webpack_require__(280),
	  __webpack_require__(275),
	  __webpack_require__(276),
	  __webpack_require__(583),
	  __webpack_require__(587),
	  __webpack_require__(603),
	  __webpack_require__(605),
	  __webpack_require__(607),
	  __webpack_require__(289)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (
	  Backbone,
	  Marionette,
	  Radio,
	  _,
	  jQuery,
	  MailPoet,
	  App,
	  CommunicationComponent,
	  BaseBlock,
	  ButtonBlock,
	  DividerBlock
	  ) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;
	  var PostsDisplayOptionsSettingsView;
	  var SinglePostSelectionSettingsView;
	  var EmptyPostSelectionSettingsView;
	  var PostSelectionSettingsView;
	  var PostsSelectionCollectionView;

	  Module.PostsBlockModel = base.BlockModel.extend({
	    stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
	    defaults: function () {
	      return this._getDefaults({
	        type: 'posts',
	        amount: '10',
	        offset: 0,
	        contentType: 'post', // 'post'|'page'|'mailpoet_page'
	        postStatus: 'publish', // 'draft'|'pending'|'private'|'publish'|'future'
	        terms: [], // List of category and tag objects
	        search: '', // Search keyword term
	        inclusionType: 'include', // 'include'|'exclude'
	        displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
	        titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
	        titleAlignment: 'left', // 'left'|'center'|'right'
	        titleIsLink: false, // false|true
	        imageFullWidth: false, // true|false
	        featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
	        // imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
	        showAuthor: 'no', // 'no'|'aboveText'|'belowText'
	        authorPrecededBy: 'Author:',
	        showCategories: 'no', // 'no'|'aboveText'|'belowText'
	        categoriesPrecededBy: 'Categories:',
	        readMoreType: 'link', // 'link'|'button'
	        readMoreText: 'Read more', // 'link'|'button'
	        readMoreButton: {
	          text: 'Read more',
	          url: '[postLink]'
	        },
	        sortBy: 'newest', // 'newest'|'oldest',
	        showDivider: true, // true|false
	        divider: {},
	        _selectedPosts: [],
	        _availablePosts: [],
	        _transformedPosts: new (App.getBlockTypeModel('container'))()
	      }, App.getConfig().get('blockDefaults.posts'));
	    },
	    relations: function () {
	      return {
	        readMoreButton: App.getBlockTypeModel('button'),
	        divider: App.getBlockTypeModel('divider'),
	        _selectedPosts: Backbone.Collection,
	        _availablePosts: Backbone.Collection,
	        _transformedPosts: App.getBlockTypeModel('container')
	      };
	    },
	    initialize: function () {
	      var POST_REFRESH_DELAY_MS = 500;
	      var refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS);
	      var refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);

	      // Attach Radio.Requests API primarily for highlighting
	      _.extend(this, Radio.Requests);

	      this.fetchAvailablePosts();
	      this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
	      this.on('loadMorePosts', this._loadMorePosts, this);

	      this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
	      this.on('change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
	      this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
	      this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);

	      this.on('insertSelectedPosts', this._insertSelectedPosts, this);
	    },
	    fetchAvailablePosts: function () {
	      var that = this;
	      this.set('offset', 0);
	      CommunicationComponent.getPosts(this.toJSON()).done(function (posts) {
	        that.get('_availablePosts').reset(posts);
	        that.get('_selectedPosts').reset(); // Empty out the collection
	        that.trigger('change:_availablePosts');
	      }).fail(function () {
	        MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
	      });
	    },
	    _loadMorePosts: function () {
	      var that = this;
	      var postCount = this.get('_availablePosts').length;
	      var nextOffset = this.get('offset') + Number(this.get('amount'));

	      if (postCount === 0 || postCount < nextOffset) {
	        // No more posts to load
	        return false;
	      }
	      this.set('offset', nextOffset);
	      this.trigger('loadingMorePosts');

	      CommunicationComponent.getPosts(this.toJSON()).done(function (posts) {
	        that.get('_availablePosts').add(posts);
	        that.trigger('change:_availablePosts');
	      }).fail(function () {
	        MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
	      }).always(function () {
	        that.trigger('morePostsLoaded');
	      });
	    },
	    _refreshTransformedPosts: function () {
	      var that = this;
	      var data = this.toJSON();

	      data.posts = this.get('_selectedPosts').pluck('ID');

	      if (data.posts.length === 0) {
	        this.get('_transformedPosts').get('blocks').reset();
	        return;
	      }

	      CommunicationComponent.getTransformedPosts(data).done(function (posts) {
	        that.get('_transformedPosts').get('blocks').reset(posts, { parse: true });
	      }).fail(function () {
	        MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
	      });
	    },
	    _insertSelectedPosts: function () {
	      var data = this.toJSON();
	      var index = this.collection.indexOf(this);
	      var collection = this.collection;

	      data.posts = this.get('_selectedPosts').pluck('ID');

	      if (data.posts.length === 0) return;

	      CommunicationComponent.getTransformedPosts(data).done(function (posts) {
	        collection.add(posts, { at: index });
	      }).fail(function () {
	        MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
	      });
	    }
	  });

	  Module.PostsBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_posts_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.postsBlock; },
	    modelEvents: {}, // Forcefully disable all events
	    regions: _.extend({
	      postsRegion: '.mailpoet_posts_block_posts'
	    }, base.BlockView.prototype.regions),
	    onDragSubstituteBy: function () { return Module.PostsWidgetView; },
	    initialize: function () {
	      base.BlockView.prototype.initialize.apply(this, arguments);

	      this.toolsView = new Module.PostsBlockToolsView({ model: this.model });
	      this.model.reply('blockView', this.notifyAboutSelf, this);
	    },
	    onRender: function () {
	      var ContainerView;
	      var renderOptions;
	      if (!this.getRegion('toolsRegion').hasView()) {
	        this.showChildView('toolsRegion', this.toolsView);
	      }
	      this.trigger('showSettings');

	      ContainerView = App.getBlockTypeView('container');
	      renderOptions = {
	        disableTextEditor: true,
	        disableDragAndDrop: true,
	        emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
	      };
	      this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
	    },
	    notifyAboutSelf: function () {
	      return this;
	    },
	    onBeforeDestroy: function () {
	      this.model.stopReplying('blockView', this.notifyAboutSelf, this);
	    }
	  });

	  Module.PostsBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.PostsBlockSettingsView; }
	  });

	  Module.PostsBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.postsBlockSettings; },
	    regions: {
	      selectionRegion: '.mailpoet_settings_posts_selection',
	      displayOptionsRegion: '.mailpoet_settings_posts_display_options'
	    },
	    events: {
	      'click .mailpoet_settings_posts_show_display_options': 'switchToDisplayOptions',
	      'click .mailpoet_settings_posts_show_post_selection': 'switchToPostSelection',
	      'click .mailpoet_settings_posts_insert_selected': 'insertPosts'
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON()
	      };
	    },
	    initialize: function () {
	      this.model.trigger('startEditing');
	      this.selectionView = new PostSelectionSettingsView({ model: this.model });
	      this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model });
	    },
	    onRender: function () {
	      var that = this;
	      this.model.request('blockView');

	      this.showChildView('selectionRegion', this.selectionView);
	      this.showChildView('displayOptionsRegion', this.displayOptionsView);

	      MailPoet.Modal.panel({
	        element: this.$el,
	        template: '',
	        position: 'right',
	        width: App.getConfig().get('sidepanelWidth'),
	        onCancel: function () {
	          // Self destroy the block if the user closes settings modal
	          that.model.destroy();
	        }
	      });

	      // Inform child views that they have been attached to document
	      this.selectionView.triggerMethod('attach');
	      this.displayOptionsView.triggerMethod('attach');
	    },
	    switchToDisplayOptions: function () {
	      // Switch content view
	      this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_closed');
	      this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_closed');

	      // Switch controls
	      this.$('.mailpoet_settings_posts_show_display_options').addClass('mailpoet_hidden');
	      this.$('.mailpoet_settings_posts_show_post_selection').removeClass('mailpoet_hidden');
	    },
	    switchToPostSelection: function () {
	      // Switch content view
	      this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_closed');
	      this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_closed');

	      // Switch controls
	      this.$('.mailpoet_settings_posts_show_post_selection').addClass('mailpoet_hidden');
	      this.$('.mailpoet_settings_posts_show_display_options').removeClass('mailpoet_hidden');
	    },
	    insertPosts: function () {
	      this.model.trigger('insertSelectedPosts');
	      this.model.destroy();
	      this.close();
	    }
	  });

	  PostsSelectionCollectionView = Marionette.CollectionView.extend({
	    className: 'mailpoet_post_scroll_container',
	    childView: function () { return SinglePostSelectionSettingsView; },
	    emptyView: function () { return EmptyPostSelectionSettingsView; },
	    childViewOptions: function () {
	      return {
	        blockModel: this.blockModel
	      };
	    },
	    initialize: function (options) {
	      this.blockModel = options.blockModel;
	    },
	    events: {
	      scroll: 'onPostsScroll'
	    },
	    onPostsScroll: function (event) {
	      var $postsBox = jQuery(event.target);
	      if ($postsBox.scrollTop() + $postsBox.innerHeight() >= $postsBox[0].scrollHeight) {
	        // Load more posts if scrolled to bottom
	        this.blockModel.trigger('loadMorePosts');
	      }
	    }
	  });

	  PostSelectionSettingsView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.postSelectionPostsBlockSettings; },
	    regions: {
	      posts: '.mailpoet_post_selection_container'
	    },
	    events: function () {
	      return {
	        'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'),
	        'change .mailpoet_posts_post_status': _.partial(this.changeField, 'postStatus'),
	        'input .mailpoet_posts_search_term': _.partial(this.changeField, 'search')
	      };
	    },
	    modelEvents: {
	      'change:offset': function (model, value) {
	        // Scroll posts view to top if settings are changed
	        if (value === 0) {
	          this.$('.mailpoet_post_scroll_container').scrollTop(0);
	        }
	      },
	      loadingMorePosts: function () {
	        this.$('.mailpoet_post_selection_loading').css('visibility', 'visible');
	      },
	      morePostsLoaded: function () {
	        this.$('.mailpoet_post_selection_loading').css('visibility', 'hidden');
	      }
	    },
	    onRender: function () {
	      var postsView;
	      // Dynamically update available post types
	      CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
	      postsView = new PostsSelectionCollectionView({
	        collection: this.model.get('_availablePosts'),
	        blockModel: this.model
	      });

	      this.showChildView('posts', postsView);
	    },
	    onAttach: function () {
	      var that = this;

	      this.$('.mailpoet_posts_categories_and_tags').select2({
	        multiple: true,
	        allowClear: true,
	        placeholder: MailPoet.I18n.t('categoriesAndTags'),
	        ajax: {
	          data: function (params) {
	            return {
	              term: params.term
	            };
	          },
	          transport: function (options, success, failure) {
	            var taxonomies;
	            var termsPromise;
	            var promise = CommunicationComponent.getTaxonomies(
	              that.model.get('contentType')
	            ).then(function (tax) {
	              taxonomies = tax;
	              // Fetch available terms based on the list of taxonomies already fetched
	              termsPromise = CommunicationComponent.getTerms({
	                search: options.data.term,
	                taxonomies: _.keys(taxonomies)
	              }).then(function (terms) {
	                return {
	                  taxonomies: taxonomies,
	                  terms: terms
	                };
	              });
	              return termsPromise;
	            });

	            promise.then(success);
	            promise.fail(failure);
	            return promise;
	          },
	          processResults: function (data) {
	            // Transform taxonomies and terms into select2 compatible format
	            return {
	              results: _.map(
	                data.terms,
	                function (item) {
	                  return _.defaults({
	                    text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
	                    id: item.term_id
	                  }, item);
	                }
	              )
	            };
	          }
	        }
	      }).on({
	        'select2:select': function (event) {
	          var terms = that.model.get('terms');
	          terms.add(event.params.data);
	          // Reset whole model in order for change events to propagate properly
	          that.model.set('terms', terms.toJSON());
	        },
	        'select2:unselect': function (event) {
	          var terms = that.model.get('terms');
	          terms.remove(event.params.data);
	          // Reset whole model in order for change events to propagate properly
	          that.model.set('terms', terms.toJSON());
	        }
	      }).trigger('change');
	    },
	    changeField: function (field, event) {
	      this.model.set(field, jQuery(event.target).val());
	    },
	    _updateContentTypes: function (postTypes) {
	      var select = this.$('.mailpoet_settings_posts_content_type');
	      var selectedValue = this.model.get('contentType');

	      select.find('option').remove();
	      _.each(postTypes, function (type) {
	        select.append(jQuery('<option>', {
	          value: type.name,
	          text: type.label
	        }));
	      });
	      select.val(selectedValue);
	    }
	  });

	  EmptyPostSelectionSettingsView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.emptyPostPostsBlockSettings; }
	  });

	  SinglePostSelectionSettingsView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.singlePostPostsBlockSettings; },
	    events: function () {
	      return {
	        'change .mailpoet_select_post_checkbox': 'postSelectionChange'
	      };
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON(),
	        index: this._index
	      };
	    },
	    initialize: function (options) {
	      this.blockModel = options.blockModel;
	    },
	    postSelectionChange: function (event) {
	      var checkBox = jQuery(event.target);
	      var selectedPostsCollection = this.blockModel.get('_selectedPosts');
	      if (checkBox.prop('checked')) {
	        selectedPostsCollection.add(this.model);
	      } else {
	        selectedPostsCollection.remove(this.model);
	      }
	    }
	  });

	  PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.displayOptionsPostsBlockSettings; },
	    events: function () {
	      return {
	        'click .mailpoet_posts_select_button': 'showButtonSettings',
	        'click .mailpoet_posts_select_divider': 'showDividerSettings',
	        'change .mailpoet_posts_read_more_type': 'changeReadMoreType',
	        'change .mailpoet_posts_display_type': 'changeDisplayType',
	        'change .mailpoet_posts_title_format': 'changeTitleFormat',
	        'change .mailpoet_posts_title_as_links': _.partial(this.changeBoolField, 'titleIsLink'),
	        'change .mailpoet_posts_show_divider': _.partial(this.changeBoolField, 'showDivider'),
	        'input .mailpoet_posts_show_amount': _.partial(this.changeField, 'amount'),
	        'change .mailpoet_posts_content_type': _.partial(this.changeField, 'contentType'),
	        'change .mailpoet_posts_include_or_exclude': _.partial(this.changeField, 'inclusionType'),
	        'change .mailpoet_posts_title_alignment': _.partial(this.changeField, 'titleAlignment'),
	        'change .mailpoet_posts_image_full_width': _.partial(this.changeBoolField, 'imageFullWidth'),
	        'change .mailpoet_posts_featured_image_position': _.partial(this.changeField, 'featuredImagePosition'),
	        'change .mailpoet_posts_show_author': _.partial(this.changeField, 'showAuthor'),
	        'input .mailpoet_posts_author_preceded_by': _.partial(this.changeField, 'authorPrecededBy'),
	        'change .mailpoet_posts_show_categories': _.partial(this.changeField, 'showCategories'),
	        'input .mailpoet_posts_categories': _.partial(this.changeField, 'categoriesPrecededBy'),
	        'input .mailpoet_posts_read_more_text': _.partial(this.changeField, 'readMoreText'),
	        'change .mailpoet_posts_sort_by': _.partial(this.changeField, 'sortBy')
	      };
	    },
	    templateContext: function () {
	      return {
	        model: this.model.toJSON()
	      };
	    },
	    showButtonSettings: function () {
	      var buttonModule = ButtonBlock;
	      (new buttonModule.ButtonBlockSettingsView({
	        model: this.model.get('readMoreButton'),
	        renderOptions: {
	          displayFormat: 'subpanel',
	          hideLink: true,
	          hideApplyToAll: true
	        }
	      })).render();
	    },
	    showDividerSettings: function () {
	      var dividerModule = DividerBlock;
	      (new dividerModule.DividerBlockSettingsView({
	        model: this.model.get('divider'),
	        renderOptions: {
	          displayFormat: 'subpanel',
	          hideApplyToAll: true
	        }
	      })).render();
	    },
	    changeReadMoreType: function (event) {
	      var value = jQuery(event.target).val();
	      if (value == 'link') {
	        this.$('.mailpoet_posts_read_more_text').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_select_button').addClass('mailpoet_hidden');
	      } else if (value == 'button') {
	        this.$('.mailpoet_posts_read_more_text').addClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_select_button').removeClass('mailpoet_hidden');
	      }
	      this.changeField('readMoreType', event);
	    },
	    changeDisplayType: function (event) {
	      var value = jQuery(event.target).val();
	      if (value == 'titleOnly') {
	        this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_image_separator').addClass('mailpoet_hidden');
	      } else {
	        this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_image_separator').removeClass('mailpoet_hidden');

	        // Reset titleFormat if it was set to List when switching away from displayType=titleOnly
	        if (this.model.get('titleFormat') === 'ul') {
	          this.model.set('titleFormat', 'h1');
	          this.$('.mailpoet_posts_title_format').val(['h1']);
	          this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
	        }
	      }

	      if (value === 'excerpt') {
	        this.$('.mailpoet_posts_featured_image_position_container').removeClass('mailpoet_hidden');
	      } else {
	        this.$('.mailpoet_posts_featured_image_position_container').addClass('mailpoet_hidden');
	      }

	      this.changeField('displayType', event);
	    },
	    changeTitleFormat: function (event) {
	      var value = jQuery(event.target).val();
	      if (value == 'ul') {
	        this.$('.mailpoet_posts_non_title_list_options').addClass('mailpoet_hidden');

	        this.model.set('titleIsLink', true);
	        this.$('.mailpoet_posts_title_as_link').addClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_title_as_links').val(['true']);
	      } else {
	        this.$('.mailpoet_posts_non_title_list_options').removeClass('mailpoet_hidden');
	        this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
	      }
	      this.changeField('titleFormat', event);
	    }
	  });

	  Module.PostsWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.postsInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.PostsBlockModel({}, { parse: true });
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('posts', {
	      blockModel: Module.PostsBlockModel,
	      blockView: Module.PostsBlockView
	    });

	    App.registerWidget({
	      name: 'posts',
	      widgetView: Module.PostsWidgetView,
	      priority: 96
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ },

/***/ 614:
/***/ function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
	 * Social icons content block
	 */
	!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	  __webpack_require__(583),
	  __webpack_require__(603),
	  __webpack_require__(544),
	  __webpack_require__(571),
	  __webpack_require__(573),
	  __webpack_require__(280),
	  __webpack_require__(275)
	], __WEBPACK_AMD_DEFINE_RESULT__ = function (App, BaseBlock, Backbone, Marionette, SuperModel, _, jQuery) {

	  'use strict';

	  var Module = {};
	  var base = BaseBlock;
	  var SocialBlockSettingsIconSelectorView;
	  var SocialBlockSettingsIconView;
	  var SocialBlockSettingsIconCollectionView;
	  var SocialBlockSettingsStylesView;
	  var SocialIconView;

	  Module.SocialIconModel = SuperModel.extend({
	    defaults: function () {
	      var defaultValues = App.getConfig().get('socialIcons.custom');
	      return {
	        type: 'socialIcon',
	        iconType: 'custom',
	        link: defaultValues.get('defaultLink'),
	        image: App.getAvailableStyles().get('socialIconSets.default.custom'),
	        height: '32px',
	        width: '32px',
	        text: defaultValues.get('title')
	      };
	    },
	    initialize: function () {
	      var that = this;
	      // Make model swap to default values for that type when iconType changes
	      this.on('change:iconType', function () {
	        var defaultValues = App.getConfig().get('socialIcons').get(that.get('iconType'));
	        var iconSet = that.collection.iconBlockModel.getIconSet();
	        this.set({
	          link: defaultValues.get('defaultLink'),
	          image: iconSet.get(that.get('iconType')),
	          text: defaultValues.get('title')
	        });
	      }, this);
	      this.on('change', function () { App.getChannel().trigger('autoSave'); });
	    }
	  });

	  Module.SocialIconCollectionModel = Backbone.Collection.extend({
	    model: Module.SocialIconModel
	  });

	  Module.SocialBlockModel = base.BlockModel.extend({
	    name: 'iconBlockModel',
	    defaults: function () {
	      return this._getDefaults({
	        type: 'social',
	        iconSet: 'default',
	        icons: new Module.SocialIconCollectionModel()
	      }, App.getConfig().get('blockDefaults.social'));
	    },
	    relations: {
	      icons: Module.SocialIconCollectionModel
	    },
	    initialize: function () {
	      this.get('icons').on('add remove change', this._iconsChanged, this);
	      this.on('change:iconSet', this.changeIconSet, this);
	    },
	    getIconSet: function () {
	      return App.getAvailableStyles().get('socialIconSets').get(this.get('iconSet'));
	    },
	    changeIconSet: function () {
	      var iconSet = this.getIconSet();
	      _.each(this.get('icons').models, function (model) {
	        model.set('image', iconSet.get(model.get('iconType')));
	      });
	    },
	    _iconsChanged: function () {
	      App.getChannel().trigger('autoSave');
	    }
	  });

	  SocialIconView = Marionette.View.extend({
	    tagName: 'span',
	    getTemplate: function () { return window.templates.socialIconBlock; },
	    modelEvents: {
	      change: 'render'
	    },
	    templateContext: function () {
	      var allIconSets = App.getAvailableStyles().get('socialIconSets');
	      return {
	        model: this.model.toJSON(),
	        allIconSets: allIconSets.toJSON(),
	        imageMissingSrc: App.getConfig().get('urls.imageMissing')
	      };
	    }
	  });

	  Module.SocialIconCollectionView = Marionette.CollectionView.extend({
	    childView: SocialIconView
	  });

	  Module.SocialBlockView = base.BlockView.extend({
	    className: 'mailpoet_block mailpoet_social_block mailpoet_droppable_block',
	    getTemplate: function () { return window.templates.socialBlock; },
	    regions: _.extend({}, base.BlockView.prototype.regions, {
	      icons: '.mailpoet_social'
	    }),
	    ui: {
	      tools: '> .mailpoet_tools'
	    },
	    behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
	      ShowSettingsBehavior: {}
	    }),
	    onDragSubstituteBy: function () { return Module.SocialWidgetView; },
	    onRender: function () {
	      this.toolsView = new Module.SocialBlockToolsView({ model: this.model });
	      this.showChildView('toolsRegion', this.toolsView);
	      this.showChildView('icons', new Module.SocialIconCollectionView({
	        collection: this.model.get('icons')
	      }));
	    }
	  });

	  Module.SocialBlockToolsView = base.BlockToolsView.extend({
	    getSettingsView: function () { return Module.SocialBlockSettingsView; }
	  });

	  // Sidebar view container
	  Module.SocialBlockSettingsView = base.BlockSettingsView.extend({
	    getTemplate: function () { return window.templates.socialBlockSettings; },
	    regions: {
	      iconRegion: '#mailpoet_social_icons_selection',
	      stylesRegion: '#mailpoet_social_icons_styles'
	    },
	    events: function () {
	      return {
	        'click .mailpoet_done_editing': 'close'
	      };
	    },
	    initialize: function () {
	      base.BlockSettingsView.prototype.initialize.apply(this, arguments);

	      this._iconSelectorView = new SocialBlockSettingsIconSelectorView({ model: this.model });
	      this._stylesView = new SocialBlockSettingsStylesView({ model: this.model });
	    },
	    onRender: function () {
	      this.showChildView('iconRegion', this._iconSelectorView);
	      this.showChildView('stylesRegion', this._stylesView);
	    }
	  });

	  // Single icon settings view, used by the selector view
	  SocialBlockSettingsIconView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.socialSettingsIcon; },
	    events: function () {
	      return {
	        'click .mailpoet_delete_block': 'deleteIcon',
	        'change .mailpoet_social_icon_field_type': _.partial(this.changeField, 'iconType'),
	        'input .mailpoet_social_icon_field_image': _.partial(this.changeField, 'image'),
	        'input .mailpoet_social_icon_field_link': this.changeLink,
	        'input .mailpoet_social_icon_field_text': _.partial(this.changeField, 'text')
	      };
	    },
	    modelEvents: {
	      'change:iconType': 'render',
	      'change:image': function () {
	        this.$('.mailpoet_social_icon_image').attr('src', this.model.get('image'));
	      },
	      'change:text': function () {
	        this.$('.mailpoet_social_icon_image').attr('alt', this.model.get('text'));
	      }
	    },
	    templateContext: function () {
	      var icons = App.getConfig().get('socialIcons');
	      // Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...]
	      var availableIconTypes = _.map(_.keys(icons.attributes), function (key) { return { iconType: key, title: icons.get(key).get('title') }; });
	      var allIconSets = App.getAvailableStyles().get('socialIconSets');
	      return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
	        iconTypes: availableIconTypes,
	        currentType: icons.get(this.model.get('iconType')).toJSON(),
	        allIconSets: allIconSets.toJSON()
	      });
	    },
	    deleteIcon: function () {
	      this.model.destroy();
	    },
	    changeLink: function (event) {
	      if (this.model.get('iconType') === 'email') {
	        this.model.set('link', 'mailto:' + jQuery(event.target).val());
	      } else {
	        return this.changeField('link', event);
	      }
	    },
	    changeField: function (field, event) {
	      this.model.set(field, jQuery(event.target).val());
	    }
	  });

	  SocialBlockSettingsIconCollectionView = Marionette.CollectionView.extend({
	    behaviors: {
	      SortableBehavior: {
	        items: '> div'
	      }
	    },
	    childViewContainer: '#mailpoet_social_icon_selector_contents',
	    childView: SocialBlockSettingsIconView
	  });

	  // Select icons section container view
	  SocialBlockSettingsIconSelectorView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.socialSettingsIconSelector; },
	    regions: {
	      icons: '#mailpoet_social_icon_selector_contents'
	    },
	    events: {
	      'click .mailpoet_add_social_icon': 'addSocialIcon'
	    },
	    modelEvents: {
	      'change:iconSet': 'render'
	    },
	    addSocialIcon: function () {
	      // Add a social icon with default values
	      this.model.get('icons').add({});
	    },
	    onRender: function () {
	      this.showChildView('icons', new SocialBlockSettingsIconCollectionView({
	        collection: this.model.get('icons')
	      }));
	    }

	  });

	  SocialBlockSettingsStylesView = Marionette.View.extend({
	    getTemplate: function () { return window.templates.socialSettingsStyles; },
	    modelEvents: {
	      change: 'render'
	    },
	    events: {
	      'click .mailpoet_social_icon_set': 'changeSocialIconSet'
	    },
	    initialize: function () {
	      this.listenTo(this.model.get('icons'), 'add remove change', this.render);
	    },
	    templateContext: function () {
	      var allIconSets = App.getAvailableStyles().get('socialIconSets');
	      return {
	        activeSet: this.model.get('iconSet'),
	        socialIconSets: allIconSets.toJSON(),
	        availableSets: _.keys(allIconSets.toJSON()),
	        availableSocialIcons: this.model.get('icons').pluck('iconType')
	      };
	    },
	    changeSocialIconSet: function (event) {
	      this.model.set('iconSet', jQuery(event.currentTarget).data('setname'));
	    },
	    onBeforeDestroy: function () {
	      this.model.get('icons').off('add remove', this.render, this);
	    }
	  });

	  Module.SocialWidgetView = base.WidgetView.extend({
	    getTemplate: function () { return window.templates.socialInsertion; },
	    behaviors: {
	      DraggableBehavior: {
	        cloneOriginal: true,
	        drop: function () {
	          return new Module.SocialBlockModel({
	            type: 'social',
	            iconSet: 'default',
	            icons: [
	              {
	                type: 'socialIcon',
	                iconType: 'facebook',
	                link: 'http://www.facebook.com',
	                image: App.getAvailableStyles().get('socialIconSets.default.facebook'),
	                height: '32px',
	                width: '32px',
	                text: 'Facebook'
	              },
	              {
	                type: 'socialIcon',
	                iconType: 'twitter',
	                link: 'http://www.twitter.com',
	                image: App.getAvailableStyles().get('socialIconSets.default.twitter'),
	                height: '32px',
	                width: '32px',
	                text: 'Twitter'
	              }
	            ]
	          }, { parse: true });
	        }
	      }
	    }
	  });

	  App.on('before:start', function (App) {
	    App.registerBlockType('social', {
	      blockModel: Module.SocialBlockModel,
	      blockView: Module.SocialBlockView
	    });

	    App.registerWidget({
	      name: 'social',
	      widgetView: Module.SocialWidgetView,
	      priority: 95
	    });
	  });

	  return Module;
	}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));


/***/ }

});