From e530bd14c782705cd2e27c6c29fac5a547ecd87e Mon Sep 17 00:00:00 2001 From: Jim Ma Date: Tue, 16 Jan 2018 20:44:43 +0800 Subject: [PATCH 1/4] add Chart.js support --- note.html | 2 +- public/js/chart.js | 14145 +++++++++++++++++++++++++++++++++++++++++ public/md/main-v2.js | 84 +- 3 files changed, 14204 insertions(+), 27 deletions(-) create mode 100644 public/js/chart.js diff --git a/note.html b/note.html index 16980e991..40ba0fcde 100755 --- a/note.html +++ b/note.html @@ -670,7 +670,7 @@ - + diff --git a/public/js/chart.js b/public/js/chart.js new file mode 100644 index 000000000..32c747fc5 --- /dev/null +++ b/public/js/chart.js @@ -0,0 +1,14145 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 2.7.1 + * + * Copyright 2017 Nick Downie + * Released under the MIT license + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(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 a(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 lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } +}; + +Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] +}; + +Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] +}; + +Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +}; + +Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + this.valid = true; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = convert[space][sname](values[space]); + } + } + + return true; +}; + +Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; +}; + +Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; +}; + +if (typeof window !== 'undefined') { + window.Color = Color; +} + +module.exports = Color; + +},{"2":2,"5":5}],4:[function(require,module,exports){ +/* MIT license */ + +module.exports = { + rgb2hsl: rgb2hsl, + rgb2hsv: rgb2hsv, + rgb2hwb: rgb2hwb, + rgb2cmyk: rgb2cmyk, + rgb2keyword: rgb2keyword, + rgb2xyz: rgb2xyz, + rgb2lab: rgb2lab, + rgb2lch: rgb2lch, + + hsl2rgb: hsl2rgb, + hsl2hsv: hsl2hsv, + hsl2hwb: hsl2hwb, + hsl2cmyk: hsl2cmyk, + hsl2keyword: hsl2keyword, + + hsv2rgb: hsv2rgb, + hsv2hsl: hsv2hsl, + hsv2hwb: hsv2hwb, + hsv2cmyk: hsv2cmyk, + hsv2keyword: hsv2keyword, + + hwb2rgb: hwb2rgb, + hwb2hsl: hwb2hsl, + hwb2hsv: hwb2hsv, + hwb2cmyk: hwb2cmyk, + hwb2keyword: hwb2keyword, + + cmyk2rgb: cmyk2rgb, + cmyk2hsl: cmyk2hsl, + cmyk2hsv: cmyk2hsv, + cmyk2hwb: cmyk2hwb, + cmyk2keyword: cmyk2keyword, + + keyword2rgb: keyword2rgb, + keyword2hsl: keyword2hsl, + keyword2hsv: keyword2hsv, + keyword2hwb: keyword2hwb, + keyword2cmyk: keyword2cmyk, + keyword2lab: keyword2lab, + keyword2xyz: keyword2xyz, + + xyz2rgb: xyz2rgb, + xyz2lab: xyz2lab, + xyz2lch: xyz2lch, + + lab2xyz: lab2xyz, + lab2rgb: lab2rgb, + lab2lch: lab2lch, + + lch2lab: lch2lab, + lch2xyz: lch2xyz, + lch2rgb: lch2rgb +} + + +function rgb2hsl(rgb) { + var r = rgb[0]/255, + g = rgb[1]/255, + b = rgb[2]/255, + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, l; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g)/ delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + l = (min + max) / 2; + + if (max == min) + s = 0; + else if (l <= 0.5) + s = delta / (max + min); + else + s = delta / (2 - max - min); + + return [h, s * 100, l * 100]; +} + +function rgb2hsv(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, v; + + if (max == 0) + s = 0; + else + s = (delta/max * 1000)/10; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g) / delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + v = ((max / 255) * 1000) / 10; + + return [h, s, v]; +} + +function rgb2hwb(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + h = rgb2hsl(rgb)[0], + w = 1/255 * Math.min(r, Math.min(g, b)), + b = 1 - 1/255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; +} + +function rgb2cmyk(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255, + c, m, y, k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + return [c * 100, m * 100, y * 100, k * 100]; +} + +function rgb2keyword(rgb) { + return reverseKeywords[JSON.stringify(rgb)]; +} + +function rgb2xyz(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y *100, z * 100]; +} + +function rgb2lab(rgb) { + var xyz = rgb2xyz(rgb), + x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +} + +function rgb2lch(args) { + return lab2lch(rgb2lab(args)); +} + +function hsl2rgb(hsl) { + var h = hsl[0] / 360, + s = hsl[1] / 100, + l = hsl[2] / 100, + t1, t2, t3, rgb, val; + + if (s == 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) + t2 = l * (1 + s); + else + t2 = l + s - l * s; + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * - (i - 1); + t3 < 0 && t3++; + t3 > 1 && t3--; + + if (6 * t3 < 1) + val = t1 + (t2 - t1) * 6 * t3; + else if (2 * t3 < 1) + val = t2; + else if (3 * t3 < 2) + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + else + val = t1; + + rgb[i] = val * 255; + } + + return rgb; +} + +function hsl2hsv(hsl) { + var h = hsl[0], + s = hsl[1] / 100, + l = hsl[2] / 100, + sv, v; + + if(l === 0) { + // no need to do calc on black + // also avoids divide by 0 error + return [0, 0, 0]; + } + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + v = (l + s) / 2; + sv = (2 * s) / (l + s); + return [h, sv * 100, v * 100]; +} + +function hsl2hwb(args) { + return rgb2hwb(hsl2rgb(args)); +} + +function hsl2cmyk(args) { + return rgb2cmyk(hsl2rgb(args)); +} + +function hsl2keyword(args) { + return rgb2keyword(hsl2rgb(args)); +} + + +function hsv2rgb(hsv) { + var h = hsv[0] / 60, + s = hsv[1] / 100, + v = hsv[2] / 100, + hi = Math.floor(h) % 6; + + var f = h - Math.floor(h), + p = 255 * v * (1 - s), + q = 255 * v * (1 - (s * f)), + t = 255 * v * (1 - (s * (1 - f))), + v = 255 * v; + + switch(hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +} + +function hsv2hsl(hsv) { + var h = hsv[0], + s = hsv[1] / 100, + v = hsv[2] / 100, + sl, l; + + l = (2 - s) * v; + sl = s * v; + sl /= (l <= 1) ? l : 2 - l; + sl = sl || 0; + l /= 2; + return [h, sl * 100, l * 100]; +} + +function hsv2hwb(args) { + return rgb2hwb(hsv2rgb(args)) +} + +function hsv2cmyk(args) { + return rgb2cmyk(hsv2rgb(args)); +} + +function hsv2keyword(args) { + return rgb2keyword(hsv2rgb(args)); +} + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +function hwb2rgb(hwb) { + var h = hwb[0] / 360, + wh = hwb[1] / 100, + bl = hwb[2] / 100, + ratio = wh + bl, + i, v, f, n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + if ((i & 0x01) != 0) { + f = 1 - f; + } + n = wh + f * (v - wh); // linear interpolation + + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +} + +function hwb2hsl(args) { + return rgb2hsl(hwb2rgb(args)); +} + +function hwb2hsv(args) { + return rgb2hsv(hwb2rgb(args)); +} + +function hwb2cmyk(args) { + return rgb2cmyk(hwb2rgb(args)); +} + +function hwb2keyword(args) { + return rgb2keyword(hwb2rgb(args)); +} + +function cmyk2rgb(cmyk) { + var c = cmyk[0] / 100, + m = cmyk[1] / 100, + y = cmyk[2] / 100, + k = cmyk[3] / 100, + r, g, b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + return [r * 255, g * 255, b * 255]; +} + +function cmyk2hsl(args) { + return rgb2hsl(cmyk2rgb(args)); +} + +function cmyk2hsv(args) { + return rgb2hsv(cmyk2rgb(args)); +} + +function cmyk2hwb(args) { + return rgb2hwb(cmyk2rgb(args)); +} + +function cmyk2keyword(args) { + return rgb2keyword(cmyk2rgb(args)); +} + + +function xyz2rgb(xyz) { + var x = xyz[0] / 100, + y = xyz[1] / 100, + z = xyz[2] / 100, + r, g, b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r = (r * 12.92); + + g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g = (g * 12.92); + + b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b = (b * 12.92); + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +} + +function xyz2lab(xyz) { + var x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +} + +function xyz2lch(args) { + return lab2lch(xyz2lab(args)); +} + +function lab2xyz(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + x, y, z, y2; + + if (l <= 8) { + y = (l * 100) / 903.3; + y2 = (7.787 * (y / 100)) + (16 / 116); + } else { + y = 100 * Math.pow((l + 16) / 116, 3); + y2 = Math.pow(y / 100, 1/3); + } + + x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); + + z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); + + return [x, y, z]; +} + +function lab2lch(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + hr, h, c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + if (h < 0) { + h += 360; + } + c = Math.sqrt(a * a + b * b); + return [l, c, h]; +} + +function lab2rgb(args) { + return xyz2rgb(lab2xyz(args)); +} + +function lch2lab(lch) { + var l = lch[0], + c = lch[1], + h = lch[2], + a, b, hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + return [l, a, b]; +} + +function lch2xyz(args) { + return lab2xyz(lch2lab(args)); +} + +function lch2rgb(args) { + return lab2rgb(lch2lab(args)); +} + +function keyword2rgb(keyword) { + return cssKeywords[keyword]; +} + +function keyword2hsl(args) { + return rgb2hsl(keyword2rgb(args)); +} + +function keyword2hsv(args) { + return rgb2hsv(keyword2rgb(args)); +} + +function keyword2hwb(args) { + return rgb2hwb(keyword2rgb(args)); +} + +function keyword2cmyk(args) { + return rgb2cmyk(keyword2rgb(args)); +} + +function keyword2lab(args) { + return rgb2lab(keyword2rgb(args)); +} + +function keyword2xyz(args) { + return rgb2xyz(keyword2rgb(args)); +} + +var cssKeywords = { + 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] +}; + +var reverseKeywords = {}; +for (var key in cssKeywords) { + reverseKeywords[JSON.stringify(cssKeywords[key])] = key; +} + +},{}],5:[function(require,module,exports){ +var conversions = require(4); + +var convert = function() { + return new Converter(); +} + +for (var func in conversions) { + // export Raw versions + convert[func + "Raw"] = (function(func) { + // accept array or plain args + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + return conversions[func](arg); + } + })(func); + + var pair = /(\w+)2(\w+)/.exec(func), + from = pair[1], + to = pair[2]; + + // export rgb2hsl and ["rgb"]["hsl"] + convert[from] = convert[from] || {}; + + convert[from][to] = convert[func] = (function(func) { + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + + var val = conversions[func](arg); + if (typeof val == "string" || val === undefined) + return val; // keyword + + for (var i = 0; i < val.length; i++) + val[i] = Math.round(val[i]); + return val; + } + })(func); +} + + +/* Converter does lazy conversion and caching */ +var Converter = function() { + this.convs = {}; +}; + +/* Either get the values for a space or + set the values for a space, depending on args */ +Converter.prototype.routeSpace = function(space, args) { + var values = args[0]; + if (values === undefined) { + // color.rgb() + return this.getValues(space); + } + // color.rgb(10, 10, 10) + if (typeof values == "number") { + values = Array.prototype.slice.call(args); + } + + return this.setValues(space, values); +}; + +/* Set the values for a space, invalidating cache */ +Converter.prototype.setValues = function(space, values) { + this.space = space; + this.convs = {}; + this.convs[space] = values; + return this; +}; + +/* Get the values for a space. If there's already + a conversion for the space, fetch it, otherwise + compute it */ +Converter.prototype.getValues = function(space) { + var vals = this.convs[space]; + if (!vals) { + var fspace = this.space, + from = this.convs[fspace]; + vals = convert[fspace][space](from); + + this.convs[space] = vals; + } + return vals; +}; + +["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { + Converter.prototype[space] = function(vals) { + return this.routeSpace(space, arguments); + } +}); + +module.exports = convert; +},{"4":4}],6:[function(require,module,exports){ +'use strict' + +module.exports = { + "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] +}; + +},{}],7:[function(require,module,exports){ +/** + * @namespace Chart + */ +var Chart = require(29)(); + +Chart.helpers = require(45); + +// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! +require(27)(Chart); + +Chart.defaults = require(25); +Chart.Element = require(26); +Chart.elements = require(40); +Chart.Interaction = require(28); +Chart.platform = require(48); + +require(31)(Chart); +require(22)(Chart); +require(23)(Chart); +require(24)(Chart); +require(30)(Chart); +require(33)(Chart); +require(32)(Chart); +require(35)(Chart); + +require(54)(Chart); +require(52)(Chart); +require(53)(Chart); +require(55)(Chart); +require(56)(Chart); +require(57)(Chart); + +// Controllers must be loaded after elements +// See Chart.core.datasetController.dataElementType +require(15)(Chart); +require(16)(Chart); +require(17)(Chart); +require(18)(Chart); +require(19)(Chart); +require(20)(Chart); +require(21)(Chart); + +require(8)(Chart); +require(9)(Chart); +require(10)(Chart); +require(11)(Chart); +require(12)(Chart); +require(13)(Chart); +require(14)(Chart); + +// Loading built-it plugins +var plugins = []; + +plugins.push( + require(49)(Chart), + require(50)(Chart), + require(51)(Chart) +); + +Chart.plugins.register(plugins); + +Chart.platform.initialize(); + +module.exports = Chart; +if (typeof window !== 'undefined') { + window.Chart = Chart; +} + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas instead. + * @namespace Chart.canvasHelpers + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +Chart.canvasHelpers = Chart.helpers.canvas; + +},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"35":35,"40":40,"45":45,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"8":8,"9":9}],8:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Bar = function(context, config) { + config.type = 'bar'; + + return new Chart(context, config); + }; + +}; + +},{}],9:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Bubble = function(context, config) { + config.type = 'bubble'; + return new Chart(context, config); + }; + +}; + +},{}],10:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Doughnut = function(context, config) { + config.type = 'doughnut'; + + return new Chart(context, config); + }; + +}; + +},{}],11:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Line = function(context, config) { + config.type = 'line'; + + return new Chart(context, config); + }; + +}; + +},{}],12:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.PolarArea = function(context, config) { + config.type = 'polarArea'; + + return new Chart(context, config); + }; + +}; + +},{}],13:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Radar = function(context, config) { + config.type = 'radar'; + + return new Chart(context, config); + }; + +}; + +},{}],14:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + Chart.Scatter = function(context, config) { + config.type = 'scatter'; + return new Chart(context, config); + }; +}; + +},{}],15:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var elements = require(40); +var helpers = require(45); + +defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + + // Specific to Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // offset settings + offset: true, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } +}); + +defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + position: 'left', + type: 'category', + + // Specific to Horizontal Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // offset settings + offset: true, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + callbacks: { + title: function(item, data) { + // Pick first xLabel for now + var title = ''; + + if (item.length > 0) { + if (item[0].yLabel) { + title = item[0].yLabel; + } else if (data.labels.length > 0 && item[0].index < data.labels.length) { + title = data.labels[item[0].index]; + } + } + + return title; + }, + + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + return datasetLabel + ': ' + item.xLabel; + } + }, + mode: 'index', + axis: 'y' + } +}); + +module.exports = function(Chart) { + + Chart.controllers.bar = Chart.DatasetController.extend({ + + dataElementType: elements.Rectangle, + + initialize: function() { + var me = this; + var meta; + + Chart.DatasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + }, + + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; + + me._ruler = me.getRuler(); + + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var custom = rectangle.custom || {}; + var rectangleOptions = chart.options.elements.rectangle; + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + + rectangle._model = { + datasetLabel: dataset.label, + label: chart.data.labels[index], + borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped, + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth) + }; + + me.updateElementGeometry(rectangle, index, reset); + + rectangle.pivot(); + }, + + /** + * @private + */ + updateElementGeometry: function(rectangle, index, reset) { + var me = this; + var model = rectangle._model; + var vscale = me.getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * @private + */ + getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + getValueScale: function() { + return this.getScaleForId(this.getValueScaleId()); + }, + + /** + * @private + */ + getIndexScale: function() { + return this.getScaleForId(this.getIndexScaleId()); + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function(last) { + var me = this; + var chart = me.chart; + var scale = me.getIndexScale(); + var stacked = scale.options.stacked; + var ilen = last === undefined ? chart.data.datasets.length : last + 1; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + if (meta.bar && chart.isDatasetVisible(i) && + (stacked === false || + (stacked === true && stacks.indexOf(meta.stack) === -1) || + (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { + stacks.push(meta.stack); + } + } + + return stacks.length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @private + */ + getStackIndex: function(datasetIndex) { + return this.getStackCount(datasetIndex) - 1; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me.getIndexScale(); + var stackCount = me.getStackCount(); + var datasetIndex = me.index; + var pixels = []; + var isHorizontal = scale.isHorizontal(); + var start = isHorizontal ? scale.left : scale.top; + var end = start + (isHorizontal ? scale.width : scale.height); + var i, ilen; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, datasetIndex)); + } + + return { + pixels: pixels, + start: start, + end: end, + stackCount: stackCount, + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var scale = me.getValueScale(); + var datasets = chart.data.datasets; + var value = scale.getRightValue(datasets[datasetIndex].data[index]); + var stacked = scale.options.stacked; + var stack = meta.stack; + var start = 0; + var i, imeta, ivalue, base, head, size; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < datasetIndex; ++i) { + imeta = chart.getDatasetMeta(i); + + if (imeta.bar && + imeta.stack === stack && + imeta.controller.getValueScaleId() === scale.id && + chart.isDatasetVisible(i)) { + + ivalue = scale.getRightValue(datasets[i].data[index]); + if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + value); + size = (head - base) / 2; + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler) { + var me = this; + var options = ruler.scale.options; + var stackIndex = me.getStackIndex(datasetIndex); + var pixels = ruler.pixels; + var base = pixels[index]; + var length = pixels.length; + var start = ruler.start; + var end = ruler.end; + var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size; + + if (length === 1) { + leftSampleSize = base > start ? base - start : end - base; + rightSampleSize = base < end ? end - base : base - start; + } else { + if (index > 0) { + leftSampleSize = (base - pixels[index - 1]) / 2; + if (index === length - 1) { + rightSampleSize = leftSampleSize; + } + } + if (index < length - 1) { + rightSampleSize = (pixels[index + 1] - base) / 2; + if (index === 0) { + leftSampleSize = rightSampleSize; + } + } + } + + leftCategorySize = leftSampleSize * options.categoryPercentage; + rightCategorySize = rightSampleSize * options.categoryPercentage; + fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount; + size = fullBarSize * options.barPercentage; + + size = Math.min( + helpers.valueOrDefault(options.barThickness, size), + helpers.valueOrDefault(options.maxBarThickness, Infinity)); + + base -= leftCategorySize; + base += fullBarSize * stackIndex; + base += (fullBarSize - size) / 2; + + return { + size: size, + base: base, + head: base + size, + center: base + size / 2 + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me.getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + if (!isNaN(scale.getRightValue(dataset.data[i]))) { + rects[i].draw(); + } + } + + helpers.canvas.unclipArea(chart.ctx); + }, + + setHoverStyle: function(rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + var custom = rectangle.custom || {}; + var model = rectangle._model; + + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function(rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + var custom = rectangle.custom || {}; + var model = rectangle._model; + var rectangleElementOptions = this.chart.options.elements.rectangle; + + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth); + } + }); + + Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ + /** + * @private + */ + getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + getIndexScaleId: function() { + return this.getMeta().yAxisID; + } + }); +}; + +},{"25":25,"40":40,"45":45}],16:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var elements = require(40); +var helpers = require(45); + +defaults._set('bubble', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; + } + } + } +}); + + +module.exports = function(Chart) { + + Chart.controllers.bubble = Chart.DatasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + + model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); + model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); + model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @protected + */ + removeHoverStyle: function(point) { + var model = point._model; + var options = point._options; + + model.backgroundColor = options.backgroundColor; + model.borderColor = options.borderColor; + model.borderWidth = options.borderWidth; + model.radius = options.radius; + }, + + /** + * @private + */ + _resolveElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var datasets = chart.data.datasets; + var dataset = datasets[me.index]; + var custom = point.custom || {}; + var options = chart.options.elements.point; + var resolve = helpers.options.resolve; + var data = dataset.data[index]; + var values = {}; + var i, ilen, key; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + var keys = [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle' + ]; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + dataset[key], + options[key] + ], context, index); + } + + // Custom radius resolution + values.radius = resolve([ + custom.radius, + data ? data.r : undefined, + dataset.radius, + options.radius + ], context, index); + + return values; + } + }); +}; + +},{"25":25,"40":40,"45":45}],17:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var elements = require(40); +var helpers = require(45); + +defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var text = []; + text.push(''); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc && arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: Math.PI * -0.5, + + // The total circumference of the chart. + circumference: Math.PI * 2.0, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +defaults._set('pie', helpers.clone(defaults.doughnut)); +defaults._set('pie', { + cutoutPercentage: 0 +}); + +module.exports = function(Chart) { + + Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers.noop, + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var arcOpts = opts.elements.arc; + var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; + var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; + var minSize = Math.min(availableWidth, availableHeight); + var offset = {x: 0, y: 0}; + var meta = me.getMeta(); + var cutoutPercentage = opts.cutoutPercentage; + var circumference = opts.circumference; + + // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc + if (circumference < Math.PI * 2.0) { + var startAngle = opts.rotation % (Math.PI * 2.0); + startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); + var endAngle = startAngle + circumference; + var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; + var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; + var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); + var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); + var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); + var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); + var cutout = cutoutPercentage / 100.0; + var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; + var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; + var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; + minSize = Math.min(availableWidth / size.width, availableHeight / size.height); + offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; + } + + chart.borderWidth = me.getMaxBorderWidth(meta.data); + chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); + chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + chart.offsetX = offset.x * chart.outerRadius; + chart.offsetY = offset.y * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); + + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + // Resets the visual styles + this.removeHoverStyle(arc); + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + removeHoverStyle: function(arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return (Math.PI * 2.0) * (value / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var max = 0; + var index = this.index; + var length = arcs.length; + var borderWidth; + var hoverWidth; + + for (var i = 0; i < length; i++) { + borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; + hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + return max; + } + }); +}; + +},{"25":25,"40":40,"45":45}],18:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var elements = require(40); +var helpers = require(45); + +defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + +module.exports = function(Chart) { + + function lineEnabled(dataset, options) { + return helpers.valueOrDefault(dataset.showLine, options.showLines); + } + + Chart.controllers.line = Chart.DatasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var lineElementOptions = options.elements.line; + var scale = me.getScaleForId(meta.yAxisID); + var i, ilen, custom; + var dataset = me.getDataset(); + var showLine = lineEnabled(dataset, options); + + // Update Line + if (showLine) { + custom = line.custom || {}; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = { + // Appearance + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), + cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), + }; + + line.pivot(); + } + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + getPointBackgroundColor: function(point, index) { + var backgroundColor = this.chart.options.elements.point.backgroundColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (custom.backgroundColor) { + backgroundColor = custom.backgroundColor; + } else if (dataset.pointBackgroundColor) { + backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); + } else if (dataset.backgroundColor) { + backgroundColor = dataset.backgroundColor; + } + + return backgroundColor; + }, + + getPointBorderColor: function(point, index) { + var borderColor = this.chart.options.elements.point.borderColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (custom.borderColor) { + borderColor = custom.borderColor; + } else if (dataset.pointBorderColor) { + borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); + } else if (dataset.borderColor) { + borderColor = dataset.borderColor; + } + + return borderColor; + }, + + getPointBorderWidth: function(point, index) { + var borderWidth = this.chart.options.elements.point.borderWidth; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (!isNaN(custom.borderWidth)) { + borderWidth = custom.borderWidth; + } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { + borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); + } else if (!isNaN(dataset.borderWidth)) { + borderWidth = dataset.borderWidth; + } + + return borderWidth; + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var yScale = me.getScaleForId(meta.yAxisID); + var xScale = me.getScaleForId(meta.xAxisID); + var pointOptions = me.chart.options.elements.point; + var x, y; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), + pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), + backgroundColor: me.getPointBackgroundColor(point, index), + borderColor: me.getPointBorderColor(point, index), + borderWidth: me.getPointBorderWidth(point, index), + tension: meta.dataset._model ? meta.dataset._model.tension : 0, + steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, + // Tooltip + hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) + }; + }, + + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta; + + if (yScale.options.stacked) { + for (i = 0; i < datasetIndex; i++) { + ds = chart.data.datasets[i]; + dsMeta = chart.getDatasetMeta(i); + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { + var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + var rightValue = Number(yScale.getRightValue(value)); + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = (meta.data || []); + var i, ilen, point, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (meta.dataset._model.cubicInterpolationMode === 'monotone') { + helpers.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + point = points[i]; + model = point._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i)._model, + model, + helpers.nextItem(points, i)._model, + meta.dataset._model.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (me.chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + }, + + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var ilen = points.length; + var i = 0; + + helpers.canvas.clipArea(chart.ctx, area); + + if (lineEnabled(me.getDataset(), chart.options)) { + meta.dataset.draw(); + } + + helpers.canvas.unclipArea(chart.ctx); + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + var custom = point.custom || {}; + var model = point._model; + + model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function(point) { + var me = this; + var dataset = me.chart.data.datasets[point._datasetIndex]; + var index = point._index; + var custom = point.custom || {}; + var model = point._model; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + + model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius); + model.backgroundColor = me.getPointBackgroundColor(point, index); + model.borderColor = me.getPointBorderColor(point, index); + model.borderWidth = me.getPointBorderWidth(point, index); + } + }); +}; + +},{"25":25,"40":40,"45":45}],19:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var elements = require(40); +var helpers = require(45); + +defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false + }, + gridLines: { + circular: true + }, + pointLabels: { + display: false + }, + ticks: { + beginAtZero: true + } + }, + + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, + + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var text = []; + text.push(''); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +module.exports = function(Chart) { + + Chart.controllers.polarArea = Chart.DatasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var meta = me.getMeta(); + var opts = chart.options; + var arcOpts = opts.elements.arc; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + + meta.count = me.countVisibleElements(); + + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var circumference = me.calculateCircumference(dataset.data[index]); + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // If there is NaN data before us, we need to calculate the starting angle correctly. + // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data + var visibleCount = 0; + var meta = me.getMeta(); + for (var i = 0; i < index; ++i) { + if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { + ++visibleCount; + } + } + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = datasetStartAngle + (circumference * visibleCount); + var endAngle = startAngle + (arc.hidden ? 0 : circumference); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + // Apply border and fill style + me.removeHoverStyle(arc); + + arc.pivot(); + }, + + removeHoverStyle: function(arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + calculateCircumference: function(value) { + var count = this.getMeta().count; + if (count > 0 && !isNaN(value)) { + return (2 * Math.PI) / count; + } + return 0; + } + }); +}; + +},{"25":25,"40":40,"45":45}],20:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var elements = require(40); +var helpers = require(45); + +defaults._set('radar', { + scale: { + type: 'radialLinear' + }, + elements: { + line: { + tension: 0 // no bezier in radar + } + } +}); + +module.exports = function(Chart) { + + Chart.controllers.radar = Chart.DatasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data; + var custom = line.custom || {}; + var dataset = me.getDataset(); + var lineElementOptions = me.chart.options.elements.line; + var scale = me.chart.scale; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + helpers.extend(meta.dataset, { + // Utility + _datasetIndex: me.index, + _scale: scale, + // Data + _children: points, + _loop: true, + // Model + _model: { + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + } + }); + + meta.dataset.pivot(); + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }, me); + + // Update bezier control points + me.updateBezierControlPoints(); + }, + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointElementOptions = me.chart.options.elements.point; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } + + helpers.extend(point, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales + y: reset ? scale.yCenter : pointPosition.y, + + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), + radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), + pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), + + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) + } + }); + + point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + updateBezierControlPoints: function() { + var chartArea = this.chart.chartArea; + var meta = this.getMeta(); + + helpers.each(meta.data, function(point, index) { + var model = point._model; + var controlPoints = helpers.splineCurve( + helpers.previousItem(meta.data, index, true)._model, + model, + helpers.nextItem(meta.data, index, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); + model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); + + model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); + model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); + + // Now pivot the point for animation + point.pivot(); + }); + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + + model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function(point) { + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + var pointElementOptions = this.chart.options.elements.point; + + model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius); + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth); + } + }); +}; + +},{"25":25,"40":40,"45":45}],21:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); + +defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + showLines: false, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +module.exports = function(Chart) { + + // Scatter charts use line controllers + Chart.controllers.scatter = Chart.controllers.line; + +}; + +},{"25":25}],22:[function(require,module,exports){ +/* global window: false */ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers.noop, + onComplete: helpers.noop + } +}); + +module.exports = function(Chart) { + + Chart.Animation = Element.extend({ + chart: null, // the animation associated chart instance + currentStep: 0, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes + }); + + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + request: null, + + /** + * @param {Chart} chart - The chart to animate. + * @param {Chart.Animation} animation - The animation that we will animate. + * @param {Number} duration - The animation duration in ms. + * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chart, animation, duration, lazy) { + var animations = this.animations; + var i, ilen; + + animation.chart = chart; + + if (!lazy) { + chart.animating = true; + } + + for (i = 0, ilen = animations.length; i < ilen; ++i) { + if (animations[i].chart === chart) { + animations[i] = animation; + return; + } + } + + animations.push(animation); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (animations.length === 1) { + this.requestAnimationFrame(); + } + }, + + cancelAnimation: function(chart) { + var index = helpers.findIndex(this.animations, function(animation) { + return animation.chart === chart; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chart.animating = false; + } + }, + + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function() { + var me = this; + var startTime = Date.now(); + var framesToDrop = 0; + + if (me.dropFrames > 1) { + framesToDrop = Math.floor(me.dropFrames); + me.dropFrames = me.dropFrames % 1; + } + + me.advance(1 + framesToDrop); + + var endTime = Date.now(); + + me.dropFrames += (endTime - startTime) / me.frameDuration; + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function(count) { + var animations = this.animations; + var animation, chart; + var i = 0; + + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + + animation.currentStep = (animation.currentStep || 0) + count; + animation.currentStep = Math.min(animation.currentStep, animation.numSteps); + + helpers.callback(animation.render, [chart, animation], chart); + helpers.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= animation.numSteps) { + helpers.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } + }; + + /** + * Provided for backward compatibility, use Chart.Animation instead + * @prop Chart.Animation#animationObject + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ + Object.defineProperty(Chart.Animation.prototype, 'animationObject', { + get: function() { + return this; + } + }); + + /** + * Provided for backward compatibility, use Chart.Animation#chart instead + * @prop Chart.Animation#chartInstance + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ + Object.defineProperty(Chart.Animation.prototype, 'chartInstance', { + get: function() { + return this.chart; + }, + set: function(value) { + this.chart = value; + } + }); + +}; + +},{"25":25,"26":26,"45":45}],23:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var helpers = require(45); +var Interaction = require(28); +var platform = require(48); + +module.exports = function(Chart) { + var plugins = Chart.plugins; + + // Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + // Store a reference to each instance - allowing us to globally resize chart instances on window resize. + // Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + /** + * Initializes the given config with global and chart default values. + */ + function initConfig(config) { + config = config || {}; + + // Do NOT use configMerge() for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = helpers.configMerge( + defaults.global, + defaults[config.type], + config.options || {}); + + return config; + } + + /** + * Updates the config of the chart + * @param chart {Chart} chart to update the options for + */ + function updateConfig(chart) { + var newOptions = chart.options; + + // Update Scale(s) with options + if (newOptions.scale) { + chart.scale.options = newOptions.scale; + } else if (newOptions.scales) { + newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) { + chart.scales[scaleOptions.id].options = scaleOptions; + }); + } + + // Tooltip + chart.tooltip._options = newOptions.tooltips; + } + + function positionIsHorizontal(position) { + return position === 'top' || position === 'bottom'; + } + + helpers.extend(Chart.prototype, /** @lends Chart */ { + /** + * @private + */ + construct: function(item, config) { + var me = this; + + config = initConfig(config); + + var context = platform.acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + me.id = helpers.uid(); + me.ctx = context; + me.canvas = canvas; + me.config = config; + me.width = width; + me.height = height; + me.aspectRatio = height ? width / height : null; + me.options = config.options; + me._bufferedRender = false; + + /** + * Provided for backward compatibility, Chart and Chart.Controller have been merged, + * the "instance" still need to be defined since it might be called from plugins. + * @prop Chart#chart + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + me.chart = me; + me.controller = me; // chart.chart.controller #inception + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + // Define alias to the config data: `chart.data === chart.config.data` + Object.defineProperty(me, 'data', { + get: function() { + return me.config.data; + }, + set: function(value) { + me.config.data = value; + } + }); + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return; + } + + me.initialize(); + me.update(); + }, + + /** + * @private + */ + initialize: function() { + var me = this; + + // Before init plugin notification + plugins.notify(me, 'beforeInit'); + + helpers.retinaScale(me, me.options.devicePixelRatio); + + me.bindEvents(); + + if (me.options.responsive) { + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + // Make sure scales have IDs and are built before we build any controllers. + me.ensureScalesHaveIDs(); + me.buildScales(); + me.initToolTip(); + + // After init plugin notification + plugins.notify(me, 'afterInit'); + + return me; + }, + + clear: function() { + helpers.canvas.clear(this); + return this; + }, + + stop: function() { + // Stops any current animation loop occurring + Chart.animationService.cancelAnimation(this); + return this; + }, + + resize: function(silent) { + var me = this; + var options = me.options; + var canvas = me.canvas; + var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; + + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + + // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased + var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas))); + var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas))); + + if (me.width === newWidth && me.height === newHeight) { + return; + } + + canvas.width = me.width = newWidth; + canvas.height = me.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; + + helpers.retinaScale(me, options.devicePixelRatio); + + if (!silent) { + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + plugins.notify(me, 'resize', [newSize]); + + // Notify of resize + if (me.options.onResize) { + me.options.onResize(me, newSize); + } + + me.stop(); + me.update(me.options.responsiveAnimationDuration); + } + }, + + ensureScalesHaveIDs: function() { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; + + helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { + xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); + }); + + helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { + yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, + + /** + * Builds a map of scale ID to scale object for future lookup. + */ + buildScales: function() { + var me = this; + var options = me.options; + var scales = me.scales = {}; + var items = []; + + if (options.scales) { + items = items.concat( + (options.scales.xAxes || []).map(function(xAxisOptions) { + return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; + }), + (options.scales.yAxes || []).map(function(yAxisOptions) { + return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; + }) + ); + } + + if (options.scale) { + items.push({ + options: options.scale, + dtype: 'radialLinear', + isDefault: true, + dposition: 'chartArea' + }); + } + + helpers.each(items, function(item) { + var scaleOptions = item.options; + var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype); + var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + + if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { + scaleOptions.position = item.dposition; + } + + var scale = new scaleClass({ + id: scaleOptions.id, + options: scaleOptions, + ctx: me.ctx, + chart: me + }); + + scales[scale.id] = scale; + scale.mergeTicksOptions(); + + // TODO(SB): I think we should be able to remove this custom case (options.scale) + // and consider it as a regular scale part of the "scales"" map only! This would + // make the logic easier and remove some useless? custom code. + if (item.isDefault) { + me.scale = scale; + } + }); + + Chart.scaleService.addScalesToLayout(this); + }, + + buildOrUpdateControllers: function() { + var me = this; + var types = []; + var newControllers = []; + + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + var meta = me.getDatasetMeta(datasetIndex); + var type = dataset.type || me.config.type; + + if (meta.type && meta.type !== type) { + me.destroyDatasetMeta(datasetIndex); + meta = me.getDatasetMeta(datasetIndex); + } + meta.type = type; + + types.push(meta.type); + + if (meta.controller) { + meta.controller.updateIndex(datasetIndex); + } else { + var ControllerClass = Chart.controllers[meta.type]; + if (ControllerClass === undefined) { + throw new Error('"' + meta.type + '" is not a chart type.'); + } + + meta.controller = new ControllerClass(me, datasetIndex); + newControllers.push(meta.controller); + } + }, me); + + return newControllers; + }, + + /** + * Reset the elements of all datasets + * @private + */ + resetElements: function() { + var me = this; + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, + + /** + * Resets the chart back to it's state before the initial animation + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function(config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + updateConfig(me); + + if (plugins.notify(me, 'beforeUpdate') === false) { + return; + } + + // In case the entire data object changed + me.tooltip._data = me.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); + + // Make sure all dataset controllers have correct meta data counts + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); + }, me); + + me.updateLayout(); + + // Can only reset the new controllers after the scales have been updated + helpers.each(newControllers, function(controller) { + controller.reset(); + }); + + me.updateDatasets(); + + // Need to reset tooltip in case it is displayed with elements that are removed + // after update. + me.tooltip.initialize(); + + // Last active contains items that were previously in the tooltip. + // When we reset the tooltip, we need to clear it + me.lastActive = []; + + // Do this before render so that any plugins that need final scale updates can use it + plugins.notify(me, 'afterUpdate'); + + if (me._bufferedRender) { + me._bufferedRequest = { + duration: config.duration, + easing: config.easing, + lazy: config.lazy + }; + } else { + me.render(config); + } + }, + + /** + * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` + * hook, in which case, plugins will not be called on `afterLayout`. + * @private + */ + updateLayout: function() { + var me = this; + + if (plugins.notify(me, 'beforeLayout') === false) { + return; + } + + Chart.layoutService.update(this, this.width, this.height); + + /** + * Provided for backward compatibility, use `afterLayout` instead. + * @method IPlugin#afterScaleUpdate + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + plugins.notify(me, 'afterScaleUpdate'); + plugins.notify(me, 'afterLayout'); + }, + + /** + * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` + * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. + * @private + */ + updateDatasets: function() { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { + return; + } + + for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.updateDataset(i); + } + + plugins.notify(me, 'afterDatasetsUpdate'); + }, + + /** + * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` + * hook, in which case, plugins will not be called on `afterDatasetUpdate`. + * @private + */ + updateDataset: function(index) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index + }; + + if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { + return; + } + + meta.controller.update(); + + plugins.notify(me, 'afterDatasetUpdate', [args]); + }, + + render: function(config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + var duration = config.duration; + var lazy = config.lazy; + + if (plugins.notify(me, 'beforeRender') === false) { + return; + } + + var animationOptions = me.options.animation; + var onComplete = function(animation) { + plugins.notify(me, 'afterRender'); + helpers.callback(animationOptions && animationOptions.onComplete, [animation], me); + }; + + if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { + var animation = new Chart.Animation({ + numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps + easing: config.easing || animationOptions.easing, + + render: function(chart, animationObject) { + var easingFunction = helpers.easing.effects[animationObject.easing]; + var currentStep = animationObject.currentStep; + var stepDecimal = currentStep / animationObject.numSteps; + + chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); + }, + + onAnimationProgress: animationOptions.onProgress, + onAnimationComplete: onComplete + }); + + Chart.animationService.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + + // See https://github.com/chartjs/Chart.js/issues/3781 + onComplete(new Chart.Animation({numSteps: 0, chart: me})); + } + + return me; + }, + + draw: function(easingValue) { + var me = this; + + me.clear(); + + if (helpers.isNullOrUndef(easingValue)) { + easingValue = 1; + } + + me.transition(easingValue); + + if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { + return; + } + + // Draw all the scales + helpers.each(me.boxes, function(box) { + box.draw(me.chartArea); + }, me); + + if (me.scale) { + me.scale.draw(); + } + + me.drawDatasets(easingValue); + me._drawTooltip(easingValue); + + plugins.notify(me, 'afterDraw', [easingValue]); + }, + + /** + * @private + */ + transition: function(easingValue) { + var me = this; + + for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { + if (me.isDatasetVisible(i)) { + me.getDatasetMeta(i).controller.transition(easingValue); + } + } + + me.tooltip.transition(easingValue); + }, + + /** + * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` + * hook, in which case, plugins will not be called on `afterDatasetsDraw`. + * @private + */ + drawDatasets: function(easingValue) { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { + return; + } + + // Draw datasets reversed to support proper line stacking + for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { + if (me.isDatasetVisible(i)) { + me.drawDataset(i, easingValue); + } + } + + plugins.notify(me, 'afterDatasetsDraw', [easingValue]); + }, + + /** + * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` + * hook, in which case, plugins will not be called on `afterDatasetDraw`. + * @private + */ + drawDataset: function(index, easingValue) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index, + easingValue: easingValue + }; + + if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { + return; + } + + meta.controller.draw(easingValue); + + plugins.notify(me, 'afterDatasetDraw', [args]); + }, + + /** + * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` + * hook, in which case, plugins will not be called on `afterTooltipDraw`. + * @private + */ + _drawTooltip: function(easingValue) { + var me = this; + var tooltip = me.tooltip; + var args = { + tooltip: tooltip, + easingValue: easingValue + }; + + if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { + return; + } + + tooltip.draw(); + + plugins.notify(me, 'afterTooltipDraw', [args]); + }, + + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + getElementAtEvent: function(e) { + return Interaction.modes.single(this, e); + }, + + getElementsAtEvent: function(e) { + return Interaction.modes.label(this, e, {intersect: true}); + }, + + getElementsAtXAxis: function(e) { + return Interaction.modes['x-axis'](this, e, {intersect: true}); + }, + + getElementsAtEventForMode: function(e, mode, options) { + var method = Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } + + return []; + }, + + getDatasetAtEvent: function(e) { + return Interaction.modes.dataset(this, e, {intersect: true}); + }, + + getDatasetMeta: function(datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } + + var meta = dataset._meta[me.id]; + if (!meta) { + meta = dataset._meta[me.id] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null + }; + } + + return meta; + }, + + getVisibleDatasetCount: function() { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { + if (this.isDatasetVisible(i)) { + count++; + } + } + return count; + }, + + isDatasetVisible: function(datasetIndex) { + var meta = this.getDatasetMeta(datasetIndex); + + // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, + // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. + return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; + }, + + generateLegend: function() { + return this.options.legendCallback(this); + }, + + /** + * @private + */ + destroyDatasetMeta: function(datasetIndex) { + var id = this.id; + var dataset = this.data.datasets[datasetIndex]; + var meta = dataset._meta && dataset._meta[id]; + + if (meta) { + meta.controller.destroy(); + delete dataset._meta[id]; + } + }, + + destroy: function() { + var me = this; + var canvas = me.canvas; + var i, ilen; + + me.stop(); + + // dataset controllers need to cleanup associated data + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.destroyDatasetMeta(i); + } + + if (canvas) { + me.unbindEvents(); + helpers.canvas.clear(me); + platform.releaseContext(me.ctx); + me.canvas = null; + me.ctx = null; + } + + plugins.notify(me, 'destroy'); + + delete Chart.instances[me.id]; + }, + + toBase64Image: function() { + return this.canvas.toDataURL.apply(this.canvas, arguments); + }, + + initToolTip: function() { + var me = this; + me.tooltip = new Chart.Tooltip({ + _chart: me, + _chartInstance: me, // deprecated, backward compatibility + _data: me.data, + _options: me.options.tooltips + }, me); + }, + + /** + * @private + */ + bindEvents: function() { + var me = this; + var listeners = me._listeners = {}; + var listener = function() { + me.eventHandler.apply(me, arguments); + }; + + helpers.each(me.options.events, function(type) { + platform.addEventListener(me, type, listener); + listeners[type] = listener; + }); + + // Elements used to detect size change should not be injected for non responsive charts. + // See https://github.com/chartjs/Chart.js/issues/2210 + if (me.options.responsive) { + listener = function() { + me.resize(); + }; + + platform.addEventListener(me, 'resize', listener); + listeners.resize = listener; + } + }, + + /** + * @private + */ + unbindEvents: function() { + var me = this; + var listeners = me._listeners; + if (!listeners) { + return; + } + + delete me._listeners; + helpers.each(listeners, function(listener, type) { + platform.removeEventListener(me, type, listener); + }); + }, + + updateHoverStyle: function(elements, mode, enabled) { + var method = enabled ? 'setHoverStyle' : 'removeHoverStyle'; + var element, i, ilen; + + for (i = 0, ilen = elements.length; i < ilen; ++i) { + element = elements[i]; + if (element) { + this.getDatasetMeta(element._datasetIndex).controller[method](element); + } + } + }, + + /** + * @private + */ + eventHandler: function(e) { + var me = this; + var tooltip = me.tooltip; + + if (plugins.notify(me, 'beforeEvent', [e]) === false) { + return; + } + + // Buffer any update calls so that renders do not occur + me._bufferedRender = true; + me._bufferedRequest = null; + + var changed = me.handleEvent(e); + changed |= tooltip && tooltip.handleEvent(e); + + plugins.notify(me, 'afterEvent', [e]); + + var bufferedRequest = me._bufferedRequest; + if (bufferedRequest) { + // If we have an update that was triggered, we need to do a normal render + me.render(bufferedRequest); + } else if (changed && !me.animating) { + // If entering, leaving, or changing elements, animate the change via pivot + me.stop(); + + // We only need to render at this point. Updating will cause scales to be + // recomputed generating flicker & using more memory than necessary. + me.render(me.options.hover.animationDuration, true); + } + + me._bufferedRender = false; + me._bufferedRequest = null; + + return me; + }, + + /** + * Handle an event + * @private + * @param {IEvent} event the event to handle + * @return {Boolean} true if the chart needs to re-render + */ + handleEvent: function(e) { + var me = this; + var options = me.options || {}; + var hoverOptions = options.hover; + var changed = false; + + me.lastActive = me.lastActive || []; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + me.active = []; + } else { + me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); + } + + // Invoke onHover hook + // Need to call with native event here to not break backwards compatibility + helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); + + if (e.type === 'mouseup' || e.type === 'click') { + if (options.onClick) { + // Use e.native here for backwards compatibility + options.onClick.call(me, e.native, me.active); + } + } + + // Remove styling for last active (even if it may still be active) + if (me.lastActive.length) { + me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); + } + + // Built in hover styling + if (me.active.length && hoverOptions.mode) { + me.updateHoverStyle(me.active, hoverOptions.mode, true); + } + + changed = !helpers.arrayEquals(me.active, me.lastActive); + + // Remember Last Actives + me.lastActive = me.active; + + return changed; + } + }); + + /** + * Provided for backward compatibility, use Chart instead. + * @class Chart.Controller + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + Chart.Controller = Chart; +}; + +},{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); + +module.exports = function(Chart) { + + var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + + /** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ + function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); + } + + /** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ + function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; + } + + // Base class for all dataset controllers (line, bar, etc) + Chart.DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); + }; + + helpers.extend(Chart.DatasetController.prototype, { + + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, + + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + + if (meta.xAxisID === null) { + meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; + } + if (meta.yAxisID === null) { + meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + reset: function() { + this.update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index + }); + }, + + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; + + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } + + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + + listenArrayEvents(data, me); + me._data = data; + } + + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, + + update: helpers.noop, + + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } + + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, + + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + if (meta.dataset) { + meta.dataset.draw(); + } + + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + removeHoverStyle: function(element, elementOpts) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var model = element._model; + + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var getHoverColor = helpers.getHoverColor; + var model = element._model; + + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + this.insertElements(this.getDataset().data.length - 1, arguments.length); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } + }); + + Chart.DatasetController.extend = helpers.inherits; +}; + +},{"45":45}],25:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); + +module.exports = { + /** + * @private + */ + _set: function(scope, values) { + return helpers.merge(this[scope] || (this[scope] = {}), values); + } +}; + +},{"45":45}],26:[function(require,module,exports){ +'use strict'; + +var color = require(3); +var helpers = require(45); + +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + + target = model[key]; + + // if a value is added to the model after pivot() has been called, the view + // doesn't contain it, so let's initialize the view to the target value. + if (!view.hasOwnProperty(key)) { + view[key] = target; + } + + actual = view[key]; + + if (actual === target || key[0] === '_') { + continue; + } + + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof target; + + if (type === typeof origin) { + if (type === 'string') { + c0 = color(origin); + if (c0.valid) { + c1 = color(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (type === 'number' && isFinite(origin) && isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); +}; + +helpers.extend(Element.prototype, { + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers.clone(me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = model; + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } +}); + +Element.extend = helpers.inherits; + +module.exports = Element; + +},{"3":3,"45":45}],27:[function(require,module,exports){ +/* global window: false */ +/* global document: false */ +'use strict'; + +var color = require(3); +var defaults = require(25); +var helpers = require(45); + +module.exports = function(Chart) { + + // -- Basic js utility methods + + helpers.configMerge = function(/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function(key, target, source, options) { + var tval = target[key] || {}; + var sval = source[key]; + + if (key === 'scales') { + // scale config merging is complex. Add our own function here for that + target[key] = helpers.scaleMerge(tval, sval); + } else if (key === 'scale') { + // used in polar area & radar charts since there is only one scale + target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]); + } else { + helpers._merger(key, target, source, options); + } + } + }); + }; + + helpers.scaleMerge = function(/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function(key, target, source, options) { + if (key === 'xAxes' || key === 'yAxes') { + var slen = source[key].length; + var i, type, scale; + + if (!target[key]) { + target[key] = []; + } + + for (i = 0; i < slen; ++i) { + scale = source[key][i]; + type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear'); + + if (i >= target[key].length) { + target[key].push({}); + } + + if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { + // new/untyped scale or type changed: let's apply the new defaults + // then merge source scale to correctly overwrite the defaults. + helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]); + } else { + // scales type are the same + helpers.merge(target[key][i], scale); + } + } + } else { + helpers._merger(key, target, source, options); + } + } + }); + }; + + helpers.where = function(collection, filterCallback) { + if (helpers.isArray(collection) && Array.prototype.filter) { + return collection.filter(filterCallback); + } + var filtered = []; + + helpers.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); + + return filtered; + }; + helpers.findIndex = Array.prototype.findIndex ? + function(array, callback, scope) { + return array.findIndex(callback, scope); + } : + function(array, callback, scope) { + scope = scope === undefined ? array : scope; + for (var i = 0, ilen = array.length; i < ilen; ++i) { + if (callback.call(scope, array[i], i, array)) { + return i; + } + } + return -1; + }; + helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (helpers.isNullOrUndef(startIndex)) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (helpers.isNullOrUndef(startIndex)) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + + // -- Math methods + helpers.isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + helpers.almostEquals = function(x, y, epsilon) { + return Math.abs(x - y) < epsilon; + }; + helpers.almostWhole = function(x, epsilon) { + var rounded = Math.round(x); + return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); + }; + helpers.max = function(array) { + return array.reduce(function(max, value) { + if (!isNaN(value)) { + return Math.max(max, value); + } + return max; + }, Number.NEGATIVE_INFINITY); + }; + helpers.min = function(array) { + return array.reduce(function(min, value) { + if (!isNaN(value)) { + return Math.min(min, value); + } + return min; + }, Number.POSITIVE_INFINITY); + }; + helpers.sign = Math.sign ? + function(x) { + return Math.sign(x); + } : + function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; + helpers.log10 = Math.log10 ? + function(x) { + return Math.log10(x); + } : + function(x) { + return Math.log(x) / Math.LN10; + }; + helpers.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }; + helpers.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }; + // Gets the angle from vertical upright to the point about a centre. + helpers.getAngleFromPoint = function(centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x; + var distanceFromYCenter = anglePoint.y - centrePoint.y; + var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }; + helpers.distanceBetweenPoints = function(pt1, pt2) { + return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); + }; + helpers.aliasPixel = function(pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }; + helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { + // Props to Rob Spencer at scaled innovation for his post on splining between points + // http://scaledinnovation.com/analytics/splines/aboutSplines.html + + // This function must also respect "skipped" points + + var previous = firstPoint.skip ? middlePoint : firstPoint; + var current = middlePoint; + var next = afterPoint.skip ? middlePoint : afterPoint; + + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + var s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; + + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; + }; + helpers.EPSILON = Number.EPSILON || 1e-14; + helpers.splineCurveMonotone = function(points) { + // This function calculates Bézier control points in a similar way than |splineCurve|, + // but preserves monotonicity of the provided data and ensures no local extremums are added + // between the dataset discrete points due to the interpolation. + // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + + var pointsWithTangents = (points || []).map(function(point) { + return { + model: point._model, + deltaK: 0, + mK: 0 + }; + }); + + // Calculate slopes (deltaK) and initialize tangents (mK) + var pointsLen = pointsWithTangents.length; + var i, pointBefore, pointCurrent, pointAfter; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointAfter && !pointAfter.model.skip) { + var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); + + // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 + pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; + } + + if (!pointBefore || pointBefore.model.skip) { + pointCurrent.mK = pointCurrent.deltaK; + } else if (!pointAfter || pointAfter.model.skip) { + pointCurrent.mK = pointBefore.deltaK; + } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { + pointCurrent.mK = 0; + } else { + pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; + } + } + + // Adjust tangents to ensure monotonic properties + var alphaK, betaK, tauK, squaredMagnitude; + for (i = 0; i < pointsLen - 1; ++i) { + pointCurrent = pointsWithTangents[i]; + pointAfter = pointsWithTangents[i + 1]; + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } + + if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { + pointCurrent.mK = pointAfter.mK = 0; + continue; + } + + alphaK = pointCurrent.mK / pointCurrent.deltaK; + betaK = pointAfter.mK / pointCurrent.deltaK; + squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); + if (squaredMagnitude <= 9) { + continue; + } + + tauK = 3 / Math.sqrt(squaredMagnitude); + pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + } + + // Compute control points + var deltaX; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointBefore && !pointBefore.model.skip) { + deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; + pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; + pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; + } + if (pointAfter && !pointAfter.model.skip) { + deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; + pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; + pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; + } + } + }; + helpers.nextItem = function(collection, index, loop) { + if (loop) { + return index >= collection.length - 1 ? collection[0] : collection[index + 1]; + } + return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; + }; + helpers.previousItem = function(collection, index, loop) { + if (loop) { + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; + } + return index <= 0 ? collection[0] : collection[index - 1]; + }; + // Implementation of the nice number algorithm used in determining where axis labels will go + helpers.niceNum = function(range, round) { + var exponent = Math.floor(helpers.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); + }; + // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + helpers.requestAnimFrame = (function() { + if (typeof window === 'undefined') { + return function(callback) { + callback(); + }; + } + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + }()); + // -- DOM methods + helpers.getRelativePosition = function(evt, chart) { + var mouseX, mouseY; + var e = evt.originalEvent || evt; + var canvas = evt.currentTarget || evt.srcElement; + var boundingRect = canvas.getBoundingClientRect(); + + var touches = e.touches; + if (touches && touches.length > 0) { + mouseX = touches[0].clientX; + mouseY = touches[0].clientY; + + } else { + mouseX = e.clientX; + mouseY = e.clientY; + } + + // Scale mouse coordinates into canvas coordinates + // by following the pattern laid out by 'jerryj' in the comments of + // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ + var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); + var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); + var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); + var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); + var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; + var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; + + // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However + // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here + mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); + mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); + + return { + x: mouseX, + y: mouseY + }; + + }; + + // Private helper function to convert max-width/max-height values that may be percentages into a number + function parseMaxStyle(styleValue, node, parentProperty) { + var valueInPixels; + if (typeof styleValue === 'string') { + valueInPixels = parseInt(styleValue, 10); + + if (styleValue.indexOf('%') !== -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } + + return valueInPixels; + } + + /** + * Returns if the given value contains an effective constraint. + * @private + */ + function isConstrainedValue(value) { + return value !== undefined && value !== null && value !== 'none'; + } + + // Private helper to get a constraint dimension + // @param domNode : the node to check the constraint on + // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) + // @param percentageProperty : property of parent to use when calculating width as a percentage + // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser + function getConstraintDimension(domNode, maxStyle, percentageProperty) { + var view = document.defaultView; + var parentNode = domNode.parentNode; + var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; + var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; + var hasCNode = isConstrainedValue(constrainedNode); + var hasCContainer = isConstrainedValue(constrainedContainer); + var infinity = Number.POSITIVE_INFINITY; + + if (hasCNode || hasCContainer) { + return Math.min( + hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, + hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); + } + + return 'none'; + } + // returns Number or undefined if no constraint + helpers.getConstraintWidth = function(domNode) { + return getConstraintDimension(domNode, 'max-width', 'clientWidth'); + }; + // returns Number or undefined if no constraint + helpers.getConstraintHeight = function(domNode) { + return getConstraintDimension(domNode, 'max-height', 'clientHeight'); + }; + helpers.getMaximumWidth = function(domNode) { + var container = domNode.parentNode; + if (!container) { + return domNode.clientWidth; + } + + var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10); + var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10); + var w = container.clientWidth - paddingLeft - paddingRight; + var cw = helpers.getConstraintWidth(domNode); + return isNaN(cw) ? w : Math.min(w, cw); + }; + helpers.getMaximumHeight = function(domNode) { + var container = domNode.parentNode; + if (!container) { + return domNode.clientHeight; + } + + var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10); + var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10); + var h = container.clientHeight - paddingTop - paddingBottom; + var ch = helpers.getConstraintHeight(domNode); + return isNaN(ch) ? h : Math.min(h, ch); + }; + helpers.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }; + helpers.retinaScale = function(chart, forceRatio) { + var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1; + if (pixelRatio === 1) { + return; + } + + var canvas = chart.canvas; + var height = chart.height; + var width = chart.width; + + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.scale(pixelRatio, pixelRatio); + + // If no style has been set on the canvas, the render size is used as display size, + // making the chart visually bigger, so let's enforce it to the "correct" values. + // See https://github.com/chartjs/Chart.js/issues/3575 + canvas.style.height = height + 'px'; + canvas.style.width = width + 'px'; + }; + // -- Canvas methods + helpers.fontString = function(pixelSize, fontStyle, fontFamily) { + return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; + }; + helpers.longestText = function(ctx, font, arrayOfThings, cache) { + cache = cache || {}; + var data = cache.data = cache.data || {}; + var gc = cache.garbageCollect = cache.garbageCollect || []; + + if (cache.font !== font) { + data = cache.data = {}; + gc = cache.garbageCollect = []; + cache.font = font; + } + + ctx.font = font; + var longest = 0; + helpers.each(arrayOfThings, function(thing) { + // Undefined strings and arrays should not be measured + if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { + longest = helpers.measureText(ctx, data, gc, longest, thing); + } else if (helpers.isArray(thing)) { + // if it is an array lets measure each element + // to do maybe simplify this function a bit so we can do this more recursively? + helpers.each(thing, function(nestedThing) { + // Undefined strings and arrays should not be measured + if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { + longest = helpers.measureText(ctx, data, gc, longest, nestedThing); + } + }); + } + }); + + var gcLen = gc.length / 2; + if (gcLen > arrayOfThings.length) { + for (var i = 0; i < gcLen; i++) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + return longest; + }; + helpers.measureText = function(ctx, data, gc, longest, string) { + var textWidth = data[string]; + if (!textWidth) { + textWidth = data[string] = ctx.measureText(string).width; + gc.push(string); + } + if (textWidth > longest) { + longest = textWidth; + } + return longest; + }; + helpers.numberOfLabelLines = function(arrayOfThings) { + var numberOfLines = 1; + helpers.each(arrayOfThings, function(thing) { + if (helpers.isArray(thing)) { + if (thing.length > numberOfLines) { + numberOfLines = thing.length; + } + } + }); + return numberOfLines; + }; + + helpers.color = !color ? + function(value) { + console.error('Color.js not found!'); + return value; + } : + function(value) { + /* global CanvasGradient */ + if (value instanceof CanvasGradient) { + value = defaults.global.defaultColor; + } + + return color(value); + }; + + helpers.getHoverColor = function(colorValue) { + /* global CanvasPattern */ + return (colorValue instanceof CanvasPattern) ? + colorValue : + helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); + }; +}; + +},{"25":25,"3":3,"45":45}],28:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); + +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {Point} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers.getRelativePosition(e, chart); +} + +/** + * Helper function to traverse all of the visible elements in the chart + * @param chart {chart} the chart + * @param handler {Function} the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var datasets = chart.data.datasets; + var meta, i, j, ilen, jlen; + + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!chart.isDatasetVisible(i)) { + continue; + } + + meta = chart.getDatasetMeta(i); + for (j = 0, jlen = meta.data.length; j < jlen; ++j) { + var element = meta.data[j]; + if (!element._view.skip) { + handler(element); + } + } + } +} + +/** + * Helper function to get the items that intersect the event position + * @param items {ChartElement[]} elements to filter + * @param position {Point} the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param chart {Chart} the chart to look at elements from + * @param position {Point} the point to be nearest to + * @param intersect {Boolean} if true, only consider items that intersect the position + * @param distanceMetric {Function} function to provide the distance between points + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); + + return nearestItems; +} + +/** + * Get a distance metric function for two points based on the + * axis mode setting + * @param {String} axis the axis mode. x|y|xy + */ +function getDistanceMetricForAxis(axis) { + var useX = axis.indexOf('x') !== -1; + var useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + options.axis = options.axis || 'x'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart.data.datasets.forEach(function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + var element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + } + }); + + return elements; +} + +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +module.exports = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, {intersect: false}); + }, + + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, + + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric); + + // We have multiple items at the same distance from the event. Now sort by smallest + if (nearestItems.length > 1) { + nearestItems.sort(function(a, b) { + var sizeA = a.getArea(); + var sizeB = b.getArea(); + var ret = sizeA - sizeB; + + if (ret === 0) { + // if equal sort by dataset index + ret = a._datasetIndex - b._datasetIndex; + } + + return ret; + }); + } + + // Return only 1 item + return nearestItems.slice(0, 1); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } +}; + +},{"45":45}],29:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); + +defaults._set('global', { + responsive: true, + responsiveAnimationDuration: 0, + maintainAspectRatio: true, + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], + hover: { + onHover: null, + mode: 'nearest', + intersect: true, + animationDuration: 400 + }, + onClick: null, + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + showLines: true, + + // Element defaults defined in element extensions + elements: {}, + + // Layout options such as padding + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + +module.exports = function() { + + // Occupy the global variable of Chart, and create a simple base class + var Chart = function(item, config) { + this.construct(item, config); + return this; + }; + + Chart.Chart = Chart; + + return Chart; +}; + +},{"25":25}],30:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); + +module.exports = function(Chart) { + + function filterByPosition(array, position) { + return helpers.where(array, function(v) { + return v.position === position; + }); + } + + function sortByWeight(array, reverse) { + array.forEach(function(v, i) { + v._tmpIndex_ = i; + return v; + }); + array.sort(function(a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0._tmpIndex_ - v1._tmpIndex_ : + v0.weight - v1.weight; + }); + array.forEach(function(v) { + delete v._tmpIndex_; + }); + } + + /** + * @interface ILayoutItem + * @prop {String} position - The position of the item in the chart layout. Possible values are + * 'left', 'top', 'right', 'bottom', and 'chartArea' + * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down + * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) + * @prop {Function} update - Takes two parameters: width and height. Returns size of item + * @prop {Function} getPadding - Returns an object with padding on the edges + * @prop {Number} width - Width of item. Must be valid after update() + * @prop {Number} height - Height of item. Must be valid after update() + * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update + * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update + * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update + * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update + */ + + // The layout service is very self explanatory. It's responsible for the layout within a chart. + // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need + // It is this service's responsibility of carrying out that layout. + Chart.layoutService = { + defaults: {}, + + /** + * Register a box to a chart. + * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. + * @param {Chart} chart - the chart to use + * @param {ILayoutItem} item - the item to add to be layed out + */ + addBox: function(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullWidth = item.fullWidth || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {Object} layoutItem - the item to remove from the layout + */ + removeBox: function(chart, layoutItem) { + var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** + * Sets (or updates) options on the given `item`. + * @param {Chart} chart - the chart in which the item lives (or will be added to) + * @param {Object} item - the item to configure with the given options + * @param {Object} options - the new item options. + */ + configure: function(chart, item, options) { + var props = ['fullWidth', 'position', 'weight']; + var ilen = props.length; + var i = 0; + var prop; + + for (; i < ilen; ++i) { + prop = props[i]; + if (options.hasOwnProperty(prop)) { + item[prop] = options[prop]; + } + } + }, + + /** + * Fits boxes of the given chart into the given size by having each box measure itself + * then running a fitting algorithm + * @param {Chart} chart - the chart + * @param {Number} width - the width to fit into + * @param {Number} height - the height to fit into + */ + update: function(chart, width, height) { + if (!chart) { + return; + } + + var layoutOptions = chart.options.layout || {}; + var padding = helpers.options.toPadding(layoutOptions.padding); + var leftPadding = padding.left; + var rightPadding = padding.right; + var topPadding = padding.top; + var bottomPadding = padding.bottom; + + var leftBoxes = filterByPosition(chart.boxes, 'left'); + var rightBoxes = filterByPosition(chart.boxes, 'right'); + var topBoxes = filterByPosition(chart.boxes, 'top'); + var bottomBoxes = filterByPosition(chart.boxes, 'bottom'); + var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea'); + + // Sort boxes by weight. A higher weight is further away from the chart area + sortByWeight(leftBoxes, true); + sortByWeight(rightBoxes, false); + sortByWeight(topBoxes, true); + sortByWeight(bottomBoxes, false); + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + // What we do to find the best sizing, we do the following + // 1. Determine the minimum size of the chart area. + // 2. Split the remaining width equally between each vertical axis + // 3. Split the remaining height equally between each horizontal axis + // 4. Give each layout the maximum size it can be. The layout will return it's minimum size + // 5. Adjust the sizes of each axis based on it's minimum reported size. + // 6. Refit each axis + // 7. Position each axis in the final location + // 8. Tell the chart the final location of the chart area + // 9. Tell any axes that overlay the chart area the positions of the chart area + + // Step 1 + var chartWidth = width - leftPadding - rightPadding; + var chartHeight = height - topPadding - bottomPadding; + var chartAreaWidth = chartWidth / 2; // min 50% + var chartAreaHeight = chartHeight / 2; // min 50% + + // Step 2 + var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); + + // Step 3 + var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); + + // Step 4 + var maxChartAreaWidth = chartWidth; + var maxChartAreaHeight = chartHeight; + var minBoxSizes = []; + + function getMinimumBoxSize(box) { + var minSize; + var isHorizontal = box.isHorizontal(); + + if (isHorizontal) { + minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); + maxChartAreaHeight -= minSize.height; + } else { + minSize = box.update(verticalBoxWidth, chartAreaHeight); + maxChartAreaWidth -= minSize.width; + } + + minBoxSizes.push({ + horizontal: isHorizontal, + minSize: minSize, + box: box, + }); + } + + helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); + + // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) + var maxHorizontalLeftPadding = 0; + var maxHorizontalRightPadding = 0; + var maxVerticalTopPadding = 0; + var maxVerticalBottomPadding = 0; + + helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) { + if (horizontalBox.getPadding) { + var boxPadding = horizontalBox.getPadding(); + maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left); + maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right); + } + }); + + helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) { + if (verticalBox.getPadding) { + var boxPadding = verticalBox.getPadding(); + maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top); + maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom); + } + }); + + // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. + // Steps 5 & 6 + var totalLeftBoxesWidth = leftPadding; + var totalRightBoxesWidth = rightPadding; + var totalTopBoxesHeight = topPadding; + var totalBottomBoxesHeight = bottomPadding; + + // Function to fit a box + function fitBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { + return minBox.box === box; + }); + + if (minBoxSize) { + if (box.isHorizontal()) { + var scaleMargin = { + left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding), + right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding), + top: 0, + bottom: 0 + }; + + // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends + // on the margin. Sometimes they need to increase in size slightly + box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); + } else { + box.update(minBoxSize.minSize.width, maxChartAreaHeight); + } + } + } + + // Update, and calculate the left and right margins for the horizontal boxes + helpers.each(leftBoxes.concat(rightBoxes), fitBox); + + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); + + // Set the Left and Right margins for the horizontal boxes + helpers.each(topBoxes.concat(bottomBoxes), fitBox); + + // Figure out how much margin is on the top and bottom of the vertical boxes + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); + + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); + + function finalFitVerticalBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { + return minSize.box === box; + }); + + var scaleMargin = { + left: 0, + right: 0, + top: totalTopBoxesHeight, + bottom: totalBottomBoxesHeight + }; + + if (minBoxSize) { + box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); + } + } + + // Let the left layout know the final margin + helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); + + // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) + totalLeftBoxesWidth = leftPadding; + totalRightBoxesWidth = rightPadding; + totalTopBoxesHeight = topPadding; + totalBottomBoxesHeight = bottomPadding; + + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); + + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); + + // We may be adding some padding to account for rotated x axis labels + var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0); + totalLeftBoxesWidth += leftPaddingAddition; + totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0); + + var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0); + totalTopBoxesHeight += topPaddingAddition; + totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0); + + // Figure out if our chart area changed. This would occur if the dataset layout label rotation + // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do + // without calling `fit` again + var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; + var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; + + if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { + helpers.each(leftBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(rightBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(topBoxes, function(box) { + if (!box.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); + + helpers.each(bottomBoxes, function(box) { + if (!box.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); + + maxChartAreaHeight = newMaxChartAreaHeight; + maxChartAreaWidth = newMaxChartAreaWidth; + } + + // Step 7 - Position the boxes + var left = leftPadding + leftPaddingAddition; + var top = topPadding + topPaddingAddition; + + function placeBox(box) { + if (box.isHorizontal()) { + box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth; + box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; + box.top = top; + box.bottom = top + box.height; + + // Move to next point + top = box.bottom; + + } else { + + box.left = left; + box.right = left + box.width; + box.top = totalTopBoxesHeight; + box.bottom = totalTopBoxesHeight + maxChartAreaHeight; + + // Move to next point + left = box.right; + } + } + + helpers.each(leftBoxes.concat(topBoxes), placeBox); + + // Account for chart width and height + left += maxChartAreaWidth; + top += maxChartAreaHeight; + + helpers.each(rightBoxes, placeBox); + helpers.each(bottomBoxes, placeBox); + + // Step 8 + chart.chartArea = { + left: totalLeftBoxesWidth, + top: totalTopBoxesHeight, + right: totalLeftBoxesWidth + maxChartAreaWidth, + bottom: totalTopBoxesHeight + maxChartAreaHeight + }; + + // Step 9 + helpers.each(chartAreaBoxes, function(box) { + box.left = chart.chartArea.left; + box.top = chart.chartArea.top; + box.right = chart.chartArea.right; + box.bottom = chart.chartArea.bottom; + + box.update(maxChartAreaWidth, maxChartAreaHeight); + }); + } + }; +}; + +},{"45":45}],31:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +defaults._set('global', { + plugins: {} +}); + +module.exports = function(Chart) { + + /** + * The plugin service singleton + * @namespace Chart.plugins + * @since 2.1.0 + */ + Chart.plugins = { + /** + * Globally registered plugins. + * @private + */ + _plugins: [], + + /** + * This identifier is used to invalidate the descriptors cache attached to each chart + * when a global plugin is registered or unregistered. In this case, the cache ID is + * incremented and descriptors are regenerated during following API calls. + * @private + */ + _cacheId: 0, + + /** + * Registers the given plugin(s) if not already registered. + * @param {Array|Object} plugins plugin instance(s). + */ + register: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + if (p.indexOf(plugin) === -1) { + p.push(plugin); + } + }); + + this._cacheId++; + }, + + /** + * Unregisters the given plugin(s) only if registered. + * @param {Array|Object} plugins plugin instance(s). + */ + unregister: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + var idx = p.indexOf(plugin); + if (idx !== -1) { + p.splice(idx, 1); + } + }); + + this._cacheId++; + }, + + /** + * Remove all registered plugins. + * @since 2.1.5 + */ + clear: function() { + this._plugins = []; + this._cacheId++; + }, + + /** + * Returns the number of registered plugins? + * @returns {Number} + * @since 2.1.5 + */ + count: function() { + return this._plugins.length; + }, + + /** + * Returns all registered plugin instances. + * @returns {Array} array of plugin objects. + * @since 2.1.5 + */ + getAll: function() { + return this._plugins; + }, + + /** + * Calls enabled plugins for `chart` on the specified hook and with the given args. + * This method immediately returns as soon as a plugin explicitly returns false. The + * returned value can be used, for instance, to interrupt the current action. + * @param {Object} chart - The chart instance for which plugins should be called. + * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). + * @param {Array} [args] - Extra arguments to apply to the hook call. + * @returns {Boolean} false if any of the plugins return false, else returns true. + */ + notify: function(chart, hook, args) { + var descriptors = this.descriptors(chart); + var ilen = descriptors.length; + var i, descriptor, plugin, params, method; + + for (i = 0; i < ilen; ++i) { + descriptor = descriptors[i]; + plugin = descriptor.plugin; + method = plugin[hook]; + if (typeof method === 'function') { + params = [chart].concat(args || []); + params.push(descriptor.options); + if (method.apply(plugin, params) === false) { + return false; + } + } + } + + return true; + }, + + /** + * Returns descriptors of enabled plugins for the given chart. + * @returns {Array} [{ plugin, options }] + * @private + */ + descriptors: function(chart) { + var cache = chart._plugins || (chart._plugins = {}); + if (cache.id === this._cacheId) { + return cache.descriptors; + } + + var plugins = []; + var descriptors = []; + var config = (chart && chart.config) || {}; + var options = (config.options && config.options.plugins) || {}; + + this._plugins.concat(config.plugins || []).forEach(function(plugin) { + var idx = plugins.indexOf(plugin); + if (idx !== -1) { + return; + } + + var id = plugin.id; + var opts = options[id]; + if (opts === false) { + return; + } + + if (opts === true) { + opts = helpers.clone(defaults.global.plugins[id]); + } + + plugins.push(plugin); + descriptors.push({ + plugin: plugin, + options: opts || {} + }); + }); + + cache.descriptors = descriptors; + cache.id = this._cacheId; + return descriptors; + } + }; + + /** + * Plugin extension hooks. + * @interface IPlugin + * @since 2.1.0 + */ + /** + * @method IPlugin#beforeInit + * @desc Called before initializing `chart`. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#afterInit + * @desc Called after `chart` has been initialized and before the first update. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeUpdate + * @desc Called before updating `chart`. If any plugin returns `false`, the update + * is cancelled (and thus subsequent render(s)) until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart update. + */ + /** + * @method IPlugin#afterUpdate + * @desc Called after `chart` has been updated and before rendering. Note that this + * hook will not be called if the chart update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetsUpdate + * @desc Called before updating the `chart` datasets. If any plugin returns `false`, + * the datasets update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} false to cancel the datasets update. + * @since version 2.1.5 + */ + /** + * @method IPlugin#afterDatasetsUpdate + * @desc Called after the `chart` datasets have been updated. Note that this hook + * will not be called if the datasets update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @since version 2.1.5 + */ + /** + * @method IPlugin#beforeDatasetUpdate + * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin + * returns `false`, the datasets update is cancelled until another `update` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ + /** + * @method IPlugin#afterDatasetUpdate + * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note + * that this hook will not be called if the datasets update has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeLayout + * @desc Called before laying out `chart`. If any plugin returns `false`, + * the layout update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart layout. + */ + /** + * @method IPlugin#afterLayout + * @desc Called after the `chart` has been layed out. Note that this hook will not + * be called if the layout update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeRender + * @desc Called before rendering `chart`. If any plugin returns `false`, + * the rendering is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart rendering. + */ + /** + * @method IPlugin#afterRender + * @desc Called after the `chart` has been fully rendered (and animation completed). Note + * that this hook will not be called if the rendering has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDraw + * @desc Called before drawing `chart` at every animation frame specified by the given + * easing value. If any plugin returns `false`, the frame drawing is cancelled until + * another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart drawing. + */ + /** + * @method IPlugin#afterDraw + * @desc Called after the `chart` has been drawn for the specific easing value. Note + * that this hook will not be called if the drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetsDraw + * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, + * the datasets drawing is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ + /** + * @method IPlugin#afterDatasetsDraw + * @desc Called after the `chart` datasets have been drawn. Note that this hook + * will not be called if the datasets drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetDraw + * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets + * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing + * is cancelled until another `render` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ + /** + * @method IPlugin#afterDatasetDraw + * @desc Called after the `chart` datasets at the given `args.index` have been drawn + * (datasets are drawn in the reverse order). Note that this hook will not be called + * if the datasets drawing has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeTooltipDraw + * @desc Called before drawing the `tooltip`. If any plugin returns `false`, + * the tooltip drawing is cancelled until another `render` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart tooltip drawing. + */ + /** + * @method IPlugin#afterTooltipDraw + * @desc Called after drawing the `tooltip`. Note that this hook will not + * be called if the tooltip drawing has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeEvent + * @desc Called before processing the specified `event`. If any plugin returns `false`, + * the event will be discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#afterEvent + * @desc Called after the `event` has been consumed. Note that this hook + * will not be called if the `event` has been previously discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#resize + * @desc Called after the chart as been resized. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} size - The new canvas display size (eq. canvas.style width & height). + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#destroy + * @desc Called after the chart as been destroyed. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + + /** + * Provided for backward compatibility, use Chart.plugins instead + * @namespace Chart.pluginService + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ + Chart.pluginService = Chart.plugins; + + /** + * Provided for backward compatibility, inheriting from Chart.PlugingBase has no + * effect, instead simply create/register plugins via plain JavaScript objects. + * @interface Chart.PluginBase + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + Chart.PluginBase = Element.extend({}); +}; + +},{"25":25,"26":26,"45":45}],32:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); +var Ticks = require(34); + +defaults._set('scale', { + display: true, + position: 'left', + offset: false, + + // grid line settings + gridLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickMarkLength: 10, + zeroLineWidth: 1, + zeroLineColor: 'rgba(0,0,0,0.25)', + zeroLineBorderDash: [], + zeroLineBorderDashOffset: 0.0, + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, + + // scale label + scaleLabel: { + // display property + display: false, + + // actual label + labelString: '', + + // line height + lineHeight: 1.2, + + // top/bottom padding + padding: { + top: 4, + bottom: 4 + } + }, + + // label settings + ticks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: Ticks.formatters.values, + minor: {}, + major: {} + } +}); + +function labelsFromTicks(ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(ticks[i].label); + } + + return labels; +} + +function getLineValue(scale, index, offsetGridLines) { + var lineValue = scale.getPixelForTick(index); + + if (offsetGridLines) { + if (index === 0) { + lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; + } else { + lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; + } + } + return lineValue; +} + +module.exports = function(Chart) { + + function computeTextSize(context, tick, font) { + return helpers.isArray(tick) ? + helpers.longestText(context, font, tick) : + context.measureText(tick).width; + } + + function parseFontOptions(options) { + var valueOrDefault = helpers.valueOrDefault; + var globalDefaults = defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); + var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); + + return { + size: size, + style: style, + family: family, + font: helpers.fontString(size, style, family) + }; + } + + function parseLineHeight(options) { + return helpers.options.toLineHeight( + helpers.valueOrDefault(options.lineHeight, 1.2), + helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); + } + + Chart.Scale = Element.extend({ + /** + * Get the padding needed for the scale + * @method getPadding + * @private + * @returns {Padding} the necessary padding + */ + getPadding: function() { + var me = this; + return { + left: me.paddingLeft || 0, + top: me.paddingTop || 0, + right: me.paddingRight || 0, + bottom: me.paddingBottom || 0 + }; + }, + + /** + * Returns the scale tick objects ({label, major}) + * @since 2.7 + */ + getTicks: function() { + return this._ticks; + }, + + // These methods are ordered by lifecyle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type + + mergeTicksOptions: function() { + var ticks = this.options.ticks; + if (ticks.minor === false) { + ticks.minor = { + display: false + }; + } + if (ticks.major === false) { + ticks.major = { + display: false + }; + } + for (var key in ticks) { + if (key !== 'major' && key !== 'minor') { + if (typeof ticks.minor[key] === 'undefined') { + ticks.minor[key] = ticks[key]; + } + if (typeof ticks.major[key] === 'undefined') { + ticks.major[key] = ticks[key]; + } + } + } + }, + beforeUpdate: function() { + helpers.callback(this.options.beforeUpdate, [this]); + }, + update: function(maxWidth, maxHeight, margins) { + var me = this; + var i, ilen, labels, label, ticks, tick; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = helpers.extend({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + me.longestTextCache = me.longestTextCache || {}; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + + // Data min/max + me.beforeDataLimits(); + me.determineDataLimits(); + me.afterDataLimits(); + + // Ticks - `this.ticks` is now DEPRECATED! + // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member + // and must not be accessed directly from outside this class. `this.ticks` being + // around for long time and not marked as private, we can't change its structure + // without unexpected breaking changes. If you need to access the scale ticks, + // use scale.getTicks() instead. + + me.beforeBuildTicks(); + + // New implementations should return an array of objects but for BACKWARD COMPAT, + // we still support no return (`this.ticks` internally set by calling this method). + ticks = me.buildTicks() || []; + + me.afterBuildTicks(); + + me.beforeTickToLabelConversion(); + + // New implementations should return the formatted tick labels but for BACKWARD + // COMPAT, we still support no return (`this.ticks` internally changed by calling + // this method and supposed to contain only string values). + labels = me.convertTicksToLabels(ticks) || me.ticks; + + me.afterTickToLabelConversion(); + + me.ticks = labels; // BACKWARD COMPATIBILITY + + // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change! + + // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) + for (i = 0, ilen = labels.length; i < ilen; ++i) { + label = labels[i]; + tick = ticks[i]; + if (!tick) { + ticks.push(tick = { + label: label, + major: false + }); + } else { + tick.label = label; + } + } + + me._ticks = ticks; + + // Tick Rotation + me.beforeCalculateTickRotation(); + me.calculateTickRotation(); + me.afterCalculateTickRotation(); + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: function() { + helpers.callback(this.options.afterUpdate, [this]); + }, + + // + + beforeSetDimensions: function() { + helpers.callback(this.options.beforeSetDimensions, [this]); + }, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + }, + afterSetDimensions: function() { + helpers.callback(this.options.afterSetDimensions, [this]); + }, + + // Data limits + beforeDataLimits: function() { + helpers.callback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers.noop, + afterDataLimits: function() { + helpers.callback(this.options.afterDataLimits, [this]); + }, + + // + beforeBuildTicks: function() { + helpers.callback(this.options.beforeBuildTicks, [this]); + }, + buildTicks: helpers.noop, + afterBuildTicks: function() { + helpers.callback(this.options.afterBuildTicks, [this]); + }, + + beforeTickToLabelConversion: function() { + helpers.callback(this.options.beforeTickToLabelConversion, [this]); + }, + convertTicksToLabels: function() { + var me = this; + // Convert ticks to strings + var tickOpts = me.options.ticks; + me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); + }, + afterTickToLabelConversion: function() { + helpers.callback(this.options.afterTickToLabelConversion, [this]); + }, + + // + + beforeCalculateTickRotation: function() { + helpers.callback(this.options.beforeCalculateTickRotation, [this]); + }, + calculateTickRotation: function() { + var me = this; + var context = me.ctx; + var tickOpts = me.options.ticks; + var labels = labelsFromTicks(me._ticks); + + // Get the width of each grid by calculating the difference + // between x offsets between 0 and 1. + var tickFont = parseFontOptions(tickOpts); + context.font = tickFont.font; + + var labelRotation = tickOpts.minRotation || 0; + + if (labels.length && me.options.display && me.isHorizontal()) { + var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache); + var labelWidth = originalLabelWidth; + var cosRotation, sinRotation; + + // Allow 3 pixels x2 padding either side for label readability + var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; + + // Max label rotation can be set or default to 90 - also act as a loop counter + while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { + var angleRadians = helpers.toRadians(labelRotation); + cosRotation = Math.cos(angleRadians); + sinRotation = Math.sin(angleRadians); + + if (sinRotation * originalLabelWidth > me.maxHeight) { + // go back one step + labelRotation--; + break; + } + + labelRotation++; + labelWidth = cosRotation * originalLabelWidth; + } + } + + me.labelRotation = labelRotation; + }, + afterCalculateTickRotation: function() { + helpers.callback(this.options.afterCalculateTickRotation, [this]); + }, + + // + + beforeFit: function() { + helpers.callback(this.options.beforeFit, [this]); + }, + fit: function() { + var me = this; + // Reset + var minSize = me.minSize = { + width: 0, + height: 0 + }; + + var labels = labelsFromTicks(me._ticks); + + var opts = me.options; + var tickOpts = opts.ticks; + var scaleLabelOpts = opts.scaleLabel; + var gridLineOpts = opts.gridLines; + var display = opts.display; + var isHorizontal = me.isHorizontal(); + + var tickFont = parseFontOptions(tickOpts); + var tickMarkLength = opts.gridLines.tickMarkLength; + + // Width + if (isHorizontal) { + // subtract the margins to line up with the chartArea if we are a full width scale + minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; + } else { + minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } + + // height + if (isHorizontal) { + minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } else { + minSize.height = me.maxHeight; // fill all the height + } + + // Are we showing a title for the scale? + if (scaleLabelOpts.display && display) { + var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); + var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); + var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; + + if (isHorizontal) { + minSize.height += deltaHeight; + } else { + minSize.width += deltaHeight; + } + } + + // Don't bother fitting the ticks if we are not showing them + if (tickOpts.display && display) { + var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache); + var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); + var lineSpace = tickFont.size * 0.5; + var tickPadding = me.options.ticks.padding; + + if (isHorizontal) { + // A horizontal axis is more constrained by the height. + me.longestLabelWidth = largestTextWidth; + + var angleRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(angleRadians); + var sinRotation = Math.sin(angleRadians); + + // TODO - improve this calculation + var labelHeight = (sinRotation * largestTextWidth) + + (tickFont.size * tallestLabelHeightInLines) + + (lineSpace * (tallestLabelHeightInLines - 1)) + + lineSpace; // padding + + minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); + + me.ctx.font = tickFont.font; + var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font); + var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font); + + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned + // which means that the right padding is dominated by the font height + if (me.labelRotation !== 0) { + me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges + me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3; + } else { + me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges + me.paddingRight = lastLabelWidth / 2 + 3; + } + } else { + // A vertical axis is more constrained by the width. Labels are the + // dominant factor here, so get that length first and account for padding + if (tickOpts.mirror) { + largestTextWidth = 0; + } else { + // use lineSpace for consistency with horizontal axis + // tickPadding is not implemented for horizontal + largestTextWidth += tickPadding + lineSpace; + } + + minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); + + me.paddingTop = tickFont.size / 2; + me.paddingBottom = tickFont.size / 2; + } + } + + me.handleMargins(); + + me.width = minSize.width; + me.height = minSize.height; + }, + + /** + * Handle margins and padding interactions + * @private + */ + handleMargins: function() { + var me = this; + if (me.margins) { + me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); + me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); + me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); + me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); + } + }, + + afterFit: function() { + helpers.callback(this.options.afterFit, [this]); + }, + + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + isFullWidth: function() { + return (this.options.fullWidth); + }, + + // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function(rawValue) { + // Null and undefined values first + if (helpers.isNullOrUndef(rawValue)) { + return NaN; + } + // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values + if (typeof rawValue === 'number' && !isFinite(rawValue)) { + return NaN; + } + // If it is in fact an object, dive in one more level + if (rawValue) { + if (this.isHorizontal()) { + if (rawValue.x !== undefined) { + return this.getRightValue(rawValue.x); + } + } else if (rawValue.y !== undefined) { + return this.getRightValue(rawValue.y); + } + } + + // Value is good, return it + return rawValue; + }, + + /** + * Used to get the value to display in the tooltip for the data at the given index + * @param index + * @param datasetIndex + */ + getLabelForIndex: helpers.noop, + + /** + * Returns the location of the given data point. Value can either be an index or a numerical value + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param value + * @param index + * @param datasetIndex + */ + getPixelForValue: helpers.noop, + + /** + * Used to get the data value from a given pixel. This is the inverse of getPixelForValue + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param pixel + */ + getValueForPixel: helpers.noop, + + /** + * Returns the location of the tick at the given index + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForTick: function(index) { + var me = this; + var offset = me.options.offset; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); + var pixel = (tickWidth * index) + me.paddingLeft; + + if (offset) { + pixel += tickWidth / 2; + } + + var finalVal = me.left + Math.round(pixel); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + var innerHeight = me.height - (me.paddingTop + me.paddingBottom); + return me.top + (index * (innerHeight / (me._ticks.length - 1))); + }, + + /** + * Utility for getting the pixel location of a percentage of scale + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForDecimal: function(decimal) { + var me = this; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var valueOffset = (innerWidth * decimal) + me.paddingLeft; + + var finalVal = me.left + Math.round(valueOffset); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + return me.top + (decimal * me.height); + }, + + /** + * Returns the pixel for the minimum chart value + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getBasePixel: function() { + return this.getPixelForValue(this.getBaseValue()); + }, + + getBaseValue: function() { + var me = this; + var min = me.min; + var max = me.max; + + return me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0; + }, + + /** + * Returns a subset of ticks to be plotted to avoid overlapping labels. + * @private + */ + _autoSkip: function(ticks) { + var skipRatio; + var me = this; + var isHorizontal = me.isHorizontal(); + var optionTicks = me.options.ticks.minor; + var tickCount = ticks.length; + var labelRotationRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(labelRotationRadians); + var longestRotatedLabel = me.longestLabelWidth * cosRotation; + var result = []; + var i, tick, shouldSkip; + + // figure out the maximum number of gridlines to show + var maxTicks; + if (optionTicks.maxTicksLimit) { + maxTicks = optionTicks.maxTicksLimit; + } + + if (isHorizontal) { + skipRatio = false; + + if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) { + skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight))); + } + + // if they defined a max number of optionTicks, + // increase skipRatio until that number is met + if (maxTicks && tickCount > maxTicks) { + skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks)); + } + } + + for (i = 0; i < tickCount; i++) { + tick = ticks[i]; + + // Since we always show the last tick,we need may need to hide the last shown one before + shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount); + if (shouldSkip && i !== tickCount - 1) { + // leave tick in place but make sure it's not displayed (#4635) + delete tick.label; + } + result.push(tick); + } + return result; + }, + + // Actually draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + var me = this; + var options = me.options; + if (!options.display) { + return; + } + + var context = me.ctx; + var globalDefaults = defaults.global; + var optionTicks = options.ticks.minor; + var optionMajorTicks = options.ticks.major || optionTicks; + var gridLines = options.gridLines; + var scaleLabel = options.scaleLabel; + + var isRotated = me.labelRotation !== 0; + var isHorizontal = me.isHorizontal(); + + var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); + var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); + var tickFont = parseFontOptions(optionTicks); + var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); + var majorTickFont = parseFontOptions(optionMajorTicks); + + var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; + + var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); + var scaleLabelFont = parseFontOptions(scaleLabel); + var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); + var labelRotationRadians = helpers.toRadians(me.labelRotation); + + var itemsToDraw = []; + + var xTickStart = options.position === 'right' ? me.left : me.right - tl; + var xTickEnd = options.position === 'right' ? me.left + tl : me.right; + var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl; + var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom; + + helpers.each(ticks, function(tick, index) { + // autoskipper skipped this tick (#4635) + if (helpers.isNullOrUndef(tick.label)) { + return; + } + + var label = tick.label; + var lineWidth, lineColor, borderDash, borderDashOffset; + if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { + // Draw the first index specially + lineWidth = gridLines.zeroLineWidth; + lineColor = gridLines.zeroLineColor; + borderDash = gridLines.zeroLineBorderDash; + borderDashOffset = gridLines.zeroLineBorderDashOffset; + } else { + lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index); + lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index); + borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash); + borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); + } + + // Common properties + var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; + var textAlign = 'middle'; + var textBaseline = 'middle'; + var tickPadding = optionTicks.padding; + + if (isHorizontal) { + var labelYOffset = tl + tickPadding; + + if (options.position === 'bottom') { + // bottom + textBaseline = !isRotated ? 'top' : 'middle'; + textAlign = !isRotated ? 'center' : 'right'; + labelY = me.top + labelYOffset; + } else { + // top + textBaseline = !isRotated ? 'bottom' : 'middle'; + textAlign = !isRotated ? 'center' : 'left'; + labelY = me.bottom - labelYOffset; + } + + var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + if (xLineValue < me.left) { + lineColor = 'rgba(0,0,0,0)'; + } + xLineValue += helpers.aliasPixel(lineWidth); + + labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) + + tx1 = tx2 = x1 = x2 = xLineValue; + ty1 = yTickStart; + ty2 = yTickEnd; + y1 = chartArea.top; + y2 = chartArea.bottom; + } else { + var isLeft = options.position === 'left'; + var labelXOffset; + + if (optionTicks.mirror) { + textAlign = isLeft ? 'left' : 'right'; + labelXOffset = tickPadding; + } else { + textAlign = isLeft ? 'right' : 'left'; + labelXOffset = tl + tickPadding; + } + + labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; + + var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + if (yLineValue < me.top) { + lineColor = 'rgba(0,0,0,0)'; + } + yLineValue += helpers.aliasPixel(lineWidth); + + labelY = me.getPixelForTick(index) + optionTicks.labelOffset; + + tx1 = xTickStart; + tx2 = xTickEnd; + x1 = chartArea.left; + x2 = chartArea.right; + ty1 = ty2 = y1 = y2 = yLineValue; + } + + itemsToDraw.push({ + tx1: tx1, + ty1: ty1, + tx2: tx2, + ty2: ty2, + x1: x1, + y1: y1, + x2: x2, + y2: y2, + labelX: labelX, + labelY: labelY, + glWidth: lineWidth, + glColor: lineColor, + glBorderDash: borderDash, + glBorderDashOffset: borderDashOffset, + rotation: -1 * labelRotationRadians, + label: label, + major: tick.major, + textBaseline: textBaseline, + textAlign: textAlign + }); + }); + + // Draw all of the tick labels, tick marks, and grid lines at the correct places + helpers.each(itemsToDraw, function(itemToDraw) { + if (gridLines.display) { + context.save(); + context.lineWidth = itemToDraw.glWidth; + context.strokeStyle = itemToDraw.glColor; + if (context.setLineDash) { + context.setLineDash(itemToDraw.glBorderDash); + context.lineDashOffset = itemToDraw.glBorderDashOffset; + } + + context.beginPath(); + + if (gridLines.drawTicks) { + context.moveTo(itemToDraw.tx1, itemToDraw.ty1); + context.lineTo(itemToDraw.tx2, itemToDraw.ty2); + } + + if (gridLines.drawOnChartArea) { + context.moveTo(itemToDraw.x1, itemToDraw.y1); + context.lineTo(itemToDraw.x2, itemToDraw.y2); + } + + context.stroke(); + context.restore(); + } + + if (optionTicks.display) { + // Make sure we draw text in the correct color and font + context.save(); + context.translate(itemToDraw.labelX, itemToDraw.labelY); + context.rotate(itemToDraw.rotation); + context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; + context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; + context.textBaseline = itemToDraw.textBaseline; + context.textAlign = itemToDraw.textAlign; + + var label = itemToDraw.label; + if (helpers.isArray(label)) { + for (var i = 0, y = 0; i < label.length; ++i) { + // We just make sure the multiline element is a string here.. + context.fillText('' + label[i], 0, y); + // apply same lineSpacing as calculated @ L#320 + y += (tickFont.size * 1.5); + } + } else { + context.fillText(label, 0, 0); + } + context.restore(); + } + }); + + if (scaleLabel.display) { + // Draw the scale label + var scaleLabelX; + var scaleLabelY; + var rotation = 0; + var halfLineHeight = parseLineHeight(scaleLabel) / 2; + + if (isHorizontal) { + scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width + scaleLabelY = options.position === 'bottom' + ? me.bottom - halfLineHeight - scaleLabelPadding.bottom + : me.top + halfLineHeight + scaleLabelPadding.top; + } else { + var isLeft = options.position === 'left'; + scaleLabelX = isLeft + ? me.left + halfLineHeight + scaleLabelPadding.top + : me.right - halfLineHeight - scaleLabelPadding.top; + scaleLabelY = me.top + ((me.bottom - me.top) / 2); + rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; + } + + context.save(); + context.translate(scaleLabelX, scaleLabelY); + context.rotate(rotation); + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = scaleLabelFontColor; // render in correct colour + context.font = scaleLabelFont.font; + context.fillText(scaleLabel.labelString, 0, 0); + context.restore(); + } + + if (gridLines.drawBorder) { + // Draw the line at the edge of the axis + context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0); + context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0); + var x1 = me.left; + var x2 = me.right; + var y1 = me.top; + var y2 = me.bottom; + + var aliasPixel = helpers.aliasPixel(context.lineWidth); + if (isHorizontal) { + y1 = y2 = options.position === 'top' ? me.bottom : me.top; + y1 += aliasPixel; + y2 += aliasPixel; + } else { + x1 = x2 = options.position === 'left' ? me.right : me.left; + x1 += aliasPixel; + x2 += aliasPixel; + } + + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + } + } + }); +}; + +},{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var helpers = require(45); + +module.exports = function(Chart) { + + Chart.scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + + // Scale config defaults + defaults: {}, + registerScaleType: function(type, scaleConstructor, scaleDefaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = helpers.clone(scaleDefaults); + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function(type) { + // Return the scale defaults merged with the global settings so that we always use the latest ones + return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {}; + }, + updateScaleDefaults: function(type, additions) { + var me = this; + if (me.defaults.hasOwnProperty(type)) { + me.defaults[type] = helpers.extend(me.defaults[type], additions); + } + }, + addScalesToLayout: function(chart) { + // Adds each scale to the chart.boxes array to be sized accordingly + helpers.each(chart.scales, function(scale) { + // Set ILayoutItem parameters for backwards compatibility + scale.fullWidth = scale.options.fullWidth; + scale.position = scale.options.position; + scale.weight = scale.options.weight; + Chart.layoutService.addBox(chart, scale); + }); + } + }; +}; + +},{"25":25,"45":45}],34:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); + +/** + * Namespace to hold static tick generation functions + * @namespace Chart.Ticks + */ +module.exports = { + /** + * Namespace to hold generators for different types of ticks + * @namespace Chart.Ticks.generators + */ + generators: { + /** + * Interface for the options provided to the numeric tick generator + * @interface INumericTickGenerationOptions + */ + /** + * The maximum number of ticks to display + * @name INumericTickGenerationOptions#maxTicks + * @type Number + */ + /** + * The distance between each tick. + * @name INumericTickGenerationOptions#stepSize + * @type Number + * @optional + */ + /** + * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum + * @name INumericTickGenerationOptions#min + * @type Number + * @optional + */ + /** + * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum + * @name INumericTickGenerationOptions#max + * @type Number + * @optional + */ + + /** + * Generate a set of linear ticks + * @method Chart.Ticks.generators.linear + * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks + * @param dataRange {IRange} the range of the data + * @returns {Array} array of tick values + */ + linear: function(generationOptions, dataRange) { + var ticks = []; + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + var spacing; + if (generationOptions.stepSize && generationOptions.stepSize > 0) { + spacing = generationOptions.stepSize; + } else { + var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); + spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); + } + var niceMin = Math.floor(dataRange.min / spacing) * spacing; + var niceMax = Math.ceil(dataRange.max / spacing) * spacing; + + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (generationOptions.min && generationOptions.max && generationOptions.stepSize) { + // If very close to our whole number, use it. + if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { + niceMin = generationOptions.min; + niceMax = generationOptions.max; + } + } + + var numSpaces = (niceMax - niceMin) / spacing; + // If very close to our rounded value, use it. + if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + + // Put the values into the ticks array + ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); + for (var j = 1; j < numSpaces; ++j) { + ticks.push(niceMin + (j * spacing)); + } + ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); + + return ticks; + }, + + /** + * Generate a set of logarithmic ticks + * @method Chart.Ticks.generators.logarithmic + * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks + * @param dataRange {IRange} the range of the data + * @returns {Array} array of tick values + */ + logarithmic: function(generationOptions, dataRange) { + var ticks = []; + var valueOrDefault = helpers.valueOrDefault; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); + + var endExp = Math.floor(helpers.log10(dataRange.max)); + var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + var exp, significand; + + if (tickVal === 0) { + exp = Math.floor(helpers.log10(dataRange.minNotZero)); + significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); + + ticks.push(tickVal); + tickVal = significand * Math.pow(10, exp); + } else { + exp = Math.floor(helpers.log10(tickVal)); + significand = Math.floor(tickVal / Math.pow(10, exp)); + } + + do { + ticks.push(tickVal); + + ++significand; + if (significand === 10) { + significand = 1; + ++exp; + } + + tickVal = significand * Math.pow(10, exp); + } while (exp < endExp || (exp === endExp && significand < endSignificand)); + + var lastTick = valueOrDefault(generationOptions.max, tickVal); + ticks.push(lastTick); + + return ticks; + } + }, + + /** + * Namespace to hold formatters for different types of ticks + * @namespace Chart.Ticks.formatters + */ + formatters: { + /** + * Formatter for value labels + * @method Chart.Ticks.formatters.values + * @param value the value to display + * @return {String|Array} the label to display + */ + values: function(value) { + return helpers.isArray(value) ? value : '' + value; + }, + + /** + * Formatter for linear numeric ticks + * @method Chart.Ticks.formatters.linear + * @param tickValue {Number} the value to be formatted + * @param index {Number} the position of the tickValue parameter in the ticks array + * @param ticks {Array} the list of ticks being converted + * @return {String} string representation of the tickValue parameter + */ + linear: function(tickValue, index, ticks) { + // If we have lots of ticks, don't use the ones + var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } + + var logDelta = helpers.log10(Math.abs(delta)); + var tickString = ''; + + if (tickValue !== 0) { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } else { + tickString = '0'; // never show decimal places for 0 + } + + return tickString; + }, + + logarithmic: function(tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); + + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; + } + } +}; + +},{"45":45}],35:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +defaults._set('global', { + tooltips: { + enabled: true, + custom: null, + mode: 'nearest', + position: 'average', + intersect: true, + backgroundColor: 'rgba(0,0,0,0.8)', + titleFontStyle: 'bold', + titleSpacing: 2, + titleMarginBottom: 6, + titleFontColor: '#fff', + titleAlign: 'left', + bodySpacing: 2, + bodyFontColor: '#fff', + bodyAlign: 'left', + footerFontStyle: 'bold', + footerSpacing: 2, + footerMarginTop: 6, + footerFontColor: '#fff', + footerAlign: 'left', + yPadding: 6, + xPadding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + displayColors: true, + borderColor: 'rgba(0,0,0,0)', + borderWidth: 0, + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: helpers.noop, + title: function(tooltipItems, data) { + // Pick first xLabel for now + var title = ''; + var labels = data.labels; + var labelCount = labels ? labels.length : 0; + + if (tooltipItems.length > 0) { + var item = tooltipItems[0]; + + if (item.xLabel) { + title = item.xLabel; + } else if (labelCount > 0 && item.index < labelCount) { + title = labels[item.index]; + } + } + + return title; + }, + afterTitle: helpers.noop, + + // Args are: (tooltipItems, data) + beforeBody: helpers.noop, + + // Args are: (tooltipItem, data) + beforeLabel: helpers.noop, + label: function(tooltipItem, data) { + var label = data.datasets[tooltipItem.datasetIndex].label || ''; + + if (label) { + label += ': '; + } + label += tooltipItem.yLabel; + return label; + }, + labelColor: function(tooltipItem, chart) { + var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); + var activeElement = meta.data[tooltipItem.index]; + var view = activeElement._view; + return { + borderColor: view.borderColor, + backgroundColor: view.backgroundColor + }; + }, + labelTextColor: function() { + return this._options.bodyFontColor; + }, + afterLabel: helpers.noop, + + // Args are: (tooltipItems, data) + afterBody: helpers.noop, + + // Args are: (tooltipItems, data) + beforeFooter: helpers.noop, + footer: helpers.noop, + afterFooter: helpers.noop + } + } +}); + +module.exports = function(Chart) { + + /** + * Helper method to merge the opacity into a color + */ + function mergeOpacity(colorString, opacity) { + var color = helpers.color(colorString); + return color.alpha(opacity * color.alpha()).rgbaString(); + } + + // Helper to push or concat based on if the 2nd parameter is an array or not + function pushOrConcat(base, toPush) { + if (toPush) { + if (helpers.isArray(toPush)) { + // base = base.concat(toPush); + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } + + return base; + } + + // Private helper to create a tooltip item model + // @param element : the chart element (point, arc, bar) to create the tooltip item for + // @return : new tooltip item + function createTooltipItem(element) { + var xScale = element._xScale; + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + var index = element._index; + var datasetIndex = element._datasetIndex; + + return { + xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', + index: index, + datasetIndex: datasetIndex, + x: element._model.x, + y: element._model.y + }; + } + + /** + * Helper to get the reset model for the tooltip + * @param tooltipOpts {Object} the tooltip options + */ + function getBaseModel(tooltipOpts) { + var globalDefaults = defaults.global; + var valueOrDefault = helpers.valueOrDefault; + + return { + // Positioning + xPadding: tooltipOpts.xPadding, + yPadding: tooltipOpts.yPadding, + xAlign: tooltipOpts.xAlign, + yAlign: tooltipOpts.yAlign, + + // Body + bodyFontColor: tooltipOpts.bodyFontColor, + _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), + _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), + _bodyAlign: tooltipOpts.bodyAlign, + bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), + bodySpacing: tooltipOpts.bodySpacing, + + // Title + titleFontColor: tooltipOpts.titleFontColor, + _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), + _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), + titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), + _titleAlign: tooltipOpts.titleAlign, + titleSpacing: tooltipOpts.titleSpacing, + titleMarginBottom: tooltipOpts.titleMarginBottom, + + // Footer + footerFontColor: tooltipOpts.footerFontColor, + _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), + _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), + footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), + _footerAlign: tooltipOpts.footerAlign, + footerSpacing: tooltipOpts.footerSpacing, + footerMarginTop: tooltipOpts.footerMarginTop, + + // Appearance + caretSize: tooltipOpts.caretSize, + cornerRadius: tooltipOpts.cornerRadius, + backgroundColor: tooltipOpts.backgroundColor, + opacity: 0, + legendColorBackground: tooltipOpts.multiKeyBackground, + displayColors: tooltipOpts.displayColors, + borderColor: tooltipOpts.borderColor, + borderWidth: tooltipOpts.borderWidth + }; + } + + /** + * Get the size of the tooltip + */ + function getTooltipSize(tooltip, model) { + var ctx = tooltip._chart.ctx; + + var height = model.yPadding * 2; // Tooltip Padding + var width = 0; + + // Count of all lines in the body + var body = model.body; + var combinedBodyLength = body.reduce(function(count, bodyItem) { + return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; + }, 0); + combinedBodyLength += model.beforeBody.length + model.afterBody.length; + + var titleLineCount = model.title.length; + var footerLineCount = model.footer.length; + var titleFontSize = model.titleFontSize; + var bodyFontSize = model.bodyFontSize; + var footerFontSize = model.footerFontSize; + + height += titleLineCount * titleFontSize; // Title Lines + height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing + height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin + height += combinedBodyLength * bodyFontSize; // Body Lines + height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing + height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin + height += footerLineCount * (footerFontSize); // Footer Lines + height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing + + // Title width + var widthPadding = 0; + var maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; + + ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); + helpers.each(model.title, maxLineWidth); + + // Body width + ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); + helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth); + + // Body lines may include some extra width due to the color box + widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; + helpers.each(body, function(bodyItem) { + helpers.each(bodyItem.before, maxLineWidth); + helpers.each(bodyItem.lines, maxLineWidth); + helpers.each(bodyItem.after, maxLineWidth); + }); + + // Reset back to 0 + widthPadding = 0; + + // Footer width + ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); + helpers.each(model.footer, maxLineWidth); + + // Add padding + width += 2 * model.xPadding; + + return { + width: width, + height: height + }; + } + + /** + * Helper to get the alignment of a tooltip given the size + */ + function determineAlignment(tooltip, size) { + var model = tooltip._model; + var chart = tooltip._chart; + var chartArea = tooltip._chart.chartArea; + var xAlign = 'center'; + var yAlign = 'center'; + + if (model.y < size.height) { + yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + yAlign = 'bottom'; + } + + var lf, rf; // functions to determine left, right alignment + var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart + var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges + var midX = (chartArea.left + chartArea.right) / 2; + var midY = (chartArea.top + chartArea.bottom) / 2; + + if (yAlign === 'center') { + lf = function(x) { + return x <= midX; + }; + rf = function(x) { + return x > midX; + }; + } else { + lf = function(x) { + return x <= (size.width / 2); + }; + rf = function(x) { + return x >= (chart.width - (size.width / 2)); + }; + } + + olf = function(x) { + return x + size.width > chart.width; + }; + orf = function(x) { + return x - size.width < 0; + }; + yf = function(y) { + return y <= midY ? 'top' : 'bottom'; + }; + + if (lf(model.x)) { + xAlign = 'left'; + + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } else if (rf(model.x)) { + xAlign = 'right'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } + + var opts = tooltip._options; + return { + xAlign: opts.xAlign ? opts.xAlign : xAlign, + yAlign: opts.yAlign ? opts.yAlign : yAlign + }; + } + + /** + * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment + */ + function getBackgroundPoint(vm, size, alignment) { + // Background Position + var x = vm.x; + var y = vm.y; + + var caretSize = vm.caretSize; + var caretPadding = vm.caretPadding; + var cornerRadius = vm.cornerRadius; + var xAlign = alignment.xAlign; + var yAlign = alignment.yAlign; + var paddingAndSize = caretSize + caretPadding; + var radiusAndPadding = cornerRadius + caretPadding; + + if (xAlign === 'right') { + x -= size.width; + } else if (xAlign === 'center') { + x -= (size.width / 2); + } + + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= size.height + paddingAndSize; + } else { + y -= (size.height / 2); + } + + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= radiusAndPadding; + } else if (xAlign === 'right') { + x += radiusAndPadding; + } + + return { + x: x, + y: y + }; + } + + Chart.Tooltip = Element.extend({ + initialize: function() { + this._model = getBaseModel(this._options); + this._lastActive = []; + }, + + // Get the title + // Args are: (tooltipItem, data) + getTitle: function() { + var me = this; + var opts = me._options; + var callbacks = opts.callbacks; + + var beforeTitle = callbacks.beforeTitle.apply(me, arguments); + var title = callbacks.title.apply(me, arguments); + var afterTitle = callbacks.afterTitle.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, beforeTitle); + lines = pushOrConcat(lines, title); + lines = pushOrConcat(lines, afterTitle); + + return lines; + }, + + // Args are: (tooltipItem, data) + getBeforeBody: function() { + var lines = this._options.callbacks.beforeBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, + + // Args are: (tooltipItem, data) + getBody: function(tooltipItems, data) { + var me = this; + var callbacks = me._options.callbacks; + var bodyItems = []; + + helpers.each(tooltipItems, function(tooltipItem) { + var bodyItem = { + before: [], + lines: [], + after: [] + }; + pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data)); + + bodyItems.push(bodyItem); + }); + + return bodyItems; + }, + + // Args are: (tooltipItem, data) + getAfterBody: function() { + var lines = this._options.callbacks.afterBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, + + // Get the footer and beforeFooter and afterFooter lines + // Args are: (tooltipItem, data) + getFooter: function() { + var me = this; + var callbacks = me._options.callbacks; + + var beforeFooter = callbacks.beforeFooter.apply(me, arguments); + var footer = callbacks.footer.apply(me, arguments); + var afterFooter = callbacks.afterFooter.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, beforeFooter); + lines = pushOrConcat(lines, footer); + lines = pushOrConcat(lines, afterFooter); + + return lines; + }, + + update: function(changed) { + var me = this; + var opts = me._options; + + // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition + // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time + // which breaks any animations. + var existingModel = me._model; + var model = me._model = getBaseModel(opts); + var active = me._active; + + var data = me._data; + + // In the case where active.length === 0 we need to keep these at existing values for good animations + var alignment = { + xAlign: existingModel.xAlign, + yAlign: existingModel.yAlign + }; + var backgroundPoint = { + x: existingModel.x, + y: existingModel.y + }; + var tooltipSize = { + width: existingModel.width, + height: existingModel.height + }; + var tooltipPosition = { + x: existingModel.caretX, + y: existingModel.caretY + }; + + var i, len; + + if (active.length) { + model.opacity = 1; + + var labelColors = []; + var labelTextColors = []; + tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition); + + var tooltipItems = []; + for (i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(active[i])); + } + + // If the user provided a filter function, use it to modify the tooltip items + if (opts.filter) { + tooltipItems = tooltipItems.filter(function(a) { + return opts.filter(a, data); + }); + } + + // If the user provided a sorting function, use it to modify the tooltip items + if (opts.itemSort) { + tooltipItems = tooltipItems.sort(function(a, b) { + return opts.itemSort(a, b, data); + }); + } + + // Determine colors for boxes + helpers.each(tooltipItems, function(tooltipItem) { + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); + labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); + }); + + + // Build the Text Lines + model.title = me.getTitle(tooltipItems, data); + model.beforeBody = me.getBeforeBody(tooltipItems, data); + model.body = me.getBody(tooltipItems, data); + model.afterBody = me.getAfterBody(tooltipItems, data); + model.footer = me.getFooter(tooltipItems, data); + + // Initial positioning and colors + model.x = Math.round(tooltipPosition.x); + model.y = Math.round(tooltipPosition.y); + model.caretPadding = opts.caretPadding; + model.labelColors = labelColors; + model.labelTextColors = labelTextColors; + + // data points + model.dataPoints = tooltipItems; + + // We need to determine alignment of the tooltip + tooltipSize = getTooltipSize(this, model); + alignment = determineAlignment(this, tooltipSize); + // Final Size and Position + backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment); + } else { + model.opacity = 0; + } + + model.xAlign = alignment.xAlign; + model.yAlign = alignment.yAlign; + model.x = backgroundPoint.x; + model.y = backgroundPoint.y; + model.width = tooltipSize.width; + model.height = tooltipSize.height; + + // Point where the caret on the tooltip points to + model.caretX = tooltipPosition.x; + model.caretY = tooltipPosition.y; + + me._model = model; + + if (changed && opts.custom) { + opts.custom.call(me, model); + } + + return me; + }, + drawCaret: function(tooltipPoint, size) { + var ctx = this._chart.ctx; + var vm = this._view; + var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); + + ctx.lineTo(caretPosition.x1, caretPosition.y1); + ctx.lineTo(caretPosition.x2, caretPosition.y2); + ctx.lineTo(caretPosition.x3, caretPosition.y3); + }, + getCaretPosition: function(tooltipPoint, size, vm) { + var x1, x2, x3, y1, y2, y3; + var caretSize = vm.caretSize; + var cornerRadius = vm.cornerRadius; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var ptX = tooltipPoint.x; + var ptY = tooltipPoint.y; + var width = size.width; + var height = size.height; + + if (yAlign === 'center') { + y2 = ptY + (height / 2); + + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + x3 = x1; + + y1 = y2 + caretSize; + y3 = y2 - caretSize; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + x3 = x1; + + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } + } else { + if (xAlign === 'left') { + x2 = ptX + cornerRadius + (caretSize); + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else if (xAlign === 'right') { + x2 = ptX + width - cornerRadius - caretSize; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else { + x2 = ptX + (width / 2); + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + y3 = y1; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + y3 = y1; + // invert drawing order + var tmp = x3; + x3 = x1; + x1 = tmp; + } + } + return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; + }, + drawTitle: function(pt, vm, ctx, opacity) { + var title = vm.title; + + if (title.length) { + ctx.textAlign = vm._titleAlign; + ctx.textBaseline = 'top'; + + var titleFontSize = vm.titleFontSize; + var titleSpacing = vm.titleSpacing; + + ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); + ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + + var i, len; + for (i = 0, len = title.length; i < len; ++i) { + ctx.fillText(title[i], pt.x, pt.y); + pt.y += titleFontSize + titleSpacing; // Line Height and spacing + + if (i + 1 === title.length) { + pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing + } + } + } + }, + drawBody: function(pt, vm, ctx, opacity) { + var bodyFontSize = vm.bodyFontSize; + var bodySpacing = vm.bodySpacing; + var body = vm.body; + + ctx.textAlign = vm._bodyAlign; + ctx.textBaseline = 'top'; + ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + + // Before Body + var xLinePadding = 0; + var fillLineOfText = function(line) { + ctx.fillText(line, pt.x + xLinePadding, pt.y); + pt.y += bodyFontSize + bodySpacing; + }; + + // Before body lines + ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); + helpers.each(vm.beforeBody, fillLineOfText); + + var drawColorBoxes = vm.displayColors; + xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; + + // Draw body lines now + helpers.each(body, function(bodyItem, i) { + var textColor = mergeOpacity(vm.labelTextColors[i], opacity); + ctx.fillStyle = textColor; + helpers.each(bodyItem.before, fillLineOfText); + + helpers.each(bodyItem.lines, function(line) { + // Draw Legend-like boxes if needed + if (drawColorBoxes) { + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); + ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + + // Border + ctx.lineWidth = 1; + ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); + ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + + // Inner square + ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); + ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); + ctx.fillStyle = textColor; + } + + fillLineOfText(line); + }); + + helpers.each(bodyItem.after, fillLineOfText); + }); + + // Reset back to 0 for after body + xLinePadding = 0; + + // After body lines + helpers.each(vm.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing + }, + drawFooter: function(pt, vm, ctx, opacity) { + var footer = vm.footer; + + if (footer.length) { + pt.y += vm.footerMarginTop; + + ctx.textAlign = vm._footerAlign; + ctx.textBaseline = 'top'; + + ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); + ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + + helpers.each(footer, function(line) { + ctx.fillText(line, pt.x, pt.y); + pt.y += vm.footerFontSize + vm.footerSpacing; + }); + } + }, + drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { + ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); + ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); + ctx.lineWidth = vm.borderWidth; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var x = pt.x; + var y = pt.y; + var width = tooltipSize.width; + var height = tooltipSize.height; + var radius = vm.cornerRadius; + + ctx.beginPath(); + ctx.moveTo(x + radius, y); + if (yAlign === 'top') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + if (yAlign === 'center' && xAlign === 'right') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + if (yAlign === 'bottom') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + if (yAlign === 'center' && xAlign === 'left') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + + ctx.fill(); + + if (vm.borderWidth > 0) { + ctx.stroke(); + } + }, + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + + if (vm.opacity === 0) { + return; + } + + var tooltipSize = { + width: vm.width, + height: vm.height + }; + var pt = { + x: vm.x, + y: vm.y + }; + + // IE11/Edge does not like very small opacities, so snap to 0 + var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; + + // Truthy/falsey value for empty tooltip + var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; + + if (this._options.enabled && hasTooltipContent) { + // Draw Background + this.drawBackground(pt, vm, ctx, tooltipSize, opacity); + + // Draw Title, Body, and Footer + pt.x += vm.xPadding; + pt.y += vm.yPadding; + + // Titles + this.drawTitle(pt, vm, ctx, opacity); + + // Body + this.drawBody(pt, vm, ctx, opacity); + + // Footer + this.drawFooter(pt, vm, ctx, opacity); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @returns {Boolean} true if the tooltip changed + */ + handleEvent: function(e) { + var me = this; + var options = me._options; + var changed = false; + + me._lastActive = me._lastActive || []; + + // Find Active Elements for tooltips + if (e.type === 'mouseout') { + me._active = []; + } else { + me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); + } + + // Remember Last Actives + changed = !helpers.arrayEquals(me._active, me._lastActive); + + // If tooltip didn't change, do not handle the target event + if (!changed) { + return false; + } + + me._lastActive = me._active; + + if (options.enabled || options.custom) { + me._eventPosition = { + x: e.x, + y: e.y + }; + + var model = me._model; + me.update(true); + me.pivot(); + + // See if our tooltip position changed + changed |= (model.x !== me._model.x) || (model.y !== me._model.y); + } + + return changed; + } + }); + + /** + * @namespace Chart.Tooltip.positioners + */ + Chart.Tooltip.positioners = { + /** + * Average mode places the tooltip at the average position of the elements shown + * @function Chart.Tooltip.positioners.average + * @param elements {ChartElement[]} the elements being displayed in the tooltip + * @returns {Point} tooltip position + */ + average: function(elements) { + if (!elements.length) { + return false; + } + + var i, len; + var x = 0; + var y = 0; + var count = 0; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + + return { + x: Math.round(x / count), + y: Math.round(y / count) + }; + }, + + /** + * Gets the tooltip position nearest of the item nearest to the event position + * @function Chart.Tooltip.positioners.nearest + * @param elements {Chart.Element[]} the tooltip elements + * @param eventPosition {Point} the position of the event in canvas coordinates + * @returns {Point} the tooltip position + */ + nearest: function(elements, eventPosition) { + var x = eventPosition.x; + var y = eventPosition.y; + var minDistance = Number.POSITIVE_INFINITY; + var i, len, nearestElement; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var center = el.getCenterPoint(); + var d = helpers.distanceBetweenPoints(eventPosition, center); + + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + + if (nearestElement) { + var tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + + return { + x: x, + y: y + }; + } + }; +}; + +},{"25":25,"26":26,"45":45}],36:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +defaults._set('global', { + elements: { + arc: { + backgroundColor: defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2 + } + } +}); + +module.exports = Element.extend({ + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY}); + var angle = pointRelativePosition.angle; + var distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += 2.0 * Math.PI; + } + while (angle > endAngle) { + angle -= 2.0 * Math.PI; + } + while (angle < startAngle) { + angle += 2.0 * Math.PI; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle); + var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); + var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var sA = vm.startAngle; + var eA = vm.endAngle; + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); + ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); + + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + ctx.fillStyle = vm.backgroundColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (vm.borderWidth) { + ctx.stroke(); + } + } +}); + +},{"25":25,"26":26,"45":45}],37:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +var globalDefaults = defaults.global; + +defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: globalDefaults.defaultColor, + borderWidth: 3, + borderColor: globalDefaults.defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + +module.exports = Element.extend({ + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var index, current, previous, currentVM; + + // If we are looping, adding the first point again + if (me._loop && points.length) { + points.push(points[0]); + } + + ctx.save(); + + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } + + ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + lastDrawnIndex = -1; + + for (index = 0; index < points.length; ++index) { + current = points[index]; + previous = helpers.previousItem(points, index); + currentVM = current._view; + + // First point moves to it's starting position no matter what + if (index === 0) { + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = index; + } + } else { + previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers.canvas.lineTo(ctx, previous._view, current._view); + } + lastDrawnIndex = index; + } + } + } + + ctx.stroke(); + ctx.restore(); + } +}); + +},{"25":25,"26":26,"45":45}],38:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +var defaultColor = defaults.global.defaultColor; + +defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor, + borderColor: defaultColor, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; +} + +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; +} + +module.exports = Element.extend({ + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + + draw: function(chartArea) { + var vm = this._view; + var model = this._model; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var color = helpers.color; + var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) + var ratio = 0; + + if (vm.skip) { + return; + } + + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + + // Cliping for Points. + // going out from inner charArea? + if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) { + // Point fade out + if (model.x < chartArea.left) { + ratio = (x - model.x) / (chartArea.left - model.x); + } else if (chartArea.right * errMargin < model.x) { + ratio = (model.x - x) / (model.x - chartArea.right); + } else if (model.y < chartArea.top) { + ratio = (y - model.y) / (chartArea.top - model.y); + } else if (chartArea.bottom * errMargin < model.y) { + ratio = (model.y - y) / (model.y - chartArea.bottom); + } + ratio = Math.round(ratio * 100) / 100; + ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString(); + ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString(); + } + + helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y); + } +}); + +},{"25":25,"26":26,"45":45}],39:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); + +defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaults.global.defaultColor, + borderColor: defaults.global.defaultColor, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + +function isVertical(bar) { + return bar._view.width !== undefined; +} + +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(bar) { + var vm = bar._view; + var x1, x2, y1, y2; + + if (isVertical(bar)) { + // vertical + var halfWidth = vm.width / 2; + x1 = vm.x - halfWidth; + x2 = vm.x + halfWidth; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + // horizontal bar + var halfHeight = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - halfHeight; + y2 = vm.y + halfHeight; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} + +module.exports = Element.extend({ + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var left, right, top, bottom, signX, signY, borderSkipped; + var borderWidth = vm.borderWidth; + + if (!vm.horizontal) { + // bar + left = vm.x - vm.width / 2; + right = vm.x + vm.width / 2; + top = vm.y; + bottom = vm.base; + signX = 1; + signY = bottom > top ? 1 : -1; + borderSkipped = vm.borderSkipped || 'bottom'; + } else { + // horizontal bar + left = vm.base; + right = vm.x; + top = vm.y - vm.height / 2; + bottom = vm.y + vm.height / 2; + signX = right > left ? 1 : -1; + signY = 1; + borderSkipped = vm.borderSkipped || 'left'; + } + + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (borderWidth) { + // borderWidth shold be less than bar width and bar height. + var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); + borderWidth = borderWidth > barSize ? barSize : borderWidth; + var halfStroke = borderWidth / 2; + // Adjust borderWidth when bar top position is near vm.base(zero). + var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); + var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); + var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); + var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); + // not become a vertical line? + if (borderLeft !== borderRight) { + top = borderTop; + bottom = borderBottom; + } + // not become a horizontal line? + if (borderTop !== borderBottom) { + left = borderLeft; + right = borderRight; + } + } + + ctx.beginPath(); + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = borderWidth; + + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [left, bottom], + [left, top], + [right, top], + [right, bottom] + ]; + + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } + + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } + + // Draw rectangle from 'startCorner' + var corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); + + for (var i = 1; i < 4; i++) { + corner = cornerAt(i); + ctx.lineTo(corner[0], corner[1]); + } + + ctx.fill(); + if (borderWidth) { + ctx.stroke(); + } + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + var inRange = false; + + if (this._view) { + var bounds = getBarBounds(this); + inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inLabelRange: function(mouseX, mouseY) { + var me = this; + if (!me._view) { + return false; + } + + var inRange = false; + var bounds = getBarBounds(me); + + if (isVertical(me)) { + inRange = mouseX >= bounds.left && mouseX <= bounds.right; + } else { + inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inXRange: function(mouseX) { + var bounds = getBarBounds(this); + return mouseX >= bounds.left && mouseX <= bounds.right; + }, + + inYRange: function(mouseY) { + var bounds = getBarBounds(this); + return mouseY >= bounds.top && mouseY <= bounds.bottom; + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(this)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + return vm.width * Math.abs(vm.y - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } +}); + +},{"25":25,"26":26}],40:[function(require,module,exports){ +'use strict'; + +module.exports = {}; +module.exports.Arc = require(36); +module.exports.Line = require(37); +module.exports.Point = require(38); +module.exports.Rectangle = require(39); + +},{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){ +'use strict'; + +var helpers = require(42); + +/** + * @namespace Chart.helpers.canvas + */ +var exports = module.exports = { + /** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ + clear: function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + + /** + * Creates a "path" for a rectangle with rounded corners at position (x, y) with a + * given size (width, height) and the same `radius` for all corners. + * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. + * @param {Number} x - The x axis of the coordinate for the rectangle starting point. + * @param {Number} y - The y axis of the coordinate for the rectangle starting point. + * @param {Number} width - The rectangle's width. + * @param {Number} height - The rectangle's height. + * @param {Number} radius - The rounded amount (in pixels) for the four corners. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? + */ + roundedRect: function(ctx, x, y, width, height, radius) { + if (radius) { + var rx = Math.min(radius, width / 2); + var ry = Math.min(radius, height / 2); + + ctx.moveTo(x + rx, y); + ctx.lineTo(x + width - rx, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + ry); + ctx.lineTo(x + width, y + height - ry); + ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height); + ctx.lineTo(x + rx, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - ry); + ctx.lineTo(x, y + ry); + ctx.quadraticCurveTo(x, y, x + rx, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function(ctx, style, radius, x, y) { + var type, edgeLength, xOffset, yOffset, height, size; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + switch (style) { + // Default includes circle + default: + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + break; + case 'triangle': + ctx.beginPath(); + edgeLength = 3 * radius / Math.sqrt(3); + height = edgeLength * Math.sqrt(3) / 2; + ctx.moveTo(x - edgeLength / 2, y + height / 3); + ctx.lineTo(x + edgeLength / 2, y + height / 3); + ctx.lineTo(x, y - 2 * height / 3); + ctx.closePath(); + ctx.fill(); + break; + case 'rect': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.fillRect(x - size, y - size, 2 * size, 2 * size); + ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); + break; + case 'rectRounded': + var offset = radius / Math.SQRT2; + var leftX = x - offset; + var topY = y - offset; + var sideSize = Math.SQRT2 * radius; + ctx.beginPath(); + this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2); + ctx.closePath(); + ctx.fill(); + break; + case 'rectRot': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.moveTo(x - size, y); + ctx.lineTo(x, y + size); + ctx.lineTo(x + size, y); + ctx.lineTo(x, y - size); + ctx.closePath(); + ctx.fill(); + break; + case 'cross': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'crossRot': + ctx.beginPath(); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'star': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'line': + ctx.beginPath(); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'dash': + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + } + + ctx.stroke(); + }, + + clipArea: function(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); + }, + + unclipArea: function(ctx) { + ctx.restore(); + }, + + lineTo: function(ctx, previous, target, flip) { + if (target.steppedLine) { + if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); + return; + } + + if (!target.tension) { + ctx.lineTo(target.x, target.y); + return; + } + + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); + } +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. + * @namespace Chart.helpers.clear + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.clear = exports.clear; + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. + * @namespace Chart.helpers.drawRoundedRectangle + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.drawRoundedRectangle = function(ctx) { + ctx.beginPath(); + exports.roundedRect.apply(exports, arguments); + ctx.closePath(); +}; + +},{"42":42}],42:[function(require,module,exports){ +'use strict'; + +/** + * @namespace Chart.helpers + */ +var helpers = { + /** + * An empty function that can be used, for example, for optional callback. + */ + noop: function() {}, + + /** + * Returns a unique id, sequentially generated from a global variable. + * @returns {Number} + * @function + */ + uid: (function() { + var id = 0; + return function() { + return id++; + }; + }()), + + /** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {Boolean} + * @since 2.7.0 + */ + isNullOrUndef: function(value) { + return value === null || typeof value === 'undefined'; + }, + + /** + * Returns true if `value` is an array, else returns false. + * @param {*} value - The value to test. + * @returns {Boolean} + * @function + */ + isArray: Array.isArray ? Array.isArray : function(value) { + return Object.prototype.toString.call(value) === '[object Array]'; + }, + + /** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {Boolean} + * @since 2.7.0 + */ + isObject: function(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + }, + + /** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ + valueOrDefault: function(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; + }, + + /** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {Number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ + valueAtIndexOrDefault: function(value, index, defaultValue) { + return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); + }, + + /** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {Function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ + callback: function(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } + }, + + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {Object|Array} loopable - The object or array to be iterated. + * @param {Function} fn - The function to call for each item. + * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {Boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } + }, + + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see http://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {Boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): this method is also used by configMerge and scaleMerge as fallback. + * @private + */ + _merger: function(key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function(key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {Object} target - The target object in which all sources are merged into. + * @param {Object|Array(Object)} source - Object(s) to merge into `target`. + * @param {Object} [options] - Merging options: + * @param {Function} [options.merger] - The merge method (key, target, source, options) + * @returns {Object} The `target` object. + */ + merge: function(target, source, options) { + var sources = helpers.isArray(source) ? source : [source]; + var ilen = sources.length; + var merge, i, keys, klen, k; + + if (!helpers.isObject(target)) { + return target; + } + + options = options || {}; + merge = options.merger || helpers._merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!helpers.isObject(source)) { + continue; + } + + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merge(keys[k], target, source, options); + } + } + + return target; + }, + + /** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {Object} target - The target object in which all sources are merged into. + * @param {Object|Array(Object)} source - Object(s) to merge into `target`. + * @returns {Object} The `target` object. + */ + mergeIf: function(target, source) { + return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {Object} target - The target object in which all objects are merged into. + * @param {Object} arg1 - Object containing additional properties to merge in target. + * @param {Object} argN - Additional objects containing properties to merge in target. + * @returns {Object} The `target` object. + */ + extend: function(target) { + var setFn = function(value, key) { + target[key] = value; + }; + for (var i = 1, ilen = arguments.length; i < ilen; ++i) { + helpers.each(arguments[i], setFn); + } + return target; + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; + } +}; + +module.exports = helpers; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.callback instead. + * @function Chart.helpers.callCallback + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +helpers.callCallback = helpers.callback; + +/** + * Provided for backward compatibility, use Array.prototype.indexOf instead. + * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ + * @function Chart.helpers.indexOf + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.indexOf = function(array, item, fromIndex) { + return Array.prototype.indexOf.call(array, item, fromIndex); +}; + +/** + * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. + * @function Chart.helpers.getValueOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueOrDefault = helpers.valueOrDefault; + +/** + * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. + * @function Chart.helpers.getValueAtIndexOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + +},{}],43:[function(require,module,exports){ +'use strict'; + +var helpers = require(42); + +/** + * Easing functions adapted from Robert Penner's easing equations. + * @namespace Chart.helpers.easingEffects + * @see http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +module.exports = { + effects: effects +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.easingEffects = effects; + +},{"42":42}],44:[function(require,module,exports){ +'use strict'; + +var helpers = require(42); + +/** + * @alias Chart.helpers.options + * @namespace + */ +module.exports = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {Number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + default: + break; + } + + return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {Number|Object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {Object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; + }, + + /** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array[]} inputs - An array of values, falling back to the last value. + * @param {Object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {Number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @since 2.7.0 + */ + resolve: function(inputs, context, index) { + var i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + } + if (index !== undefined && helpers.isArray(value)) { + value = value[index]; + } + if (value !== undefined) { + return value; + } + } + } +}; + +},{"42":42}],45:[function(require,module,exports){ +'use strict'; + +module.exports = require(42); +module.exports.easing = require(43); +module.exports.canvas = require(41); +module.exports.options = require(44); + +},{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){ +/** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + +module.exports = { + acquireContext: function(item) { + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + return item && item.getContext('2d') || null; + } +}; + +},{}],47:[function(require,module,exports){ +/** + * Chart.Platform implementation for targeting a web browser + */ + +'use strict'; + +var helpers = require(45); + +var EXPANDO_KEY = '$chartjs'; +var CSS_PREFIX = 'chartjs-'; +var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; +var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; +var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; + +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ +var EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {Number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; + +function addEventListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeEventListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = EVENT_TYPES[event.type] || event.type; + var pos = helpers.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function throttled(fn, thisArg) { + var ticking = false; + var args = []; + + return function() { + args = Array.prototype.slice.call(arguments); + thisArg = thisArg || this; + + if (!ticking) { + ticking = true; + helpers.requestAnimFrame.call(window, function() { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} + +// Implementation based on https://github.com/marcj/css-element-queries +function createResizer(handler) { + var resizer = document.createElement('div'); + var cls = CSS_PREFIX + 'size-monitor'; + var maxSize = 1000000; + var style = + 'position:absolute;' + + 'left:0;' + + 'top:0;' + + 'right:0;' + + 'bottom:0;' + + 'overflow:hidden;' + + 'pointer-events:none;' + + 'visibility:hidden;' + + 'z-index:-1;'; + + resizer.style.cssText = style; + resizer.className = cls; + resizer.innerHTML = + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'; + + var expand = resizer.childNodes[0]; + var shrink = resizer.childNodes[1]; + + resizer._reset = function() { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + var onScroll = function() { + resizer._reset(); + handler(); + }; + + addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + + return resizer; +} + +// https://davidwalsh.name/detect-node-insertion +function watchForRender(node, handler) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + var proxy = expando.renderProxy = function(e) { + if (e.animationName === CSS_RENDER_ANIMATION) { + handler(); + } + }; + + helpers.each(ANIMATION_START_EVENTS, function(type) { + addEventListener(node, type, proxy); + }); + + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + + node.classList.add(CSS_RENDER_MONITOR); +} + +function unwatchForRender(node) { + var expando = node[EXPANDO_KEY] || {}; + var proxy = expando.renderProxy; + + if (proxy) { + helpers.each(ANIMATION_START_EVENTS, function(type) { + removeEventListener(node, type, proxy); + }); + + delete expando.renderProxy; + } + + node.classList.remove(CSS_RENDER_MONITOR); +} + +function addResizeListener(node, listener, chart) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + + // Let's keep track of this added resizer and thus avoid DOM query when removing it. + var resizer = expando.resizer = createResizer(throttled(function() { + if (expando.resizer) { + return listener(createEvent('resize', chart)); + } + })); + + // The resizer needs to be attached to the node parent, so we first need to be + // sure that `node` is attached to the DOM before injecting the resizer element. + watchForRender(node, function() { + if (expando.resizer) { + var container = node.parentNode; + if (container && container !== resizer.parentNode) { + container.insertBefore(resizer, container.firstChild); + } + + // The container size might have changed, let's reset the resizer state. + resizer._reset(); + } + }); +} + +function removeResizeListener(node) { + var expando = node[EXPANDO_KEY] || {}; + var resizer = expando.resizer; + + delete expando.resizer; + unwatchForRender(node); + + if (resizer && resizer.parentNode) { + resizer.parentNode.removeChild(resizer); + } +} + +function injectCSS(platform, css) { + // http://stackoverflow.com/q/3922139 + var style = platform._style || document.createElement('style'); + if (!platform._style) { + platform._style = style; + css = '/* Chart.js */\n' + css; + style.setAttribute('type', 'text/css'); + document.getElementsByTagName('head')[0].appendChild(style); + } + + style.appendChild(document.createTextNode(css)); +} + +module.exports = { + /** + * This property holds whether this platform is enabled for the current environment. + * Currently used by platform.js to select the proper implementation. + * @private + */ + _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', + + initialize: function() { + var keyframes = 'from{opacity:0.99}to{opacity:1}'; + + injectCSS(this, + // DOM rendering detection + // https://davidwalsh.name/detect-node-insertion + '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + + '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + + '.' + CSS_RENDER_MONITOR + '{' + + '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + + 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + + '}' + ); + }, + + acquireContext: function(item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item && item.getContext && item.getContext('2d'); + + // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is + // inside an iframe or when running in a protected environment. We could guess the + // types from their toString() value but let's keep things flexible and assume it's + // a sufficient condition if the item has a context2D which has item as `canvas`. + // https://github.com/chartjs/Chart.js/issues/3887 + // https://github.com/chartjs/Chart.js/issues/4102 + // https://github.com/chartjs/Chart.js/issues/4152 + if (context && context.canvas === item) { + initCanvas(item, config); + return context; + } + + return null; + }, + + releaseContext: function(context) { + var canvas = context.canvas; + if (!canvas[EXPANDO_KEY]) { + return; + } + + var initial = canvas[EXPANDO_KEY].initial; + ['height', 'width'].forEach(function(prop) { + var value = initial[prop]; + if (helpers.isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + helpers.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + canvas.width = canvas.width; + + delete canvas[EXPANDO_KEY]; + }, + + addEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + addResizeListener(canvas, listener, chart); + return; + } + + var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); + var proxies = expando.proxies || (expando.proxies = {}); + var proxy = proxies[chart.id + '_' + type] = function(event) { + listener(fromNativeEvent(event, chart)); + }; + + addEventListener(canvas, type, proxy); + }, + + removeEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + removeResizeListener(canvas, listener); + return; + } + + var expando = listener[EXPANDO_KEY] || {}; + var proxies = expando.proxies || {}; + var proxy = proxies[chart.id + '_' + type]; + if (!proxy) { + return; + } + + removeEventListener(canvas, type, proxy); + } +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use EventTarget.addEventListener instead. + * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @function Chart.helpers.addEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.addEvent = addEventListener; + +/** + * Provided for backward compatibility, use EventTarget.removeEventListener instead. + * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @function Chart.helpers.removeEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.removeEvent = removeEventListener; + +},{"45":45}],48:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); +var basic = require(46); +var dom = require(47); + +// @TODO Make possible to select another platform at build time. +var implementation = dom._enabled ? dom : basic; + +/** + * @namespace Chart.platform + * @see https://chartjs.gitbooks.io/proposals/content/Platform.html + * @since 2.4.0 + */ +module.exports = helpers.extend({ + /** + * @since 2.7.0 + */ + initialize: function() {}, + + /** + * Called at chart construction time, returns a context2d instance implementing + * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. + * @param {*} item - The native item from which to acquire context (platform specific) + * @param {Object} options - The chart options + * @returns {CanvasRenderingContext2D} context2d instance + */ + acquireContext: function() {}, + + /** + * Called at chart destruction time, releases any resources associated to the context + * previously returned by the acquireContext() method. + * @param {CanvasRenderingContext2D} context - The context2d instance + * @returns {Boolean} true if the method succeeded, else false + */ + releaseContext: function() {}, + + /** + * Registers the specified listener on the given chart. + * @param {Chart} chart - Chart from which to listen for event + * @param {String} type - The ({@link IEvent}) type to listen for + * @param {Function} listener - Receives a notification (an object that implements + * the {@link IEvent} interface) when an event of the specified type occurs. + */ + addEventListener: function() {}, + + /** + * Removes the specified listener previously registered with addEventListener. + * @param {Chart} chart -Chart from which to remove the listener + * @param {String} type - The ({@link IEvent}) type to remove + * @param {Function} listener - The listener function to remove from the event target. + */ + removeEventListener: function() {} + +}, implementation); + +/** + * @interface IPlatform + * Allows abstracting platform dependencies away from the chart + * @borrows Chart.platform.acquireContext as acquireContext + * @borrows Chart.platform.releaseContext as releaseContext + * @borrows Chart.platform.addEventListener as addEventListener + * @borrows Chart.platform.removeEventListener as removeEventListener + */ + +/** + * @interface IEvent + * @prop {String} type - The event type name, possible values are: + * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', + * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' + * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') + * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) + * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) + */ + +},{"45":45,"46":46,"47":47}],49:[function(require,module,exports){ +/** + * Plugin based on discussion from the following Chart.js issues: + * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 + * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 + */ + +'use strict'; + +var defaults = require(25); +var elements = require(40); +var helpers = require(45); + +defaults._set('global', { + plugins: { + filler: { + propagate: true + } + } +}); + +module.exports = function() { + + var mappers = { + dataset: function(source) { + var index = source.fill; + var chart = source.chart; + var meta = chart.getDatasetMeta(index); + var visible = meta && chart.isDatasetVisible(index); + var points = (visible && meta.dataset._children) || []; + var length = points.length || 0; + + return !length ? null : function(point, i) { + return (i < length && points[i]._view) || null; + }; + }, + + boundary: function(source) { + var boundary = source.boundary; + var x = boundary ? boundary.x : null; + var y = boundary ? boundary.y : null; + + return function(point) { + return { + x: x === null ? point.x : x, + y: y === null ? point.y : y, + }; + }; + } + }; + + // @todo if (fill[0] === '#') + function decodeFill(el, index, count) { + var model = el._model || {}; + var fill = model.fill; + var target; + + if (fill === undefined) { + fill = !!model.backgroundColor; + } + + if (fill === false || fill === null) { + return false; + } + + if (fill === true) { + return 'origin'; + } + + target = parseFloat(fill, 10); + if (isFinite(target) && Math.floor(target) === target) { + if (fill[0] === '-' || fill[0] === '+') { + target = index + target; + } + + if (target === index || target < 0 || target >= count) { + return false; + } + + return target; + } + + switch (fill) { + // compatibility + case 'bottom': + return 'start'; + case 'top': + return 'end'; + case 'zero': + return 'origin'; + // supported boundaries + case 'origin': + case 'start': + case 'end': + return fill; + // invalid fill values + default: + return false; + } + } + + function computeBoundary(source) { + var model = source.el._model || {}; + var scale = source.el._scale || {}; + var fill = source.fill; + var target = null; + var horizontal; + + if (isFinite(fill)) { + return null; + } + + // Backward compatibility: until v3, we still need to support boundary values set on + // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and + // controllers might still use it (e.g. the Smith chart). + + if (fill === 'start') { + target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; + } else if (fill === 'end') { + target = model.scaleTop === undefined ? scale.top : model.scaleTop; + } else if (model.scaleZero !== undefined) { + target = model.scaleZero; + } else if (scale.getBasePosition) { + target = scale.getBasePosition(); + } else if (scale.getBasePixel) { + target = scale.getBasePixel(); + } + + if (target !== undefined && target !== null) { + if (target.x !== undefined && target.y !== undefined) { + return target; + } + + if (typeof target === 'number' && isFinite(target)) { + horizontal = scale.isHorizontal(); + return { + x: horizontal ? target : null, + y: horizontal ? null : target + }; + } + } + + return null; + } + + function resolveTarget(sources, index, propagate) { + var source = sources[index]; + var fill = source.fill; + var visited = [index]; + var target; + + if (!propagate) { + return fill; + } + + while (fill !== false && visited.indexOf(fill) === -1) { + if (!isFinite(fill)) { + return fill; + } + + target = sources[fill]; + if (!target) { + return false; + } + + if (target.visible) { + return fill; + } + + visited.push(fill); + fill = target.fill; + } + + return false; + } + + function createMapper(source) { + var fill = source.fill; + var type = 'dataset'; + + if (fill === false) { + return null; + } + + if (!isFinite(fill)) { + type = 'boundary'; + } + + return mappers[type](source); + } + + function isDrawable(point) { + return point && !point.skip; + } + + function drawArea(ctx, curve0, curve1, len0, len1) { + var i; + + if (!len0 || !len1) { + return; + } + + // building first area curve (normal) + ctx.moveTo(curve0[0].x, curve0[0].y); + for (i = 1; i < len0; ++i) { + helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); + } + + // joining the two area curves + ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); + + // building opposite area curve (reverse) + for (i = len1 - 1; i > 0; --i) { + helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); + } + } + + function doFill(ctx, points, mapper, view, color, loop) { + var count = points.length; + var span = view.spanGaps; + var curve0 = []; + var curve1 = []; + var len0 = 0; + var len1 = 0; + var i, ilen, index, p0, p1, d0, d1; + + ctx.beginPath(); + + for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { + index = i % count; + p0 = points[index]._view; + p1 = mapper(p0, index, view); + d0 = isDrawable(p0); + d1 = isDrawable(p1); + + if (d0 && d1) { + len0 = curve0.push(p0); + len1 = curve1.push(p1); + } else if (len0 && len1) { + if (!span) { + drawArea(ctx, curve0, curve1, len0, len1); + len0 = len1 = 0; + curve0 = []; + curve1 = []; + } else { + if (d0) { + curve0.push(p0); + } + if (d1) { + curve1.push(p1); + } + } + } + } + + drawArea(ctx, curve0, curve1, len0, len1); + + ctx.closePath(); + ctx.fillStyle = color; + ctx.fill(); + } + + return { + id: 'filler', + + afterDatasetsUpdate: function(chart, options) { + var count = (chart.data.datasets || []).length; + var propagate = options.propagate; + var sources = []; + var meta, i, el, source; + + for (i = 0; i < count; ++i) { + meta = chart.getDatasetMeta(i); + el = meta.dataset; + source = null; + + if (el && el._model && el instanceof elements.Line) { + source = { + visible: chart.isDatasetVisible(i), + fill: decodeFill(el, i, count), + chart: chart, + el: el + }; + } + + meta.$filler = source; + sources.push(source); + } + + for (i = 0; i < count; ++i) { + source = sources[i]; + if (!source) { + continue; + } + + source.fill = resolveTarget(sources, i, propagate); + source.boundary = computeBoundary(source); + source.mapper = createMapper(source); + } + }, + + beforeDatasetDraw: function(chart, args) { + var meta = args.meta.$filler; + if (!meta) { + return; + } + + var ctx = chart.ctx; + var el = meta.el; + var view = el._view; + var points = el._children || []; + var mapper = meta.mapper; + var color = view.backgroundColor || defaults.global.defaultColor; + + if (mapper && color && points.length) { + helpers.canvas.clipArea(ctx, chart.chartArea); + doFill(ctx, points, mapper, view, color, el._loop); + helpers.canvas.unclipArea(ctx); + } + } + }; +}; + +},{"25":25,"40":40,"45":45}],50:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +defaults._set('global', { + legend: { + display: true, + position: 'top', + fullWidth: true, + reverse: false, + weight: 1000, + + // a callback that will handle + onClick: function(e, legendItem) { + var index = legendItem.datasetIndex; + var ci = this.chart; + var meta = ci.getDatasetMeta(index); + + // See controller.isDatasetVisible comment + meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; + + // We hid a dataset ... rerender the chart + ci.update(); + }, + + onHover: null, + + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels: function(chart) { + var data = chart.data; + return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { + return { + text: dataset.label, + fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), + hidden: !chart.isDatasetVisible(i), + lineCap: dataset.borderCapStyle, + lineDash: dataset.borderDash, + lineDashOffset: dataset.borderDashOffset, + lineJoin: dataset.borderJoinStyle, + lineWidth: dataset.borderWidth, + strokeStyle: dataset.borderColor, + pointStyle: dataset.pointStyle, + + // Below is extra data used for toggling the datasets + datasetIndex: i + }; + }, this) : []; + } + } + }, + + legendCallback: function(chart) { + var text = []; + text.push('
    '); + for (var i = 0; i < chart.data.datasets.length; i++) { + text.push('
  • '); + if (chart.data.datasets[i].label) { + text.push(chart.data.datasets[i].label); + } + text.push('
  • '); + } + text.push('
'); + return text.join(''); + } +}); + +module.exports = function(Chart) { + + var layout = Chart.layoutService; + var noop = helpers.noop; + + /** + * Helper function to get the box width based on the usePointStyle option + * @param labelopts {Object} the label options on the legend + * @param fontSize {Number} the label font size + * @return {Number} width of the color box area + */ + function getBoxWidth(labelOpts, fontSize) { + return labelOpts.usePointStyle ? + fontSize * Math.SQRT2 : + labelOpts.boxWidth; + } + + Chart.Legend = Element.extend({ + + initialize: function(config) { + helpers.extend(this, config); + + // Contains hit boxes for each dataset (in dataset order) + this.legendHitBoxes = []; + + // Are we in doughnut mode which has a different data type + this.doughnutMode = false; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all legend types. + // Any function can be extended by the legend type + + beforeUpdate: noop, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + }, + afterUpdate: noop, + + // + + beforeSetDimensions: noop, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, + + // + + beforeBuildLabels: noop, + buildLabels: function() { + var me = this; + var labelOpts = me.options.labels || {}; + var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || []; + + if (labelOpts.filter) { + legendItems = legendItems.filter(function(item) { + return labelOpts.filter(item, me.chart.data); + }); + } + + if (me.options.reverse) { + legendItems.reverse(); + } + + me.legendItems = legendItems; + }, + afterBuildLabels: noop, + + // + + beforeFit: noop, + fit: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var display = opts.display; + + var ctx = me.ctx; + + var globalDefault = defaults.global; + var valueOrDefault = helpers.valueOrDefault; + var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); + var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); + var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + + // Reset hit boxes + var hitboxes = me.legendHitBoxes = []; + + var minSize = me.minSize; + var isHorizontal = me.isHorizontal(); + + if (isHorizontal) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = display ? 10 : 0; + } else { + minSize.width = display ? 10 : 0; + minSize.height = me.maxHeight; // fill all the height + } + + // Increase sizes here + if (display) { + ctx.font = labelFont; + + if (isHorizontal) { + // Labels + + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + var lineWidths = me.lineWidths = [0]; + var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; + + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + helpers.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { + totalHeight += fontSize + (labelOpts.padding); + lineWidths[lineWidths.length] = me.left; + } + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: width, + height: fontSize + }; + + lineWidths[lineWidths.length - 1] += width + labelOpts.padding; + }); + + minSize.height += totalHeight; + + } else { + var vPadding = labelOpts.padding; + var columnWidths = me.columnWidths = []; + var totalWidth = labelOpts.padding; + var currentColWidth = 0; + var currentColHeight = 0; + var itemHeight = fontSize + vPadding; + + helpers.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + // If too tall, go to new column + if (currentColHeight + itemHeight > minSize.height) { + totalWidth += currentColWidth + labelOpts.padding; + columnWidths.push(currentColWidth); // previous column width + + currentColWidth = 0; + currentColHeight = 0; + } + + // Get max width + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += itemHeight; + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: itemWidth, + height: fontSize + }; + }); + + totalWidth += currentColWidth; + columnWidths.push(currentColWidth); + minSize.width += totalWidth; + } + } + + me.width = minSize.width; + me.height = minSize.height; + }, + afterFit: noop, + + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + + // Actually draw the legend on the canvas + draw: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var globalDefault = defaults.global; + var lineDefault = globalDefault.elements.line; + var legendWidth = me.width; + var lineWidths = me.lineWidths; + + if (opts.display) { + var ctx = me.ctx; + var valueOrDefault = helpers.valueOrDefault; + var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor); + var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); + var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); + var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var cursor; + + // Canvas setup + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont; + + var boxWidth = getBoxWidth(labelOpts, fontSize); + var hitboxes = me.legendHitBoxes; + + // current position + var drawLegendBox = function(x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0) { + return; + } + + // Set the ctx for the box + ctx.save(); + + ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); + ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); + ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); + ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); + ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); + ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); + var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); + + if (ctx.setLineDash) { + // IE 9 and 10 do not support line dash + ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash)); + } + + if (opts.labels && opts.labels.usePointStyle) { + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + var radius = fontSize * Math.SQRT2 / 2; + var offSet = radius / Math.SQRT2; + var centerX = x + offSet; + var centerY = y + offSet; + + // Draw pointStyle as legend symbol + helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); + } else { + // Draw box as legend symbol + if (!isLineWidthZero) { + ctx.strokeRect(x, y, boxWidth, fontSize); + } + ctx.fillRect(x, y, boxWidth, fontSize); + } + + ctx.restore(); + }; + var fillText = function(x, y, legendItem, textWidth) { + var halfFontSize = fontSize / 2; + var xLeft = boxWidth + halfFontSize + x; + var yMiddle = y + halfFontSize; + + ctx.fillText(legendItem.text, xLeft, yMiddle); + + if (legendItem.hidden) { + // Strikethrough the text if hidden + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(xLeft, yMiddle); + ctx.lineTo(xLeft + textWidth, yMiddle); + ctx.stroke(); + } + }; + + // Horizontal + var isHorizontal = me.isHorizontal(); + if (isHorizontal) { + cursor = { + x: me.left + ((legendWidth - lineWidths[0]) / 2), + y: me.top + labelOpts.padding, + line: 0 + }; + } else { + cursor = { + x: me.left + labelOpts.padding, + y: me.top + labelOpts.padding, + line: 0 + }; + } + + var itemHeight = fontSize + labelOpts.padding; + helpers.each(me.legendItems, function(legendItem, i) { + var textWidth = ctx.measureText(legendItem.text).width; + var width = boxWidth + (fontSize / 2) + textWidth; + var x = cursor.x; + var y = cursor.y; + + if (isHorizontal) { + if (x + width >= legendWidth) { + y = cursor.y += itemHeight; + cursor.line++; + x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); + } + } else if (y + itemHeight > me.bottom) { + x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; + y = cursor.y = me.top + labelOpts.padding; + cursor.line++; + } + + drawLegendBox(x, y, legendItem); + + hitboxes[i].left = x; + hitboxes[i].top = y; + + // Fill the actual label + fillText(x, y, legendItem, textWidth); + + if (isHorizontal) { + cursor.x += width + (labelOpts.padding); + } else { + cursor.y += itemHeight; + } + + }); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @return {Boolean} true if a change occured + */ + handleEvent: function(e) { + var me = this; + var opts = me.options; + var type = e.type === 'mouseup' ? 'click' : e.type; + var changed = false; + + if (type === 'mousemove') { + if (!opts.onHover) { + return; + } + } else if (type === 'click') { + if (!opts.onClick) { + return; + } + } else { + return; + } + + // Chart event already has relative position in it + var x = e.x; + var y = e.y; + + if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { + // See if we are touching one of the dataset boxes + var lh = me.legendHitBoxes; + for (var i = 0; i < lh.length; ++i) { + var hitBox = lh[i]; + + if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { + // Touching an element + if (type === 'click') { + // use e.native for backwards compatibility + opts.onClick.call(me, e.native, me.legendItems[i]); + changed = true; + break; + } else if (type === 'mousemove') { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, me.legendItems[i]); + changed = true; + break; + } + } + } + } + + return changed; + } + }); + + function createNewLegendAndAttach(chart, legendOpts) { + var legend = new Chart.Legend({ + ctx: chart.ctx, + options: legendOpts, + chart: chart + }); + + layout.configure(chart, legend, legendOpts); + layout.addBox(chart, legend); + chart.legend = legend; + } + + return { + id: 'legend', + + beforeInit: function(chart) { + var legendOpts = chart.options.legend; + + if (legendOpts) { + createNewLegendAndAttach(chart, legendOpts); + } + }, + + beforeUpdate: function(chart) { + var legendOpts = chart.options.legend; + var legend = chart.legend; + + if (legendOpts) { + helpers.mergeIf(legendOpts, defaults.global.legend); + + if (legend) { + layout.configure(chart, legend, legendOpts); + legend.options = legendOpts; + } else { + createNewLegendAndAttach(chart, legendOpts); + } + } else if (legend) { + layout.removeBox(chart, legend); + delete chart.legend; + } + }, + + afterEvent: function(chart, e) { + var legend = chart.legend; + if (legend) { + legend.handleEvent(e); + } + } + }; +}; + +},{"25":25,"26":26,"45":45}],51:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var Element = require(26); +var helpers = require(45); + +defaults._set('global', { + title: { + display: false, + fontStyle: 'bold', + fullWidth: true, + lineHeight: 1.2, + padding: 10, + position: 'top', + text: '', + weight: 2000 // by default greater than legend (1000) to be above + } +}); + +module.exports = function(Chart) { + + var layout = Chart.layoutService; + var noop = helpers.noop; + + Chart.Title = Element.extend({ + initialize: function(config) { + var me = this; + helpers.extend(me, config); + + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + + beforeUpdate: noop, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: noop, + + // + + beforeSetDimensions: noop, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, + + // + + beforeBuildLabels: noop, + buildLabels: noop, + afterBuildLabels: noop, + + // + + beforeFit: noop, + fit: function() { + var me = this; + var valueOrDefault = helpers.valueOrDefault; + var opts = me.options; + var display = opts.display; + var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize); + var minSize = me.minSize; + var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; + var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); + var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; + + if (me.isHorizontal()) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = textSize; + } else { + minSize.width = textSize; + minSize.height = me.maxHeight; // fill all the height + } + + me.width = minSize.width; + me.height = minSize.height; + + }, + afterFit: noop, + + // Shared Methods + isHorizontal: function() { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, + + // Actually draw the title block on the canvas + draw: function() { + var me = this; + var ctx = me.ctx; + var valueOrDefault = helpers.valueOrDefault; + var opts = me.options; + var globalDefaults = defaults.global; + + if (opts.display) { + var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize); + var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle); + var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily); + var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); + var offset = lineHeight / 2 + opts.padding; + var rotation = 0; + var top = me.top; + var left = me.left; + var bottom = me.bottom; + var right = me.right; + var maxWidth, titleX, titleY; + + ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour + ctx.font = titleFont; + + // Horizontal + if (me.isHorizontal()) { + titleX = left + ((right - left) / 2); // midpoint of the width + titleY = top + offset; + maxWidth = right - left; + } else { + titleX = opts.position === 'left' ? left + offset : right - offset; + titleY = top + ((bottom - top) / 2); + maxWidth = bottom - top; + rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); + } + + ctx.save(); + ctx.translate(titleX, titleY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + var text = opts.text; + if (helpers.isArray(text)) { + var y = 0; + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], 0, y, maxWidth); + y += lineHeight; + } + } else { + ctx.fillText(text, 0, 0, maxWidth); + } + + ctx.restore(); + } + } + }); + + function createNewTitleBlockAndAttach(chart, titleOpts) { + var title = new Chart.Title({ + ctx: chart.ctx, + options: titleOpts, + chart: chart + }); + + layout.configure(chart, title, titleOpts); + layout.addBox(chart, title); + chart.titleBlock = title; + } + + return { + id: 'title', + + beforeInit: function(chart) { + var titleOpts = chart.options.title; + + if (titleOpts) { + createNewTitleBlockAndAttach(chart, titleOpts); + } + }, + + beforeUpdate: function(chart) { + var titleOpts = chart.options.title; + var titleBlock = chart.titleBlock; + + if (titleOpts) { + helpers.mergeIf(titleOpts, defaults.global.title); + + if (titleBlock) { + layout.configure(chart, titleBlock, titleOpts); + titleBlock.options = titleOpts; + } else { + createNewTitleBlockAndAttach(chart, titleOpts); + } + } else if (titleBlock) { + Chart.layoutService.removeBox(chart, titleBlock); + delete chart.titleBlock; + } + } + }; +}; + +},{"25":25,"26":26,"45":45}],52:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + // Default config for a category scale + var defaultConfig = { + position: 'bottom' + }; + + var DatasetScale = Chart.Scale.extend({ + /** + * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those + * else fall back to data.labels + * @private + */ + getLabels: function() { + var data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; + }, + + determineDataLimits: function() { + var me = this; + var labels = me.getLabels(); + me.minIndex = 0; + me.maxIndex = labels.length - 1; + var findIndex; + + if (me.options.ticks.min !== undefined) { + // user specified min value + findIndex = labels.indexOf(me.options.ticks.min); + me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; + } + + if (me.options.ticks.max !== undefined) { + // user specified max value + findIndex = labels.indexOf(me.options.ticks.max); + me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; + } + + me.min = labels[me.minIndex]; + me.max = labels[me.maxIndex]; + }, + + buildTicks: function() { + var me = this; + var labels = me.getLabels(); + // If we are viewing some subset of labels, slice the original array + me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var isHorizontal = me.isHorizontal(); + + if (data.yLabels && !isHorizontal) { + return me.getRightValue(data.datasets[datasetIndex].data[index]); + } + return me.ticks[index - me.minIndex]; + }, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index) { + var me = this; + var offset = me.options.offset; + // 1 is added because we need the length but we have the indexes + var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); + + // If value is a data object, then index is the index in the data array, + // not the index of the scale. We need to change that. + var valueCategory; + if (value !== undefined && value !== null) { + valueCategory = me.isHorizontal() ? value.x : value.y; + } + if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { + var labels = me.getLabels(); + value = valueCategory || value; + var idx = labels.indexOf(value); + index = idx !== -1 ? idx : index; + } + + if (me.isHorizontal()) { + var valueWidth = me.width / offsetAmt; + var widthOffset = (valueWidth * (index - me.minIndex)); + + if (offset) { + widthOffset += (valueWidth / 2); + } + + return me.left + Math.round(widthOffset); + } + var valueHeight = me.height / offsetAmt; + var heightOffset = (valueHeight * (index - me.minIndex)); + + if (offset) { + heightOffset += (valueHeight / 2); + } + + return me.top + Math.round(heightOffset); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); + }, + getValueForPixel: function(pixel) { + var me = this; + var offset = me.options.offset; + var value; + var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); + var horz = me.isHorizontal(); + var valueDimension = (horz ? me.width : me.height) / offsetAmt; + + pixel -= horz ? me.left : me.top; + + if (offset) { + pixel -= (valueDimension / 2); + } + + if (pixel <= 0) { + value = 0; + } else { + value = Math.round(pixel / valueDimension); + } + + return value + me.minIndex; + }, + getBasePixel: function() { + return this.bottom; + } + }); + + Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig); + +}; + +},{}],53:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var helpers = require(45); +var Ticks = require(34); + +module.exports = function(Chart) { + + var defaultConfig = { + position: 'left', + ticks: { + callback: Ticks.formatters.linear + } + }; + + var LinearScale = Chart.LinearScaleBase.extend({ + + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); + var DEFAULT_MIN = 0; + var DEFAULT_MAX = 1; + + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + + // First Calculate the range + me.min = null; + me.max = null; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = { + positiveValues: [], + negativeValues: [] + }; + } + + // Store these per type + var positiveValues = valuesPerStack[key].positiveValues; + var negativeValues = valuesPerStack[key].negativeValues; + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (opts.relativePoints) { + positiveValues[index] = 100; + } else if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + }); + } + }); + + helpers.each(valuesPerStack, function(valuesForType) { + var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); + var minVal = helpers.min(values); + var maxVal = helpers.max(values); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); + + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } + + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + }); + } + }); + } + + me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; + me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + this.handleTickRangeOptions(); + }, + getTickLimit: function() { + var maxTicks; + var me = this; + var tickOpts = me.options.ticks; + + if (me.isHorizontal()) { + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); + } + + return maxTicks; + }, + // Called after the ticks are built. We need + handleDirectionalChanges: function() { + if (!this.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + // Utils + getPixelForValue: function(value) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + var me = this; + var start = me.start; + + var rightValue = +me.getRightValue(value); + var pixel; + var range = me.end - start; + + if (me.isHorizontal()) { + pixel = me.left + (me.width / range * (rightValue - start)); + return Math.round(pixel); + } + + pixel = me.bottom - (me.height / range * (rightValue - start)); + return Math.round(pixel); + }, + getValueForPixel: function(pixel) { + var me = this; + var isHorizontal = me.isHorizontal(); + var innerDimension = isHorizontal ? me.width : me.height; + var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; + return me.start + ((me.end - me.start) * offset); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.ticksAsNumbers[index]); + } + }); + Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig); + +}; + +},{"25":25,"34":34,"45":45}],54:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); +var Ticks = require(34); + +module.exports = function(Chart) { + + var noop = helpers.noop; + + Chart.LinearScaleBase = Chart.Scale.extend({ + getRightValue: function(value) { + if (typeof value === 'string') { + return +value; + } + return Chart.Scale.prototype.getRightValue.call(this, value); + }, + + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (tickOpts.beginAtZero) { + var minSign = helpers.sign(me.min); + var maxSign = helpers.sign(me.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + me.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the bottom down to 0 + me.min = 0; + } + } + + var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; + var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; + + if (tickOpts.min !== undefined) { + me.min = tickOpts.min; + } else if (tickOpts.suggestedMin !== undefined) { + if (me.min === null) { + me.min = tickOpts.suggestedMin; + } else { + me.min = Math.min(me.min, tickOpts.suggestedMin); + } + } + + if (tickOpts.max !== undefined) { + me.max = tickOpts.max; + } else if (tickOpts.suggestedMax !== undefined) { + if (me.max === null) { + me.max = tickOpts.suggestedMax; + } else { + me.max = Math.max(me.max, tickOpts.suggestedMax); + } + } + + if (setMin !== setMax) { + // We set the min or the max but not both. + // So ensure that our range is good + // Inverted or 0 length range can happen when + // ticks.min is set, and no datasets are visible + if (me.min >= me.max) { + if (setMin) { + me.max = me.min + 1; + } else { + me.min = me.max - 1; + } + } + } + + if (me.min === me.max) { + me.max++; + + if (!tickOpts.beginAtZero) { + me.min--; + } + } + }, + getTickLimit: noop, + handleDirectionalChanges: noop, + + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + var maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + + var numericGeneratorOptions = { + maxTicks: maxTicks, + min: tickOpts.min, + max: tickOpts.max, + stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me); + + me.handleDirectionalChanges(); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + convertTicksToLabels: function() { + var me = this; + me.ticksAsNumbers = me.ticks.slice(); + me.zeroLineIndex = me.ticks.indexOf(0); + + Chart.Scale.prototype.convertTicksToLabels.call(me); + } + }); +}; + +},{"34":34,"45":45}],55:[function(require,module,exports){ +'use strict'; + +var helpers = require(45); +var Ticks = require(34); + +module.exports = function(Chart) { + + var defaultConfig = { + position: 'left', + + // label settings + ticks: { + callback: Ticks.formatters.logarithmic + } + }; + + var LogarithmicScale = Chart.Scale.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var valueOrDefault = helpers.valueOrDefault; + var isHorizontal = me.isHorizontal(); + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + + // Calculate Range + me.min = null; + me.max = null; + me.minNotZero = null; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; + } + + helpers.each(dataset.data, function(rawValue, index) { + var values = valuesPerStack[key]; + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + values[index] = values[index] || 0; + + if (opts.relativePoints) { + values[index] = 100; + } else { + // Don't need to split positive and negative since the log scale can't handle a 0 crossing + values[index] += value; + } + }); + } + }); + + helpers.each(valuesPerStack, function(valuesForType) { + var minVal = helpers.min(valuesForType); + var maxVal = helpers.max(valuesForType); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); + + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } + + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + + if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { + me.minNotZero = value; + } + }); + } + }); + } + + me.min = valueOrDefault(tickOpts.min, me.min); + me.max = valueOrDefault(tickOpts.max, me.max); + + if (me.min === me.max) { + if (me.min !== 0 && me.min !== null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); + } else { + me.min = 1; + me.max = 10; + } + } + }, + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + var generationOptions = { + min: tickOpts.min, + max: tickOpts.max + }; + var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me); + + if (!me.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + ticks.reverse(); + } + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + convertTicksToLabels: function() { + this.tickValues = this.ticks.slice(); + + Chart.Scale.prototype.convertTicksToLabels.call(this); + }, + // Get the correct tooltip label + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.tickValues[index]); + }, + getPixelForValue: function(value) { + var me = this; + var start = me.start; + var newVal = +me.getRightValue(value); + var opts = me.options; + var tickOpts = opts.ticks; + var innerDimension, pixel, range; + + if (me.isHorizontal()) { + range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0 + if (newVal === 0) { + pixel = me.left; + } else { + innerDimension = me.width; + pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); + } + } else { + // Bottom - top since pixels increase downward on a screen + innerDimension = me.height; + if (start === 0 && !tickOpts.reverse) { + range = helpers.log10(me.end) - helpers.log10(me.minNotZero); + if (newVal === start) { + pixel = me.bottom; + } else if (newVal === me.minNotZero) { + pixel = me.bottom - innerDimension * 0.02; + } else { + pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero))); + } + } else if (me.end === 0 && tickOpts.reverse) { + range = helpers.log10(me.start) - helpers.log10(me.minNotZero); + if (newVal === me.end) { + pixel = me.top; + } else if (newVal === me.minNotZero) { + pixel = me.top + innerDimension * 0.02; + } else { + pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero))); + } + } else if (newVal === 0) { + pixel = tickOpts.reverse ? me.top : me.bottom; + } else { + range = helpers.log10(me.end) - helpers.log10(start); + innerDimension = me.height; + pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start))); + } + } + return pixel; + }, + getValueForPixel: function(pixel) { + var me = this; + var range = helpers.log10(me.end) - helpers.log10(me.start); + var value, innerDimension; + + if (me.isHorizontal()) { + innerDimension = me.width; + value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension); + } else { // todo: if start === 0 + innerDimension = me.height; + value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start; + } + return value; + } + }); + Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); + +}; + +},{"34":34,"45":45}],56:[function(require,module,exports){ +'use strict'; + +var defaults = require(25); +var helpers = require(45); +var Ticks = require(34); + +module.exports = function(Chart) { + + var globalDefaults = defaults.global; + + var defaultConfig = { + display: true, + + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + position: 'chartArea', + + angleLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1 + }, + + gridLines: { + circular: false + }, + + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', + + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + + callback: Ticks.formatters.linear + }, + + pointLabels: { + // Boolean - if true, show point labels + display: true, + + // Number - Point label font size in pixels + fontSize: 10, + + // Function - Used to convert point labels + callback: function(label) { + return label; + } + } + }; + + function getValueCount(scale) { + var opts = scale.options; + return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; + } + + function getPointLabelFontOptions(scale) { + var pointLabelOptions = scale.options.pointLabels; + var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); + var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); + var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); + var font = helpers.fontString(fontSize, fontStyle, fontFamily); + + return { + size: fontSize, + style: fontStyle, + family: fontFamily, + font: font + }; + } + + function measureLabelSize(ctx, fontSize, label) { + if (helpers.isArray(label)) { + return { + w: helpers.longestText(ctx, ctx.font, label), + h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) + }; + } + + return { + w: ctx.measureText(label).width, + h: fontSize + }; + } + + function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { + return { + start: pos - size - 5, + end: pos + }; + } + + return { + start: pos, + end: pos + size + 5 + }; + } + + /** + * Helper function to fit a radial linear scale with point labels + */ + function fitWithPointLabels(scale) { + /* + * Right, this is really confusing and there is a lot of maths going on here + * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + * + * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + * + * Solution: + * + * We assume the radius of the polygon is half the size of the canvas at first + * at each index we check if the text overlaps. + * + * Where it does, we store that angle and that index. + * + * After finding the largest index and angle we calculate how much we need to remove + * from the shape radius to move the point inwards by that x. + * + * We average the left and right distances to get the maximum shape radius that can fit in the box + * along with labels. + * + * Once we have that, we can find the centre point for the chart, by taking the x text protrusion + * on each side, removing that from the size, halving it and adding the left x protrusion width. + * + * This will mean we have a shape fitted to the canvas, as large as it can be with the labels + * and position it in the most space efficient manner + * + * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + */ + + var plFont = getPointLabelFontOptions(scale); + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); + var furthestLimits = { + r: scale.width, + l: 0, + t: scale.height, + b: 0 + }; + var furthestAngles = {}; + var i, textSize, pointPosition; + + scale.ctx.font = plFont.font; + scale._pointLabelSizes = []; + + var valueCount = getValueCount(scale); + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, largestPossibleRadius); + textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians) % 360; + var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } + + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } + + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } + + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } + + scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); + } + + /** + * Helper function to fit a radial linear scale with no point labels + */ + function fit(scale) { + var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); + scale.drawingArea = Math.round(largestPossibleRadius); + scale.setCenterPoint(0, 0, 0, 0); + } + + function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } + + return 'right'; + } + + function fillText(ctx, text, position, fontSize) { + if (helpers.isArray(text)) { + var y = position.y; + var spacing = 1.5 * fontSize; + + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], position.x, y); + y += spacing; + } + } else { + ctx.fillText(text, position.x, position.y); + } + } + + function adjustPointPositionForLabelHeight(angle, textSize, position) { + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } + } + + function drawPointLabels(scale) { + var ctx = scale.ctx; + var valueOrDefault = helpers.valueOrDefault; + var opts = scale.options; + var angleLineOpts = opts.angleLines; + var pointLabelOpts = opts.pointLabels; + + ctx.lineWidth = angleLineOpts.lineWidth; + ctx.strokeStyle = angleLineOpts.color; + + var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + + // Point Label Font + var plFont = getPointLabelFontOptions(scale); + + ctx.textBaseline = 'top'; + + for (var i = getValueCount(scale) - 1; i >= 0; i--) { + if (angleLineOpts.display) { + var outerPosition = scale.getPointPosition(i, outerDistance); + ctx.beginPath(); + ctx.moveTo(scale.xCenter, scale.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); + ctx.closePath(); + } + + if (pointLabelOpts.display) { + // Extra 3px out for some label spacing + var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); + + // Keep this in loop since we may support array properties here + var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor); + ctx.font = plFont.font; + ctx.fillStyle = pointLabelFontColor; + + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians); + ctx.textAlign = getTextAlignForAngle(angle); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); + } + } + } + + function drawRadiusLine(scale, gridLineOpts, radius, index) { + var ctx = scale.ctx; + ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); + ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + + if (scale.options.gridLines.circular) { + // Draw circular arcs between the points + ctx.beginPath(); + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.stroke(); + } else { + // Draw straight lines connecting each index + var valueCount = getValueCount(scale); + + if (valueCount === 0) { + return; + } + + ctx.beginPath(); + var pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (var i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + + ctx.closePath(); + ctx.stroke(); + } + } + + function numberOrZero(param) { + return helpers.isNumber(param) ? param : 0; + } + + var LinearRadialScale = Chart.LinearScaleBase.extend({ + setDimensions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.xCenter = Math.round(me.width / 2); + me.yCenter = Math.round(me.height / 2); + + var minSize = helpers.min([me.height, me.width]); + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); + }, + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var min = Number.POSITIVE_INFINITY; + var max = Number.NEGATIVE_INFINITY; + + helpers.each(chart.data.datasets, function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + min = Math.min(value, min); + max = Math.max(value, max); + }); + } + }); + + me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); + me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + getTickLimit: function() { + var tickOpts = this.options.ticks; + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); + }, + convertTicksToLabels: function() { + var me = this; + + Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); + + // Point labels + me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + fit: function() { + if (this.options.pointLabels.display) { + fitWithPointLabels(this); + } else { + fit(this); + } + }, + /** + * Set radius reductions and determine new radius and center point + * @private + */ + setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { + var me = this; + var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); + + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); + + me.drawingArea = Math.min( + Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); + }, + setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { + var me = this; + var maxRight = me.width - rightMovement - me.drawingArea; + var maxLeft = leftMovement + me.drawingArea; + var maxTop = topMovement + me.drawingArea; + var maxBottom = me.height - bottomMovement - me.drawingArea; + + me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); + }, + + getIndexAngle: function(index) { + var angleMultiplier = (Math.PI * 2) / getValueCount(this); + var startAngle = this.chart.options && this.chart.options.startAngle ? + this.chart.options.startAngle : + 0; + + var startAngleRadians = startAngle * Math.PI * 2 / 360; + + // Start from the top instead of right, so remove a quarter of the circle + return index * angleMultiplier + startAngleRadians; + }, + getDistanceFromCenterForValue: function(value) { + var me = this; + + if (value === null) { + return 0; // null always in center + } + + // Take into account half font size + the yPadding of the top value + var scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.ticks.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + }, + getPointPosition: function(index, distanceFromCenter) { + var me = this; + var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); + return { + x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, + y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter + }; + }, + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + + getBasePosition: function() { + var me = this; + var min = me.min; + var max = me.max; + + return me.getPointPositionForValue(0, + me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0); + }, + + draw: function() { + var me = this; + var opts = me.options; + var gridLineOpts = opts.gridLines; + var tickOpts = opts.ticks; + var valueOrDefault = helpers.valueOrDefault; + + if (opts.display) { + var ctx = me.ctx; + var startAngle = this.getIndexAngle(0); + + // Tick Font + var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); + var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + + helpers.each(me.ticks, function(label, index) { + // Don't draw a centre value (if it is minimum) + if (index > 0 || tickOpts.reverse) { + var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + + // Draw circular lines around the scale + if (gridLineOpts.display && index !== 0) { + drawRadiusLine(me, gridLineOpts, yCenterOffset, index); + } + + if (tickOpts.display) { + var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); + ctx.font = tickLabelFont; + + ctx.save(); + ctx.translate(me.xCenter, me.yCenter); + ctx.rotate(startAngle); + + if (tickOpts.showLabelBackdrop) { + var labelWidth = ctx.measureText(label).width; + ctx.fillStyle = tickOpts.backdropColor; + ctx.fillRect( + -labelWidth / 2 - tickOpts.backdropPaddingX, + -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY, + labelWidth + tickOpts.backdropPaddingX * 2, + tickFontSize + tickOpts.backdropPaddingY * 2 + ); + } + + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = tickFontColor; + ctx.fillText(label, 0, -yCenterOffset); + ctx.restore(); + } + } + }); + + if (opts.angleLines.display || opts.pointLabels.display) { + drawPointLabels(me); + } + } + } + }); + Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); + +}; + +},{"25":25,"34":34,"45":45}],57:[function(require,module,exports){ +/* global window: false */ +'use strict'; + +var moment = require(1); +moment = typeof moment === 'function' ? moment : window.moment; + +var defaults = require(25); +var helpers = require(45); + +// Integer constants are from the ES6 spec. +var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; +var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + +var INTERVALS = { + millisecond: { + common: true, + size: 1, + steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] + }, + second: { + common: true, + size: 1000, + steps: [1, 2, 5, 10, 30] + }, + minute: { + common: true, + size: 60000, + steps: [1, 2, 5, 10, 30] + }, + hour: { + common: true, + size: 3600000, + steps: [1, 2, 3, 6, 12] + }, + day: { + common: true, + size: 86400000, + steps: [1, 2, 5] + }, + week: { + common: false, + size: 604800000, + steps: [1, 2, 3, 4] + }, + month: { + common: true, + size: 2.628e9, + steps: [1, 2, 3] + }, + quarter: { + common: false, + size: 7.884e9, + steps: [1, 2, 3, 4] + }, + year: { + common: true, + size: 3.154e10 + } +}; + +var UNITS = Object.keys(INTERVALS); + +function sorter(a, b) { + return a - b; +} + +function arrayUnique(items) { + var hash = {}; + var out = []; + var i, ilen, item; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + if (!hash[item]) { + hash[item] = true; + out.push(item); + } + } + + return out; +} + +/** + * Returns an array of {time, pos} objects used to interpolate a specific `time` or position + * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is + * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other + * extremity (left + width or top + height). Note that it would be more optimized to directly + * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need + * to create the lookup table. The table ALWAYS contains at least two items: min and max. + * + * @param {Number[]} timestamps - timestamps sorted from lowest to highest. + * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min + * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. + * If 'series', timestamps will be positioned at the same distance from each other. In this + * case, only timestamps that break the time linearity are registered, meaning that in the + * best case, all timestamps are linear, the table contains only min and max. + */ +function buildLookupTable(timestamps, min, max, distribution) { + if (distribution === 'linear' || !timestamps.length) { + return [ + {time: min, pos: 0}, + {time: max, pos: 1} + ]; + } + + var table = []; + var items = [min]; + var i, ilen, prev, curr, next; + + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + curr = timestamps[i]; + if (curr > min && curr < max) { + items.push(curr); + } + } + + items.push(max); + + for (i = 0, ilen = items.length; i < ilen; ++i) { + next = items[i + 1]; + prev = items[i - 1]; + curr = items[i]; + + // only add points that breaks the scale linearity + if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { + table.push({time: curr, pos: i / (ilen - 1)}); + } + } + + return table; +} + +// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ +function lookup(table, key, value) { + var lo = 0; + var hi = table.length - 1; + var mid, i0, i1; + + while (lo >= 0 && lo <= hi) { + mid = (lo + hi) >> 1; + i0 = table[mid - 1] || null; + i1 = table[mid]; + + if (!i0) { + // given value is outside table (before first item) + return {lo: null, hi: i1}; + } else if (i1[key] < value) { + lo = mid + 1; + } else if (i0[key] > value) { + hi = mid - 1; + } else { + return {lo: i0, hi: i1}; + } + } + + // given value is outside table (after last item) + return {lo: i1, hi: null}; +} + +/** + * Linearly interpolates the given source `value` using the table items `skey` values and + * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') + * returns the position for a timestamp equal to 42. If value is out of bounds, values at + * index [0, 1] or [n - 1, n] are used for the interpolation. + */ +function interpolate(table, skey, sval, tkey) { + var range = lookup(table, skey, sval); + + // Note: the lookup table ALWAYS contains at least 2 items (min and max) + var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; + var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; + + var span = next[skey] - prev[skey]; + var ratio = span ? (sval - prev[skey]) / span : 0; + var offset = (next[tkey] - prev[tkey]) * ratio; + + return prev[tkey] + offset; +} + +/** + * Convert the given value to a moment object using the given time options. + * @see http://momentjs.com/docs/#/parsing/ + */ +function momentify(value, options) { + var parser = options.parser; + var format = options.parser || options.format; + + if (typeof parser === 'function') { + return parser(value); + } + + if (typeof value === 'string' && typeof format === 'string') { + return moment(value, format); + } + + if (!(value instanceof moment)) { + value = moment(value); + } + + if (value.isValid()) { + return value; + } + + // Labels are in an incompatible moment format and no `parser` has been provided. + // The user might still use the deprecated `format` option to convert his inputs. + if (typeof format === 'function') { + return format(value); + } + + return value; +} + +function parse(input, scale) { + if (helpers.isNullOrUndef(input)) { + return null; + } + + var options = scale.options.time; + var value = momentify(scale.getRightValue(input), options); + if (!value.isValid()) { + return null; + } + + if (options.round) { + value.startOf(options.round); + } + + return value.valueOf(); +} + +/** + * Returns the number of unit to skip to be able to display up to `capacity` number of ticks + * in `unit` for the given `min` / `max` range and respecting the interval steps constraints. + */ +function determineStepSize(min, max, unit, capacity) { + var range = max - min; + var interval = INTERVALS[unit]; + var milliseconds = interval.size; + var steps = interval.steps; + var i, ilen, factor; + + if (!steps) { + return Math.ceil(range / ((capacity || 1) * milliseconds)); + } + + for (i = 0, ilen = steps.length; i < ilen; ++i) { + factor = steps[i]; + if (Math.ceil(range / (milliseconds * factor)) <= capacity) { + break; + } + } + + return factor; +} + +/** + * Figures out what unit results in an appropriate number of auto-generated ticks + */ +function determineUnitForAutoTicks(minUnit, min, max, capacity) { + var ilen = UNITS.length; + var i, interval, factor; + + for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { + interval = INTERVALS[UNITS[i]]; + factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; + + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + return UNITS[i]; + } + } + + return UNITS[ilen - 1]; +} + +/** + * Figures out what unit to format a set of ticks with + */ +function determineUnitForFormatting(ticks, minUnit, min, max) { + var duration = moment.duration(moment(max).diff(moment(min))); + var ilen = UNITS.length; + var i, unit; + + for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { + unit = UNITS[i]; + if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) { + return unit; + } + } + + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; +} + +function determineMajorUnit(unit) { + for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { + if (INTERVALS[UNITS[i]].common) { + return UNITS[i]; + } + } +} + +/** + * Generates a maximum of `capacity` timestamps between min and max, rounded to the + * `minor` unit, aligned on the `major` unit and using the given scale time `options`. + * Important: this method can return ticks outside the min and max range, it's the + * responsibility of the calling code to clamp values if needed. + */ +function generate(min, max, capacity, options) { + var timeOpts = options.time; + var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); + var major = determineMajorUnit(minor); + var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); + var weekday = minor === 'week' ? timeOpts.isoWeekday : false; + var majorTicksEnabled = options.ticks.major.enabled; + var interval = INTERVALS[minor]; + var first = moment(min); + var last = moment(max); + var ticks = []; + var time; + + if (!stepSize) { + stepSize = determineStepSize(min, max, minor, capacity); + } + + // For 'week' unit, handle the first day of week option + if (weekday) { + first = first.isoWeekday(weekday); + last = last.isoWeekday(weekday); + } + + // Align first/last ticks on unit + first = first.startOf(weekday ? 'day' : minor); + last = last.startOf(weekday ? 'day' : minor); + + // Make sure that the last tick include max + if (last < max) { + last.add(1, minor); + } + + time = moment(first); + + if (majorTicksEnabled && major && !weekday && !timeOpts.round) { + // Align the first tick on the previous `minor` unit aligned on the `major` unit: + // we first aligned time on the previous `major` unit then add the number of full + // stepSize there is between first and the previous major time. + time.startOf(major); + time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor); + } + + for (; time < last; time.add(stepSize, minor)) { + ticks.push(+time); + } + + ticks.push(+time); + + return ticks; +} + +/** + * Returns the right and left offsets from edges in the form of {left, right}. + * Offsets are added when the `offset` option is true. + */ +function computeOffsets(table, ticks, min, max, options) { + var left = 0; + var right = 0; + var upper, lower; + + if (options.offset && ticks.length) { + if (!options.time.min) { + upper = ticks.length > 1 ? ticks[1] : max; + lower = ticks[0]; + left = ( + interpolate(table, 'time', upper, 'pos') - + interpolate(table, 'time', lower, 'pos') + ) / 2; + } + if (!options.time.max) { + upper = ticks[ticks.length - 1]; + lower = ticks.length > 1 ? ticks[ticks.length - 2] : min; + right = ( + interpolate(table, 'time', upper, 'pos') - + interpolate(table, 'time', lower, 'pos') + ) / 2; + } + } + + return {left: left, right: right}; +} + +function ticksFromTimestamps(values, majorUnit) { + var ticks = []; + var i, ilen, value, major; + + for (i = 0, ilen = values.length; i < ilen; ++i) { + value = values[i]; + major = majorUnit ? value === +moment(value).startOf(majorUnit) : false; + + ticks.push({ + value: value, + major: major + }); + } + + return ticks; +} + +module.exports = function(Chart) { + + var defaultConfig = { + position: 'bottom', + + /** + * Data distribution along the scale: + * - 'linear': data are spread according to their time (distances can vary), + * - 'series': data are spread at the same distance from each other. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + distribution: 'linear', + + /** + * Scale boundary strategy (bypassed by min/max time options) + * - `data`: make sure data are fully visible, ticks outside are removed + * - `ticks`: make sure ticks are fully visible, data outside are truncated + * @see https://github.com/chartjs/Chart.js/pull/4556 + * @since 2.7.0 + */ + bounds: 'data', + + time: { + parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ + minUnit: 'millisecond', + + // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ + displayFormats: { + millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, + second: 'h:mm:ss a', // 11:20:01 AM + minute: 'h:mm a', // 11:20 AM + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 + week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? + month: 'MMM YYYY', // Sept 2015 + quarter: '[Q]Q - YYYY', // Q3 + year: 'YYYY' // 2015 + }, + }, + ticks: { + autoSkip: false, + + /** + * Ticks generation input values: + * - 'auto': generates "optimal" ticks based on scale size and time options. + * - 'data': generates ticks from data (including labels from data {t|x|y} objects). + * - 'labels': generates ticks from user given `data.labels` values ONLY. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + source: 'auto', + + major: { + enabled: false + } + } + }; + + var TimeScale = Chart.Scale.extend({ + initialize: function() { + if (!moment) { + throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); + } + + this.mergeTicksOptions(); + + Chart.Scale.prototype.initialize.call(this); + }, + + update: function() { + var me = this; + var options = me.options; + + // DEPRECATIONS: output a message only one time per update + if (options.time && options.time.format) { + console.warn('options.time.format is deprecated and replaced by options.time.parser.'); + } + + return Chart.Scale.prototype.update.apply(me, arguments); + }, + + /** + * Allows data to be referenced via 't' attribute + */ + getRightValue: function(rawValue) { + if (rawValue && rawValue.t !== undefined) { + rawValue = rawValue.t; + } + return Chart.Scale.prototype.getRightValue.call(this, rawValue); + }, + + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var timeOpts = me.options.time; + var min = MAX_INTEGER; + var max = MIN_INTEGER; + var timestamps = []; + var datasets = []; + var labels = []; + var i, j, ilen, jlen, data, timestamp; + + // Convert labels to timestamps + for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { + labels.push(parse(chart.data.labels[i], me)); + } + + // Convert data to timestamps + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + data = chart.data.datasets[i].data; + + // Let's consider that all data have the same format. + if (helpers.isObject(data[0])) { + datasets[i] = []; + + for (j = 0, jlen = data.length; j < jlen; ++j) { + timestamp = parse(data[j], me); + timestamps.push(timestamp); + datasets[i][j] = timestamp; + } + } else { + timestamps.push.apply(timestamps, labels); + datasets[i] = labels.slice(0); + } + } else { + datasets[i] = []; + } + } + + if (labels.length) { + // Sort labels **after** data have been converted + labels = arrayUnique(labels).sort(sorter); + min = Math.min(min, labels[0]); + max = Math.max(max, labels[labels.length - 1]); + } + + if (timestamps.length) { + timestamps = arrayUnique(timestamps).sort(sorter); + min = Math.min(min, timestamps[0]); + max = Math.max(max, timestamps[timestamps.length - 1]); + } + + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + + // In case there is no valid min/max, let's use today limits + min = min === MAX_INTEGER ? +moment().startOf('day') : min; + max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max; + + // Make sure that max is strictly higher than min (required by the lookup table) + me.min = Math.min(min, max); + me.max = Math.max(min + 1, max); + + // PRIVATE + me._horizontal = me.isHorizontal(); + me._table = []; + me._timestamps = { + data: timestamps, + datasets: datasets, + labels: labels + }; + }, + + buildTicks: function() { + var me = this; + var min = me.min; + var max = me.max; + var options = me.options; + var timeOpts = options.time; + var timestamps = []; + var ticks = []; + var i, ilen, timestamp; + + switch (options.ticks.source) { + case 'data': + timestamps = me._timestamps.data; + break; + case 'labels': + timestamps = me._timestamps.labels; + break; + case 'auto': + default: + timestamps = generate(min, max, me.getLabelCapacity(min), options); + } + + if (options.bounds === 'ticks' && timestamps.length) { + min = timestamps[0]; + max = timestamps[timestamps.length - 1]; + } + + // Enforce limits with user min/max options + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + + // Remove ticks outside the min/max range + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + timestamp = timestamps[i]; + if (timestamp >= min && timestamp <= max) { + ticks.push(timestamp); + } + } + + me.min = min; + me.max = max; + + // PRIVATE + me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); + me._majorUnit = determineMajorUnit(me._unit); + me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); + me._offsets = computeOffsets(me._table, ticks, min, max, options); + + return ticksFromTimestamps(ticks, me._majorUnit); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var timeOpts = me.options.time; + var label = data.labels && index < data.labels.length ? data.labels[index] : ''; + var value = data.datasets[datasetIndex].data[index]; + + if (helpers.isObject(value)) { + label = me.getRightValue(value); + } + if (timeOpts.tooltipFormat) { + label = momentify(label, timeOpts).format(timeOpts.tooltipFormat); + } + + return label; + }, + + /** + * Function to format an individual tick mark + * @private + */ + tickFormatFunction: function(tick, index, ticks, formatOverride) { + var me = this; + var options = me.options; + var time = tick.valueOf(); + var formats = options.time.displayFormats; + var minorFormat = formats[me._unit]; + var majorUnit = me._majorUnit; + var majorFormat = formats[majorUnit]; + var majorTime = tick.clone().startOf(majorUnit).valueOf(); + var majorTickOpts = options.ticks.major; + var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; + var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); + var tickOpts = major ? majorTickOpts : options.ticks.minor; + var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); + + return formatter ? formatter(label, index, ticks) : label; + }, + + convertTicksToLabels: function(ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); + } + + return labels; + }, + + /** + * @private + */ + getPixelForOffset: function(time) { + var me = this; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? me.left : me.top; + var pos = interpolate(me._table, 'time', time, 'pos'); + + return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); + }, + + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var time = null; + + if (index !== undefined && datasetIndex !== undefined) { + time = me._timestamps.datasets[datasetIndex][index]; + } + + if (time === null) { + time = parse(value, me); + } + + if (time !== null) { + return me.getPixelForOffset(time); + } + }, + + getPixelForTick: function(index) { + var ticks = this.getTicks(); + return index >= 0 && index < ticks.length ? + this.getPixelForOffset(ticks[index].value) : + null; + }, + + getValueForPixel: function(pixel) { + var me = this; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? me.left : me.top; + var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right; + var time = interpolate(me._table, 'pos', pos, 'time'); + + return moment(time); + }, + + /** + * Crude approximation of what the label width might be + * @private + */ + getLabelWidth: function(label) { + var me = this; + var ticksOpts = me.options.ticks; + var tickLabelWidth = me.ctx.measureText(label).width; + var angle = helpers.toRadians(ticksOpts.maxRotation); + var cosRotation = Math.cos(angle); + var sinRotation = Math.sin(angle); + var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); + + return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); + }, + + /** + * @private + */ + getLabelCapacity: function(exampleTime) { + var me = this; + + var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation + + var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); + var tickLabelWidth = me.getLabelWidth(exampleLabel); + var innerWidth = me.isHorizontal() ? me.width : me.height; + + return Math.floor(innerWidth / tickLabelWidth); + } + }); + + Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig); +}; + +},{"1":1,"25":25,"45":45}]},{},[7])(7) +}); \ No newline at end of file diff --git a/public/md/main-v2.js b/public/md/main-v2.js index 55a46d2c1..f17b9a7fe 100644 --- a/public/md/main-v2.js +++ b/public/md/main-v2.js @@ -12075,7 +12075,7 @@ SmartyPants does not modify characters within
, , , or 
 
 
+
+
+
 
 
 
diff --git a/public/css-libs/emoji.css b/public/css-libs/emoji.css
new file mode 100644
index 000000000..7d1a2b8e5
--- /dev/null
+++ b/public/css-libs/emoji.css
@@ -0,0 +1 @@
+.em,.em-svg{height:1.5em;width:1.5em;background-position:center;background-repeat:no-repeat;background-size:contain;display:inline-block;vertical-align:middle}.em---1{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f44d.png")}.em---1.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f44d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f44d.svg"),none}.em--1{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f44e.png")}.em--1.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f44e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f44e.svg"),none}.em-100{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4af.png")}.em-100.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4af.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4af.svg"),none}.em-1234{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f522.png")}.em-1234.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f522.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f522.svg"),none}.em-8ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b1.png")}.em-8ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b1.svg"),none}.em-a{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f170.png")}.em-a.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f170.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f170.svg"),none}.em-ab{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f18e.png")}.em-ab.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f18e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f18e.svg"),none}.em-abc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f524.png")}.em-abc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f524.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f524.svg"),none}.em-abcd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f521.png")}.em-abcd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f521.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f521.svg"),none}.em-accept{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f251.png")}.em-accept.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f251.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f251.svg"),none}.em-admission_tickets{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f39f.png")}.em-admission_tickets.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f39f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f39f.svg"),none}.em-adult{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d1.png")}.em-adult.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d1.svg"),none}.em-aerial_tramway{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a1.png")}.em-aerial_tramway.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a1.svg"),none}.em-airplane{background-image:url("https://twemoji.maxcdn.com/2/72x72/2708.png")}.em-airplane.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2708.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2708.svg"),none}.em-airplane_arriving{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6ec.png")}.em-airplane_arriving.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ec.svg"),none}.em-airplane_departure{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6eb.png")}.em-airplane_departure.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6eb.svg"),none}.em-alarm_clock{background-image:url("https://twemoji.maxcdn.com/2/72x72/23f0.png")}.em-alarm_clock.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23f0.svg"),none}.em-alembic{background-image:url("https://twemoji.maxcdn.com/2/72x72/2697.png")}.em-alembic.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2697.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2697.svg"),none}.em-alien{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f47d.png")}.em-alien.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f47d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f47d.svg"),none}.em-ambulance{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f691.png")}.em-ambulance.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f691.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f691.svg"),none}.em-amphora{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3fa.png")}.em-amphora.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fa.svg"),none}.em-anchor{background-image:url("https://twemoji.maxcdn.com/2/72x72/2693.png")}.em-anchor.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2693.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2693.svg"),none}.em-angel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f47c.png")}.em-angel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f47c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f47c.svg"),none}.em-anger{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a2.png")}.em-anger.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a2.svg"),none}.em-angry{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f620.png")}.em-angry.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f620.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f620.svg"),none}.em-anguished{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f627.png")}.em-anguished.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f627.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f627.svg"),none}.em-ant{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f41c.png")}.em-ant.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f41c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f41c.svg"),none}.em-apple{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f34e.png")}.em-apple.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f34e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f34e.svg"),none}.em-aquarius{background-image:url("https://twemoji.maxcdn.com/2/72x72/2652.png")}.em-aquarius.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2652.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2652.svg"),none}.em-aries{background-image:url("https://twemoji.maxcdn.com/2/72x72/2648.png")}.em-aries.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2648.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2648.svg"),none}.em-arrow_backward{background-image:url("https://twemoji.maxcdn.com/2/72x72/25c0.png")}.em-arrow_backward.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25c0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25c0.svg"),none}.em-arrow_double_down{background-image:url("https://twemoji.maxcdn.com/2/72x72/23ec.png")}.em-arrow_double_down.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23ec.svg"),none}.em-arrow_double_up{background-image:url("https://twemoji.maxcdn.com/2/72x72/23eb.png")}.em-arrow_double_up.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23eb.svg"),none}.em-arrow_down{background-image:url("https://twemoji.maxcdn.com/2/72x72/2b07.png")}.em-arrow_down.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2b07.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2b07.svg"),none}.em-arrow_down_small{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f53d.png")}.em-arrow_down_small.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f53d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f53d.svg"),none}.em-arrow_forward{background-image:url("https://twemoji.maxcdn.com/2/72x72/25b6.png")}.em-arrow_forward.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25b6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25b6.svg"),none}.em-arrow_heading_down{background-image:url("https://twemoji.maxcdn.com/2/72x72/2935.png")}.em-arrow_heading_down.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2935.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2935.svg"),none}.em-arrow_heading_up{background-image:url("https://twemoji.maxcdn.com/2/72x72/2934.png")}.em-arrow_heading_up.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2934.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2934.svg"),none}.em-arrow_left{background-image:url("https://twemoji.maxcdn.com/2/72x72/2b05.png")}.em-arrow_left.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2b05.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2b05.svg"),none}.em-arrow_lower_left{background-image:url("https://twemoji.maxcdn.com/2/72x72/2199.png")}.em-arrow_lower_left.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2199.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2199.svg"),none}.em-arrow_lower_right{background-image:url("https://twemoji.maxcdn.com/2/72x72/2198.png")}.em-arrow_lower_right.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2198.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2198.svg"),none}.em-arrow_right{background-image:url("https://twemoji.maxcdn.com/2/72x72/27a1.png")}.em-arrow_right.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/27a1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/27a1.svg"),none}.em-arrow_right_hook{background-image:url("https://twemoji.maxcdn.com/2/72x72/21aa.png")}.em-arrow_right_hook.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/21aa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/21aa.svg"),none}.em-arrow_up{background-image:url("https://twemoji.maxcdn.com/2/72x72/2b06.png")}.em-arrow_up.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2b06.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2b06.svg"),none}.em-arrow_up_down{background-image:url("https://twemoji.maxcdn.com/2/72x72/2195.png")}.em-arrow_up_down.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2195.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2195.svg"),none}.em-arrow_up_small{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f53c.png")}.em-arrow_up_small.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f53c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f53c.svg"),none}.em-arrow_upper_left{background-image:url("https://twemoji.maxcdn.com/2/72x72/2196.png")}.em-arrow_upper_left.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2196.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2196.svg"),none}.em-arrow_upper_right{background-image:url("https://twemoji.maxcdn.com/2/72x72/2197.png")}.em-arrow_upper_right.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2197.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2197.svg"),none}.em-arrows_clockwise{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f503.png")}.em-arrows_clockwise.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f503.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f503.svg"),none}.em-arrows_counterclockwise{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f504.png")}.em-arrows_counterclockwise.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f504.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f504.svg"),none}.em-art{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a8.png")}.em-art.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a8.svg"),none}.em-articulated_lorry{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f69b.png")}.em-articulated_lorry.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f69b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f69b.svg"),none}.em-astonished{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f632.png")}.em-astonished.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f632.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f632.svg"),none}.em-athletic_shoe{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f45f.png")}.em-athletic_shoe.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f45f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f45f.svg"),none}.em-atm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e7.png")}.em-atm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e7.svg"),none}.em-atom_symbol{background-image:url("https://twemoji.maxcdn.com/2/72x72/269b.png")}.em-atom_symbol.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/269b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/269b.svg"),none}.em-avocado{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f951.png")}.em-avocado.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f951.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f951.svg"),none}.em-b{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f171.png")}.em-b.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f171.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f171.svg"),none}.em-baby{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f476.png")}.em-baby.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f476.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f476.svg"),none}.em-baby_bottle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f37c.png")}.em-baby_bottle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f37c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f37c.svg"),none}.em-baby_chick{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f424.png")}.em-baby_chick.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f424.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f424.svg"),none}.em-baby_symbol{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6bc.png")}.em-baby_symbol.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6bc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bc.svg"),none}.em-back{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f519.png")}.em-back.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f519.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f519.svg"),none}.em-bacon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f953.png")}.em-bacon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f953.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f953.svg"),none}.em-badminton_racquet_and_shuttlecock{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f8.png")}.em-badminton_racquet_and_shuttlecock.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f8.svg"),none}.em-baggage_claim{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6c4.png")}.em-baggage_claim.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6c4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c4.svg"),none}.em-baguette_bread{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f956.png")}.em-baguette_bread.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f956.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f956.svg"),none}.em-balloon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f388.png")}.em-balloon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f388.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f388.svg"),none}.em-ballot_box_with_ballot{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5f3.png")}.em-ballot_box_with_ballot.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5f3.svg"),none}.em-ballot_box_with_check{background-image:url("https://twemoji.maxcdn.com/2/72x72/2611.png")}.em-ballot_box_with_check.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2611.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2611.svg"),none}.em-bamboo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f38d.png")}.em-bamboo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f38d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f38d.svg"),none}.em-banana{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f34c.png")}.em-banana.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f34c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f34c.svg"),none}.em-bangbang{background-image:url("https://twemoji.maxcdn.com/2/72x72/203c.png")}.em-bangbang.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/203c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/203c.svg"),none}.em-bank{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e6.png")}.em-bank.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e6.svg"),none}.em-bar_chart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ca.png")}.em-bar_chart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ca.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ca.svg"),none}.em-barber{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f488.png")}.em-barber.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f488.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f488.svg"),none}.em-barely_sunny{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f325.png")}.em-barely_sunny.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f325.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f325.svg"),none}.em-baseball{background-image:url("https://twemoji.maxcdn.com/2/72x72/26be.png")}.em-baseball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26be.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26be.svg"),none}.em-basketball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c0.png")}.em-basketball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c0.svg"),none}.em-bat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f987.png")}.em-bat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f987.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f987.svg"),none}.em-bath{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6c0.png")}.em-bath.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6c0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c0.svg"),none}.em-bathtub{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6c1.png")}.em-bathtub.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6c1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c1.svg"),none}.em-battery{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f50b.png")}.em-battery.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f50b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f50b.svg"),none}.em-beach_with_umbrella{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d6.png")}.em-beach_with_umbrella.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d6.svg"),none}.em-bear{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f43b.png")}.em-bear.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f43b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f43b.svg"),none}.em-bearded_person{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d4.png")}.em-bearded_person.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d4.svg"),none}.em-bed{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6cf.png")}.em-bed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6cf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cf.svg"),none}.em-bee{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f41d.png")}.em-bee.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f41d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f41d.svg"),none}.em-beer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f37a.png")}.em-beer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f37a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f37a.svg"),none}.em-beers{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f37b.png")}.em-beers.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f37b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f37b.svg"),none}.em-beetle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f41e.png")}.em-beetle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f41e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f41e.svg"),none}.em-beginner{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f530.png")}.em-beginner.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f530.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f530.svg"),none}.em-bell{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f514.png")}.em-bell.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f514.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f514.svg"),none}.em-bellhop_bell{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6ce.png")}.em-bellhop_bell.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6ce.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ce.svg"),none}.em-bento{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f371.png")}.em-bento.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f371.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f371.svg"),none}.em-bicyclist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b4.png")}.em-bicyclist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4.svg"),none}.em-bike{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b2.png")}.em-bike.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b2.svg"),none}.em-bikini{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f459.png")}.em-bikini.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f459.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f459.svg"),none}.em-billed_cap{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9e2.png")}.em-billed_cap.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9e2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9e2.svg"),none}.em-biohazard_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/2623.png")}.em-biohazard_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2623.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2623.svg"),none}.em-bird{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f426.png")}.em-bird.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f426.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f426.svg"),none}.em-birthday{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f382.png")}.em-birthday.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f382.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f382.svg"),none}.em-black_circle{background-image:url("https://twemoji.maxcdn.com/2/72x72/26ab.png")}.em-black_circle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26ab.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26ab.svg"),none}.em-black_circle_for_record{background-image:url("https://twemoji.maxcdn.com/2/72x72/23fa.png")}.em-black_circle_for_record.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23fa.svg"),none}.em-black_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5a4.png")}.em-black_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5a4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5a4.svg"),none}.em-black_joker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f0cf.png")}.em-black_joker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f0cf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f0cf.svg"),none}.em-black_large_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/2b1b.png")}.em-black_large_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2b1b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2b1b.svg"),none}.em-black_left_pointing_double_triangle_with_vertical_bar{background-image:url("https://twemoji.maxcdn.com/2/72x72/23ee.png")}.em-black_left_pointing_double_triangle_with_vertical_bar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23ee.svg"),none}.em-black_medium_small_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/25fe.png")}.em-black_medium_small_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25fe.svg"),none}.em-black_medium_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/25fc.png")}.em-black_medium_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25fc.svg"),none}.em-black_nib{background-image:url("https://twemoji.maxcdn.com/2/72x72/2712.png")}.em-black_nib.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2712.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2712.svg"),none}.em-black_right_pointing_double_triangle_with_vertical_bar{background-image:url("https://twemoji.maxcdn.com/2/72x72/23ed.png")}.em-black_right_pointing_double_triangle_with_vertical_bar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23ed.svg"),none}.em-black_right_pointing_triangle_with_double_vertical_bar{background-image:url("https://twemoji.maxcdn.com/2/72x72/23ef.png")}.em-black_right_pointing_triangle_with_double_vertical_bar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23ef.svg"),none}.em-black_small_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/25aa.png")}.em-black_small_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25aa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25aa.svg"),none}.em-black_square_button{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f532.png")}.em-black_square_button.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f532.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f532.svg"),none}.em-black_square_for_stop{background-image:url("https://twemoji.maxcdn.com/2/72x72/23f9.png")}.em-black_square_for_stop.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23f9.svg"),none}.em-blond-haired-man{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f471-200d-2642-fe0f.png")}.em-blond-haired-man.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f471-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f471-200d-2642-fe0f.svg"),none}.em-blond-haired-woman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f471-200d-2640-fe0f.png")}.em-blond-haired-woman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f471-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f471-200d-2640-fe0f.svg"),none}.em-blossom{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f33c.png")}.em-blossom.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f33c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f33c.svg"),none}.em-blowfish{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f421.png")}.em-blowfish.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f421.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f421.svg"),none}.em-blue_book{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d8.png")}.em-blue_book.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d8.svg"),none}.em-blue_car{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f699.png")}.em-blue_car.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f699.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f699.svg"),none}.em-blue_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f499.png")}.em-blue_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f499.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f499.svg"),none}.em-blush{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f60a.png")}.em-blush.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f60a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f60a.svg"),none}.em-boar{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f417.png")}.em-boar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f417.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f417.svg"),none}.em-boat{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f5.png")}.em-boat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f5.svg"),none}.em-bomb{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a3.png")}.em-bomb.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a3.svg"),none}.em-book{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d6.png")}.em-book.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d6.svg"),none}.em-bookmark{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f516.png")}.em-bookmark.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f516.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f516.svg"),none}.em-bookmark_tabs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d1.png")}.em-bookmark_tabs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d1.svg"),none}.em-books{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4da.png")}.em-books.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4da.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4da.svg"),none}.em-boom{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a5.png")}.em-boom.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a5.svg"),none}.em-boot{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f462.png")}.em-boot.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f462.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f462.svg"),none}.em-bouquet{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f490.png")}.em-bouquet.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f490.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f490.svg"),none}.em-bow{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f647.png")}.em-bow.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f647.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f647.svg"),none}.em-bow_and_arrow{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f9.png")}.em-bow_and_arrow.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f9.svg"),none}.em-bowl_with_spoon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f963.png")}.em-bowl_with_spoon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f963.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f963.svg"),none}.em-bowling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b3.png")}.em-bowling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b3.svg"),none}.em-boxing_glove{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f94a.png")}.em-boxing_glove.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f94a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f94a.svg"),none}.em-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f466.png")}.em-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f466.svg"),none}.em-brain{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9e0.png")}.em-brain.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9e0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9e0.svg"),none}.em-bread{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f35e.png")}.em-bread.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f35e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f35e.svg"),none}.em-breast-feeding{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f931.png")}.em-breast-feeding.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f931.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f931.svg"),none}.em-bride_with_veil{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f470.png")}.em-bride_with_veil.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f470.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f470.svg"),none}.em-bridge_at_night{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f309.png")}.em-bridge_at_night.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f309.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f309.svg"),none}.em-briefcase{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4bc.png")}.em-briefcase.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4bc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bc.svg"),none}.em-broccoli{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f966.png")}.em-broccoli.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f966.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f966.svg"),none}.em-broken_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f494.png")}.em-broken_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f494.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f494.svg"),none}.em-bug{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f41b.png")}.em-bug.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f41b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f41b.svg"),none}.em-building_construction{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d7.png")}.em-building_construction.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d7.svg"),none}.em-bulb{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a1.png")}.em-bulb.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a1.svg"),none}.em-bullettrain_front{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f685.png")}.em-bullettrain_front.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f685.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f685.svg"),none}.em-bullettrain_side{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f684.png")}.em-bullettrain_side.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f684.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f684.svg"),none}.em-burrito{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f32f.png")}.em-burrito.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f32f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f32f.svg"),none}.em-bus{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f68c.png")}.em-bus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f68c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f68c.svg"),none}.em-busstop{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f68f.png")}.em-busstop.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f68f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f68f.svg"),none}.em-bust_in_silhouette{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f464.png")}.em-bust_in_silhouette.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f464.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f464.svg"),none}.em-busts_in_silhouette{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f465.png")}.em-busts_in_silhouette.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f465.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f465.svg"),none}.em-butterfly{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f98b.png")}.em-butterfly.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f98b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f98b.svg"),none}.em-cactus{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f335.png")}.em-cactus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f335.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f335.svg"),none}.em-cake{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f370.png")}.em-cake.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f370.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f370.svg"),none}.em-calendar{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c6.png")}.em-calendar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c6.svg"),none}.em-call_me_hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f919.png")}.em-call_me_hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f919.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f919.svg"),none}.em-calling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f2.png")}.em-calling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f2.svg"),none}.em-camel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f42b.png")}.em-camel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f42b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f42b.svg"),none}.em-camera{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f7.png")}.em-camera.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f7.svg"),none}.em-camera_with_flash{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f8.png")}.em-camera_with_flash.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f8.svg"),none}.em-camping{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d5.png")}.em-camping.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d5.svg"),none}.em-cancer{background-image:url("https://twemoji.maxcdn.com/2/72x72/264b.png")}.em-cancer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/264b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/264b.svg"),none}.em-candle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f56f.png")}.em-candle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f56f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f56f.svg"),none}.em-candy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f36c.png")}.em-candy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f36c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f36c.svg"),none}.em-canned_food{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f96b.png")}.em-canned_food.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f96b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f96b.svg"),none}.em-canoe{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6f6.png")}.em-canoe.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6f6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f6.svg"),none}.em-capital_abcd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f520.png")}.em-capital_abcd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f520.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f520.svg"),none}.em-capricorn{background-image:url("https://twemoji.maxcdn.com/2/72x72/2651.png")}.em-capricorn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2651.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2651.svg"),none}.em-car{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f697.png")}.em-car.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f697.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f697.svg"),none}.em-card_file_box{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5c3.png")}.em-card_file_box.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5c3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5c3.svg"),none}.em-card_index{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c7.png")}.em-card_index.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c7.svg"),none}.em-card_index_dividers{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5c2.png")}.em-card_index_dividers.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5c2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5c2.svg"),none}.em-carousel_horse{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a0.png")}.em-carousel_horse.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a0.svg"),none}.em-carrot{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f955.png")}.em-carrot.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f955.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f955.svg"),none}.em-cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f431.png")}.em-cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f431.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f431.svg"),none}.em-cat2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f408.png")}.em-cat2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f408.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f408.svg"),none}.em-cd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4bf.png")}.em-cd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4bf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bf.svg"),none}.em-chains{background-image:url("https://twemoji.maxcdn.com/2/72x72/26d3.png")}.em-chains.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26d3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26d3.svg"),none}.em-champagne{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f37e.png")}.em-champagne.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f37e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f37e.svg"),none}.em-chart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b9.png")}.em-chart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b9.svg"),none}.em-chart_with_downwards_trend{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c9.png")}.em-chart_with_downwards_trend.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c9.svg"),none}.em-chart_with_upwards_trend{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c8.png")}.em-chart_with_upwards_trend.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c8.svg"),none}.em-checkered_flag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c1.png")}.em-checkered_flag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c1.svg"),none}.em-cheese_wedge{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9c0.png")}.em-cheese_wedge.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9c0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9c0.svg"),none}.em-cherries{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f352.png")}.em-cherries.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f352.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f352.svg"),none}.em-cherry_blossom{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f338.png")}.em-cherry_blossom.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f338.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f338.svg"),none}.em-chestnut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f330.png")}.em-chestnut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f330.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f330.svg"),none}.em-chicken{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f414.png")}.em-chicken.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f414.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f414.svg"),none}.em-child{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d2.png")}.em-child.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d2.svg"),none}.em-children_crossing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b8.png")}.em-children_crossing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b8.svg"),none}.em-chipmunk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f43f.png")}.em-chipmunk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f43f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f43f.svg"),none}.em-chocolate_bar{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f36b.png")}.em-chocolate_bar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f36b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f36b.svg"),none}.em-chopsticks{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f962.png")}.em-chopsticks.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f962.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f962.svg"),none}.em-christmas_tree{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f384.png")}.em-christmas_tree.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f384.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f384.svg"),none}.em-church{background-image:url("https://twemoji.maxcdn.com/2/72x72/26ea.png")}.em-church.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26ea.svg"),none}.em-cinema{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a6.png")}.em-cinema.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a6.svg"),none}.em-circus_tent{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3aa.png")}.em-circus_tent.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3aa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3aa.svg"),none}.em-city_sunrise{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f307.png")}.em-city_sunrise.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f307.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f307.svg"),none}.em-city_sunset{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f306.png")}.em-city_sunset.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f306.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f306.svg"),none}.em-cityscape{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d9.png")}.em-cityscape.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d9.svg"),none}.em-cl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f191.png")}.em-cl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f191.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f191.svg"),none}.em-clap{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f44f.png")}.em-clap.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f44f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f44f.svg"),none}.em-clapper{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ac.png")}.em-clapper.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ac.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ac.svg"),none}.em-classical_building{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3db.png")}.em-classical_building.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3db.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3db.svg"),none}.em-clinking_glasses{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f942.png")}.em-clinking_glasses.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f942.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f942.svg"),none}.em-clipboard{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4cb.png")}.em-clipboard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4cb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cb.svg"),none}.em-clock1{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f550.png")}.em-clock1.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f550.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f550.svg"),none}.em-clock10{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f559.png")}.em-clock10.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f559.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f559.svg"),none}.em-clock1030{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f565.png")}.em-clock1030.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f565.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f565.svg"),none}.em-clock11{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f55a.png")}.em-clock11.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f55a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f55a.svg"),none}.em-clock1130{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f566.png")}.em-clock1130.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f566.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f566.svg"),none}.em-clock12{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f55b.png")}.em-clock12.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f55b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f55b.svg"),none}.em-clock1230{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f567.png")}.em-clock1230.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f567.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f567.svg"),none}.em-clock130{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f55c.png")}.em-clock130.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f55c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f55c.svg"),none}.em-clock2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f551.png")}.em-clock2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f551.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f551.svg"),none}.em-clock230{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f55d.png")}.em-clock230.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f55d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f55d.svg"),none}.em-clock3{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f552.png")}.em-clock3.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f552.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f552.svg"),none}.em-clock330{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f55e.png")}.em-clock330.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f55e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f55e.svg"),none}.em-clock4{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f553.png")}.em-clock4.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f553.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f553.svg"),none}.em-clock430{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f55f.png")}.em-clock430.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f55f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f55f.svg"),none}.em-clock5{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f554.png")}.em-clock5.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f554.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f554.svg"),none}.em-clock530{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f560.png")}.em-clock530.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f560.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f560.svg"),none}.em-clock6{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f555.png")}.em-clock6.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f555.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f555.svg"),none}.em-clock630{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f561.png")}.em-clock630.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f561.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f561.svg"),none}.em-clock7{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f556.png")}.em-clock7.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f556.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f556.svg"),none}.em-clock730{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f562.png")}.em-clock730.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f562.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f562.svg"),none}.em-clock8{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f557.png")}.em-clock8.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f557.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f557.svg"),none}.em-clock830{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f563.png")}.em-clock830.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f563.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f563.svg"),none}.em-clock9{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f558.png")}.em-clock9.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f558.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f558.svg"),none}.em-clock930{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f564.png")}.em-clock930.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f564.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f564.svg"),none}.em-closed_book{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d5.png")}.em-closed_book.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d5.svg"),none}.em-closed_lock_with_key{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f510.png")}.em-closed_lock_with_key.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f510.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f510.svg"),none}.em-closed_umbrella{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f302.png")}.em-closed_umbrella.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f302.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f302.svg"),none}.em-cloud{background-image:url("https://twemoji.maxcdn.com/2/72x72/2601.png")}.em-cloud.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2601.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2601.svg"),none}.em-clown_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f921.png")}.em-clown_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f921.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f921.svg"),none}.em-clubs{background-image:url("https://twemoji.maxcdn.com/2/72x72/2663.png")}.em-clubs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2663.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2663.svg"),none}.em-cn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f3.png")}.em-cn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f3.svg"),none}.em-coat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9e5.png")}.em-coat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9e5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9e5.svg"),none}.em-cocktail{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f378.png")}.em-cocktail.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f378.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f378.svg"),none}.em-coconut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f965.png")}.em-coconut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f965.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f965.svg"),none}.em-coffee{background-image:url("https://twemoji.maxcdn.com/2/72x72/2615.png")}.em-coffee.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2615.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2615.svg"),none}.em-coffin{background-image:url("https://twemoji.maxcdn.com/2/72x72/26b0.png")}.em-coffin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26b0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26b0.svg"),none}.em-cold_sweat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f630.png")}.em-cold_sweat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f630.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f630.svg"),none}.em-comet{background-image:url("https://twemoji.maxcdn.com/2/72x72/2604.png")}.em-comet.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2604.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2604.svg"),none}.em-compression{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5dc.png")}.em-compression.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5dc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5dc.svg"),none}.em-computer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4bb.png")}.em-computer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4bb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bb.svg"),none}.em-confetti_ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f38a.png")}.em-confetti_ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f38a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f38a.svg"),none}.em-confounded{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f616.png")}.em-confounded.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f616.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f616.svg"),none}.em-confused{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f615.png")}.em-confused.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f615.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f615.svg"),none}.em-congratulations{background-image:url("https://twemoji.maxcdn.com/2/72x72/3297.png")}.em-congratulations.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/3297.png");background-image:url("https://twemoji.maxcdn.com/2/svg/3297.svg"),none}.em-construction{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a7.png")}.em-construction.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a7.svg"),none}.em-construction_worker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f477.png")}.em-construction_worker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f477.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f477.svg"),none}.em-control_knobs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f39b.png")}.em-control_knobs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f39b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f39b.svg"),none}.em-convenience_store{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ea.png")}.em-convenience_store.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ea.svg"),none}.em-cookie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f36a.png")}.em-cookie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f36a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f36a.svg"),none}.em-cool{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f192.png")}.em-cool.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f192.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f192.svg"),none}.em-cop{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46e.png")}.em-cop.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e.svg"),none}.em-copyright{background-image:url("https://twemoji.maxcdn.com/2/72x72/a9.png")}.em-copyright.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/a9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/a9.svg"),none}.em-corn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f33d.png")}.em-corn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f33d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f33d.svg"),none}.em-couch_and_lamp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6cb.png")}.em-couch_and_lamp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6cb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cb.svg"),none}.em-couple{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46b.png")}.em-couple.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46b.svg"),none}.em-couple_with_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f491.png")}.em-couple_with_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f491.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f491.svg"),none}.em-couplekiss{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f48f.png")}.em-couplekiss.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f48f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f48f.svg"),none}.em-cow{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f42e.png")}.em-cow.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f42e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f42e.svg"),none}.em-cow2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f404.png")}.em-cow2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f404.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f404.svg"),none}.em-crab{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f980.png")}.em-crab.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f980.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f980.svg"),none}.em-credit_card{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b3.png")}.em-credit_card.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b3.svg"),none}.em-crescent_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f319.png")}.em-crescent_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f319.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f319.svg"),none}.em-cricket{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f997.png")}.em-cricket.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f997.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f997.svg"),none}.em-cricket_bat_and_ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cf.png")}.em-cricket_bat_and_ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cf.svg"),none}.em-crocodile{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f40a.png")}.em-crocodile.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f40a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f40a.svg"),none}.em-croissant{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f950.png")}.em-croissant.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f950.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f950.svg"),none}.em-crossed_flags{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f38c.png")}.em-crossed_flags.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f38c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f38c.svg"),none}.em-crossed_swords{background-image:url("https://twemoji.maxcdn.com/2/72x72/2694.png")}.em-crossed_swords.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2694.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2694.svg"),none}.em-crown{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f451.png")}.em-crown.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f451.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f451.svg"),none}.em-cry{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f622.png")}.em-cry.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f622.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f622.svg"),none}.em-crying_cat_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f63f.png")}.em-crying_cat_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f63f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f63f.svg"),none}.em-crystal_ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f52e.png")}.em-crystal_ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f52e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f52e.svg"),none}.em-cucumber{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f952.png")}.em-cucumber.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f952.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f952.svg"),none}.em-cup_with_straw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f964.png")}.em-cup_with_straw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f964.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f964.svg"),none}.em-cupid{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f498.png")}.em-cupid.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f498.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f498.svg"),none}.em-curling_stone{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f94c.png")}.em-curling_stone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f94c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f94c.svg"),none}.em-curly_loop{background-image:url("https://twemoji.maxcdn.com/2/72x72/27b0.png")}.em-curly_loop.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/27b0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/27b0.svg"),none}.em-currency_exchange{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b1.png")}.em-currency_exchange.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b1.svg"),none}.em-curry{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f35b.png")}.em-curry.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f35b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f35b.svg"),none}.em-custard{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f36e.png")}.em-custard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f36e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f36e.svg"),none}.em-customs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6c3.png")}.em-customs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6c3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c3.svg"),none}.em-cut_of_meat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f969.png")}.em-cut_of_meat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f969.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f969.svg"),none}.em-cyclone{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f300.png")}.em-cyclone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f300.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f300.svg"),none}.em-dagger_knife{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5e1.png")}.em-dagger_knife.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5e1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5e1.svg"),none}.em-dancer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f483.png")}.em-dancer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f483.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f483.svg"),none}.em-dancers{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46f.png")}.em-dancers.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46f.svg"),none}.em-dango{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f361.png")}.em-dango.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f361.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f361.svg"),none}.em-dark_sunglasses{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f576.png")}.em-dark_sunglasses.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f576.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f576.svg"),none}.em-dart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3af.png")}.em-dart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3af.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3af.svg"),none}.em-dash{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a8.png")}.em-dash.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a8.svg"),none}.em-date{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c5.png")}.em-date.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c5.svg"),none}.em-de{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ea.png")}.em-de.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ea.svg"),none}.em-deciduous_tree{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f333.png")}.em-deciduous_tree.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f333.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f333.svg"),none}.em-deer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f98c.png")}.em-deer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f98c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f98c.svg"),none}.em-department_store{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ec.png")}.em-department_store.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ec.svg"),none}.em-derelict_house_building{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3da.png")}.em-derelict_house_building.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3da.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3da.svg"),none}.em-desert{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3dc.png")}.em-desert.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3dc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3dc.svg"),none}.em-desert_island{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3dd.png")}.em-desert_island.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3dd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3dd.svg"),none}.em-desktop_computer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5a5.png")}.em-desktop_computer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5a5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5a5.svg"),none}.em-diamond_shape_with_a_dot_inside{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a0.png")}.em-diamond_shape_with_a_dot_inside.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a0.svg"),none}.em-diamonds{background-image:url("https://twemoji.maxcdn.com/2/72x72/2666.png")}.em-diamonds.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2666.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2666.svg"),none}.em-disappointed{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f61e.png")}.em-disappointed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f61e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f61e.svg"),none}.em-disappointed_relieved{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f625.png")}.em-disappointed_relieved.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f625.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f625.svg"),none}.em-dizzy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ab.png")}.em-dizzy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ab.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ab.svg"),none}.em-dizzy_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f635.png")}.em-dizzy_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f635.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f635.svg"),none}.em-do_not_litter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6af.png")}.em-do_not_litter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6af.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6af.svg"),none}.em-dog{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f436.png")}.em-dog.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f436.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f436.svg"),none}.em-dog2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f415.png")}.em-dog2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f415.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f415.svg"),none}.em-dollar{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b5.png")}.em-dollar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b5.svg"),none}.em-dolls{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f38e.png")}.em-dolls.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f38e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f38e.svg"),none}.em-dolphin{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f42c.png")}.em-dolphin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f42c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f42c.svg"),none}.em-door{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6aa.png")}.em-door.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6aa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6aa.svg"),none}.em-double_vertical_bar{background-image:url("https://twemoji.maxcdn.com/2/72x72/23f8.png")}.em-double_vertical_bar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23f8.svg"),none}.em-doughnut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f369.png")}.em-doughnut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f369.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f369.svg"),none}.em-dove_of_peace{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f54a.png")}.em-dove_of_peace.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f54a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f54a.svg"),none}.em-dragon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f409.png")}.em-dragon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f409.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f409.svg"),none}.em-dragon_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f432.png")}.em-dragon_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f432.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f432.svg"),none}.em-dress{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f457.png")}.em-dress.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f457.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f457.svg"),none}.em-dromedary_camel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f42a.png")}.em-dromedary_camel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f42a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f42a.svg"),none}.em-drooling_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f924.png")}.em-drooling_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f924.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f924.svg"),none}.em-droplet{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a7.png")}.em-droplet.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a7.svg"),none}.em-drum_with_drumsticks{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f941.png")}.em-drum_with_drumsticks.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f941.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f941.svg"),none}.em-duck{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f986.png")}.em-duck.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f986.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f986.svg"),none}.em-dumpling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f95f.png")}.em-dumpling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f95f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f95f.svg"),none}.em-dvd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c0.png")}.em-dvd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c0.svg"),none}.em-e-mail{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e7.png")}.em-e-mail.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e7.svg"),none}.em-eagle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f985.png")}.em-eagle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f985.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f985.svg"),none}.em-ear{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f442.png")}.em-ear.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f442.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f442.svg"),none}.em-ear_of_rice{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f33e.png")}.em-ear_of_rice.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f33e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f33e.svg"),none}.em-earth_africa{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f30d.png")}.em-earth_africa.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f30d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f30d.svg"),none}.em-earth_americas{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f30e.png")}.em-earth_americas.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f30e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f30e.svg"),none}.em-earth_asia{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f30f.png")}.em-earth_asia.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f30f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f30f.svg"),none}.em-egg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f95a.png")}.em-egg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f95a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f95a.svg"),none}.em-eggplant{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f346.png")}.em-eggplant.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f346.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f346.svg"),none}.em-eight{background-image:url("https://twemoji.maxcdn.com/2/72x72/38-20e3.png")}.em-eight.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/38-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/38-20e3.svg"),none}.em-eight_pointed_black_star{background-image:url("https://twemoji.maxcdn.com/2/72x72/2734.png")}.em-eight_pointed_black_star.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2734.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2734.svg"),none}.em-eight_spoked_asterisk{background-image:url("https://twemoji.maxcdn.com/2/72x72/2733.png")}.em-eight_spoked_asterisk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2733.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2733.svg"),none}.em-eject{background-image:url("https://twemoji.maxcdn.com/2/72x72/23cf.png")}.em-eject.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23cf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23cf.svg"),none}.em-electric_plug{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f50c.png")}.em-electric_plug.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f50c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f50c.svg"),none}.em-elephant{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f418.png")}.em-elephant.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f418.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f418.svg"),none}.em-elf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9dd.png")}.em-elf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9dd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9dd.svg"),none}.em-email{background-image:url("https://twemoji.maxcdn.com/2/72x72/2709.png")}.em-email.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2709.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2709.svg"),none}.em-end{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f51a.png")}.em-end.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f51a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f51a.svg"),none}.em-envelope_with_arrow{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e9.png")}.em-envelope_with_arrow.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e9.svg"),none}.em-es{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1f8.png")}.em-es.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1f8.svg"),none}.em-euro{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b6.png")}.em-euro.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b6.svg"),none}.em-european_castle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f0.png")}.em-european_castle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f0.svg"),none}.em-european_post_office{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e4.png")}.em-european_post_office.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e4.svg"),none}.em-evergreen_tree{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f332.png")}.em-evergreen_tree.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f332.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f332.svg"),none}.em-exclamation{background-image:url("https://twemoji.maxcdn.com/2/72x72/2757.png")}.em-exclamation.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2757.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2757.svg"),none}.em-expressionless{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f611.png")}.em-expressionless.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f611.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f611.svg"),none}.em-eye{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f441.png")}.em-eye.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f441.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f441.svg"),none}.em-eyeglasses{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f453.png")}.em-eyeglasses.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f453.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f453.svg"),none}.em-eyes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f440.png")}.em-eyes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f440.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f440.svg"),none}.em-face_palm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f926.png")}.em-face_palm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f926.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f926.svg"),none}.em-face_with_cowboy_hat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f920.png")}.em-face_with_cowboy_hat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f920.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f920.svg"),none}.em-face_with_finger_covering_closed_lips{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f92b.png")}.em-face_with_finger_covering_closed_lips.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f92b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f92b.svg"),none}.em-face_with_head_bandage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f915.png")}.em-face_with_head_bandage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f915.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f915.svg"),none}.em-face_with_monocle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d0.png")}.em-face_with_monocle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d0.svg"),none}.em-face_with_one_eyebrow_raised{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f928.png")}.em-face_with_one_eyebrow_raised.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f928.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f928.svg"),none}.em-face_with_open_mouth_vomiting{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f92e.png")}.em-face_with_open_mouth_vomiting.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f92e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f92e.svg"),none}.em-face_with_rolling_eyes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f644.png")}.em-face_with_rolling_eyes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f644.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f644.svg"),none}.em-face_with_thermometer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f912.png")}.em-face_with_thermometer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f912.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f912.svg"),none}.em-facepunch{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f44a.png")}.em-facepunch.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f44a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f44a.svg"),none}.em-factory{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ed.png")}.em-factory.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ed.svg"),none}.em-fairy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9da.png")}.em-fairy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9da.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9da.svg"),none}.em-fallen_leaf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f342.png")}.em-fallen_leaf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f342.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f342.svg"),none}.em-family{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46a.png")}.em-family.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46a.svg"),none}.em-fast_forward{background-image:url("https://twemoji.maxcdn.com/2/72x72/23e9.png")}.em-fast_forward.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23e9.svg"),none}.em-fax{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e0.png")}.em-fax.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e0.svg"),none}.em-fearful{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f628.png")}.em-fearful.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f628.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f628.svg"),none}.em-feet{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f43e.png")}.em-feet.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f43e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f43e.svg"),none}.em-female-artist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3a8.png")}.em-female-artist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3a8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f3a8.svg"),none}.em-female-astronaut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f680.png")}.em-female-astronaut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f680.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f680.svg"),none}.em-female-construction-worker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f477-200d-2640-fe0f.png")}.em-female-construction-worker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f477-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f477-200d-2640-fe0f.svg"),none}.em-female-cook{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f373.png")}.em-female-cook.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f373.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f373.svg"),none}.em-female-detective{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f575-fe0f-200d-2640-fe0f.png")}.em-female-detective.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f575-fe0f-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f575-fe0f-200d-2640-fe0f.svg"),none}.em-female-doctor{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2695-fe0f.png")}.em-female-doctor.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2695-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2695-fe0f.svg"),none}.em-female-factory-worker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3ed.png")}.em-female-factory-worker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f3ed.svg"),none}.em-female-farmer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f33e.png")}.em-female-farmer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f33e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f33e.svg"),none}.em-female-firefighter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f692.png")}.em-female-firefighter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f692.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f692.svg"),none}.em-female-guard{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f482-200d-2640-fe0f.png")}.em-female-guard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f482-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f482-200d-2640-fe0f.svg"),none}.em-female-judge{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2696-fe0f.png")}.em-female-judge.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2696-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2696-fe0f.svg"),none}.em-female-mechanic{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f527.png")}.em-female-mechanic.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f527.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f527.svg"),none}.em-female-office-worker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f4bc.png")}.em-female-office-worker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f4bc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f4bc.svg"),none}.em-female-pilot{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2708-fe0f.png")}.em-female-pilot.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2708-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2708-fe0f.svg"),none}.em-female-police-officer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46e-200d-2640-fe0f.png")}.em-female-police-officer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46e-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e-200d-2640-fe0f.svg"),none}.em-female-scientist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f52c.png")}.em-female-scientist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f52c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f52c.svg"),none}.em-female-singer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3a4.png")}.em-female-singer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3a4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f3a4.svg"),none}.em-female-student{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f393.png")}.em-female-student.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f393.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f393.svg"),none}.em-female-teacher{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3eb.png")}.em-female-teacher.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f3eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f3eb.svg"),none}.em-female-technologist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f4bb.png")}.em-female-technologist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f4bb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f4bb.svg"),none}.em-female_elf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9dd-200d-2640-fe0f.png")}.em-female_elf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9dd-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9dd-200d-2640-fe0f.svg"),none}.em-female_fairy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9da-200d-2640-fe0f.png")}.em-female_fairy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9da-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9da-200d-2640-fe0f.svg"),none}.em-female_genie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9de-200d-2640-fe0f.png")}.em-female_genie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9de-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9de-200d-2640-fe0f.svg"),none}.em-female_mage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d9-200d-2640-fe0f.png")}.em-female_mage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d9-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d9-200d-2640-fe0f.svg"),none}.em-female_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/2640.png")}.em-female_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2640.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2640.svg"),none}.em-female_vampire{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9db-200d-2640-fe0f.png")}.em-female_vampire.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9db-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9db-200d-2640-fe0f.svg"),none}.em-female_zombie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9df-200d-2640-fe0f.png")}.em-female_zombie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9df-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9df-200d-2640-fe0f.svg"),none}.em-fencer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93a.png")}.em-fencer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93a.svg"),none}.em-ferris_wheel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a1.png")}.em-ferris_wheel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a1.svg"),none}.em-ferry{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f4.png")}.em-ferry.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f4.svg"),none}.em-field_hockey_stick_and_ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d1.png")}.em-field_hockey_stick_and_ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d1.svg"),none}.em-file_cabinet{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5c4.png")}.em-file_cabinet.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5c4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5c4.svg"),none}.em-file_folder{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c1.png")}.em-file_folder.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c1.svg"),none}.em-film_frames{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f39e.png")}.em-film_frames.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f39e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f39e.svg"),none}.em-film_projector{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4fd.png")}.em-film_projector.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fd.svg"),none}.em-fire{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f525.png")}.em-fire.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f525.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f525.svg"),none}.em-fire_engine{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f692.png")}.em-fire_engine.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f692.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f692.svg"),none}.em-fireworks{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f386.png")}.em-fireworks.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f386.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f386.svg"),none}.em-first_place_medal{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f947.png")}.em-first_place_medal.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f947.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f947.svg"),none}.em-first_quarter_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f313.png")}.em-first_quarter_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f313.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f313.svg"),none}.em-first_quarter_moon_with_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f31b.png")}.em-first_quarter_moon_with_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f31b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f31b.svg"),none}.em-fish{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f41f.png")}.em-fish.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f41f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f41f.svg"),none}.em-fish_cake{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f365.png")}.em-fish_cake.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f365.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f365.svg"),none}.em-fishing_pole_and_fish{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a3.png")}.em-fishing_pole_and_fish.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a3.svg"),none}.em-fist{background-image:url("https://twemoji.maxcdn.com/2/72x72/270a.png")}.em-fist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/270a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/270a.svg"),none}.em-five{background-image:url("https://twemoji.maxcdn.com/2/72x72/35-20e3.png")}.em-five.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/35-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/35-20e3.svg"),none}.em-flag-ac{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1e8.png")}.em-flag-ac.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1e8.svg"),none}.em-flag-ad{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1e9.png")}.em-flag-ad.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1e9.svg"),none}.em-flag-ae{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ea.png")}.em-flag-ae.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ea.svg"),none}.em-flag-af{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1eb.png")}.em-flag-af.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1eb.svg"),none}.em-flag-ag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ec.png")}.em-flag-ag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ec.svg"),none}.em-flag-ai{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ee.png")}.em-flag-ai.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ee.svg"),none}.em-flag-al{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f1.png")}.em-flag-al.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f1.svg"),none}.em-flag-am{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f2.png")}.em-flag-am.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f2.svg"),none}.em-flag-ao{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f4.png")}.em-flag-ao.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f4.svg"),none}.em-flag-aq{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f6.png")}.em-flag-aq.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f6.svg"),none}.em-flag-ar{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f7.png")}.em-flag-ar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f7.svg"),none}.em-flag-as{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f8.png")}.em-flag-as.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f8.svg"),none}.em-flag-at{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f9.png")}.em-flag-at.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1f9.svg"),none}.em-flag-au{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1fa.png")}.em-flag-au.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1fa.svg"),none}.em-flag-aw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1fc.png")}.em-flag-aw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1fc.svg"),none}.em-flag-ax{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1fd.png")}.em-flag-ax.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1fd.svg"),none}.em-flag-az{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ff.png")}.em-flag-az.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e6-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e6-1f1ff.svg"),none}.em-flag-ba{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1e6.png")}.em-flag-ba.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1e6.svg"),none}.em-flag-bb{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1e7.png")}.em-flag-bb.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1e7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1e7.svg"),none}.em-flag-bd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1e9.png")}.em-flag-bd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1e9.svg"),none}.em-flag-be{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ea.png")}.em-flag-be.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ea.svg"),none}.em-flag-bf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1eb.png")}.em-flag-bf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1eb.svg"),none}.em-flag-bg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ec.png")}.em-flag-bg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ec.svg"),none}.em-flag-bh{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ed.png")}.em-flag-bh.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ed.svg"),none}.em-flag-bi{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ee.png")}.em-flag-bi.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ee.svg"),none}.em-flag-bj{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ef.png")}.em-flag-bj.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ef.svg"),none}.em-flag-bl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f1.png")}.em-flag-bl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f1.svg"),none}.em-flag-bm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f2.png")}.em-flag-bm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f2.svg"),none}.em-flag-bn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f3.png")}.em-flag-bn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f3.svg"),none}.em-flag-bo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f4.png")}.em-flag-bo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f4.svg"),none}.em-flag-bq{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f6.png")}.em-flag-bq.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f6.svg"),none}.em-flag-br{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f7.png")}.em-flag-br.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f7.svg"),none}.em-flag-bs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f8.png")}.em-flag-bs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f8.svg"),none}.em-flag-bt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f9.png")}.em-flag-bt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1f9.svg"),none}.em-flag-bv{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1fb.png")}.em-flag-bv.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1fb.svg"),none}.em-flag-bw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1fc.png")}.em-flag-bw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1fc.svg"),none}.em-flag-by{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1fe.png")}.em-flag-by.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1fe.svg"),none}.em-flag-bz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ff.png")}.em-flag-bz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e7-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e7-1f1ff.svg"),none}.em-flag-ca{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1e6.png")}.em-flag-ca.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1e6.svg"),none}.em-flag-cc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1e8.png")}.em-flag-cc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1e8.svg"),none}.em-flag-cd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1e9.png")}.em-flag-cd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1e9.svg"),none}.em-flag-cf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1eb.png")}.em-flag-cf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1eb.svg"),none}.em-flag-cg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ec.png")}.em-flag-cg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ec.svg"),none}.em-flag-ch{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ed.png")}.em-flag-ch.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ed.svg"),none}.em-flag-ci{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ee.png")}.em-flag-ci.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ee.svg"),none}.em-flag-ck{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f0.png")}.em-flag-ck.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f0.svg"),none}.em-flag-cl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f1.png")}.em-flag-cl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f1.svg"),none}.em-flag-cm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f2.png")}.em-flag-cm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f2.svg"),none}.em-flag-co{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f4.png")}.em-flag-co.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f4.svg"),none}.em-flag-cp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f5.png")}.em-flag-cp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f5.svg"),none}.em-flag-cr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f7.png")}.em-flag-cr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1f7.svg"),none}.em-flag-cu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fa.png")}.em-flag-cu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fa.svg"),none}.em-flag-cv{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fb.png")}.em-flag-cv.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fb.svg"),none}.em-flag-cw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fc.png")}.em-flag-cw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fc.svg"),none}.em-flag-cx{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fd.png")}.em-flag-cx.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fd.svg"),none}.em-flag-cy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fe.png")}.em-flag-cy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1fe.svg"),none}.em-flag-cz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ff.png")}.em-flag-cz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e8-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e8-1f1ff.svg"),none}.em-flag-dg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ec.png")}.em-flag-dg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ec.svg"),none}.em-flag-dj{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ef.png")}.em-flag-dj.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ef.svg"),none}.em-flag-dk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1f0.png")}.em-flag-dk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1f0.svg"),none}.em-flag-dm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1f2.png")}.em-flag-dm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1f2.svg"),none}.em-flag-do{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1f4.png")}.em-flag-do.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1f4.svg"),none}.em-flag-dz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ff.png")}.em-flag-dz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1e9-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1e9-1f1ff.svg"),none}.em-flag-ea{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1e6.png")}.em-flag-ea.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1e6.svg"),none}.em-flag-ec{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1e8.png")}.em-flag-ec.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1e8.svg"),none}.em-flag-ee{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1ea.png")}.em-flag-ee.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1ea.svg"),none}.em-flag-eg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1ec.png")}.em-flag-eg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1ec.svg"),none}.em-flag-eh{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1ed.png")}.em-flag-eh.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1ed.svg"),none}.em-flag-england{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png")}.em-flag-england.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.svg"),none}.em-flag-er{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1f7.png")}.em-flag-er.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1f7.svg"),none}.em-flag-et{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1f9.png")}.em-flag-et.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1f9.svg"),none}.em-flag-eu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1fa.png")}.em-flag-eu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ea-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ea-1f1fa.svg"),none}.em-flag-fi{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1ee.png")}.em-flag-fi.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1ee.svg"),none}.em-flag-fj{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1ef.png")}.em-flag-fj.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1ef.svg"),none}.em-flag-fk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f0.png")}.em-flag-fk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f0.svg"),none}.em-flag-fm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f2.png")}.em-flag-fm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f2.svg"),none}.em-flag-fo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f4.png")}.em-flag-fo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f4.svg"),none}.em-flag-ga{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1e6.png")}.em-flag-ga.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1e6.svg"),none}.em-flag-gd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1e9.png")}.em-flag-gd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1e9.svg"),none}.em-flag-ge{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ea.png")}.em-flag-ge.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ea.svg"),none}.em-flag-gf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1eb.png")}.em-flag-gf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1eb.svg"),none}.em-flag-gg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ec.png")}.em-flag-gg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ec.svg"),none}.em-flag-gh{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ed.png")}.em-flag-gh.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ed.svg"),none}.em-flag-gi{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ee.png")}.em-flag-gi.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1ee.svg"),none}.em-flag-gl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f1.png")}.em-flag-gl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f1.svg"),none}.em-flag-gm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f2.png")}.em-flag-gm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f2.svg"),none}.em-flag-gn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f3.png")}.em-flag-gn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f3.svg"),none}.em-flag-gp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f5.png")}.em-flag-gp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f5.svg"),none}.em-flag-gq{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f6.png")}.em-flag-gq.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f6.svg"),none}.em-flag-gr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f7.png")}.em-flag-gr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f7.svg"),none}.em-flag-gs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f8.png")}.em-flag-gs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f8.svg"),none}.em-flag-gt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f9.png")}.em-flag-gt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1f9.svg"),none}.em-flag-gu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1fa.png")}.em-flag-gu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1fa.svg"),none}.em-flag-gw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1fc.png")}.em-flag-gw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1fc.svg"),none}.em-flag-gy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1fe.png")}.em-flag-gy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1fe.svg"),none}.em-flag-hk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f0.png")}.em-flag-hk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f0.svg"),none}.em-flag-hm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f2.png")}.em-flag-hm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f2.svg"),none}.em-flag-hn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f3.png")}.em-flag-hn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f3.svg"),none}.em-flag-hr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f7.png")}.em-flag-hr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f7.svg"),none}.em-flag-ht{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f9.png")}.em-flag-ht.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1f9.svg"),none}.em-flag-hu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1fa.png")}.em-flag-hu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ed-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ed-1f1fa.svg"),none}.em-flag-ic{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1e8.png")}.em-flag-ic.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1e8.svg"),none}.em-flag-id{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1e9.png")}.em-flag-id.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1e9.svg"),none}.em-flag-ie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1ea.png")}.em-flag-ie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1ea.svg"),none}.em-flag-il{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f1.png")}.em-flag-il.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f1.svg"),none}.em-flag-im{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f2.png")}.em-flag-im.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f2.svg"),none}.em-flag-in{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f3.png")}.em-flag-in.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f3.svg"),none}.em-flag-io{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f4.png")}.em-flag-io.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f4.svg"),none}.em-flag-iq{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f6.png")}.em-flag-iq.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f6.svg"),none}.em-flag-ir{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f7.png")}.em-flag-ir.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f7.svg"),none}.em-flag-is{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f8.png")}.em-flag-is.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f8.svg"),none}.em-flag-je{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1ea.png")}.em-flag-je.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1ea.svg"),none}.em-flag-jm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1f2.png")}.em-flag-jm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1f2.svg"),none}.em-flag-jo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1f4.png")}.em-flag-jo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1f4.svg"),none}.em-flag-ke{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ea.png")}.em-flag-ke.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ea.svg"),none}.em-flag-kg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ec.png")}.em-flag-kg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ec.svg"),none}.em-flag-kh{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ed.png")}.em-flag-kh.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ed.svg"),none}.em-flag-ki{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ee.png")}.em-flag-ki.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ee.svg"),none}.em-flag-km{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f2.png")}.em-flag-km.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f2.svg"),none}.em-flag-kn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f3.png")}.em-flag-kn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f3.svg"),none}.em-flag-kp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f5.png")}.em-flag-kp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f5.svg"),none}.em-flag-kw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1fc.png")}.em-flag-kw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1fc.svg"),none}.em-flag-ky{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1fe.png")}.em-flag-ky.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1fe.svg"),none}.em-flag-kz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ff.png")}.em-flag-kz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1ff.svg"),none}.em-flag-la{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1e6.png")}.em-flag-la.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1e6.svg"),none}.em-flag-lb{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1e7.png")}.em-flag-lb.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1e7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1e7.svg"),none}.em-flag-lc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1e8.png")}.em-flag-lc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1e8.svg"),none}.em-flag-li{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1ee.png")}.em-flag-li.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1ee.svg"),none}.em-flag-lk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f0.png")}.em-flag-lk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f0.svg"),none}.em-flag-lr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f7.png")}.em-flag-lr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f7.svg"),none}.em-flag-ls{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f8.png")}.em-flag-ls.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f8.svg"),none}.em-flag-lt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f9.png")}.em-flag-lt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1f9.svg"),none}.em-flag-lu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1fa.png")}.em-flag-lu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1fa.svg"),none}.em-flag-lv{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1fb.png")}.em-flag-lv.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1fb.svg"),none}.em-flag-ly{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1fe.png")}.em-flag-ly.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f1-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f1-1f1fe.svg"),none}.em-flag-ma{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1e6.png")}.em-flag-ma.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1e6.svg"),none}.em-flag-mc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1e8.png")}.em-flag-mc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1e8.svg"),none}.em-flag-md{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1e9.png")}.em-flag-md.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1e9.svg"),none}.em-flag-me{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ea.png")}.em-flag-me.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ea.svg"),none}.em-flag-mf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1eb.png")}.em-flag-mf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1eb.svg"),none}.em-flag-mg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ec.png")}.em-flag-mg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ec.svg"),none}.em-flag-mh{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ed.png")}.em-flag-mh.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ed.svg"),none}.em-flag-mk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f0.png")}.em-flag-mk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f0.svg"),none}.em-flag-ml{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f1.png")}.em-flag-ml.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f1.svg"),none}.em-flag-mm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f2.png")}.em-flag-mm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f2.svg"),none}.em-flag-mn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f3.png")}.em-flag-mn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f3.svg"),none}.em-flag-mo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f4.png")}.em-flag-mo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f4.svg"),none}.em-flag-mp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f5.png")}.em-flag-mp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f5.svg"),none}.em-flag-mq{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f6.png")}.em-flag-mq.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f6.svg"),none}.em-flag-mr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f7.png")}.em-flag-mr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f7.svg"),none}.em-flag-ms{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f8.png")}.em-flag-ms.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f8.svg"),none}.em-flag-mt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f9.png")}.em-flag-mt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1f9.svg"),none}.em-flag-mu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fa.png")}.em-flag-mu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fa.svg"),none}.em-flag-mv{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fb.png")}.em-flag-mv.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fb.svg"),none}.em-flag-mw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fc.png")}.em-flag-mw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fc.svg"),none}.em-flag-mx{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fd.png")}.em-flag-mx.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fd.svg"),none}.em-flag-my{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fe.png")}.em-flag-my.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1fe.svg"),none}.em-flag-mz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ff.png")}.em-flag-mz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f2-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f2-1f1ff.svg"),none}.em-flag-na{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1e6.png")}.em-flag-na.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1e6.svg"),none}.em-flag-nc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1e8.png")}.em-flag-nc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1e8.svg"),none}.em-flag-ne{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ea.png")}.em-flag-ne.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ea.svg"),none}.em-flag-nf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1eb.png")}.em-flag-nf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1eb.svg"),none}.em-flag-ng{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ec.png")}.em-flag-ng.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ec.svg"),none}.em-flag-ni{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ee.png")}.em-flag-ni.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ee.svg"),none}.em-flag-nl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f1.png")}.em-flag-nl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f1.svg"),none}.em-flag-no{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f4.png")}.em-flag-no.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f4.svg"),none}.em-flag-np{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f5.png")}.em-flag-np.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f5.svg"),none}.em-flag-nr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f7.png")}.em-flag-nr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1f7.svg"),none}.em-flag-nu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1fa.png")}.em-flag-nu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1fa.svg"),none}.em-flag-nz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ff.png")}.em-flag-nz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f3-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f3-1f1ff.svg"),none}.em-flag-om{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f4-1f1f2.png")}.em-flag-om.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f4-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f4-1f1f2.svg"),none}.em-flag-pa{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1e6.png")}.em-flag-pa.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1e6.svg"),none}.em-flag-pe{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1ea.png")}.em-flag-pe.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1ea.svg"),none}.em-flag-pf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1eb.png")}.em-flag-pf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1eb.svg"),none}.em-flag-pg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1ec.png")}.em-flag-pg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1ec.svg"),none}.em-flag-ph{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1ed.png")}.em-flag-ph.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1ed.svg"),none}.em-flag-pk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f0.png")}.em-flag-pk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f0.svg"),none}.em-flag-pl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f1.png")}.em-flag-pl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f1.svg"),none}.em-flag-pm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f2.png")}.em-flag-pm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f2.svg"),none}.em-flag-pn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f3.png")}.em-flag-pn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f3.svg"),none}.em-flag-pr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f7.png")}.em-flag-pr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f7.svg"),none}.em-flag-ps{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f8.png")}.em-flag-ps.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f8.svg"),none}.em-flag-pt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f9.png")}.em-flag-pt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1f9.svg"),none}.em-flag-pw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1fc.png")}.em-flag-pw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1fc.svg"),none}.em-flag-py{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1fe.png")}.em-flag-py.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f5-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f5-1f1fe.svg"),none}.em-flag-qa{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f6-1f1e6.png")}.em-flag-qa.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f6-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f6-1f1e6.svg"),none}.em-flag-re{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1ea.png")}.em-flag-re.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1ea.svg"),none}.em-flag-ro{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1f4.png")}.em-flag-ro.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1f4.svg"),none}.em-flag-rs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1f8.png")}.em-flag-rs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1f8.svg"),none}.em-flag-rw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1fc.png")}.em-flag-rw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1fc.svg"),none}.em-flag-sa{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e6.png")}.em-flag-sa.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e6.svg"),none}.em-flag-sb{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e7.png")}.em-flag-sb.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e7.svg"),none}.em-flag-sc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e8.png")}.em-flag-sc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e8.svg"),none}.em-flag-scotland{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png")}.em-flag-scotland.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.svg"),none}.em-flag-sd{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e9.png")}.em-flag-sd.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1e9.svg"),none}.em-flag-se{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ea.png")}.em-flag-se.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ea.svg"),none}.em-flag-sg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ec.png")}.em-flag-sg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ec.svg"),none}.em-flag-sh{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ed.png")}.em-flag-sh.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ed.svg"),none}.em-flag-si{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ee.png")}.em-flag-si.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ee.svg"),none}.em-flag-sj{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ef.png")}.em-flag-sj.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ef.svg"),none}.em-flag-sk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f0.png")}.em-flag-sk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f0.svg"),none}.em-flag-sl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f1.png")}.em-flag-sl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f1.svg"),none}.em-flag-sm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f2.png")}.em-flag-sm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f2.svg"),none}.em-flag-sn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f3.png")}.em-flag-sn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f3.svg"),none}.em-flag-so{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f4.png")}.em-flag-so.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f4.svg"),none}.em-flag-sr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f7.png")}.em-flag-sr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f7.svg"),none}.em-flag-ss{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f8.png")}.em-flag-ss.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f8.svg"),none}.em-flag-st{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f9.png")}.em-flag-st.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1f9.svg"),none}.em-flag-sv{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1fb.png")}.em-flag-sv.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1fb.svg"),none}.em-flag-sx{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1fd.png")}.em-flag-sx.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1fd.svg"),none}.em-flag-sy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1fe.png")}.em-flag-sy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1fe.svg"),none}.em-flag-sz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ff.png")}.em-flag-sz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f8-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f8-1f1ff.svg"),none}.em-flag-ta{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1e6.png")}.em-flag-ta.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1e6.svg"),none}.em-flag-tc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1e8.png")}.em-flag-tc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1e8.svg"),none}.em-flag-td{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1e9.png")}.em-flag-td.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1e9.svg"),none}.em-flag-tf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1eb.png")}.em-flag-tf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1eb.svg"),none}.em-flag-tg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ec.png")}.em-flag-tg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ec.svg"),none}.em-flag-th{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ed.png")}.em-flag-th.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ed.svg"),none}.em-flag-tj{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ef.png")}.em-flag-tj.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ef.svg"),none}.em-flag-tk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f0.png")}.em-flag-tk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f0.svg"),none}.em-flag-tl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f1.png")}.em-flag-tl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f1.svg"),none}.em-flag-tm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f2.png")}.em-flag-tm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f2.svg"),none}.em-flag-tn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f3.png")}.em-flag-tn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f3.svg"),none}.em-flag-to{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f4.png")}.em-flag-to.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f4.svg"),none}.em-flag-tr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f7.png")}.em-flag-tr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f7.svg"),none}.em-flag-tt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f9.png")}.em-flag-tt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1f9.svg"),none}.em-flag-tv{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1fb.png")}.em-flag-tv.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1fb.svg"),none}.em-flag-tw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1fc.png")}.em-flag-tw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1fc.svg"),none}.em-flag-tz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ff.png")}.em-flag-tz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f9-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f9-1f1ff.svg"),none}.em-flag-ua{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1e6.png")}.em-flag-ua.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1e6.svg"),none}.em-flag-ug{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1ec.png")}.em-flag-ug.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1ec.svg"),none}.em-flag-um{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1f2.png")}.em-flag-um.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1f2.svg"),none}.em-flag-un{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1f3.png")}.em-flag-un.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1f3.svg"),none}.em-flag-uy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1fe.png")}.em-flag-uy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1fe.svg"),none}.em-flag-uz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1ff.png")}.em-flag-uz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1ff.svg"),none}.em-flag-va{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1e6.png")}.em-flag-va.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1e6.svg"),none}.em-flag-vc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1e8.png")}.em-flag-vc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1e8.svg"),none}.em-flag-ve{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1ea.png")}.em-flag-ve.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1ea.svg"),none}.em-flag-vg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1ec.png")}.em-flag-vg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1ec.svg"),none}.em-flag-vi{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1ee.png")}.em-flag-vi.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1ee.svg"),none}.em-flag-vn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1f3.png")}.em-flag-vn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1f3.svg"),none}.em-flag-vu{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1fa.png")}.em-flag-vu.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fb-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fb-1f1fa.svg"),none}.em-flag-wales{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png")}.em-flag-wales.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.svg"),none}.em-flag-wf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fc-1f1eb.png")}.em-flag-wf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fc-1f1eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fc-1f1eb.svg"),none}.em-flag-ws{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fc-1f1f8.png")}.em-flag-ws.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fc-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fc-1f1f8.svg"),none}.em-flag-xk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fd-1f1f0.png")}.em-flag-xk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fd-1f1f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fd-1f1f0.svg"),none}.em-flag-ye{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fe-1f1ea.png")}.em-flag-ye.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fe-1f1ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fe-1f1ea.svg"),none}.em-flag-yt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fe-1f1f9.png")}.em-flag-yt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fe-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fe-1f1f9.svg"),none}.em-flag-za{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ff-1f1e6.png")}.em-flag-za.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ff-1f1e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff-1f1e6.svg"),none}.em-flag-zm{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ff-1f1f2.png")}.em-flag-zm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ff-1f1f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff-1f1f2.svg"),none}.em-flag-zw{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ff-1f1fc.png")}.em-flag-zw.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ff-1f1fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ff-1f1fc.svg"),none}.em-flags{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f38f.png")}.em-flags.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f38f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f38f.svg"),none}.em-flashlight{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f526.png")}.em-flashlight.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f526.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f526.svg"),none}.em-fleur_de_lis{background-image:url("https://twemoji.maxcdn.com/2/72x72/269c.png")}.em-fleur_de_lis.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/269c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/269c.svg"),none}.em-floppy_disk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4be.png")}.em-floppy_disk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4be.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4be.svg"),none}.em-flower_playing_cards{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b4.png")}.em-flower_playing_cards.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b4.svg"),none}.em-flushed{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f633.png")}.em-flushed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f633.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f633.svg"),none}.em-flying_saucer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6f8.png")}.em-flying_saucer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f8.svg"),none}.em-fog{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f32b.png")}.em-fog.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f32b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f32b.svg"),none}.em-foggy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f301.png")}.em-foggy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f301.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f301.svg"),none}.em-football{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c8.png")}.em-football.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c8.svg"),none}.em-footprints{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f463.png")}.em-footprints.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f463.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f463.svg"),none}.em-fork_and_knife{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f374.png")}.em-fork_and_knife.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f374.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f374.svg"),none}.em-fortune_cookie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f960.png")}.em-fortune_cookie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f960.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f960.svg"),none}.em-fountain{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f2.png")}.em-fountain.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f2.svg"),none}.em-four{background-image:url("https://twemoji.maxcdn.com/2/72x72/34-20e3.png")}.em-four.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/34-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/34-20e3.svg"),none}.em-four_leaf_clover{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f340.png")}.em-four_leaf_clover.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f340.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f340.svg"),none}.em-fox_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f98a.png")}.em-fox_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f98a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f98a.svg"),none}.em-fr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f7.png")}.em-fr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1eb-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1eb-1f1f7.svg"),none}.em-frame_with_picture{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5bc.png")}.em-frame_with_picture.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5bc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5bc.svg"),none}.em-free{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f193.png")}.em-free.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f193.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f193.svg"),none}.em-fried_egg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f373.png")}.em-fried_egg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f373.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f373.svg"),none}.em-fried_shrimp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f364.png")}.em-fried_shrimp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f364.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f364.svg"),none}.em-fries{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f35f.png")}.em-fries.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f35f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f35f.svg"),none}.em-frog{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f438.png")}.em-frog.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f438.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f438.svg"),none}.em-frowning{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f626.png")}.em-frowning.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f626.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f626.svg"),none}.em-fuelpump{background-image:url("https://twemoji.maxcdn.com/2/72x72/26fd.png")}.em-fuelpump.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26fd.svg"),none}.em-full_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f315.png")}.em-full_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f315.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f315.svg"),none}.em-full_moon_with_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f31d.png")}.em-full_moon_with_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f31d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f31d.svg"),none}.em-funeral_urn{background-image:url("https://twemoji.maxcdn.com/2/72x72/26b1.png")}.em-funeral_urn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26b1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26b1.svg"),none}.em-game_die{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b2.png")}.em-game_die.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b2.svg"),none}.em-gb{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1e7.png")}.em-gb.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ec-1f1e7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ec-1f1e7.svg"),none}.em-gear{background-image:url("https://twemoji.maxcdn.com/2/72x72/2699.png")}.em-gear.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2699.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2699.svg"),none}.em-gem{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f48e.png")}.em-gem.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f48e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f48e.svg"),none}.em-gemini{background-image:url("https://twemoji.maxcdn.com/2/72x72/264a.png")}.em-gemini.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/264a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/264a.svg"),none}.em-genie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9de.png")}.em-genie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9de.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9de.svg"),none}.em-ghost{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f47b.png")}.em-ghost.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f47b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f47b.svg"),none}.em-gift{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f381.png")}.em-gift.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f381.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f381.svg"),none}.em-gift_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f49d.png")}.em-gift_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f49d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f49d.svg"),none}.em-giraffe_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f992.png")}.em-giraffe_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f992.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f992.svg"),none}.em-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f467.png")}.em-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f467.svg"),none}.em-glass_of_milk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f95b.png")}.em-glass_of_milk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f95b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f95b.svg"),none}.em-globe_with_meridians{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f310.png")}.em-globe_with_meridians.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f310.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f310.svg"),none}.em-gloves{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9e4.png")}.em-gloves.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9e4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9e4.svg"),none}.em-goal_net{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f945.png")}.em-goal_net.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f945.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f945.svg"),none}.em-goat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f410.png")}.em-goat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f410.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f410.svg"),none}.em-golf{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f3.png")}.em-golf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f3.svg"),none}.em-golfer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cc.png")}.em-golfer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cc.svg"),none}.em-gorilla{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f98d.png")}.em-gorilla.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f98d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f98d.svg"),none}.em-grapes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f347.png")}.em-grapes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f347.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f347.svg"),none}.em-green_apple{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f34f.png")}.em-green_apple.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f34f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f34f.svg"),none}.em-green_book{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d7.png")}.em-green_book.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d7.svg"),none}.em-green_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f49a.png")}.em-green_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f49a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f49a.svg"),none}.em-green_salad{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f957.png")}.em-green_salad.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f957.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f957.svg"),none}.em-grey_exclamation{background-image:url("https://twemoji.maxcdn.com/2/72x72/2755.png")}.em-grey_exclamation.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2755.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2755.svg"),none}.em-grey_question{background-image:url("https://twemoji.maxcdn.com/2/72x72/2754.png")}.em-grey_question.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2754.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2754.svg"),none}.em-grimacing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f62c.png")}.em-grimacing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f62c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f62c.svg"),none}.em-grin{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f601.png")}.em-grin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f601.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f601.svg"),none}.em-grinning{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f600.png")}.em-grinning.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f600.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f600.svg"),none}.em-grinning_face_with_one_large_and_one_small_eye{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f92a.png")}.em-grinning_face_with_one_large_and_one_small_eye.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f92a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f92a.svg"),none}.em-grinning_face_with_star_eyes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f929.png")}.em-grinning_face_with_star_eyes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f929.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f929.svg"),none}.em-guardsman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f482.png")}.em-guardsman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f482.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f482.svg"),none}.em-guitar{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b8.png")}.em-guitar.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b8.svg"),none}.em-gun{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f52b.png")}.em-gun.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f52b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f52b.svg"),none}.em-haircut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f487.png")}.em-haircut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f487.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f487.svg"),none}.em-hamburger{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f354.png")}.em-hamburger.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f354.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f354.svg"),none}.em-hammer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f528.png")}.em-hammer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f528.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f528.svg"),none}.em-hammer_and_pick{background-image:url("https://twemoji.maxcdn.com/2/72x72/2692.png")}.em-hammer_and_pick.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2692.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2692.svg"),none}.em-hammer_and_wrench{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6e0.png")}.em-hammer_and_wrench.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6e0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e0.svg"),none}.em-hamster{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f439.png")}.em-hamster.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f439.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f439.svg"),none}.em-hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/270b.png")}.em-hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/270b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/270b.svg"),none}.em-hand_with_index_and_middle_fingers_crossed{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f91e.png")}.em-hand_with_index_and_middle_fingers_crossed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f91e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f91e.svg"),none}.em-handbag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f45c.png")}.em-handbag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f45c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f45c.svg"),none}.em-handball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93e.png")}.em-handball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e.svg"),none}.em-handshake{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f91d.png")}.em-handshake.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f91d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f91d.svg"),none}.em-hankey{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a9.png")}.em-hankey.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a9.svg"),none}.em-hash{background-image:url("https://twemoji.maxcdn.com/2/72x72/23-20e3.png")}.em-hash.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23-20e3.svg"),none}.em-hatched_chick{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f425.png")}.em-hatched_chick.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f425.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f425.svg"),none}.em-hatching_chick{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f423.png")}.em-hatching_chick.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f423.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f423.svg"),none}.em-headphones{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a7.png")}.em-headphones.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a7.svg"),none}.em-hear_no_evil{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f649.png")}.em-hear_no_evil.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f649.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f649.svg"),none}.em-heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/2764.png")}.em-heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2764.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2764.svg"),none}.em-heart_decoration{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f49f.png")}.em-heart_decoration.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f49f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f49f.svg"),none}.em-heart_eyes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f60d.png")}.em-heart_eyes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f60d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f60d.svg"),none}.em-heart_eyes_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f63b.png")}.em-heart_eyes_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f63b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f63b.svg"),none}.em-heartbeat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f493.png")}.em-heartbeat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f493.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f493.svg"),none}.em-heartpulse{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f497.png")}.em-heartpulse.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f497.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f497.svg"),none}.em-hearts{background-image:url("https://twemoji.maxcdn.com/2/72x72/2665.png")}.em-hearts.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2665.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2665.svg"),none}.em-heavy_check_mark{background-image:url("https://twemoji.maxcdn.com/2/72x72/2714.png")}.em-heavy_check_mark.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2714.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2714.svg"),none}.em-heavy_division_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/2797.png")}.em-heavy_division_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2797.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2797.svg"),none}.em-heavy_dollar_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b2.png")}.em-heavy_dollar_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b2.svg"),none}.em-heavy_heart_exclamation_mark_ornament{background-image:url("https://twemoji.maxcdn.com/2/72x72/2763.png")}.em-heavy_heart_exclamation_mark_ornament.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2763.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2763.svg"),none}.em-heavy_minus_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/2796.png")}.em-heavy_minus_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2796.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2796.svg"),none}.em-heavy_multiplication_x{background-image:url("https://twemoji.maxcdn.com/2/72x72/2716.png")}.em-heavy_multiplication_x.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2716.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2716.svg"),none}.em-heavy_plus_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/2795.png")}.em-heavy_plus_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2795.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2795.svg"),none}.em-hedgehog{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f994.png")}.em-hedgehog.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f994.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f994.svg"),none}.em-helicopter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f681.png")}.em-helicopter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f681.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f681.svg"),none}.em-helmet_with_white_cross{background-image:url("https://twemoji.maxcdn.com/2/72x72/26d1.png")}.em-helmet_with_white_cross.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26d1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26d1.svg"),none}.em-herb{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f33f.png")}.em-herb.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f33f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f33f.svg"),none}.em-hibiscus{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f33a.png")}.em-hibiscus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f33a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f33a.svg"),none}.em-high_brightness{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f506.png")}.em-high_brightness.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f506.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f506.svg"),none}.em-high_heel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f460.png")}.em-high_heel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f460.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f460.svg"),none}.em-hocho{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f52a.png")}.em-hocho.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f52a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f52a.svg"),none}.em-hole{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f573.png")}.em-hole.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f573.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f573.svg"),none}.em-honey_pot{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f36f.png")}.em-honey_pot.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f36f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f36f.svg"),none}.em-horse{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f434.png")}.em-horse.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f434.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f434.svg"),none}.em-horse_racing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c7.png")}.em-horse_racing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c7.svg"),none}.em-hospital{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e5.png")}.em-hospital.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e5.svg"),none}.em-hot_pepper{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f336.png")}.em-hot_pepper.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f336.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f336.svg"),none}.em-hotdog{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f32d.png")}.em-hotdog.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f32d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f32d.svg"),none}.em-hotel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e8.png")}.em-hotel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e8.svg"),none}.em-hotsprings{background-image:url("https://twemoji.maxcdn.com/2/72x72/2668.png")}.em-hotsprings.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2668.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2668.svg"),none}.em-hourglass{background-image:url("https://twemoji.maxcdn.com/2/72x72/231b.png")}.em-hourglass.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/231b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/231b.svg"),none}.em-hourglass_flowing_sand{background-image:url("https://twemoji.maxcdn.com/2/72x72/23f3.png")}.em-hourglass_flowing_sand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23f3.svg"),none}.em-house{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e0.png")}.em-house.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e0.svg"),none}.em-house_buildings{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d8.png")}.em-house_buildings.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d8.svg"),none}.em-house_with_garden{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e1.png")}.em-house_with_garden.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e1.svg"),none}.em-hugging_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f917.png")}.em-hugging_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f917.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f917.svg"),none}.em-hushed{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f62f.png")}.em-hushed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f62f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f62f.svg"),none}.em-i_love_you_hand_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f91f.png")}.em-i_love_you_hand_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f91f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f91f.svg"),none}.em-ice_cream{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f368.png")}.em-ice_cream.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f368.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f368.svg"),none}.em-ice_hockey_stick_and_puck{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d2.png")}.em-ice_hockey_stick_and_puck.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d2.svg"),none}.em-ice_skate{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f8.png")}.em-ice_skate.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f8.svg"),none}.em-icecream{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f366.png")}.em-icecream.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f366.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f366.svg"),none}.em-id{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f194.png")}.em-id.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f194.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f194.svg"),none}.em-ideograph_advantage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f250.png")}.em-ideograph_advantage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f250.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f250.svg"),none}.em-imp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f47f.png")}.em-imp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f47f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f47f.svg"),none}.em-inbox_tray{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e5.png")}.em-inbox_tray.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e5.svg"),none}.em-incoming_envelope{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e8.png")}.em-incoming_envelope.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e8.svg"),none}.em-information_desk_person{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f481.png")}.em-information_desk_person.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f481.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f481.svg"),none}.em-information_source{background-image:url("https://twemoji.maxcdn.com/2/72x72/2139.png")}.em-information_source.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2139.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2139.svg"),none}.em-innocent{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f607.png")}.em-innocent.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f607.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f607.svg"),none}.em-interrobang{background-image:url("https://twemoji.maxcdn.com/2/72x72/2049.png")}.em-interrobang.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2049.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2049.svg"),none}.em-iphone{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f1.png")}.em-iphone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f1.svg"),none}.em-it{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f9.png")}.em-it.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ee-1f1f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ee-1f1f9.svg"),none}.em-izakaya_lantern{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ee.png")}.em-izakaya_lantern.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ee.svg"),none}.em-jack_o_lantern{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f383.png")}.em-jack_o_lantern.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f383.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f383.svg"),none}.em-japan{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5fe.png")}.em-japan.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fe.svg"),none}.em-japanese_castle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ef.png")}.em-japanese_castle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ef.svg"),none}.em-japanese_goblin{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f47a.png")}.em-japanese_goblin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f47a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f47a.svg"),none}.em-japanese_ogre{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f479.png")}.em-japanese_ogre.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f479.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f479.svg"),none}.em-jeans{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f456.png")}.em-jeans.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f456.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f456.svg"),none}.em-joy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f602.png")}.em-joy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f602.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f602.svg"),none}.em-joy_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f639.png")}.em-joy_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f639.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f639.svg"),none}.em-joystick{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f579.png")}.em-joystick.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f579.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f579.svg"),none}.em-jp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1f5.png")}.em-jp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1ef-1f1f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1ef-1f1f5.svg"),none}.em-juggling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f939.png")}.em-juggling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f939.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f939.svg"),none}.em-kaaba{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f54b.png")}.em-kaaba.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f54b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f54b.svg"),none}.em-key{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f511.png")}.em-key.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f511.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f511.svg"),none}.em-keyboard{background-image:url("https://twemoji.maxcdn.com/2/72x72/2328.png")}.em-keyboard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2328.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2328.svg"),none}.em-keycap_star{background-image:url("https://twemoji.maxcdn.com/2/72x72/2a-20e3.png")}.em-keycap_star.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2a-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2a-20e3.svg"),none}.em-keycap_ten{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f51f.png")}.em-keycap_ten.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f51f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f51f.svg"),none}.em-kimono{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f458.png")}.em-kimono.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f458.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f458.svg"),none}.em-kiss{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f48b.png")}.em-kiss.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f48b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f48b.svg"),none}.em-kissing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f617.png")}.em-kissing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f617.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f617.svg"),none}.em-kissing_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f63d.png")}.em-kissing_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f63d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f63d.svg"),none}.em-kissing_closed_eyes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f61a.png")}.em-kissing_closed_eyes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f61a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f61a.svg"),none}.em-kissing_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f618.png")}.em-kissing_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f618.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f618.svg"),none}.em-kissing_smiling_eyes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f619.png")}.em-kissing_smiling_eyes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f619.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f619.svg"),none}.em-kiwifruit{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f95d.png")}.em-kiwifruit.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f95d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f95d.svg"),none}.em-knife_fork_plate{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f37d.png")}.em-knife_fork_plate.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f37d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f37d.svg"),none}.em-koala{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f428.png")}.em-koala.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f428.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f428.svg"),none}.em-koko{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f201.png")}.em-koko.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f201.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f201.svg"),none}.em-kr{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f7.png")}.em-kr.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f0-1f1f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f0-1f1f7.svg"),none}.em-label{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f7.png")}.em-label.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f7.svg"),none}.em-large_blue_circle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f535.png")}.em-large_blue_circle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f535.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f535.svg"),none}.em-large_blue_diamond{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f537.png")}.em-large_blue_diamond.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f537.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f537.svg"),none}.em-large_orange_diamond{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f536.png")}.em-large_orange_diamond.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f536.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f536.svg"),none}.em-last_quarter_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f317.png")}.em-last_quarter_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f317.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f317.svg"),none}.em-last_quarter_moon_with_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f31c.png")}.em-last_quarter_moon_with_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f31c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f31c.svg"),none}.em-latin_cross{background-image:url("https://twemoji.maxcdn.com/2/72x72/271d.png")}.em-latin_cross.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/271d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/271d.svg"),none}.em-laughing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f606.png")}.em-laughing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f606.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f606.svg"),none}.em-leaves{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f343.png")}.em-leaves.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f343.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f343.svg"),none}.em-ledger{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d2.png")}.em-ledger.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d2.svg"),none}.em-left-facing_fist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f91b.png")}.em-left-facing_fist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f91b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f91b.svg"),none}.em-left_luggage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6c5.png")}.em-left_luggage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6c5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c5.svg"),none}.em-left_right_arrow{background-image:url("https://twemoji.maxcdn.com/2/72x72/2194.png")}.em-left_right_arrow.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2194.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2194.svg"),none}.em-left_speech_bubble{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5e8.png")}.em-left_speech_bubble.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5e8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5e8.svg"),none}.em-leftwards_arrow_with_hook{background-image:url("https://twemoji.maxcdn.com/2/72x72/21a9.png")}.em-leftwards_arrow_with_hook.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/21a9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/21a9.svg"),none}.em-lemon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f34b.png")}.em-lemon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f34b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f34b.svg"),none}.em-leo{background-image:url("https://twemoji.maxcdn.com/2/72x72/264c.png")}.em-leo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/264c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/264c.svg"),none}.em-leopard{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f406.png")}.em-leopard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f406.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f406.svg"),none}.em-level_slider{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f39a.png")}.em-level_slider.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f39a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f39a.svg"),none}.em-libra{background-image:url("https://twemoji.maxcdn.com/2/72x72/264e.png")}.em-libra.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/264e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/264e.svg"),none}.em-light_rail{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f688.png")}.em-light_rail.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f688.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f688.svg"),none}.em-lightning{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f329.png")}.em-lightning.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f329.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f329.svg"),none}.em-link{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f517.png")}.em-link.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f517.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f517.svg"),none}.em-linked_paperclips{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f587.png")}.em-linked_paperclips.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f587.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f587.svg"),none}.em-lion_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f981.png")}.em-lion_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f981.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f981.svg"),none}.em-lips{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f444.png")}.em-lips.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f444.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f444.svg"),none}.em-lipstick{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f484.png")}.em-lipstick.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f484.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f484.svg"),none}.em-lizard{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f98e.png")}.em-lizard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f98e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f98e.svg"),none}.em-lock{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f512.png")}.em-lock.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f512.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f512.svg"),none}.em-lock_with_ink_pen{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f50f.png")}.em-lock_with_ink_pen.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f50f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f50f.svg"),none}.em-lollipop{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f36d.png")}.em-lollipop.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f36d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f36d.svg"),none}.em-loop{background-image:url("https://twemoji.maxcdn.com/2/72x72/27bf.png")}.em-loop.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/27bf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/27bf.svg"),none}.em-loud_sound{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f50a.png")}.em-loud_sound.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f50a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f50a.svg"),none}.em-loudspeaker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e2.png")}.em-loudspeaker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e2.svg"),none}.em-love_hotel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e9.png")}.em-love_hotel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e9.svg"),none}.em-love_letter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f48c.png")}.em-love_letter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f48c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f48c.svg"),none}.em-low_brightness{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f505.png")}.em-low_brightness.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f505.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f505.svg"),none}.em-lower_left_ballpoint_pen{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f58a.png")}.em-lower_left_ballpoint_pen.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f58a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f58a.svg"),none}.em-lower_left_crayon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f58d.png")}.em-lower_left_crayon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f58d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f58d.svg"),none}.em-lower_left_fountain_pen{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f58b.png")}.em-lower_left_fountain_pen.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f58b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f58b.svg"),none}.em-lower_left_paintbrush{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f58c.png")}.em-lower_left_paintbrush.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f58c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f58c.svg"),none}.em-lying_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f925.png")}.em-lying_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f925.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f925.svg"),none}.em-m{background-image:url("https://twemoji.maxcdn.com/2/72x72/24c2.png")}.em-m.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/24c2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/24c2.svg"),none}.em-mag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f50d.png")}.em-mag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f50d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f50d.svg"),none}.em-mag_right{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f50e.png")}.em-mag_right.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f50e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f50e.svg"),none}.em-mage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d9.png")}.em-mage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d9.svg"),none}.em-mahjong{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f004.png")}.em-mahjong.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f004.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f004.svg"),none}.em-mailbox{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4eb.png")}.em-mailbox.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4eb.svg"),none}.em-mailbox_closed{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ea.png")}.em-mailbox_closed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ea.svg"),none}.em-mailbox_with_mail{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ec.png")}.em-mailbox_with_mail.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ec.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ec.svg"),none}.em-mailbox_with_no_mail{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ed.png")}.em-mailbox_with_no_mail.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ed.svg"),none}.em-male-artist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3a8.png")}.em-male-artist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3a8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f3a8.svg"),none}.em-male-astronaut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f680.png")}.em-male-astronaut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f680.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f680.svg"),none}.em-male-construction-worker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f477-200d-2642-fe0f.png")}.em-male-construction-worker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f477-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f477-200d-2642-fe0f.svg"),none}.em-male-cook{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f373.png")}.em-male-cook.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f373.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f373.svg"),none}.em-male-detective{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f575-fe0f-200d-2642-fe0f.png")}.em-male-detective.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f575-fe0f-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f575-fe0f-200d-2642-fe0f.svg"),none}.em-male-doctor{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2695-fe0f.png")}.em-male-doctor.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2695-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-2695-fe0f.svg"),none}.em-male-factory-worker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3ed.png")}.em-male-factory-worker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3ed.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f3ed.svg"),none}.em-male-farmer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f33e.png")}.em-male-farmer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f33e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f33e.svg"),none}.em-male-firefighter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f692.png")}.em-male-firefighter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f692.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f692.svg"),none}.em-male-guard{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f482-200d-2642-fe0f.png")}.em-male-guard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f482-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f482-200d-2642-fe0f.svg"),none}.em-male-judge{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2696-fe0f.png")}.em-male-judge.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2696-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-2696-fe0f.svg"),none}.em-male-mechanic{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f527.png")}.em-male-mechanic.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f527.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f527.svg"),none}.em-male-office-worker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f4bc.png")}.em-male-office-worker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f4bc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f4bc.svg"),none}.em-male-pilot{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2708-fe0f.png")}.em-male-pilot.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2708-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-2708-fe0f.svg"),none}.em-male-police-officer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46e-200d-2642-fe0f.png")}.em-male-police-officer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46e-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46e-200d-2642-fe0f.svg"),none}.em-male-scientist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f52c.png")}.em-male-scientist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f52c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f52c.svg"),none}.em-male-singer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3a4.png")}.em-male-singer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3a4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f3a4.svg"),none}.em-male-student{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f393.png")}.em-male-student.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f393.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f393.svg"),none}.em-male-teacher{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3eb.png")}.em-male-teacher.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f3eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f3eb.svg"),none}.em-male-technologist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f4bb.png")}.em-male-technologist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f4bb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f4bb.svg"),none}.em-male_elf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9dd-200d-2642-fe0f.png")}.em-male_elf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9dd-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9dd-200d-2642-fe0f.svg"),none}.em-male_fairy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9da-200d-2642-fe0f.png")}.em-male_fairy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9da-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9da-200d-2642-fe0f.svg"),none}.em-male_genie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9de-200d-2642-fe0f.png")}.em-male_genie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9de-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9de-200d-2642-fe0f.svg"),none}.em-male_mage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d9-200d-2642-fe0f.png")}.em-male_mage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d9-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d9-200d-2642-fe0f.svg"),none}.em-male_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/2642.png")}.em-male_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2642.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2642.svg"),none}.em-male_vampire{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9db-200d-2642-fe0f.png")}.em-male_vampire.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9db-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9db-200d-2642-fe0f.svg"),none}.em-male_zombie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9df-200d-2642-fe0f.png")}.em-male_zombie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9df-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9df-200d-2642-fe0f.svg"),none}.em-man{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468.png")}.em-man.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468.svg"),none}.em-man-biking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b4-200d-2642-fe0f.png")}.em-man-biking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b4-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4-200d-2642-fe0f.svg"),none}.em-man-bouncing-ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f9-fe0f-200d-2642-fe0f.png")}.em-man-bouncing-ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f9-fe0f-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f9-fe0f-200d-2642-fe0f.svg"),none}.em-man-bowing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f647-200d-2642-fe0f.png")}.em-man-bowing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f647-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f647-200d-2642-fe0f.svg"),none}.em-man-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f466.png")}.em-man-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f466.svg"),none}.em-man-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f466-200d-1f466.png")}.em-man-boy-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f466-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f466-200d-1f466.svg"),none}.em-man-cartwheeling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f938-200d-2642-fe0f.png")}.em-man-cartwheeling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f938-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f938-200d-2642-fe0f.svg"),none}.em-man-facepalming{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f926-200d-2642-fe0f.png")}.em-man-facepalming.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f926-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f926-200d-2642-fe0f.svg"),none}.em-man-frowning{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64d-200d-2642-fe0f.png")}.em-man-frowning.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64d-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d-200d-2642-fe0f.svg"),none}.em-man-gesturing-no{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f645-200d-2642-fe0f.png")}.em-man-gesturing-no.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f645-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f645-200d-2642-fe0f.svg"),none}.em-man-gesturing-ok{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f646-200d-2642-fe0f.png")}.em-man-gesturing-ok.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f646-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f646-200d-2642-fe0f.svg"),none}.em-man-getting-haircut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f487-200d-2642-fe0f.png")}.em-man-getting-haircut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f487-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f487-200d-2642-fe0f.svg"),none}.em-man-getting-massage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f486-200d-2642-fe0f.png")}.em-man-getting-massage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f486-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f486-200d-2642-fe0f.svg"),none}.em-man-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f467.png")}.em-man-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f467.svg"),none}.em-man-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f467-200d-1f466.png")}.em-man-girl-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f467-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f467-200d-1f466.svg"),none}.em-man-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f467-200d-1f467.png")}.em-man-girl-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f467-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f467-200d-1f467.svg"),none}.em-man-golfing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cc-fe0f-200d-2642-fe0f.png")}.em-man-golfing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cc-fe0f-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cc-fe0f-200d-2642-fe0f.svg"),none}.em-man-heart-man{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2764-fe0f-200d-1f468.png")}.em-man-heart-man.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2764-fe0f-200d-1f468.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-2764-fe0f-200d-1f468.svg"),none}.em-man-juggling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f939-200d-2642-fe0f.png")}.em-man-juggling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f939-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f939-200d-2642-fe0f.svg"),none}.em-man-kiss-man{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.png")}.em-man-kiss-man.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.svg"),none}.em-man-lifting-weights{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cb-fe0f-200d-2642-fe0f.png")}.em-man-lifting-weights.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cb-fe0f-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb-fe0f-200d-2642-fe0f.svg"),none}.em-man-man-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f466.png")}.em-man-man-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f466.svg"),none}.em-man-man-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f466-200d-1f466.png")}.em-man-man-boy-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f466-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f466-200d-1f466.svg"),none}.em-man-man-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f467.png")}.em-man-man-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f467.svg"),none}.em-man-man-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f467-200d-1f466.png")}.em-man-man-girl-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f467-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f467-200d-1f466.svg"),none}.em-man-man-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f467-200d-1f467.png")}.em-man-man-girl-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f468-200d-1f467-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f468-200d-1f467-200d-1f467.svg"),none}.em-man-mountain-biking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b5-200d-2642-fe0f.png")}.em-man-mountain-biking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b5-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5-200d-2642-fe0f.svg"),none}.em-man-playing-handball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93e-200d-2642-fe0f.png")}.em-man-playing-handball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93e-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e-200d-2642-fe0f.svg"),none}.em-man-playing-water-polo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93d-200d-2642-fe0f.png")}.em-man-playing-water-polo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93d-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d-200d-2642-fe0f.svg"),none}.em-man-pouting{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64e-200d-2642-fe0f.png")}.em-man-pouting.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64e-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e-200d-2642-fe0f.svg"),none}.em-man-raising-hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64b-200d-2642-fe0f.png")}.em-man-raising-hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64b-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b-200d-2642-fe0f.svg"),none}.em-man-rowing-boat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a3-200d-2642-fe0f.png")}.em-man-rowing-boat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a3-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3-200d-2642-fe0f.svg"),none}.em-man-running{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c3-200d-2642-fe0f.png")}.em-man-running.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c3-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3-200d-2642-fe0f.svg"),none}.em-man-shrugging{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f937-200d-2642-fe0f.png")}.em-man-shrugging.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f937-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f937-200d-2642-fe0f.svg"),none}.em-man-surfing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c4-200d-2642-fe0f.png")}.em-man-surfing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c4-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4-200d-2642-fe0f.svg"),none}.em-man-swimming{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ca-200d-2642-fe0f.png")}.em-man-swimming.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ca-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca-200d-2642-fe0f.svg"),none}.em-man-tipping-hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f481-200d-2642-fe0f.png")}.em-man-tipping-hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f481-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f481-200d-2642-fe0f.svg"),none}.em-man-walking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b6-200d-2642-fe0f.png")}.em-man-walking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b6-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6-200d-2642-fe0f.svg"),none}.em-man-wearing-turban{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f473-200d-2642-fe0f.png")}.em-man-wearing-turban.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f473-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f473-200d-2642-fe0f.svg"),none}.em-man-with-bunny-ears-partying{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46f-200d-2642-fe0f.png")}.em-man-with-bunny-ears-partying.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46f-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46f-200d-2642-fe0f.svg"),none}.em-man-woman-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f466.png")}.em-man-woman-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f466.svg"),none}.em-man-woman-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f466-200d-1f466.png")}.em-man-woman-boy-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f466-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f466-200d-1f466.svg"),none}.em-man-woman-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f467.png")}.em-man-woman-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f467.svg"),none}.em-man-woman-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f467-200d-1f466.png")}.em-man-woman-girl-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f467-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f467-200d-1f466.svg"),none}.em-man-woman-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f467-200d-1f467.png")}.em-man-woman-girl-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f468-200d-1f469-200d-1f467-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f468-200d-1f469-200d-1f467-200d-1f467.svg"),none}.em-man-wrestling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93c-200d-2642-fe0f.png")}.em-man-wrestling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93c-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c-200d-2642-fe0f.svg"),none}.em-man_climbing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d7-200d-2642-fe0f.png")}.em-man_climbing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d7-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d7-200d-2642-fe0f.svg"),none}.em-man_dancing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f57a.png")}.em-man_dancing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f57a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f57a.svg"),none}.em-man_in_business_suit_levitating{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f574.png")}.em-man_in_business_suit_levitating.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f574.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f574.svg"),none}.em-man_in_lotus_position{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d8-200d-2642-fe0f.png")}.em-man_in_lotus_position.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d8-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d8-200d-2642-fe0f.svg"),none}.em-man_in_steamy_room{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d6-200d-2642-fe0f.png")}.em-man_in_steamy_room.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d6-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d6-200d-2642-fe0f.svg"),none}.em-man_in_tuxedo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f935.png")}.em-man_in_tuxedo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f935.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f935.svg"),none}.em-man_with_gua_pi_mao{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f472.png")}.em-man_with_gua_pi_mao.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f472.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f472.svg"),none}.em-man_with_turban{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f473.png")}.em-man_with_turban.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f473.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f473.svg"),none}.em-mans_shoe{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f45e.png")}.em-mans_shoe.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f45e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f45e.svg"),none}.em-mantelpiece_clock{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f570.png")}.em-mantelpiece_clock.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f570.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f570.svg"),none}.em-maple_leaf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f341.png")}.em-maple_leaf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f341.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f341.svg"),none}.em-martial_arts_uniform{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f94b.png")}.em-martial_arts_uniform.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f94b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f94b.svg"),none}.em-mask{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f637.png")}.em-mask.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f637.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f637.svg"),none}.em-massage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f486.png")}.em-massage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f486.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f486.svg"),none}.em-meat_on_bone{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f356.png")}.em-meat_on_bone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f356.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f356.svg"),none}.em-medal{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f396.png")}.em-medal.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f396.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f396.svg"),none}.em-mega{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e3.png")}.em-mega.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e3.svg"),none}.em-melon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f348.png")}.em-melon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f348.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f348.svg"),none}.em-memo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4dd.png")}.em-memo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4dd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4dd.svg"),none}.em-menorah_with_nine_branches{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f54e.png")}.em-menorah_with_nine_branches.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f54e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f54e.svg"),none}.em-mens{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b9.png")}.em-mens.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b9.svg"),none}.em-mermaid{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9dc-200d-2640-fe0f.png")}.em-mermaid.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9dc-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9dc-200d-2640-fe0f.svg"),none}.em-merman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9dc-200d-2642-fe0f.png")}.em-merman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9dc-200d-2642-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9dc-200d-2642-fe0f.svg"),none}.em-merperson{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9dc.png")}.em-merperson.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9dc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9dc.svg"),none}.em-metro{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f687.png")}.em-metro.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f687.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f687.svg"),none}.em-microphone{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a4.png")}.em-microphone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a4.svg"),none}.em-microscope{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f52c.png")}.em-microscope.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f52c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f52c.svg"),none}.em-middle_finger{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f595.png")}.em-middle_finger.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f595.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f595.svg"),none}.em-milky_way{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f30c.png")}.em-milky_way.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f30c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f30c.svg"),none}.em-minibus{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f690.png")}.em-minibus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f690.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f690.svg"),none}.em-minidisc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4bd.png")}.em-minidisc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4bd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4bd.svg"),none}.em-mobile_phone_off{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f4.png")}.em-mobile_phone_off.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f4.svg"),none}.em-money_mouth_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f911.png")}.em-money_mouth_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f911.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f911.svg"),none}.em-money_with_wings{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b8.png")}.em-money_with_wings.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b8.svg"),none}.em-moneybag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b0.png")}.em-moneybag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b0.svg"),none}.em-monkey{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f412.png")}.em-monkey.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f412.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f412.svg"),none}.em-monkey_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f435.png")}.em-monkey_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f435.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f435.svg"),none}.em-monorail{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f69d.png")}.em-monorail.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f69d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f69d.svg"),none}.em-moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f314.png")}.em-moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f314.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f314.svg"),none}.em-mortar_board{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f393.png")}.em-mortar_board.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f393.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f393.svg"),none}.em-mosque{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f54c.png")}.em-mosque.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f54c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f54c.svg"),none}.em-mostly_sunny{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f324.png")}.em-mostly_sunny.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f324.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f324.svg"),none}.em-mother_christmas{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f936.png")}.em-mother_christmas.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f936.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f936.svg"),none}.em-motor_boat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6e5.png")}.em-motor_boat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6e5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e5.svg"),none}.em-motor_scooter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6f5.png")}.em-motor_scooter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f5.svg"),none}.em-motorway{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6e3.png")}.em-motorway.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e3.svg"),none}.em-mount_fuji{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5fb.png")}.em-mount_fuji.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fb.svg"),none}.em-mountain{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f0.png")}.em-mountain.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f0.svg"),none}.em-mountain_bicyclist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b5.png")}.em-mountain_bicyclist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5.svg"),none}.em-mountain_cableway{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a0.png")}.em-mountain_cableway.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a0.svg"),none}.em-mountain_railway{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f69e.png")}.em-mountain_railway.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f69e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f69e.svg"),none}.em-mouse{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f42d.png")}.em-mouse.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f42d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f42d.svg"),none}.em-mouse2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f401.png")}.em-mouse2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f401.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f401.svg"),none}.em-movie_camera{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a5.png")}.em-movie_camera.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a5.svg"),none}.em-moyai{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5ff.png")}.em-moyai.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5ff.svg"),none}.em-muscle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4aa.png")}.em-muscle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4aa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4aa.svg"),none}.em-mushroom{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f344.png")}.em-mushroom.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f344.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f344.svg"),none}.em-musical_keyboard{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b9.png")}.em-musical_keyboard.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b9.svg"),none}.em-musical_note{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b5.png")}.em-musical_note.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b5.svg"),none}.em-musical_score{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3bc.png")}.em-musical_score.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3bc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bc.svg"),none}.em-mute{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f507.png")}.em-mute.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f507.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f507.svg"),none}.em-nail_care{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f485.png")}.em-nail_care.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f485.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f485.svg"),none}.em-name_badge{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4db.png")}.em-name_badge.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4db.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4db.svg"),none}.em-national_park{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3de.png")}.em-national_park.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3de.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3de.svg"),none}.em-nauseated_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f922.png")}.em-nauseated_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f922.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f922.svg"),none}.em-necktie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f454.png")}.em-necktie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f454.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f454.svg"),none}.em-negative_squared_cross_mark{background-image:url("https://twemoji.maxcdn.com/2/72x72/274e.png")}.em-negative_squared_cross_mark.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/274e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/274e.svg"),none}.em-nerd_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f913.png")}.em-nerd_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f913.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f913.svg"),none}.em-neutral_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f610.png")}.em-neutral_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f610.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f610.svg"),none}.em-new{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f195.png")}.em-new.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f195.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f195.svg"),none}.em-new_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f311.png")}.em-new_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f311.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f311.svg"),none}.em-new_moon_with_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f31a.png")}.em-new_moon_with_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f31a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f31a.svg"),none}.em-newspaper{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f0.png")}.em-newspaper.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f0.svg"),none}.em-ng{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f196.png")}.em-ng.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f196.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f196.svg"),none}.em-night_with_stars{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f303.png")}.em-night_with_stars.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f303.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f303.svg"),none}.em-nine{background-image:url("https://twemoji.maxcdn.com/2/72x72/39-20e3.png")}.em-nine.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/39-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/39-20e3.svg"),none}.em-no_bell{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f515.png")}.em-no_bell.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f515.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f515.svg"),none}.em-no_bicycles{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b3.png")}.em-no_bicycles.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b3.svg"),none}.em-no_entry{background-image:url("https://twemoji.maxcdn.com/2/72x72/26d4.png")}.em-no_entry.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26d4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26d4.svg"),none}.em-no_entry_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6ab.png")}.em-no_entry_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6ab.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ab.svg"),none}.em-no_good{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f645.png")}.em-no_good.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f645.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f645.svg"),none}.em-no_mobile_phones{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f5.png")}.em-no_mobile_phones.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f5.svg"),none}.em-no_mouth{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f636.png")}.em-no_mouth.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f636.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f636.svg"),none}.em-no_pedestrians{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b7.png")}.em-no_pedestrians.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b7.svg"),none}.em-no_smoking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6ad.png")}.em-no_smoking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6ad.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ad.svg"),none}.em-non-potable_water{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b1.png")}.em-non-potable_water.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b1.svg"),none}.em-nose{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f443.png")}.em-nose.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f443.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f443.svg"),none}.em-notebook{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d3.png")}.em-notebook.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d3.svg"),none}.em-notebook_with_decorative_cover{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d4.png")}.em-notebook_with_decorative_cover.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d4.svg"),none}.em-notes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b6.png")}.em-notes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b6.svg"),none}.em-nut_and_bolt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f529.png")}.em-nut_and_bolt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f529.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f529.svg"),none}.em-o{background-image:url("https://twemoji.maxcdn.com/2/72x72/2b55.png")}.em-o.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2b55.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2b55.svg"),none}.em-o2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f17e.png")}.em-o2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f17e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f17e.svg"),none}.em-ocean{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f30a.png")}.em-ocean.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f30a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f30a.svg"),none}.em-octagonal_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6d1.png")}.em-octagonal_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6d1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6d1.svg"),none}.em-octopus{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f419.png")}.em-octopus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f419.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f419.svg"),none}.em-oden{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f362.png")}.em-oden.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f362.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f362.svg"),none}.em-office{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e2.png")}.em-office.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e2.svg"),none}.em-oil_drum{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6e2.png")}.em-oil_drum.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6e2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e2.svg"),none}.em-ok{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f197.png")}.em-ok.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f197.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f197.svg"),none}.em-ok_hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f44c.png")}.em-ok_hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f44c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f44c.svg"),none}.em-ok_woman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f646.png")}.em-ok_woman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f646.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f646.svg"),none}.em-old_key{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5dd.png")}.em-old_key.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5dd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5dd.svg"),none}.em-older_adult{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d3.png")}.em-older_adult.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d3.svg"),none}.em-older_man{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f474.png")}.em-older_man.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f474.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f474.svg"),none}.em-older_woman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f475.png")}.em-older_woman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f475.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f475.svg"),none}.em-om_symbol{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f549.png")}.em-om_symbol.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f549.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f549.svg"),none}.em-on{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f51b.png")}.em-on.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f51b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f51b.svg"),none}.em-oncoming_automobile{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f698.png")}.em-oncoming_automobile.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f698.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f698.svg"),none}.em-oncoming_bus{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f68d.png")}.em-oncoming_bus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f68d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f68d.svg"),none}.em-oncoming_police_car{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f694.png")}.em-oncoming_police_car.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f694.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f694.svg"),none}.em-oncoming_taxi{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f696.png")}.em-oncoming_taxi.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f696.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f696.svg"),none}.em-one{background-image:url("https://twemoji.maxcdn.com/2/72x72/31-20e3.png")}.em-one.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/31-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/31-20e3.svg"),none}.em-open_file_folder{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c2.png")}.em-open_file_folder.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c2.svg"),none}.em-open_hands{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f450.png")}.em-open_hands.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f450.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f450.svg"),none}.em-open_mouth{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f62e.png")}.em-open_mouth.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f62e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f62e.svg"),none}.em-ophiuchus{background-image:url("https://twemoji.maxcdn.com/2/72x72/26ce.png")}.em-ophiuchus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26ce.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26ce.svg"),none}.em-orange_book{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d9.png")}.em-orange_book.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d9.svg"),none}.em-orange_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9e1.png")}.em-orange_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9e1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9e1.svg"),none}.em-orthodox_cross{background-image:url("https://twemoji.maxcdn.com/2/72x72/2626.png")}.em-orthodox_cross.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2626.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2626.svg"),none}.em-outbox_tray{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e4.png")}.em-outbox_tray.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e4.svg"),none}.em-owl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f989.png")}.em-owl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f989.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f989.svg"),none}.em-ox{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f402.png")}.em-ox.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f402.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f402.svg"),none}.em-package{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e6.png")}.em-package.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e6.svg"),none}.em-page_facing_up{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c4.png")}.em-page_facing_up.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c4.svg"),none}.em-page_with_curl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4c3.png")}.em-page_with_curl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4c3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4c3.svg"),none}.em-pager{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4df.png")}.em-pager.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4df.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4df.svg"),none}.em-palm_tree{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f334.png")}.em-palm_tree.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f334.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f334.svg"),none}.em-palms_up_together{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f932.png")}.em-palms_up_together.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f932.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f932.svg"),none}.em-pancakes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f95e.png")}.em-pancakes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f95e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f95e.svg"),none}.em-panda_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f43c.png")}.em-panda_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f43c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f43c.svg"),none}.em-paperclip{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ce.png")}.em-paperclip.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ce.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ce.svg"),none}.em-parking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f17f.png")}.em-parking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f17f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f17f.svg"),none}.em-part_alternation_mark{background-image:url("https://twemoji.maxcdn.com/2/72x72/303d.png")}.em-part_alternation_mark.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/303d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/303d.svg"),none}.em-partly_sunny{background-image:url("https://twemoji.maxcdn.com/2/72x72/26c5.png")}.em-partly_sunny.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26c5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26c5.svg"),none}.em-partly_sunny_rain{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f326.png")}.em-partly_sunny_rain.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f326.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f326.svg"),none}.em-passenger_ship{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6f3.png")}.em-passenger_ship.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f3.svg"),none}.em-passport_control{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6c2.png")}.em-passport_control.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6c2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6c2.svg"),none}.em-peace_symbol{background-image:url("https://twemoji.maxcdn.com/2/72x72/262e.png")}.em-peace_symbol.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/262e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/262e.svg"),none}.em-peach{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f351.png")}.em-peach.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f351.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f351.svg"),none}.em-peanuts{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f95c.png")}.em-peanuts.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f95c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f95c.svg"),none}.em-pear{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f350.png")}.em-pear.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f350.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f350.svg"),none}.em-pencil2{background-image:url("https://twemoji.maxcdn.com/2/72x72/270f.png")}.em-pencil2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/270f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/270f.svg"),none}.em-penguin{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f427.png")}.em-penguin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f427.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f427.svg"),none}.em-pensive{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f614.png")}.em-pensive.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f614.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f614.svg"),none}.em-performing_arts{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ad.png")}.em-performing_arts.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ad.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ad.svg"),none}.em-persevere{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f623.png")}.em-persevere.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f623.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f623.svg"),none}.em-person_climbing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d7.png")}.em-person_climbing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d7.svg"),none}.em-person_doing_cartwheel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f938.png")}.em-person_doing_cartwheel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f938.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f938.svg"),none}.em-person_frowning{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64d.png")}.em-person_frowning.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d.svg"),none}.em-person_in_lotus_position{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d8.png")}.em-person_in_lotus_position.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d8.svg"),none}.em-person_in_steamy_room{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d6.png")}.em-person_in_steamy_room.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d6.svg"),none}.em-person_with_ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f9.png")}.em-person_with_ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f9.svg"),none}.em-person_with_blond_hair{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f471.png")}.em-person_with_blond_hair.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f471.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f471.svg"),none}.em-person_with_headscarf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d5.png")}.em-person_with_headscarf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d5.svg"),none}.em-person_with_pouting_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64e.png")}.em-person_with_pouting_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e.svg"),none}.em-phone{background-image:url("https://twemoji.maxcdn.com/2/72x72/260e.png")}.em-phone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/260e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/260e.svg"),none}.em-pick{background-image:url("https://twemoji.maxcdn.com/2/72x72/26cf.png")}.em-pick.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26cf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26cf.svg"),none}.em-pie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f967.png")}.em-pie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f967.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f967.svg"),none}.em-pig{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f437.png")}.em-pig.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f437.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f437.svg"),none}.em-pig2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f416.png")}.em-pig2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f416.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f416.svg"),none}.em-pig_nose{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f43d.png")}.em-pig_nose.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f43d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f43d.svg"),none}.em-pill{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f48a.png")}.em-pill.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f48a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f48a.svg"),none}.em-pineapple{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f34d.png")}.em-pineapple.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f34d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f34d.svg"),none}.em-pisces{background-image:url("https://twemoji.maxcdn.com/2/72x72/2653.png")}.em-pisces.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2653.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2653.svg"),none}.em-pizza{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f355.png")}.em-pizza.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f355.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f355.svg"),none}.em-place_of_worship{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6d0.png")}.em-place_of_worship.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6d0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6d0.svg"),none}.em-point_down{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f447.png")}.em-point_down.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f447.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f447.svg"),none}.em-point_left{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f448.png")}.em-point_left.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f448.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f448.svg"),none}.em-point_right{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f449.png")}.em-point_right.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f449.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f449.svg"),none}.em-point_up{background-image:url("https://twemoji.maxcdn.com/2/72x72/261d.png")}.em-point_up.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/261d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/261d.svg"),none}.em-point_up_2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f446.png")}.em-point_up_2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f446.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f446.svg"),none}.em-police_car{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f693.png")}.em-police_car.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f693.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f693.svg"),none}.em-poodle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f429.png")}.em-poodle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f429.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f429.svg"),none}.em-popcorn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f37f.png")}.em-popcorn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f37f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f37f.svg"),none}.em-post_office{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3e3.png")}.em-post_office.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3e3.svg"),none}.em-postal_horn{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ef.png")}.em-postal_horn.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ef.svg"),none}.em-postbox{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ee.png")}.em-postbox.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ee.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ee.svg"),none}.em-potable_water{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b0.png")}.em-potable_water.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b0.svg"),none}.em-potato{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f954.png")}.em-potato.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f954.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f954.svg"),none}.em-pouch{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f45d.png")}.em-pouch.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f45d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f45d.svg"),none}.em-poultry_leg{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f357.png")}.em-poultry_leg.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f357.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f357.svg"),none}.em-pound{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b7.png")}.em-pound.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b7.svg"),none}.em-pouting_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f63e.png")}.em-pouting_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f63e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f63e.svg"),none}.em-pray{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64f.png")}.em-pray.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64f.svg"),none}.em-prayer_beads{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ff.png")}.em-prayer_beads.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ff.svg"),none}.em-pregnant_woman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f930.png")}.em-pregnant_woman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f930.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f930.svg"),none}.em-pretzel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f968.png")}.em-pretzel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f968.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f968.svg"),none}.em-prince{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f934.png")}.em-prince.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f934.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f934.svg"),none}.em-princess{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f478.png")}.em-princess.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f478.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f478.svg"),none}.em-printer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5a8.png")}.em-printer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5a8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5a8.svg"),none}.em-purple_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f49c.png")}.em-purple_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f49c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f49c.svg"),none}.em-purse{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f45b.png")}.em-purse.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f45b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f45b.svg"),none}.em-pushpin{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4cc.png")}.em-pushpin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4cc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cc.svg"),none}.em-put_litter_in_its_place{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6ae.png")}.em-put_litter_in_its_place.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6ae.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ae.svg"),none}.em-question{background-image:url("https://twemoji.maxcdn.com/2/72x72/2753.png")}.em-question.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2753.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2753.svg"),none}.em-rabbit{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f430.png")}.em-rabbit.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f430.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f430.svg"),none}.em-rabbit2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f407.png")}.em-rabbit2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f407.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f407.svg"),none}.em-racehorse{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f40e.png")}.em-racehorse.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f40e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f40e.svg"),none}.em-racing_car{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ce.png")}.em-racing_car.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ce.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ce.svg"),none}.em-racing_motorcycle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cd.png")}.em-racing_motorcycle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cd.svg"),none}.em-radio{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4fb.png")}.em-radio.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fb.svg"),none}.em-radio_button{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f518.png")}.em-radio_button.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f518.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f518.svg"),none}.em-radioactive_sign{background-image:url("https://twemoji.maxcdn.com/2/72x72/2622.png")}.em-radioactive_sign.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2622.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2622.svg"),none}.em-rage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f621.png")}.em-rage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f621.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f621.svg"),none}.em-railway_car{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f683.png")}.em-railway_car.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f683.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f683.svg"),none}.em-railway_track{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6e4.png")}.em-railway_track.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6e4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e4.svg"),none}.em-rain_cloud{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f327.png")}.em-rain_cloud.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f327.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f327.svg"),none}.em-rainbow{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f308.png")}.em-rainbow.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f308.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f308.svg"),none}.em-rainbow-flag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f3-fe0f-200d-1f308.png")}.em-rainbow-flag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f3-fe0f-200d-1f308.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f3-fe0f-200d-1f308.svg"),none}.em-raised_back_of_hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f91a.png")}.em-raised_back_of_hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f91a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f91a.svg"),none}.em-raised_hand_with_fingers_splayed{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f590.png")}.em-raised_hand_with_fingers_splayed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f590.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f590.svg"),none}.em-raised_hands{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64c.png")}.em-raised_hands.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64c.svg"),none}.em-raising_hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64b.png")}.em-raising_hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b.svg"),none}.em-ram{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f40f.png")}.em-ram.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f40f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f40f.svg"),none}.em-ramen{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f35c.png")}.em-ramen.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f35c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f35c.svg"),none}.em-rat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f400.png")}.em-rat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f400.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f400.svg"),none}.em-recycle{background-image:url("https://twemoji.maxcdn.com/2/72x72/267b.png")}.em-recycle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/267b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/267b.svg"),none}.em-red_circle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f534.png")}.em-red_circle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f534.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f534.svg"),none}.em-registered{background-image:url("https://twemoji.maxcdn.com/2/72x72/ae.png")}.em-registered.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/ae.png");background-image:url("https://twemoji.maxcdn.com/2/svg/ae.svg"),none}.em-relaxed{background-image:url("https://twemoji.maxcdn.com/2/72x72/263a.png")}.em-relaxed.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/263a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/263a.svg"),none}.em-relieved{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f60c.png")}.em-relieved.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f60c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f60c.svg"),none}.em-reminder_ribbon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f397.png")}.em-reminder_ribbon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f397.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f397.svg"),none}.em-repeat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f501.png")}.em-repeat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f501.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f501.svg"),none}.em-repeat_one{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f502.png")}.em-repeat_one.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f502.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f502.svg"),none}.em-restroom{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6bb.png")}.em-restroom.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6bb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bb.svg"),none}.em-revolving_hearts{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f49e.png")}.em-revolving_hearts.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f49e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f49e.svg"),none}.em-rewind{background-image:url("https://twemoji.maxcdn.com/2/72x72/23ea.png")}.em-rewind.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23ea.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23ea.svg"),none}.em-rhinoceros{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f98f.png")}.em-rhinoceros.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f98f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f98f.svg"),none}.em-ribbon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f380.png")}.em-ribbon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f380.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f380.svg"),none}.em-rice{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f35a.png")}.em-rice.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f35a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f35a.svg"),none}.em-rice_ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f359.png")}.em-rice_ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f359.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f359.svg"),none}.em-rice_cracker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f358.png")}.em-rice_cracker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f358.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f358.svg"),none}.em-rice_scene{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f391.png")}.em-rice_scene.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f391.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f391.svg"),none}.em-right-facing_fist{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f91c.png")}.em-right-facing_fist.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f91c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f91c.svg"),none}.em-right_anger_bubble{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5ef.png")}.em-right_anger_bubble.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5ef.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5ef.svg"),none}.em-ring{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f48d.png")}.em-ring.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f48d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f48d.svg"),none}.em-robot_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f916.png")}.em-robot_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f916.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f916.svg"),none}.em-rocket{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f680.png")}.em-rocket.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f680.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f680.svg"),none}.em-rolled_up_newspaper{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5de.png")}.em-rolled_up_newspaper.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5de.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5de.svg"),none}.em-roller_coaster{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a2.png")}.em-roller_coaster.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a2.svg"),none}.em-rolling_on_the_floor_laughing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f923.png")}.em-rolling_on_the_floor_laughing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f923.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f923.svg"),none}.em-rooster{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f413.png")}.em-rooster.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f413.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f413.svg"),none}.em-rose{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f339.png")}.em-rose.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f339.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f339.svg"),none}.em-rosette{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f5.png")}.em-rosette.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f5.svg"),none}.em-rotating_light{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a8.png")}.em-rotating_light.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a8.svg"),none}.em-round_pushpin{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4cd.png")}.em-round_pushpin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4cd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cd.svg"),none}.em-rowboat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a3.png")}.em-rowboat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3.svg"),none}.em-ru{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1fa.png")}.em-ru.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1f7-1f1fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1f7-1f1fa.svg"),none}.em-rugby_football{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c9.png")}.em-rugby_football.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c9.svg"),none}.em-runner{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c3.png")}.em-runner.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3.svg"),none}.em-running_shirt_with_sash{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3bd.png")}.em-running_shirt_with_sash.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3bd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bd.svg"),none}.em-sa{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f202.png")}.em-sa.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f202.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f202.svg"),none}.em-sagittarius{background-image:url("https://twemoji.maxcdn.com/2/72x72/2650.png")}.em-sagittarius.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2650.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2650.svg"),none}.em-sake{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f376.png")}.em-sake.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f376.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f376.svg"),none}.em-sandal{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f461.png")}.em-sandal.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f461.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f461.svg"),none}.em-sandwich{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f96a.png")}.em-sandwich.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f96a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f96a.svg"),none}.em-santa{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f385.png")}.em-santa.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f385.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f385.svg"),none}.em-satellite{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6f0.png")}.em-satellite.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6f0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f0.svg"),none}.em-satellite_antenna{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4e1.png")}.em-satellite_antenna.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4e1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4e1.svg"),none}.em-sauropod{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f995.png")}.em-sauropod.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f995.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f995.svg"),none}.em-saxophone{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b7.png")}.em-saxophone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b7.svg"),none}.em-scales{background-image:url("https://twemoji.maxcdn.com/2/72x72/2696.png")}.em-scales.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2696.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2696.svg"),none}.em-scarf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9e3.png")}.em-scarf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9e3.svg"),none}.em-school{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3eb.png")}.em-school.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3eb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3eb.svg"),none}.em-school_satchel{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f392.png")}.em-school_satchel.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f392.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f392.svg"),none}.em-scissors{background-image:url("https://twemoji.maxcdn.com/2/72x72/2702.png")}.em-scissors.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2702.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2702.svg"),none}.em-scooter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6f4.png")}.em-scooter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f4.svg"),none}.em-scorpion{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f982.png")}.em-scorpion.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f982.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f982.svg"),none}.em-scorpius{background-image:url("https://twemoji.maxcdn.com/2/72x72/264f.png")}.em-scorpius.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/264f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/264f.svg"),none}.em-scream{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f631.png")}.em-scream.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f631.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f631.svg"),none}.em-scream_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f640.png")}.em-scream_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f640.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f640.svg"),none}.em-scroll{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4dc.png")}.em-scroll.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4dc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4dc.svg"),none}.em-seat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ba.png")}.em-seat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ba.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ba.svg"),none}.em-second_place_medal{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f948.png")}.em-second_place_medal.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f948.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f948.svg"),none}.em-secret{background-image:url("https://twemoji.maxcdn.com/2/72x72/3299.png")}.em-secret.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/3299.png");background-image:url("https://twemoji.maxcdn.com/2/svg/3299.svg"),none}.em-see_no_evil{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f648.png")}.em-see_no_evil.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f648.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f648.svg"),none}.em-seedling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f331.png")}.em-seedling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f331.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f331.svg"),none}.em-selfie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f933.png")}.em-selfie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f933.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f933.svg"),none}.em-serious_face_with_symbols_covering_mouth{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f92c.png")}.em-serious_face_with_symbols_covering_mouth.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f92c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f92c.svg"),none}.em-seven{background-image:url("https://twemoji.maxcdn.com/2/72x72/37-20e3.png")}.em-seven.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/37-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/37-20e3.svg"),none}.em-shallow_pan_of_food{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f958.png")}.em-shallow_pan_of_food.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f958.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f958.svg"),none}.em-shamrock{background-image:url("https://twemoji.maxcdn.com/2/72x72/2618.png")}.em-shamrock.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2618.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2618.svg"),none}.em-shark{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f988.png")}.em-shark.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f988.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f988.svg"),none}.em-shaved_ice{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f367.png")}.em-shaved_ice.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f367.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f367.svg"),none}.em-sheep{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f411.png")}.em-sheep.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f411.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f411.svg"),none}.em-shell{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f41a.png")}.em-shell.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f41a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f41a.svg"),none}.em-shield{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6e1.png")}.em-shield.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6e1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e1.svg"),none}.em-shinto_shrine{background-image:url("https://twemoji.maxcdn.com/2/72x72/26e9.png")}.em-shinto_shrine.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26e9.svg"),none}.em-ship{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a2.png")}.em-ship.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a2.svg"),none}.em-shirt{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f455.png")}.em-shirt.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f455.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f455.svg"),none}.em-shocked_face_with_exploding_head{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f92f.png")}.em-shocked_face_with_exploding_head.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f92f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f92f.svg"),none}.em-shopping_bags{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6cd.png")}.em-shopping_bags.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6cd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cd.svg"),none}.em-shopping_trolley{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6d2.png")}.em-shopping_trolley.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6d2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6d2.svg"),none}.em-shower{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6bf.png")}.em-shower.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6bf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bf.svg"),none}.em-shrimp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f990.png")}.em-shrimp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f990.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f990.svg"),none}.em-shrug{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f937.png")}.em-shrug.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f937.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f937.svg"),none}.em-signal_strength{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f6.png")}.em-signal_strength.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f6.svg"),none}.em-six{background-image:url("https://twemoji.maxcdn.com/2/72x72/36-20e3.png")}.em-six.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/36-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/36-20e3.svg"),none}.em-six_pointed_star{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f52f.png")}.em-six_pointed_star.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f52f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f52f.svg"),none}.em-ski{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3bf.png")}.em-ski.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3bf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bf.svg"),none}.em-skier{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f7.png")}.em-skier.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f7.svg"),none}.em-skin-tone-2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3fb.png")}.em-skin-tone-2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fb.svg"),none}.em-skin-tone-3{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3fc.png")}.em-skin-tone-3.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fc.svg"),none}.em-skin-tone-4{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3fd.png")}.em-skin-tone-4.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fd.svg"),none}.em-skin-tone-5{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3fe.png")}.em-skin-tone-5.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3fe.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3fe.svg"),none}.em-skin-tone-6{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ff.png")}.em-skin-tone-6.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ff.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ff.svg"),none}.em-skull{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f480.png")}.em-skull.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f480.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f480.svg"),none}.em-skull_and_crossbones{background-image:url("https://twemoji.maxcdn.com/2/72x72/2620.png")}.em-skull_and_crossbones.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2620.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2620.svg"),none}.em-sled{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6f7.png")}.em-sled.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6f7.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6f7.svg"),none}.em-sleeping{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f634.png")}.em-sleeping.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f634.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f634.svg"),none}.em-sleeping_accommodation{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6cc.png")}.em-sleeping_accommodation.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6cc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6cc.svg"),none}.em-sleepy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f62a.png")}.em-sleepy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f62a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f62a.svg"),none}.em-sleuth_or_spy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f575.png")}.em-sleuth_or_spy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f575.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f575.svg"),none}.em-slightly_frowning_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f641.png")}.em-slightly_frowning_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f641.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f641.svg"),none}.em-slightly_smiling_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f642.png")}.em-slightly_smiling_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f642.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f642.svg"),none}.em-slot_machine{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3b0.png")}.em-slot_machine.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3b0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3b0.svg"),none}.em-small_airplane{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6e9.png")}.em-small_airplane.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6e9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6e9.svg"),none}.em-small_blue_diamond{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f539.png")}.em-small_blue_diamond.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f539.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f539.svg"),none}.em-small_orange_diamond{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f538.png")}.em-small_orange_diamond.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f538.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f538.svg"),none}.em-small_red_triangle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f53a.png")}.em-small_red_triangle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f53a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f53a.svg"),none}.em-small_red_triangle_down{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f53b.png")}.em-small_red_triangle_down.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f53b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f53b.svg"),none}.em-smile{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f604.png")}.em-smile.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f604.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f604.svg"),none}.em-smile_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f638.png")}.em-smile_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f638.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f638.svg"),none}.em-smiley{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f603.png")}.em-smiley.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f603.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f603.svg"),none}.em-smiley_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f63a.png")}.em-smiley_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f63a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f63a.svg"),none}.em-smiling_face_with_smiling_eyes_and_hand_covering_mouth{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f92d.png")}.em-smiling_face_with_smiling_eyes_and_hand_covering_mouth.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f92d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f92d.svg"),none}.em-smiling_imp{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f608.png")}.em-smiling_imp.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f608.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f608.svg"),none}.em-smirk{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f60f.png")}.em-smirk.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f60f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f60f.svg"),none}.em-smirk_cat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f63c.png")}.em-smirk_cat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f63c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f63c.svg"),none}.em-smoking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6ac.png")}.em-smoking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6ac.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ac.svg"),none}.em-snail{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f40c.png")}.em-snail.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f40c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f40c.svg"),none}.em-snake{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f40d.png")}.em-snake.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f40d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f40d.svg"),none}.em-sneezing_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f927.png")}.em-sneezing_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f927.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f927.svg"),none}.em-snow_capped_mountain{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d4.png")}.em-snow_capped_mountain.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d4.svg"),none}.em-snow_cloud{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f328.png")}.em-snow_cloud.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f328.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f328.svg"),none}.em-snowboarder{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c2.png")}.em-snowboarder.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c2.svg"),none}.em-snowflake{background-image:url("https://twemoji.maxcdn.com/2/72x72/2744.png")}.em-snowflake.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2744.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2744.svg"),none}.em-snowman{background-image:url("https://twemoji.maxcdn.com/2/72x72/2603.png")}.em-snowman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2603.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2603.svg"),none}.em-snowman_without_snow{background-image:url("https://twemoji.maxcdn.com/2/72x72/26c4.png")}.em-snowman_without_snow.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26c4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26c4.svg"),none}.em-sob{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f62d.png")}.em-sob.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f62d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f62d.svg"),none}.em-soccer{background-image:url("https://twemoji.maxcdn.com/2/72x72/26bd.png")}.em-soccer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26bd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26bd.svg"),none}.em-socks{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9e6.png")}.em-socks.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9e6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9e6.svg"),none}.em-soon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f51c.png")}.em-soon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f51c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f51c.svg"),none}.em-sos{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f198.png")}.em-sos.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f198.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f198.svg"),none}.em-sound{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f509.png")}.em-sound.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f509.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f509.svg"),none}.em-space_invader{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f47e.png")}.em-space_invader.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f47e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f47e.svg"),none}.em-spades{background-image:url("https://twemoji.maxcdn.com/2/72x72/2660.png")}.em-spades.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2660.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2660.svg"),none}.em-spaghetti{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f35d.png")}.em-spaghetti.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f35d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f35d.svg"),none}.em-sparkle{background-image:url("https://twemoji.maxcdn.com/2/72x72/2747.png")}.em-sparkle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2747.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2747.svg"),none}.em-sparkler{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f387.png")}.em-sparkler.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f387.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f387.svg"),none}.em-sparkles{background-image:url("https://twemoji.maxcdn.com/2/72x72/2728.png")}.em-sparkles.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2728.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2728.svg"),none}.em-sparkling_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f496.png")}.em-sparkling_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f496.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f496.svg"),none}.em-speak_no_evil{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64a.png")}.em-speak_no_evil.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64a.svg"),none}.em-speaker{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f508.png")}.em-speaker.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f508.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f508.svg"),none}.em-speaking_head_in_silhouette{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5e3.png")}.em-speaking_head_in_silhouette.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5e3.svg"),none}.em-speech_balloon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ac.png")}.em-speech_balloon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ac.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ac.svg"),none}.em-speedboat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a4.png")}.em-speedboat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a4.svg"),none}.em-spider{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f577.png")}.em-spider.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f577.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f577.svg"),none}.em-spider_web{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f578.png")}.em-spider_web.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f578.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f578.svg"),none}.em-spiral_calendar_pad{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5d3.png")}.em-spiral_calendar_pad.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5d3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5d3.svg"),none}.em-spiral_note_pad{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5d2.png")}.em-spiral_note_pad.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5d2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5d2.svg"),none}.em-spock-hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f596.png")}.em-spock-hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f596.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f596.svg"),none}.em-spoon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f944.png")}.em-spoon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f944.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f944.svg"),none}.em-sports_medal{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c5.png")}.em-sports_medal.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c5.svg"),none}.em-squid{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f991.png")}.em-squid.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f991.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f991.svg"),none}.em-stadium{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3df.png")}.em-stadium.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3df.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3df.svg"),none}.em-staff_of_aesculapius{background-image:url("https://twemoji.maxcdn.com/2/72x72/2695.png")}.em-staff_of_aesculapius.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2695.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2695.svg"),none}.em-star{background-image:url("https://twemoji.maxcdn.com/2/72x72/2b50.png")}.em-star.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2b50.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2b50.svg"),none}.em-star2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f31f.png")}.em-star2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f31f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f31f.svg"),none}.em-star_and_crescent{background-image:url("https://twemoji.maxcdn.com/2/72x72/262a.png")}.em-star_and_crescent.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/262a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/262a.svg"),none}.em-star_of_david{background-image:url("https://twemoji.maxcdn.com/2/72x72/2721.png")}.em-star_of_david.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2721.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2721.svg"),none}.em-stars{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f320.png")}.em-stars.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f320.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f320.svg"),none}.em-station{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f689.png")}.em-station.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f689.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f689.svg"),none}.em-statue_of_liberty{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5fd.png")}.em-statue_of_liberty.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fd.svg"),none}.em-steam_locomotive{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f682.png")}.em-steam_locomotive.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f682.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f682.svg"),none}.em-stew{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f372.png")}.em-stew.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f372.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f372.svg"),none}.em-stopwatch{background-image:url("https://twemoji.maxcdn.com/2/72x72/23f1.png")}.em-stopwatch.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23f1.svg"),none}.em-straight_ruler{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4cf.png")}.em-straight_ruler.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4cf.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4cf.svg"),none}.em-strawberry{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f353.png")}.em-strawberry.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f353.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f353.svg"),none}.em-stuck_out_tongue{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f61b.png")}.em-stuck_out_tongue.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f61b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f61b.svg"),none}.em-stuck_out_tongue_closed_eyes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f61d.png")}.em-stuck_out_tongue_closed_eyes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f61d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f61d.svg"),none}.em-stuck_out_tongue_winking_eye{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f61c.png")}.em-stuck_out_tongue_winking_eye.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f61c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f61c.svg"),none}.em-studio_microphone{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f399.png")}.em-studio_microphone.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f399.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f399.svg"),none}.em-stuffed_flatbread{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f959.png")}.em-stuffed_flatbread.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f959.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f959.svg"),none}.em-sun_with_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f31e.png")}.em-sun_with_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f31e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f31e.svg"),none}.em-sunflower{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f33b.png")}.em-sunflower.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f33b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f33b.svg"),none}.em-sunglasses{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f60e.png")}.em-sunglasses.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f60e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f60e.svg"),none}.em-sunny{background-image:url("https://twemoji.maxcdn.com/2/72x72/2600.png")}.em-sunny.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2600.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2600.svg"),none}.em-sunrise{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f305.png")}.em-sunrise.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f305.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f305.svg"),none}.em-sunrise_over_mountains{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f304.png")}.em-sunrise_over_mountains.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f304.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f304.svg"),none}.em-surfer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c4.png")}.em-surfer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4.svg"),none}.em-sushi{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f363.png")}.em-sushi.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f363.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f363.svg"),none}.em-suspension_railway{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f69f.png")}.em-suspension_railway.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f69f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f69f.svg"),none}.em-sweat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f613.png")}.em-sweat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f613.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f613.svg"),none}.em-sweat_drops{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a6.png")}.em-sweat_drops.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a6.svg"),none}.em-sweat_smile{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f605.png")}.em-sweat_smile.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f605.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f605.svg"),none}.em-sweet_potato{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f360.png")}.em-sweet_potato.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f360.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f360.svg"),none}.em-swimmer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ca.png")}.em-swimmer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ca.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca.svg"),none}.em-symbols{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f523.png")}.em-symbols.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f523.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f523.svg"),none}.em-synagogue{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f54d.png")}.em-synagogue.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f54d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f54d.svg"),none}.em-syringe{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f489.png")}.em-syringe.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f489.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f489.svg"),none}.em-t-rex{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f996.png")}.em-t-rex.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f996.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f996.svg"),none}.em-table_tennis_paddle_and_ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d3.png")}.em-table_tennis_paddle_and_ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d3.svg"),none}.em-taco{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f32e.png")}.em-taco.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f32e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f32e.svg"),none}.em-tada{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f389.png")}.em-tada.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f389.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f389.svg"),none}.em-takeout_box{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f961.png")}.em-takeout_box.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f961.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f961.svg"),none}.em-tanabata_tree{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f38b.png")}.em-tanabata_tree.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f38b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f38b.svg"),none}.em-tangerine{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f34a.png")}.em-tangerine.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f34a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f34a.svg"),none}.em-taurus{background-image:url("https://twemoji.maxcdn.com/2/72x72/2649.png")}.em-taurus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2649.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2649.svg"),none}.em-taxi{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f695.png")}.em-taxi.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f695.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f695.svg"),none}.em-tea{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f375.png")}.em-tea.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f375.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f375.svg"),none}.em-telephone_receiver{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4de.png")}.em-telephone_receiver.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4de.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4de.svg"),none}.em-telescope{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f52d.png")}.em-telescope.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f52d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f52d.svg"),none}.em-tennis{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3be.png")}.em-tennis.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3be.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3be.svg"),none}.em-tent{background-image:url("https://twemoji.maxcdn.com/2/72x72/26fa.png")}.em-tent.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26fa.svg"),none}.em-the_horns{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f918.png")}.em-the_horns.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f918.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f918.svg"),none}.em-thermometer{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f321.png")}.em-thermometer.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f321.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f321.svg"),none}.em-thinking_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f914.png")}.em-thinking_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f914.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f914.svg"),none}.em-third_place_medal{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f949.png")}.em-third_place_medal.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f949.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f949.svg"),none}.em-thought_balloon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ad.png")}.em-thought_balloon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ad.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ad.svg"),none}.em-three{background-image:url("https://twemoji.maxcdn.com/2/72x72/33-20e3.png")}.em-three.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/33-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/33-20e3.svg"),none}.em-three_button_mouse{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5b1.png")}.em-three_button_mouse.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5b1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5b1.svg"),none}.em-thunder_cloud_and_rain{background-image:url("https://twemoji.maxcdn.com/2/72x72/26c8.png")}.em-thunder_cloud_and_rain.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26c8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26c8.svg"),none}.em-ticket{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ab.png")}.em-ticket.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ab.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ab.svg"),none}.em-tiger{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f42f.png")}.em-tiger.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f42f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f42f.svg"),none}.em-tiger2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f405.png")}.em-tiger2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f405.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f405.svg"),none}.em-timer_clock{background-image:url("https://twemoji.maxcdn.com/2/72x72/23f2.png")}.em-timer_clock.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/23f2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/23f2.svg"),none}.em-tired_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f62b.png")}.em-tired_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f62b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f62b.svg"),none}.em-tm{background-image:url("https://twemoji.maxcdn.com/2/72x72/2122.png")}.em-tm.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2122.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2122.svg"),none}.em-toilet{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6bd.png")}.em-toilet.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6bd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6bd.svg"),none}.em-tokyo_tower{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5fc.png")}.em-tokyo_tower.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fc.svg"),none}.em-tomato{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f345.png")}.em-tomato.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f345.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f345.svg"),none}.em-tongue{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f445.png")}.em-tongue.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f445.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f445.svg"),none}.em-top{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f51d.png")}.em-top.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f51d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f51d.svg"),none}.em-tophat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3a9.png")}.em-tophat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3a9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3a9.svg"),none}.em-tornado{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f32a.png")}.em-tornado.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f32a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f32a.svg"),none}.em-trackball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5b2.png")}.em-trackball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5b2.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5b2.svg"),none}.em-tractor{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f69c.png")}.em-tractor.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f69c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f69c.svg"),none}.em-traffic_light{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a5.png")}.em-traffic_light.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a5.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a5.svg"),none}.em-train{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f68b.png")}.em-train.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f68b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f68b.svg"),none}.em-train2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f686.png")}.em-train2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f686.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f686.svg"),none}.em-tram{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f68a.png")}.em-tram.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f68a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f68a.svg"),none}.em-triangular_flag_on_post{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a9.png")}.em-triangular_flag_on_post.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a9.svg"),none}.em-triangular_ruler{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4d0.png")}.em-triangular_ruler.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4d0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4d0.svg"),none}.em-trident{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f531.png")}.em-trident.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f531.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f531.svg"),none}.em-triumph{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f624.png")}.em-triumph.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f624.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f624.svg"),none}.em-trolleybus{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f68e.png")}.em-trolleybus.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f68e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f68e.svg"),none}.em-trophy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c6.png")}.em-trophy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c6.svg"),none}.em-tropical_drink{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f379.png")}.em-tropical_drink.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f379.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f379.svg"),none}.em-tropical_fish{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f420.png")}.em-tropical_fish.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f420.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f420.svg"),none}.em-truck{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f69a.png")}.em-truck.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f69a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f69a.svg"),none}.em-trumpet{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ba.png")}.em-trumpet.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ba.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ba.svg"),none}.em-tulip{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f337.png")}.em-tulip.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f337.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f337.svg"),none}.em-tumbler_glass{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f943.png")}.em-tumbler_glass.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f943.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f943.svg"),none}.em-turkey{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f983.png")}.em-turkey.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f983.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f983.svg"),none}.em-turtle{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f422.png")}.em-turtle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f422.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f422.svg"),none}.em-tv{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4fa.png")}.em-tv.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fa.svg"),none}.em-twisted_rightwards_arrows{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f500.png")}.em-twisted_rightwards_arrows.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f500.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f500.svg"),none}.em-two{background-image:url("https://twemoji.maxcdn.com/2/72x72/32-20e3.png")}.em-two.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/32-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/32-20e3.svg"),none}.em-two_hearts{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f495.png")}.em-two_hearts.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f495.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f495.svg"),none}.em-two_men_holding_hands{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46c.png")}.em-two_men_holding_hands.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46c.svg"),none}.em-two_women_holding_hands{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46d.png")}.em-two_women_holding_hands.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46d.svg"),none}.em-u5272{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f239.png")}.em-u5272.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f239.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f239.svg"),none}.em-u5408{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f234.png")}.em-u5408.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f234.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f234.svg"),none}.em-u55b6{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f23a.png")}.em-u55b6.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f23a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f23a.svg"),none}.em-u6307{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f22f.png")}.em-u6307.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f22f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f22f.svg"),none}.em-u6708{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f237.png")}.em-u6708.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f237.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f237.svg"),none}.em-u6709{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f236.png")}.em-u6709.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f236.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f236.svg"),none}.em-u6e80{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f235.png")}.em-u6e80.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f235.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f235.svg"),none}.em-u7121{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f21a.png")}.em-u7121.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f21a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f21a.svg"),none}.em-u7533{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f238.png")}.em-u7533.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f238.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f238.svg"),none}.em-u7981{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f232.png")}.em-u7981.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f232.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f232.svg"),none}.em-u7a7a{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f233.png")}.em-u7a7a.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f233.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f233.svg"),none}.em-umbrella{background-image:url("https://twemoji.maxcdn.com/2/72x72/2602.png")}.em-umbrella.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2602.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2602.svg"),none}.em-umbrella_on_ground{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f1.png")}.em-umbrella_on_ground.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f1.svg"),none}.em-umbrella_with_rain_drops{background-image:url("https://twemoji.maxcdn.com/2/72x72/2614.png")}.em-umbrella_with_rain_drops.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2614.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2614.svg"),none}.em-unamused{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f612.png")}.em-unamused.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f612.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f612.svg"),none}.em-underage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f51e.png")}.em-underage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f51e.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f51e.svg"),none}.em-unicorn_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f984.png")}.em-unicorn_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f984.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f984.svg"),none}.em-unlock{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f513.png")}.em-unlock.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f513.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f513.svg"),none}.em-up{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f199.png")}.em-up.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f199.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f199.svg"),none}.em-upside_down_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f643.png")}.em-upside_down_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f643.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f643.svg"),none}.em-us{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1f8.png")}.em-us.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f1fa-1f1f8.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f1fa-1f1f8.svg"),none}.em-v{background-image:url("https://twemoji.maxcdn.com/2/72x72/270c.png")}.em-v.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/270c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/270c.svg"),none}.em-vampire{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9db.png")}.em-vampire.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9db.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9db.svg"),none}.em-vertical_traffic_light{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a6.png")}.em-vertical_traffic_light.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a6.svg"),none}.em-vhs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4fc.png")}.em-vhs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4fc.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4fc.svg"),none}.em-vibration_mode{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f3.png")}.em-vibration_mode.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f3.svg"),none}.em-video_camera{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4f9.png")}.em-video_camera.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4f9.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4f9.svg"),none}.em-video_game{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ae.png")}.em-video_game.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ae.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ae.svg"),none}.em-violin{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3bb.png")}.em-violin.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3bb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3bb.svg"),none}.em-virgo{background-image:url("https://twemoji.maxcdn.com/2/72x72/264d.png")}.em-virgo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/264d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/264d.svg"),none}.em-volcano{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f30b.png")}.em-volcano.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f30b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f30b.svg"),none}.em-volleyball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3d0.png")}.em-volleyball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3d0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3d0.svg"),none}.em-vs{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f19a.png")}.em-vs.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f19a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f19a.svg"),none}.em-walking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b6.png")}.em-walking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b6.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6.svg"),none}.em-waning_crescent_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f318.png")}.em-waning_crescent_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f318.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f318.svg"),none}.em-waning_gibbous_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f316.png")}.em-waning_gibbous_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f316.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f316.svg"),none}.em-warning{background-image:url("https://twemoji.maxcdn.com/2/72x72/26a0.png")}.em-warning.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26a0.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26a0.svg"),none}.em-wastebasket{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5d1.png")}.em-wastebasket.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5d1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5d1.svg"),none}.em-watch{background-image:url("https://twemoji.maxcdn.com/2/72x72/231a.png")}.em-watch.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/231a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/231a.svg"),none}.em-water_buffalo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f403.png")}.em-water_buffalo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f403.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f403.svg"),none}.em-water_polo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93d.png")}.em-water_polo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d.svg"),none}.em-watermelon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f349.png")}.em-watermelon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f349.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f349.svg"),none}.em-wave{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f44b.png")}.em-wave.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f44b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f44b.svg"),none}.em-waving_black_flag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f4.png")}.em-waving_black_flag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f4.svg"),none}.em-waving_white_flag{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3f3.png")}.em-waving_white_flag.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3f3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3f3.svg"),none}.em-wavy_dash{background-image:url("https://twemoji.maxcdn.com/2/72x72/3030.png")}.em-wavy_dash.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/3030.png");background-image:url("https://twemoji.maxcdn.com/2/svg/3030.svg"),none}.em-waxing_crescent_moon{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f312.png")}.em-waxing_crescent_moon.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f312.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f312.svg"),none}.em-wc{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6be.png")}.em-wc.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6be.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6be.svg"),none}.em-weary{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f629.png")}.em-weary.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f629.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f629.svg"),none}.em-wedding{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f492.png")}.em-wedding.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f492.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f492.svg"),none}.em-weight_lifter{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cb.png")}.em-weight_lifter.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb.svg"),none}.em-whale{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f433.png")}.em-whale.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f433.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f433.svg"),none}.em-whale2{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f40b.png")}.em-whale2.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f40b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f40b.svg"),none}.em-wheel_of_dharma{background-image:url("https://twemoji.maxcdn.com/2/72x72/2638.png")}.em-wheel_of_dharma.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2638.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2638.svg"),none}.em-wheelchair{background-image:url("https://twemoji.maxcdn.com/2/72x72/267f.png")}.em-wheelchair.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/267f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/267f.svg"),none}.em-white_check_mark{background-image:url("https://twemoji.maxcdn.com/2/72x72/2705.png")}.em-white_check_mark.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2705.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2705.svg"),none}.em-white_circle{background-image:url("https://twemoji.maxcdn.com/2/72x72/26aa.png")}.em-white_circle.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26aa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26aa.svg"),none}.em-white_flower{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4ae.png")}.em-white_flower.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4ae.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4ae.svg"),none}.em-white_frowning_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/2639.png")}.em-white_frowning_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2639.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2639.svg"),none}.em-white_large_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/2b1c.png")}.em-white_large_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/2b1c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/2b1c.svg"),none}.em-white_medium_small_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/25fd.png")}.em-white_medium_small_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25fd.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25fd.svg"),none}.em-white_medium_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/25fb.png")}.em-white_medium_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25fb.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25fb.svg"),none}.em-white_small_square{background-image:url("https://twemoji.maxcdn.com/2/72x72/25ab.png")}.em-white_small_square.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/25ab.png");background-image:url("https://twemoji.maxcdn.com/2/svg/25ab.svg"),none}.em-white_square_button{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f533.png")}.em-white_square_button.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f533.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f533.svg"),none}.em-wilted_flower{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f940.png")}.em-wilted_flower.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f940.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f940.svg"),none}.em-wind_blowing_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f32c.png")}.em-wind_blowing_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f32c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f32c.svg"),none}.em-wind_chime{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f390.png")}.em-wind_chime.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f390.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f390.svg"),none}.em-wine_glass{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f377.png")}.em-wine_glass.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f377.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f377.svg"),none}.em-wink{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f609.png")}.em-wink.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f609.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f609.svg"),none}.em-wolf{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f43a.png")}.em-wolf.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f43a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f43a.svg"),none}.em-woman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469.png")}.em-woman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469.svg"),none}.em-woman-biking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b4-200d-2640-fe0f.png")}.em-woman-biking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b4-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b4-200d-2640-fe0f.svg"),none}.em-woman-bouncing-ball{background-image:url("https://twemoji.maxcdn.com/2/72x72/26f9-fe0f-200d-2640-fe0f.png")}.em-woman-bouncing-ball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26f9-fe0f-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26f9-fe0f-200d-2640-fe0f.svg"),none}.em-woman-bowing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f647-200d-2640-fe0f.png")}.em-woman-bowing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f647-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f647-200d-2640-fe0f.svg"),none}.em-woman-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f466.png")}.em-woman-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f466.svg"),none}.em-woman-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f466-200d-1f466.png")}.em-woman-boy-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f466-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f466-200d-1f466.svg"),none}.em-woman-cartwheeling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f938-200d-2640-fe0f.png")}.em-woman-cartwheeling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f938-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f938-200d-2640-fe0f.svg"),none}.em-woman-facepalming{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f926-200d-2640-fe0f.png")}.em-woman-facepalming.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f926-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f926-200d-2640-fe0f.svg"),none}.em-woman-frowning{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64d-200d-2640-fe0f.png")}.em-woman-frowning.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64d-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64d-200d-2640-fe0f.svg"),none}.em-woman-gesturing-no{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f645-200d-2640-fe0f.png")}.em-woman-gesturing-no.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f645-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f645-200d-2640-fe0f.svg"),none}.em-woman-gesturing-ok{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f646-200d-2640-fe0f.png")}.em-woman-gesturing-ok.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f646-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f646-200d-2640-fe0f.svg"),none}.em-woman-getting-haircut{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f487-200d-2640-fe0f.png")}.em-woman-getting-haircut.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f487-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f487-200d-2640-fe0f.svg"),none}.em-woman-getting-massage{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f486-200d-2640-fe0f.png")}.em-woman-getting-massage.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f486-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f486-200d-2640-fe0f.svg"),none}.em-woman-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f467.png")}.em-woman-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f467.svg"),none}.em-woman-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f467-200d-1f466.png")}.em-woman-girl-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f467-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f467-200d-1f466.svg"),none}.em-woman-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f467-200d-1f467.png")}.em-woman-girl-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f467-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f467-200d-1f467.svg"),none}.em-woman-golfing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cc-fe0f-200d-2640-fe0f.png")}.em-woman-golfing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cc-fe0f-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cc-fe0f-200d-2640-fe0f.svg"),none}.em-woman-heart-man{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f468.png")}.em-woman-heart-man.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f468.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f468.svg"),none}.em-woman-heart-woman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f469.png")}.em-woman-heart-woman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f469.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f469.svg"),none}.em-woman-juggling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f939-200d-2640-fe0f.png")}.em-woman-juggling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f939-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f939-200d-2640-fe0f.svg"),none}.em-woman-kiss-man{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f48b-200d-1f468.png")}.em-woman-kiss-man.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f48b-200d-1f468.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f48b-200d-1f468.svg"),none}.em-woman-kiss-woman{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.png")}.em-woman-kiss-woman.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.svg"),none}.em-woman-lifting-weights{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3cb-fe0f-200d-2640-fe0f.png")}.em-woman-lifting-weights.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3cb-fe0f-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3cb-fe0f-200d-2640-fe0f.svg"),none}.em-woman-mountain-biking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b5-200d-2640-fe0f.png")}.em-woman-mountain-biking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b5-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b5-200d-2640-fe0f.svg"),none}.em-woman-playing-handball{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93e-200d-2640-fe0f.png")}.em-woman-playing-handball.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93e-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93e-200d-2640-fe0f.svg"),none}.em-woman-playing-water-polo{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93d-200d-2640-fe0f.png")}.em-woman-playing-water-polo.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93d-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93d-200d-2640-fe0f.svg"),none}.em-woman-pouting{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64e-200d-2640-fe0f.png")}.em-woman-pouting.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64e-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64e-200d-2640-fe0f.svg"),none}.em-woman-raising-hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f64b-200d-2640-fe0f.png")}.em-woman-raising-hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f64b-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f64b-200d-2640-fe0f.svg"),none}.em-woman-rowing-boat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6a3-200d-2640-fe0f.png")}.em-woman-rowing-boat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6a3-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6a3-200d-2640-fe0f.svg"),none}.em-woman-running{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c3-200d-2640-fe0f.png")}.em-woman-running.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c3-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c3-200d-2640-fe0f.svg"),none}.em-woman-shrugging{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f937-200d-2640-fe0f.png")}.em-woman-shrugging.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f937-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f937-200d-2640-fe0f.svg"),none}.em-woman-surfing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3c4-200d-2640-fe0f.png")}.em-woman-surfing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3c4-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3c4-200d-2640-fe0f.svg"),none}.em-woman-swimming{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f3ca-200d-2640-fe0f.png")}.em-woman-swimming.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f3ca-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f3ca-200d-2640-fe0f.svg"),none}.em-woman-tipping-hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f481-200d-2640-fe0f.png")}.em-woman-tipping-hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f481-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f481-200d-2640-fe0f.svg"),none}.em-woman-walking{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6b6-200d-2640-fe0f.png")}.em-woman-walking.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6b6-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6b6-200d-2640-fe0f.svg"),none}.em-woman-wearing-turban{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f473-200d-2640-fe0f.png")}.em-woman-wearing-turban.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f473-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f473-200d-2640-fe0f.svg"),none}.em-woman-with-bunny-ears-partying{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f46f-200d-2640-fe0f.png")}.em-woman-with-bunny-ears-partying.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f46f-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f46f-200d-2640-fe0f.svg"),none}.em-woman-woman-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f466.png")}.em-woman-woman-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f466.svg"),none}.em-woman-woman-boy-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f466-200d-1f466.png")}.em-woman-woman-boy-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f466-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f466-200d-1f466.svg"),none}.em-woman-woman-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f467.png")}.em-woman-woman-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f467.svg"),none}.em-woman-woman-girl-boy{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f467-200d-1f466.png")}.em-woman-woman-girl-boy.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f467-200d-1f466.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f467-200d-1f466.svg"),none}.em-woman-woman-girl-girl{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f467-200d-1f467.png")}.em-woman-woman-girl-girl.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f469-200d-1f469-200d-1f467-200d-1f467.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f469-200d-1f469-200d-1f467-200d-1f467.svg"),none}.em-woman-wrestling{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93c-200d-2640-fe0f.png")}.em-woman-wrestling.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93c-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c-200d-2640-fe0f.svg"),none}.em-woman_climbing{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d7-200d-2640-fe0f.png")}.em-woman_climbing.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d7-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d7-200d-2640-fe0f.svg"),none}.em-woman_in_lotus_position{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d8-200d-2640-fe0f.png")}.em-woman_in_lotus_position.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d8-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d8-200d-2640-fe0f.svg"),none}.em-woman_in_steamy_room{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9d6-200d-2640-fe0f.png")}.em-woman_in_steamy_room.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9d6-200d-2640-fe0f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9d6-200d-2640-fe0f.svg"),none}.em-womans_clothes{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f45a.png")}.em-womans_clothes.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f45a.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f45a.svg"),none}.em-womans_hat{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f452.png")}.em-womans_hat.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f452.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f452.svg"),none}.em-womens{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f6ba.png")}.em-womens.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f6ba.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f6ba.svg"),none}.em-world_map{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f5fa.png")}.em-world_map.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f5fa.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f5fa.svg"),none}.em-worried{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f61f.png")}.em-worried.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f61f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f61f.svg"),none}.em-wrench{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f527.png")}.em-wrench.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f527.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f527.svg"),none}.em-wrestlers{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f93c.png")}.em-wrestlers.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f93c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f93c.svg"),none}.em-writing_hand{background-image:url("https://twemoji.maxcdn.com/2/72x72/270d.png")}.em-writing_hand.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/270d.png");background-image:url("https://twemoji.maxcdn.com/2/svg/270d.svg"),none}.em-x{background-image:url("https://twemoji.maxcdn.com/2/72x72/274c.png")}.em-x.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/274c.png");background-image:url("https://twemoji.maxcdn.com/2/svg/274c.svg"),none}.em-yellow_heart{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f49b.png")}.em-yellow_heart.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f49b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f49b.svg"),none}.em-yen{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4b4.png")}.em-yen.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4b4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4b4.svg"),none}.em-yin_yang{background-image:url("https://twemoji.maxcdn.com/2/72x72/262f.png")}.em-yin_yang.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/262f.png");background-image:url("https://twemoji.maxcdn.com/2/svg/262f.svg"),none}.em-yum{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f60b.png")}.em-yum.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f60b.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f60b.svg"),none}.em-zap{background-image:url("https://twemoji.maxcdn.com/2/72x72/26a1.png")}.em-zap.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/26a1.png");background-image:url("https://twemoji.maxcdn.com/2/svg/26a1.svg"),none}.em-zebra_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f993.png")}.em-zebra_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f993.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f993.svg"),none}.em-zero{background-image:url("https://twemoji.maxcdn.com/2/72x72/30-20e3.png")}.em-zero.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/30-20e3.png");background-image:url("https://twemoji.maxcdn.com/2/svg/30-20e3.svg"),none}.em-zipper_mouth_face{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f910.png")}.em-zipper_mouth_face.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f910.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f910.svg"),none}.em-zombie{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f9df.png")}.em-zombie.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f9df.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f9df.svg"),none}.em-zzz{background-image:url("https://twemoji.maxcdn.com/2/72x72/1f4a4.png")}.em-zzz.em-svg{background:url("https://twemoji.maxcdn.com/2/72x72/1f4a4.png");background-image:url("https://twemoji.maxcdn.com/2/svg/1f4a4.svg"),none}
diff --git a/public/libs/uml/mermaid.js b/public/libs/uml/mermaid.js
new file mode 100644
index 000000000..e004aeaa2
--- /dev/null
+++ b/public/libs/uml/mermaid.js
@@ -0,0 +1,65431 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["mermaid"] = factory();
+	else
+		root["mermaid"] = factory();
+})(typeof self !== 'undefined' ? self : this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 204);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* WEBPACK VAR INJECTION */(function(module) {var require;//! moment.js
+//! version : 2.20.1
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+
+;(function (global, factory) {
+     true ? module.exports = factory() :
+    typeof define === 'function' && define.amd ? define(factory) :
+    global.moment = factory()
+}(this, (function () { 'use strict';
+
+var hookCallback;
+
+function hooks () {
+    return hookCallback.apply(null, arguments);
+}
+
+// This is done to register the method called with moment()
+// without creating circular dependencies.
+function setHookCallback (callback) {
+    hookCallback = callback;
+}
+
+function isArray(input) {
+    return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
+}
+
+function isObject(input) {
+    // IE8 will treat undefined and null as object if it wasn't for
+    // input != null
+    return input != null && Object.prototype.toString.call(input) === '[object Object]';
+}
+
+function isObjectEmpty(obj) {
+    if (Object.getOwnPropertyNames) {
+        return (Object.getOwnPropertyNames(obj).length === 0);
+    } else {
+        var k;
+        for (k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+function isUndefined(input) {
+    return input === void 0;
+}
+
+function isNumber(input) {
+    return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
+}
+
+function isDate(input) {
+    return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
+}
+
+function map(arr, fn) {
+    var res = [], i;
+    for (i = 0; i < arr.length; ++i) {
+        res.push(fn(arr[i], i));
+    }
+    return res;
+}
+
+function hasOwnProp(a, b) {
+    return Object.prototype.hasOwnProperty.call(a, b);
+}
+
+function extend(a, b) {
+    for (var i in b) {
+        if (hasOwnProp(b, i)) {
+            a[i] = b[i];
+        }
+    }
+
+    if (hasOwnProp(b, 'toString')) {
+        a.toString = b.toString;
+    }
+
+    if (hasOwnProp(b, 'valueOf')) {
+        a.valueOf = b.valueOf;
+    }
+
+    return a;
+}
+
+function createUTC (input, format, locale, strict) {
+    return createLocalOrUTC(input, format, locale, strict, true).utc();
+}
+
+function defaultParsingFlags() {
+    // We need to deep clone this object.
+    return {
+        empty           : false,
+        unusedTokens    : [],
+        unusedInput     : [],
+        overflow        : -2,
+        charsLeftOver   : 0,
+        nullInput       : false,
+        invalidMonth    : null,
+        invalidFormat   : false,
+        userInvalidated : false,
+        iso             : false,
+        parsedDateParts : [],
+        meridiem        : null,
+        rfc2822         : false,
+        weekdayMismatch : false
+    };
+}
+
+function getParsingFlags(m) {
+    if (m._pf == null) {
+        m._pf = defaultParsingFlags();
+    }
+    return m._pf;
+}
+
+var some;
+if (Array.prototype.some) {
+    some = Array.prototype.some;
+} else {
+    some = function (fun) {
+        var t = Object(this);
+        var len = t.length >>> 0;
+
+        for (var i = 0; i < len; i++) {
+            if (i in t && fun.call(this, t[i], i, t)) {
+                return true;
+            }
+        }
+
+        return false;
+    };
+}
+
+function isValid(m) {
+    if (m._isValid == null) {
+        var flags = getParsingFlags(m);
+        var parsedParts = some.call(flags.parsedDateParts, function (i) {
+            return i != null;
+        });
+        var isNowValid = !isNaN(m._d.getTime()) &&
+            flags.overflow < 0 &&
+            !flags.empty &&
+            !flags.invalidMonth &&
+            !flags.invalidWeekday &&
+            !flags.weekdayMismatch &&
+            !flags.nullInput &&
+            !flags.invalidFormat &&
+            !flags.userInvalidated &&
+            (!flags.meridiem || (flags.meridiem && parsedParts));
+
+        if (m._strict) {
+            isNowValid = isNowValid &&
+                flags.charsLeftOver === 0 &&
+                flags.unusedTokens.length === 0 &&
+                flags.bigHour === undefined;
+        }
+
+        if (Object.isFrozen == null || !Object.isFrozen(m)) {
+            m._isValid = isNowValid;
+        }
+        else {
+            return isNowValid;
+        }
+    }
+    return m._isValid;
+}
+
+function createInvalid (flags) {
+    var m = createUTC(NaN);
+    if (flags != null) {
+        extend(getParsingFlags(m), flags);
+    }
+    else {
+        getParsingFlags(m).userInvalidated = true;
+    }
+
+    return m;
+}
+
+// Plugins that add properties should also add the key here (null value),
+// so we can properly clone ourselves.
+var momentProperties = hooks.momentProperties = [];
+
+function copyConfig(to, from) {
+    var i, prop, val;
+
+    if (!isUndefined(from._isAMomentObject)) {
+        to._isAMomentObject = from._isAMomentObject;
+    }
+    if (!isUndefined(from._i)) {
+        to._i = from._i;
+    }
+    if (!isUndefined(from._f)) {
+        to._f = from._f;
+    }
+    if (!isUndefined(from._l)) {
+        to._l = from._l;
+    }
+    if (!isUndefined(from._strict)) {
+        to._strict = from._strict;
+    }
+    if (!isUndefined(from._tzm)) {
+        to._tzm = from._tzm;
+    }
+    if (!isUndefined(from._isUTC)) {
+        to._isUTC = from._isUTC;
+    }
+    if (!isUndefined(from._offset)) {
+        to._offset = from._offset;
+    }
+    if (!isUndefined(from._pf)) {
+        to._pf = getParsingFlags(from);
+    }
+    if (!isUndefined(from._locale)) {
+        to._locale = from._locale;
+    }
+
+    if (momentProperties.length > 0) {
+        for (i = 0; i < momentProperties.length; i++) {
+            prop = momentProperties[i];
+            val = from[prop];
+            if (!isUndefined(val)) {
+                to[prop] = val;
+            }
+        }
+    }
+
+    return to;
+}
+
+var updateInProgress = false;
+
+// Moment prototype object
+function Moment(config) {
+    copyConfig(this, config);
+    this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+    if (!this.isValid()) {
+        this._d = new Date(NaN);
+    }
+    // Prevent infinite loop in case updateOffset creates new moment
+    // objects.
+    if (updateInProgress === false) {
+        updateInProgress = true;
+        hooks.updateOffset(this);
+        updateInProgress = false;
+    }
+}
+
+function isMoment (obj) {
+    return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
+}
+
+function absFloor (number) {
+    if (number < 0) {
+        // -0 -> 0
+        return Math.ceil(number) || 0;
+    } else {
+        return Math.floor(number);
+    }
+}
+
+function toInt(argumentForCoercion) {
+    var coercedNumber = +argumentForCoercion,
+        value = 0;
+
+    if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+        value = absFloor(coercedNumber);
+    }
+
+    return value;
+}
+
+// compare two arrays, return the number of differences
+function compareArrays(array1, array2, dontConvert) {
+    var len = Math.min(array1.length, array2.length),
+        lengthDiff = Math.abs(array1.length - array2.length),
+        diffs = 0,
+        i;
+    for (i = 0; i < len; i++) {
+        if ((dontConvert && array1[i] !== array2[i]) ||
+            (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
+            diffs++;
+        }
+    }
+    return diffs + lengthDiff;
+}
+
+function warn(msg) {
+    if (hooks.suppressDeprecationWarnings === false &&
+            (typeof console !==  'undefined') && console.warn) {
+        console.warn('Deprecation warning: ' + msg);
+    }
+}
+
+function deprecate(msg, fn) {
+    var firstTime = true;
+
+    return extend(function () {
+        if (hooks.deprecationHandler != null) {
+            hooks.deprecationHandler(null, msg);
+        }
+        if (firstTime) {
+            var args = [];
+            var arg;
+            for (var i = 0; i < arguments.length; i++) {
+                arg = '';
+                if (typeof arguments[i] === 'object') {
+                    arg += '\n[' + i + '] ';
+                    for (var key in arguments[0]) {
+                        arg += key + ': ' + arguments[0][key] + ', ';
+                    }
+                    arg = arg.slice(0, -2); // Remove trailing comma and space
+                } else {
+                    arg = arguments[i];
+                }
+                args.push(arg);
+            }
+            warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
+            firstTime = false;
+        }
+        return fn.apply(this, arguments);
+    }, fn);
+}
+
+var deprecations = {};
+
+function deprecateSimple(name, msg) {
+    if (hooks.deprecationHandler != null) {
+        hooks.deprecationHandler(name, msg);
+    }
+    if (!deprecations[name]) {
+        warn(msg);
+        deprecations[name] = true;
+    }
+}
+
+hooks.suppressDeprecationWarnings = false;
+hooks.deprecationHandler = null;
+
+function isFunction(input) {
+    return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
+}
+
+function set (config) {
+    var prop, i;
+    for (i in config) {
+        prop = config[i];
+        if (isFunction(prop)) {
+            this[i] = prop;
+        } else {
+            this['_' + i] = prop;
+        }
+    }
+    this._config = config;
+    // Lenient ordinal parsing accepts just a number in addition to
+    // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
+    // TODO: Remove "ordinalParse" fallback in next major release.
+    this._dayOfMonthOrdinalParseLenient = new RegExp(
+        (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
+            '|' + (/\d{1,2}/).source);
+}
+
+function mergeConfigs(parentConfig, childConfig) {
+    var res = extend({}, parentConfig), prop;
+    for (prop in childConfig) {
+        if (hasOwnProp(childConfig, prop)) {
+            if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                res[prop] = {};
+                extend(res[prop], parentConfig[prop]);
+                extend(res[prop], childConfig[prop]);
+            } else if (childConfig[prop] != null) {
+                res[prop] = childConfig[prop];
+            } else {
+                delete res[prop];
+            }
+        }
+    }
+    for (prop in parentConfig) {
+        if (hasOwnProp(parentConfig, prop) &&
+                !hasOwnProp(childConfig, prop) &&
+                isObject(parentConfig[prop])) {
+            // make sure changes to properties don't modify parent config
+            res[prop] = extend({}, res[prop]);
+        }
+    }
+    return res;
+}
+
+function Locale(config) {
+    if (config != null) {
+        this.set(config);
+    }
+}
+
+var keys;
+
+if (Object.keys) {
+    keys = Object.keys;
+} else {
+    keys = function (obj) {
+        var i, res = [];
+        for (i in obj) {
+            if (hasOwnProp(obj, i)) {
+                res.push(i);
+            }
+        }
+        return res;
+    };
+}
+
+var defaultCalendar = {
+    sameDay : '[Today at] LT',
+    nextDay : '[Tomorrow at] LT',
+    nextWeek : 'dddd [at] LT',
+    lastDay : '[Yesterday at] LT',
+    lastWeek : '[Last] dddd [at] LT',
+    sameElse : 'L'
+};
+
+function calendar (key, mom, now) {
+    var output = this._calendar[key] || this._calendar['sameElse'];
+    return isFunction(output) ? output.call(mom, now) : output;
+}
+
+var defaultLongDateFormat = {
+    LTS  : 'h:mm:ss A',
+    LT   : 'h:mm A',
+    L    : 'MM/DD/YYYY',
+    LL   : 'MMMM D, YYYY',
+    LLL  : 'MMMM D, YYYY h:mm A',
+    LLLL : 'dddd, MMMM D, YYYY h:mm A'
+};
+
+function longDateFormat (key) {
+    var format = this._longDateFormat[key],
+        formatUpper = this._longDateFormat[key.toUpperCase()];
+
+    if (format || !formatUpper) {
+        return format;
+    }
+
+    this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
+        return val.slice(1);
+    });
+
+    return this._longDateFormat[key];
+}
+
+var defaultInvalidDate = 'Invalid date';
+
+function invalidDate () {
+    return this._invalidDate;
+}
+
+var defaultOrdinal = '%d';
+var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
+
+function ordinal (number) {
+    return this._ordinal.replace('%d', number);
+}
+
+var defaultRelativeTime = {
+    future : 'in %s',
+    past   : '%s ago',
+    s  : 'a few seconds',
+    ss : '%d seconds',
+    m  : 'a minute',
+    mm : '%d minutes',
+    h  : 'an hour',
+    hh : '%d hours',
+    d  : 'a day',
+    dd : '%d days',
+    M  : 'a month',
+    MM : '%d months',
+    y  : 'a year',
+    yy : '%d years'
+};
+
+function relativeTime (number, withoutSuffix, string, isFuture) {
+    var output = this._relativeTime[string];
+    return (isFunction(output)) ?
+        output(number, withoutSuffix, string, isFuture) :
+        output.replace(/%d/i, number);
+}
+
+function pastFuture (diff, output) {
+    var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+    return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+}
+
+var aliases = {};
+
+function addUnitAlias (unit, shorthand) {
+    var lowerCase = unit.toLowerCase();
+    aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+}
+
+function normalizeUnits(units) {
+    return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+}
+
+function normalizeObjectUnits(inputObject) {
+    var normalizedInput = {},
+        normalizedProp,
+        prop;
+
+    for (prop in inputObject) {
+        if (hasOwnProp(inputObject, prop)) {
+            normalizedProp = normalizeUnits(prop);
+            if (normalizedProp) {
+                normalizedInput[normalizedProp] = inputObject[prop];
+            }
+        }
+    }
+
+    return normalizedInput;
+}
+
+var priorities = {};
+
+function addUnitPriority(unit, priority) {
+    priorities[unit] = priority;
+}
+
+function getPrioritizedUnits(unitsObj) {
+    var units = [];
+    for (var u in unitsObj) {
+        units.push({unit: u, priority: priorities[u]});
+    }
+    units.sort(function (a, b) {
+        return a.priority - b.priority;
+    });
+    return units;
+}
+
+function zeroFill(number, targetLength, forceSign) {
+    var absNumber = '' + Math.abs(number),
+        zerosToFill = targetLength - absNumber.length,
+        sign = number >= 0;
+    return (sign ? (forceSign ? '+' : '') : '-') +
+        Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
+}
+
+var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
+
+var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
+
+var formatFunctions = {};
+
+var formatTokenFunctions = {};
+
+// token:    'M'
+// padded:   ['MM', 2]
+// ordinal:  'Mo'
+// callback: function () { this.month() + 1 }
+function addFormatToken (token, padded, ordinal, callback) {
+    var func = callback;
+    if (typeof callback === 'string') {
+        func = function () {
+            return this[callback]();
+        };
+    }
+    if (token) {
+        formatTokenFunctions[token] = func;
+    }
+    if (padded) {
+        formatTokenFunctions[padded[0]] = function () {
+            return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+        };
+    }
+    if (ordinal) {
+        formatTokenFunctions[ordinal] = function () {
+            return this.localeData().ordinal(func.apply(this, arguments), token);
+        };
+    }
+}
+
+function removeFormattingTokens(input) {
+    if (input.match(/\[[\s\S]/)) {
+        return input.replace(/^\[|\]$/g, '');
+    }
+    return input.replace(/\\/g, '');
+}
+
+function makeFormatFunction(format) {
+    var array = format.match(formattingTokens), i, length;
+
+    for (i = 0, length = array.length; i < length; i++) {
+        if (formatTokenFunctions[array[i]]) {
+            array[i] = formatTokenFunctions[array[i]];
+        } else {
+            array[i] = removeFormattingTokens(array[i]);
+        }
+    }
+
+    return function (mom) {
+        var output = '', i;
+        for (i = 0; i < length; i++) {
+            output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
+        }
+        return output;
+    };
+}
+
+// format date using native date object
+function formatMoment(m, format) {
+    if (!m.isValid()) {
+        return m.localeData().invalidDate();
+    }
+
+    format = expandFormat(format, m.localeData());
+    formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
+
+    return formatFunctions[format](m);
+}
+
+function expandFormat(format, locale) {
+    var i = 5;
+
+    function replaceLongDateFormatTokens(input) {
+        return locale.longDateFormat(input) || input;
+    }
+
+    localFormattingTokens.lastIndex = 0;
+    while (i >= 0 && localFormattingTokens.test(format)) {
+        format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+        localFormattingTokens.lastIndex = 0;
+        i -= 1;
+    }
+
+    return format;
+}
+
+var match1         = /\d/;            //       0 - 9
+var match2         = /\d\d/;          //      00 - 99
+var match3         = /\d{3}/;         //     000 - 999
+var match4         = /\d{4}/;         //    0000 - 9999
+var match6         = /[+-]?\d{6}/;    // -999999 - 999999
+var match1to2      = /\d\d?/;         //       0 - 99
+var match3to4      = /\d\d\d\d?/;     //     999 - 9999
+var match5to6      = /\d\d\d\d\d\d?/; //   99999 - 999999
+var match1to3      = /\d{1,3}/;       //       0 - 999
+var match1to4      = /\d{1,4}/;       //       0 - 9999
+var match1to6      = /[+-]?\d{1,6}/;  // -999999 - 999999
+
+var matchUnsigned  = /\d+/;           //       0 - inf
+var matchSigned    = /[+-]?\d+/;      //    -inf - inf
+
+var matchOffset    = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
+var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+
+var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
+
+// any word (or two) characters or numbers including two/three word month in arabic.
+// includes scottish gaelic two word and hyphenated months
+var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
+
+
+var regexes = {};
+
+function addRegexToken (token, regex, strictRegex) {
+    regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
+        return (isStrict && strictRegex) ? strictRegex : regex;
+    };
+}
+
+function getParseRegexForToken (token, config) {
+    if (!hasOwnProp(regexes, token)) {
+        return new RegExp(unescapeFormat(token));
+    }
+
+    return regexes[token](config._strict, config._locale);
+}
+
+// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+function unescapeFormat(s) {
+    return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+        return p1 || p2 || p3 || p4;
+    }));
+}
+
+function regexEscape(s) {
+    return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
+var tokens = {};
+
+function addParseToken (token, callback) {
+    var i, func = callback;
+    if (typeof token === 'string') {
+        token = [token];
+    }
+    if (isNumber(callback)) {
+        func = function (input, array) {
+            array[callback] = toInt(input);
+        };
+    }
+    for (i = 0; i < token.length; i++) {
+        tokens[token[i]] = func;
+    }
+}
+
+function addWeekParseToken (token, callback) {
+    addParseToken(token, function (input, array, config, token) {
+        config._w = config._w || {};
+        callback(input, config._w, config, token);
+    });
+}
+
+function addTimeToArrayFromToken(token, input, config) {
+    if (input != null && hasOwnProp(tokens, token)) {
+        tokens[token](input, config._a, config, token);
+    }
+}
+
+var YEAR = 0;
+var MONTH = 1;
+var DATE = 2;
+var HOUR = 3;
+var MINUTE = 4;
+var SECOND = 5;
+var MILLISECOND = 6;
+var WEEK = 7;
+var WEEKDAY = 8;
+
+// FORMATTING
+
+addFormatToken('Y', 0, 0, function () {
+    var y = this.year();
+    return y <= 9999 ? '' + y : '+' + y;
+});
+
+addFormatToken(0, ['YY', 2], 0, function () {
+    return this.year() % 100;
+});
+
+addFormatToken(0, ['YYYY',   4],       0, 'year');
+addFormatToken(0, ['YYYYY',  5],       0, 'year');
+addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+
+// ALIASES
+
+addUnitAlias('year', 'y');
+
+// PRIORITIES
+
+addUnitPriority('year', 1);
+
+// PARSING
+
+addRegexToken('Y',      matchSigned);
+addRegexToken('YY',     match1to2, match2);
+addRegexToken('YYYY',   match1to4, match4);
+addRegexToken('YYYYY',  match1to6, match6);
+addRegexToken('YYYYYY', match1to6, match6);
+
+addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+addParseToken('YYYY', function (input, array) {
+    array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
+});
+addParseToken('YY', function (input, array) {
+    array[YEAR] = hooks.parseTwoDigitYear(input);
+});
+addParseToken('Y', function (input, array) {
+    array[YEAR] = parseInt(input, 10);
+});
+
+// HELPERS
+
+function daysInYear(year) {
+    return isLeapYear(year) ? 366 : 365;
+}
+
+function isLeapYear(year) {
+    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+}
+
+// HOOKS
+
+hooks.parseTwoDigitYear = function (input) {
+    return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+};
+
+// MOMENTS
+
+var getSetYear = makeGetSet('FullYear', true);
+
+function getIsLeapYear () {
+    return isLeapYear(this.year());
+}
+
+function makeGetSet (unit, keepTime) {
+    return function (value) {
+        if (value != null) {
+            set$1(this, unit, value);
+            hooks.updateOffset(this, keepTime);
+            return this;
+        } else {
+            return get(this, unit);
+        }
+    };
+}
+
+function get (mom, unit) {
+    return mom.isValid() ?
+        mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
+}
+
+function set$1 (mom, unit, value) {
+    if (mom.isValid() && !isNaN(value)) {
+        if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
+            mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
+        }
+        else {
+            mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+        }
+    }
+}
+
+// MOMENTS
+
+function stringGet (units) {
+    units = normalizeUnits(units);
+    if (isFunction(this[units])) {
+        return this[units]();
+    }
+    return this;
+}
+
+
+function stringSet (units, value) {
+    if (typeof units === 'object') {
+        units = normalizeObjectUnits(units);
+        var prioritized = getPrioritizedUnits(units);
+        for (var i = 0; i < prioritized.length; i++) {
+            this[prioritized[i].unit](units[prioritized[i].unit]);
+        }
+    } else {
+        units = normalizeUnits(units);
+        if (isFunction(this[units])) {
+            return this[units](value);
+        }
+    }
+    return this;
+}
+
+function mod(n, x) {
+    return ((n % x) + x) % x;
+}
+
+var indexOf;
+
+if (Array.prototype.indexOf) {
+    indexOf = Array.prototype.indexOf;
+} else {
+    indexOf = function (o) {
+        // I know
+        var i;
+        for (i = 0; i < this.length; ++i) {
+            if (this[i] === o) {
+                return i;
+            }
+        }
+        return -1;
+    };
+}
+
+function daysInMonth(year, month) {
+    if (isNaN(year) || isNaN(month)) {
+        return NaN;
+    }
+    var modMonth = mod(month, 12);
+    year += (month - modMonth) / 12;
+    return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
+}
+
+// FORMATTING
+
+addFormatToken('M', ['MM', 2], 'Mo', function () {
+    return this.month() + 1;
+});
+
+addFormatToken('MMM', 0, 0, function (format) {
+    return this.localeData().monthsShort(this, format);
+});
+
+addFormatToken('MMMM', 0, 0, function (format) {
+    return this.localeData().months(this, format);
+});
+
+// ALIASES
+
+addUnitAlias('month', 'M');
+
+// PRIORITY
+
+addUnitPriority('month', 8);
+
+// PARSING
+
+addRegexToken('M',    match1to2);
+addRegexToken('MM',   match1to2, match2);
+addRegexToken('MMM',  function (isStrict, locale) {
+    return locale.monthsShortRegex(isStrict);
+});
+addRegexToken('MMMM', function (isStrict, locale) {
+    return locale.monthsRegex(isStrict);
+});
+
+addParseToken(['M', 'MM'], function (input, array) {
+    array[MONTH] = toInt(input) - 1;
+});
+
+addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+    var month = config._locale.monthsParse(input, token, config._strict);
+    // if we didn't find a month name, mark the date as invalid.
+    if (month != null) {
+        array[MONTH] = month;
+    } else {
+        getParsingFlags(config).invalidMonth = input;
+    }
+});
+
+// LOCALES
+
+var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
+var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+function localeMonths (m, format) {
+    if (!m) {
+        return isArray(this._months) ? this._months :
+            this._months['standalone'];
+    }
+    return isArray(this._months) ? this._months[m.month()] :
+        this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+function localeMonthsShort (m, format) {
+    if (!m) {
+        return isArray(this._monthsShort) ? this._monthsShort :
+            this._monthsShort['standalone'];
+    }
+    return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
+        this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+function handleStrictParse(monthName, format, strict) {
+    var i, ii, mom, llc = monthName.toLocaleLowerCase();
+    if (!this._monthsParse) {
+        // this is not used
+        this._monthsParse = [];
+        this._longMonthsParse = [];
+        this._shortMonthsParse = [];
+        for (i = 0; i < 12; ++i) {
+            mom = createUTC([2000, i]);
+            this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
+            this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+        }
+    }
+
+    if (strict) {
+        if (format === 'MMM') {
+            ii = indexOf.call(this._shortMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._longMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    } else {
+        if (format === 'MMM') {
+            ii = indexOf.call(this._shortMonthsParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._longMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._longMonthsParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._shortMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    }
+}
+
+function localeMonthsParse (monthName, format, strict) {
+    var i, mom, regex;
+
+    if (this._monthsParseExact) {
+        return handleStrictParse.call(this, monthName, format, strict);
+    }
+
+    if (!this._monthsParse) {
+        this._monthsParse = [];
+        this._longMonthsParse = [];
+        this._shortMonthsParse = [];
+    }
+
+    // TODO: add sorting
+    // Sorting makes sure if one month (or abbr) is a prefix of another
+    // see sorting in computeMonthsParse
+    for (i = 0; i < 12; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, i]);
+        if (strict && !this._longMonthsParse[i]) {
+            this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
+            this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
+        }
+        if (!strict && !this._monthsParse[i]) {
+            regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+            this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+        }
+        // test the regex
+        if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+            return i;
+        } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+            return i;
+        } else if (!strict && this._monthsParse[i].test(monthName)) {
+            return i;
+        }
+    }
+}
+
+// MOMENTS
+
+function setMonth (mom, value) {
+    var dayOfMonth;
+
+    if (!mom.isValid()) {
+        // No op
+        return mom;
+    }
+
+    if (typeof value === 'string') {
+        if (/^\d+$/.test(value)) {
+            value = toInt(value);
+        } else {
+            value = mom.localeData().monthsParse(value);
+            // TODO: Another silent failure?
+            if (!isNumber(value)) {
+                return mom;
+            }
+        }
+    }
+
+    dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+    mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+    return mom;
+}
+
+function getSetMonth (value) {
+    if (value != null) {
+        setMonth(this, value);
+        hooks.updateOffset(this, true);
+        return this;
+    } else {
+        return get(this, 'Month');
+    }
+}
+
+function getDaysInMonth () {
+    return daysInMonth(this.year(), this.month());
+}
+
+var defaultMonthsShortRegex = matchWord;
+function monthsShortRegex (isStrict) {
+    if (this._monthsParseExact) {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            computeMonthsParse.call(this);
+        }
+        if (isStrict) {
+            return this._monthsShortStrictRegex;
+        } else {
+            return this._monthsShortRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_monthsShortRegex')) {
+            this._monthsShortRegex = defaultMonthsShortRegex;
+        }
+        return this._monthsShortStrictRegex && isStrict ?
+            this._monthsShortStrictRegex : this._monthsShortRegex;
+    }
+}
+
+var defaultMonthsRegex = matchWord;
+function monthsRegex (isStrict) {
+    if (this._monthsParseExact) {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            computeMonthsParse.call(this);
+        }
+        if (isStrict) {
+            return this._monthsStrictRegex;
+        } else {
+            return this._monthsRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            this._monthsRegex = defaultMonthsRegex;
+        }
+        return this._monthsStrictRegex && isStrict ?
+            this._monthsStrictRegex : this._monthsRegex;
+    }
+}
+
+function computeMonthsParse () {
+    function cmpLenRev(a, b) {
+        return b.length - a.length;
+    }
+
+    var shortPieces = [], longPieces = [], mixedPieces = [],
+        i, mom;
+    for (i = 0; i < 12; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, i]);
+        shortPieces.push(this.monthsShort(mom, ''));
+        longPieces.push(this.months(mom, ''));
+        mixedPieces.push(this.months(mom, ''));
+        mixedPieces.push(this.monthsShort(mom, ''));
+    }
+    // Sorting makes sure if one month (or abbr) is a prefix of another it
+    // will match the longer piece.
+    shortPieces.sort(cmpLenRev);
+    longPieces.sort(cmpLenRev);
+    mixedPieces.sort(cmpLenRev);
+    for (i = 0; i < 12; i++) {
+        shortPieces[i] = regexEscape(shortPieces[i]);
+        longPieces[i] = regexEscape(longPieces[i]);
+    }
+    for (i = 0; i < 24; i++) {
+        mixedPieces[i] = regexEscape(mixedPieces[i]);
+    }
+
+    this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+    this._monthsShortRegex = this._monthsRegex;
+    this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+    this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+}
+
+function createDate (y, m, d, h, M, s, ms) {
+    // can't just apply() to create a date:
+    // https://stackoverflow.com/q/181348
+    var date = new Date(y, m, d, h, M, s, ms);
+
+    // the date constructor remaps years 0-99 to 1900-1999
+    if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
+        date.setFullYear(y);
+    }
+    return date;
+}
+
+function createUTCDate (y) {
+    var date = new Date(Date.UTC.apply(null, arguments));
+
+    // the Date.UTC function remaps years 0-99 to 1900-1999
+    if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
+        date.setUTCFullYear(y);
+    }
+    return date;
+}
+
+// start-of-first-week - start-of-year
+function firstWeekOffset(year, dow, doy) {
+    var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+        fwd = 7 + dow - doy,
+        // first-week day local weekday -- which local weekday is fwd
+        fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+    return -fwdlw + fwd - 1;
+}
+
+// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+    var localWeekday = (7 + weekday - dow) % 7,
+        weekOffset = firstWeekOffset(year, dow, doy),
+        dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+        resYear, resDayOfYear;
+
+    if (dayOfYear <= 0) {
+        resYear = year - 1;
+        resDayOfYear = daysInYear(resYear) + dayOfYear;
+    } else if (dayOfYear > daysInYear(year)) {
+        resYear = year + 1;
+        resDayOfYear = dayOfYear - daysInYear(year);
+    } else {
+        resYear = year;
+        resDayOfYear = dayOfYear;
+    }
+
+    return {
+        year: resYear,
+        dayOfYear: resDayOfYear
+    };
+}
+
+function weekOfYear(mom, dow, doy) {
+    var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+        week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+        resWeek, resYear;
+
+    if (week < 1) {
+        resYear = mom.year() - 1;
+        resWeek = week + weeksInYear(resYear, dow, doy);
+    } else if (week > weeksInYear(mom.year(), dow, doy)) {
+        resWeek = week - weeksInYear(mom.year(), dow, doy);
+        resYear = mom.year() + 1;
+    } else {
+        resYear = mom.year();
+        resWeek = week;
+    }
+
+    return {
+        week: resWeek,
+        year: resYear
+    };
+}
+
+function weeksInYear(year, dow, doy) {
+    var weekOffset = firstWeekOffset(year, dow, doy),
+        weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+    return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+}
+
+// FORMATTING
+
+addFormatToken('w', ['ww', 2], 'wo', 'week');
+addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+// ALIASES
+
+addUnitAlias('week', 'w');
+addUnitAlias('isoWeek', 'W');
+
+// PRIORITIES
+
+addUnitPriority('week', 5);
+addUnitPriority('isoWeek', 5);
+
+// PARSING
+
+addRegexToken('w',  match1to2);
+addRegexToken('ww', match1to2, match2);
+addRegexToken('W',  match1to2);
+addRegexToken('WW', match1to2, match2);
+
+addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
+    week[token.substr(0, 1)] = toInt(input);
+});
+
+// HELPERS
+
+// LOCALES
+
+function localeWeek (mom) {
+    return weekOfYear(mom, this._week.dow, this._week.doy).week;
+}
+
+var defaultLocaleWeek = {
+    dow : 0, // Sunday is the first day of the week.
+    doy : 6  // The week that contains Jan 1st is the first week of the year.
+};
+
+function localeFirstDayOfWeek () {
+    return this._week.dow;
+}
+
+function localeFirstDayOfYear () {
+    return this._week.doy;
+}
+
+// MOMENTS
+
+function getSetWeek (input) {
+    var week = this.localeData().week(this);
+    return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+function getSetISOWeek (input) {
+    var week = weekOfYear(this, 1, 4).week;
+    return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+// FORMATTING
+
+addFormatToken('d', 0, 'do', 'day');
+
+addFormatToken('dd', 0, 0, function (format) {
+    return this.localeData().weekdaysMin(this, format);
+});
+
+addFormatToken('ddd', 0, 0, function (format) {
+    return this.localeData().weekdaysShort(this, format);
+});
+
+addFormatToken('dddd', 0, 0, function (format) {
+    return this.localeData().weekdays(this, format);
+});
+
+addFormatToken('e', 0, 0, 'weekday');
+addFormatToken('E', 0, 0, 'isoWeekday');
+
+// ALIASES
+
+addUnitAlias('day', 'd');
+addUnitAlias('weekday', 'e');
+addUnitAlias('isoWeekday', 'E');
+
+// PRIORITY
+addUnitPriority('day', 11);
+addUnitPriority('weekday', 11);
+addUnitPriority('isoWeekday', 11);
+
+// PARSING
+
+addRegexToken('d',    match1to2);
+addRegexToken('e',    match1to2);
+addRegexToken('E',    match1to2);
+addRegexToken('dd',   function (isStrict, locale) {
+    return locale.weekdaysMinRegex(isStrict);
+});
+addRegexToken('ddd',   function (isStrict, locale) {
+    return locale.weekdaysShortRegex(isStrict);
+});
+addRegexToken('dddd',   function (isStrict, locale) {
+    return locale.weekdaysRegex(isStrict);
+});
+
+addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+    var weekday = config._locale.weekdaysParse(input, token, config._strict);
+    // if we didn't get a weekday name, mark the date as invalid
+    if (weekday != null) {
+        week.d = weekday;
+    } else {
+        getParsingFlags(config).invalidWeekday = input;
+    }
+});
+
+addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+    week[token] = toInt(input);
+});
+
+// HELPERS
+
+function parseWeekday(input, locale) {
+    if (typeof input !== 'string') {
+        return input;
+    }
+
+    if (!isNaN(input)) {
+        return parseInt(input, 10);
+    }
+
+    input = locale.weekdaysParse(input);
+    if (typeof input === 'number') {
+        return input;
+    }
+
+    return null;
+}
+
+function parseIsoWeekday(input, locale) {
+    if (typeof input === 'string') {
+        return locale.weekdaysParse(input) % 7 || 7;
+    }
+    return isNaN(input) ? null : input;
+}
+
+// LOCALES
+
+var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+function localeWeekdays (m, format) {
+    if (!m) {
+        return isArray(this._weekdays) ? this._weekdays :
+            this._weekdays['standalone'];
+    }
+    return isArray(this._weekdays) ? this._weekdays[m.day()] :
+        this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
+}
+
+var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+function localeWeekdaysShort (m) {
+    return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
+}
+
+var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+function localeWeekdaysMin (m) {
+    return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
+}
+
+function handleStrictParse$1(weekdayName, format, strict) {
+    var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
+    if (!this._weekdaysParse) {
+        this._weekdaysParse = [];
+        this._shortWeekdaysParse = [];
+        this._minWeekdaysParse = [];
+
+        for (i = 0; i < 7; ++i) {
+            mom = createUTC([2000, 1]).day(i);
+            this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
+            this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
+            this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+        }
+    }
+
+    if (strict) {
+        if (format === 'dddd') {
+            ii = indexOf.call(this._weekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else if (format === 'ddd') {
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    } else {
+        if (format === 'dddd') {
+            ii = indexOf.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else if (format === 'ddd') {
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    }
+}
+
+function localeWeekdaysParse (weekdayName, format, strict) {
+    var i, mom, regex;
+
+    if (this._weekdaysParseExact) {
+        return handleStrictParse$1.call(this, weekdayName, format, strict);
+    }
+
+    if (!this._weekdaysParse) {
+        this._weekdaysParse = [];
+        this._minWeekdaysParse = [];
+        this._shortWeekdaysParse = [];
+        this._fullWeekdaysParse = [];
+    }
+
+    for (i = 0; i < 7; i++) {
+        // make the regex if we don't have it already
+
+        mom = createUTC([2000, 1]).day(i);
+        if (strict && !this._fullWeekdaysParse[i]) {
+            this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
+            this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
+            this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
+        }
+        if (!this._weekdaysParse[i]) {
+            regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+            this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+        }
+        // test the regex
+        if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+            return i;
+        }
+    }
+}
+
+// MOMENTS
+
+function getSetDayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+    if (input != null) {
+        input = parseWeekday(input, this.localeData());
+        return this.add(input - day, 'd');
+    } else {
+        return day;
+    }
+}
+
+function getSetLocaleDayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+    return input == null ? weekday : this.add(input - weekday, 'd');
+}
+
+function getSetISODayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+
+    // behaves the same as moment#day except
+    // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+    // as a setter, sunday should belong to the previous week.
+
+    if (input != null) {
+        var weekday = parseIsoWeekday(input, this.localeData());
+        return this.day(this.day() % 7 ? weekday : weekday - 7);
+    } else {
+        return this.day() || 7;
+    }
+}
+
+var defaultWeekdaysRegex = matchWord;
+function weekdaysRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysStrictRegex;
+        } else {
+            return this._weekdaysRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            this._weekdaysRegex = defaultWeekdaysRegex;
+        }
+        return this._weekdaysStrictRegex && isStrict ?
+            this._weekdaysStrictRegex : this._weekdaysRegex;
+    }
+}
+
+var defaultWeekdaysShortRegex = matchWord;
+function weekdaysShortRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysShortStrictRegex;
+        } else {
+            return this._weekdaysShortRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysShortRegex')) {
+            this._weekdaysShortRegex = defaultWeekdaysShortRegex;
+        }
+        return this._weekdaysShortStrictRegex && isStrict ?
+            this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
+    }
+}
+
+var defaultWeekdaysMinRegex = matchWord;
+function weekdaysMinRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysMinStrictRegex;
+        } else {
+            return this._weekdaysMinRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysMinRegex')) {
+            this._weekdaysMinRegex = defaultWeekdaysMinRegex;
+        }
+        return this._weekdaysMinStrictRegex && isStrict ?
+            this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
+    }
+}
+
+
+function computeWeekdaysParse () {
+    function cmpLenRev(a, b) {
+        return b.length - a.length;
+    }
+
+    var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
+        i, mom, minp, shortp, longp;
+    for (i = 0; i < 7; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, 1]).day(i);
+        minp = this.weekdaysMin(mom, '');
+        shortp = this.weekdaysShort(mom, '');
+        longp = this.weekdays(mom, '');
+        minPieces.push(minp);
+        shortPieces.push(shortp);
+        longPieces.push(longp);
+        mixedPieces.push(minp);
+        mixedPieces.push(shortp);
+        mixedPieces.push(longp);
+    }
+    // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+    // will match the longer piece.
+    minPieces.sort(cmpLenRev);
+    shortPieces.sort(cmpLenRev);
+    longPieces.sort(cmpLenRev);
+    mixedPieces.sort(cmpLenRev);
+    for (i = 0; i < 7; i++) {
+        shortPieces[i] = regexEscape(shortPieces[i]);
+        longPieces[i] = regexEscape(longPieces[i]);
+        mixedPieces[i] = regexEscape(mixedPieces[i]);
+    }
+
+    this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+    this._weekdaysShortRegex = this._weekdaysRegex;
+    this._weekdaysMinRegex = this._weekdaysRegex;
+
+    this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+    this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+    this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
+}
+
+// FORMATTING
+
+function hFormat() {
+    return this.hours() % 12 || 12;
+}
+
+function kFormat() {
+    return this.hours() || 24;
+}
+
+addFormatToken('H', ['HH', 2], 0, 'hour');
+addFormatToken('h', ['hh', 2], 0, hFormat);
+addFormatToken('k', ['kk', 2], 0, kFormat);
+
+addFormatToken('hmm', 0, 0, function () {
+    return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('hmmss', 0, 0, function () {
+    return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
+        zeroFill(this.seconds(), 2);
+});
+
+addFormatToken('Hmm', 0, 0, function () {
+    return '' + this.hours() + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('Hmmss', 0, 0, function () {
+    return '' + this.hours() + zeroFill(this.minutes(), 2) +
+        zeroFill(this.seconds(), 2);
+});
+
+function meridiem (token, lowercase) {
+    addFormatToken(token, 0, 0, function () {
+        return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
+    });
+}
+
+meridiem('a', true);
+meridiem('A', false);
+
+// ALIASES
+
+addUnitAlias('hour', 'h');
+
+// PRIORITY
+addUnitPriority('hour', 13);
+
+// PARSING
+
+function matchMeridiem (isStrict, locale) {
+    return locale._meridiemParse;
+}
+
+addRegexToken('a',  matchMeridiem);
+addRegexToken('A',  matchMeridiem);
+addRegexToken('H',  match1to2);
+addRegexToken('h',  match1to2);
+addRegexToken('k',  match1to2);
+addRegexToken('HH', match1to2, match2);
+addRegexToken('hh', match1to2, match2);
+addRegexToken('kk', match1to2, match2);
+
+addRegexToken('hmm', match3to4);
+addRegexToken('hmmss', match5to6);
+addRegexToken('Hmm', match3to4);
+addRegexToken('Hmmss', match5to6);
+
+addParseToken(['H', 'HH'], HOUR);
+addParseToken(['k', 'kk'], function (input, array, config) {
+    var kInput = toInt(input);
+    array[HOUR] = kInput === 24 ? 0 : kInput;
+});
+addParseToken(['a', 'A'], function (input, array, config) {
+    config._isPm = config._locale.isPM(input);
+    config._meridiem = input;
+});
+addParseToken(['h', 'hh'], function (input, array, config) {
+    array[HOUR] = toInt(input);
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmm', function (input, array, config) {
+    var pos = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos));
+    array[MINUTE] = toInt(input.substr(pos));
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmmss', function (input, array, config) {
+    var pos1 = input.length - 4;
+    var pos2 = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos1));
+    array[MINUTE] = toInt(input.substr(pos1, 2));
+    array[SECOND] = toInt(input.substr(pos2));
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('Hmm', function (input, array, config) {
+    var pos = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos));
+    array[MINUTE] = toInt(input.substr(pos));
+});
+addParseToken('Hmmss', function (input, array, config) {
+    var pos1 = input.length - 4;
+    var pos2 = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos1));
+    array[MINUTE] = toInt(input.substr(pos1, 2));
+    array[SECOND] = toInt(input.substr(pos2));
+});
+
+// LOCALES
+
+function localeIsPM (input) {
+    // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+    // Using charAt should be more compatible.
+    return ((input + '').toLowerCase().charAt(0) === 'p');
+}
+
+var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
+function localeMeridiem (hours, minutes, isLower) {
+    if (hours > 11) {
+        return isLower ? 'pm' : 'PM';
+    } else {
+        return isLower ? 'am' : 'AM';
+    }
+}
+
+
+// MOMENTS
+
+// Setting the hour should keep the time, because the user explicitly
+// specified which hour he wants. So trying to maintain the same hour (in
+// a new timezone) makes sense. Adding/subtracting hours does not follow
+// this rule.
+var getSetHour = makeGetSet('Hours', true);
+
+// months
+// week
+// weekdays
+// meridiem
+var baseConfig = {
+    calendar: defaultCalendar,
+    longDateFormat: defaultLongDateFormat,
+    invalidDate: defaultInvalidDate,
+    ordinal: defaultOrdinal,
+    dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
+    relativeTime: defaultRelativeTime,
+
+    months: defaultLocaleMonths,
+    monthsShort: defaultLocaleMonthsShort,
+
+    week: defaultLocaleWeek,
+
+    weekdays: defaultLocaleWeekdays,
+    weekdaysMin: defaultLocaleWeekdaysMin,
+    weekdaysShort: defaultLocaleWeekdaysShort,
+
+    meridiemParse: defaultLocaleMeridiemParse
+};
+
+// internal storage for locale config files
+var locales = {};
+var localeFamilies = {};
+var globalLocale;
+
+function normalizeLocale(key) {
+    return key ? key.toLowerCase().replace('_', '-') : key;
+}
+
+// pick the locale from the array
+// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+function chooseLocale(names) {
+    var i = 0, j, next, locale, split;
+
+    while (i < names.length) {
+        split = normalizeLocale(names[i]).split('-');
+        j = split.length;
+        next = normalizeLocale(names[i + 1]);
+        next = next ? next.split('-') : null;
+        while (j > 0) {
+            locale = loadLocale(split.slice(0, j).join('-'));
+            if (locale) {
+                return locale;
+            }
+            if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+                //the next array item is better than a shallower substring of this one
+                break;
+            }
+            j--;
+        }
+        i++;
+    }
+    return null;
+}
+
+function loadLocale(name) {
+    var oldLocale = null;
+    // TODO: Find a better way to register and load all the locales in Node
+    if (!locales[name] && (typeof module !== 'undefined') &&
+            module && module.exports) {
+        try {
+            oldLocale = globalLocale._abbr;
+            var aliasedRequire = require;
+            __webpack_require__(207)("./" + name);
+            getSetGlobalLocale(oldLocale);
+        } catch (e) {}
+    }
+    return locales[name];
+}
+
+// This function will load locale and then set the global locale.  If
+// no arguments are passed in, it will simply return the current global
+// locale key.
+function getSetGlobalLocale (key, values) {
+    var data;
+    if (key) {
+        if (isUndefined(values)) {
+            data = getLocale(key);
+        }
+        else {
+            data = defineLocale(key, values);
+        }
+
+        if (data) {
+            // moment.duration._locale = moment._locale = data;
+            globalLocale = data;
+        }
+    }
+
+    return globalLocale._abbr;
+}
+
+function defineLocale (name, config) {
+    if (config !== null) {
+        var parentConfig = baseConfig;
+        config.abbr = name;
+        if (locales[name] != null) {
+            deprecateSimple('defineLocaleOverride',
+                    'use moment.updateLocale(localeName, config) to change ' +
+                    'an existing locale. moment.defineLocale(localeName, ' +
+                    'config) should only be used for creating a new locale ' +
+                    'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
+            parentConfig = locales[name]._config;
+        } else if (config.parentLocale != null) {
+            if (locales[config.parentLocale] != null) {
+                parentConfig = locales[config.parentLocale]._config;
+            } else {
+                if (!localeFamilies[config.parentLocale]) {
+                    localeFamilies[config.parentLocale] = [];
+                }
+                localeFamilies[config.parentLocale].push({
+                    name: name,
+                    config: config
+                });
+                return null;
+            }
+        }
+        locales[name] = new Locale(mergeConfigs(parentConfig, config));
+
+        if (localeFamilies[name]) {
+            localeFamilies[name].forEach(function (x) {
+                defineLocale(x.name, x.config);
+            });
+        }
+
+        // backwards compat for now: also set the locale
+        // make sure we set the locale AFTER all child locales have been
+        // created, so we won't end up with the child locale set.
+        getSetGlobalLocale(name);
+
+
+        return locales[name];
+    } else {
+        // useful for testing
+        delete locales[name];
+        return null;
+    }
+}
+
+function updateLocale(name, config) {
+    if (config != null) {
+        var locale, tmpLocale, parentConfig = baseConfig;
+        // MERGE
+        tmpLocale = loadLocale(name);
+        if (tmpLocale != null) {
+            parentConfig = tmpLocale._config;
+        }
+        config = mergeConfigs(parentConfig, config);
+        locale = new Locale(config);
+        locale.parentLocale = locales[name];
+        locales[name] = locale;
+
+        // backwards compat for now: also set the locale
+        getSetGlobalLocale(name);
+    } else {
+        // pass null for config to unupdate, useful for tests
+        if (locales[name] != null) {
+            if (locales[name].parentLocale != null) {
+                locales[name] = locales[name].parentLocale;
+            } else if (locales[name] != null) {
+                delete locales[name];
+            }
+        }
+    }
+    return locales[name];
+}
+
+// returns locale data
+function getLocale (key) {
+    var locale;
+
+    if (key && key._locale && key._locale._abbr) {
+        key = key._locale._abbr;
+    }
+
+    if (!key) {
+        return globalLocale;
+    }
+
+    if (!isArray(key)) {
+        //short-circuit everything else
+        locale = loadLocale(key);
+        if (locale) {
+            return locale;
+        }
+        key = [key];
+    }
+
+    return chooseLocale(key);
+}
+
+function listLocales() {
+    return keys(locales);
+}
+
+function checkOverflow (m) {
+    var overflow;
+    var a = m._a;
+
+    if (a && getParsingFlags(m).overflow === -2) {
+        overflow =
+            a[MONTH]       < 0 || a[MONTH]       > 11  ? MONTH :
+            a[DATE]        < 1 || a[DATE]        > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
+            a[HOUR]        < 0 || a[HOUR]        > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
+            a[MINUTE]      < 0 || a[MINUTE]      > 59  ? MINUTE :
+            a[SECOND]      < 0 || a[SECOND]      > 59  ? SECOND :
+            a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
+            -1;
+
+        if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+            overflow = DATE;
+        }
+        if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+            overflow = WEEK;
+        }
+        if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+            overflow = WEEKDAY;
+        }
+
+        getParsingFlags(m).overflow = overflow;
+    }
+
+    return m;
+}
+
+// Pick the first defined of two or three arguments.
+function defaults(a, b, c) {
+    if (a != null) {
+        return a;
+    }
+    if (b != null) {
+        return b;
+    }
+    return c;
+}
+
+function currentDateArray(config) {
+    // hooks is actually the exported moment object
+    var nowValue = new Date(hooks.now());
+    if (config._useUTC) {
+        return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
+    }
+    return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+}
+
+// convert an array to a date.
+// the array should mirror the parameters below
+// note: all values past the year are optional and will default to the lowest possible value.
+// [year, month, day , hour, minute, second, millisecond]
+function configFromArray (config) {
+    var i, date, input = [], currentDate, expectedWeekday, yearToUse;
+
+    if (config._d) {
+        return;
+    }
+
+    currentDate = currentDateArray(config);
+
+    //compute day of the year from weeks and weekdays
+    if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+        dayOfYearFromWeekInfo(config);
+    }
+
+    //if the day of the year is set, figure out what it is
+    if (config._dayOfYear != null) {
+        yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
+
+        if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
+            getParsingFlags(config)._overflowDayOfYear = true;
+        }
+
+        date = createUTCDate(yearToUse, 0, config._dayOfYear);
+        config._a[MONTH] = date.getUTCMonth();
+        config._a[DATE] = date.getUTCDate();
+    }
+
+    // Default to current date.
+    // * if no year, month, day of month are given, default to today
+    // * if day of month is given, default month and year
+    // * if month is given, default only year
+    // * if year is given, don't default anything
+    for (i = 0; i < 3 && config._a[i] == null; ++i) {
+        config._a[i] = input[i] = currentDate[i];
+    }
+
+    // Zero out whatever was not defaulted, including time
+    for (; i < 7; i++) {
+        config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+    }
+
+    // Check for 24:00:00.000
+    if (config._a[HOUR] === 24 &&
+            config._a[MINUTE] === 0 &&
+            config._a[SECOND] === 0 &&
+            config._a[MILLISECOND] === 0) {
+        config._nextDay = true;
+        config._a[HOUR] = 0;
+    }
+
+    config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+    expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
+
+    // Apply timezone offset from input. The actual utcOffset can be changed
+    // with parseZone.
+    if (config._tzm != null) {
+        config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+    }
+
+    if (config._nextDay) {
+        config._a[HOUR] = 24;
+    }
+
+    // check for mismatching day of week
+    if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
+        getParsingFlags(config).weekdayMismatch = true;
+    }
+}
+
+function dayOfYearFromWeekInfo(config) {
+    var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
+
+    w = config._w;
+    if (w.GG != null || w.W != null || w.E != null) {
+        dow = 1;
+        doy = 4;
+
+        // TODO: We need to take the current isoWeekYear, but that depends on
+        // how we interpret now (local, utc, fixed offset). So create
+        // a now version of current config (take local/utc/offset flags, and
+        // create now).
+        weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
+        week = defaults(w.W, 1);
+        weekday = defaults(w.E, 1);
+        if (weekday < 1 || weekday > 7) {
+            weekdayOverflow = true;
+        }
+    } else {
+        dow = config._locale._week.dow;
+        doy = config._locale._week.doy;
+
+        var curWeek = weekOfYear(createLocal(), dow, doy);
+
+        weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
+
+        // Default to current week.
+        week = defaults(w.w, curWeek.week);
+
+        if (w.d != null) {
+            // weekday -- low day numbers are considered next week
+            weekday = w.d;
+            if (weekday < 0 || weekday > 6) {
+                weekdayOverflow = true;
+            }
+        } else if (w.e != null) {
+            // local weekday -- counting starts from begining of week
+            weekday = w.e + dow;
+            if (w.e < 0 || w.e > 6) {
+                weekdayOverflow = true;
+            }
+        } else {
+            // default to begining of week
+            weekday = dow;
+        }
+    }
+    if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+        getParsingFlags(config)._overflowWeeks = true;
+    } else if (weekdayOverflow != null) {
+        getParsingFlags(config)._overflowWeekday = true;
+    } else {
+        temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+        config._a[YEAR] = temp.year;
+        config._dayOfYear = temp.dayOfYear;
+    }
+}
+
+// iso 8601 regex
+// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+
+var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
+
+var isoDates = [
+    ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+    ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+    ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+    ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+    ['YYYY-DDD', /\d{4}-\d{3}/],
+    ['YYYY-MM', /\d{4}-\d\d/, false],
+    ['YYYYYYMMDD', /[+-]\d{10}/],
+    ['YYYYMMDD', /\d{8}/],
+    // YYYYMM is NOT allowed by the standard
+    ['GGGG[W]WWE', /\d{4}W\d{3}/],
+    ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+    ['YYYYDDD', /\d{7}/]
+];
+
+// iso time formats and regexes
+var isoTimes = [
+    ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+    ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+    ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+    ['HH:mm', /\d\d:\d\d/],
+    ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+    ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+    ['HHmmss', /\d\d\d\d\d\d/],
+    ['HHmm', /\d\d\d\d/],
+    ['HH', /\d\d/]
+];
+
+var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+
+// date from iso format
+function configFromISO(config) {
+    var i, l,
+        string = config._i,
+        match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+        allowTime, dateFormat, timeFormat, tzFormat;
+
+    if (match) {
+        getParsingFlags(config).iso = true;
+
+        for (i = 0, l = isoDates.length; i < l; i++) {
+            if (isoDates[i][1].exec(match[1])) {
+                dateFormat = isoDates[i][0];
+                allowTime = isoDates[i][2] !== false;
+                break;
+            }
+        }
+        if (dateFormat == null) {
+            config._isValid = false;
+            return;
+        }
+        if (match[3]) {
+            for (i = 0, l = isoTimes.length; i < l; i++) {
+                if (isoTimes[i][1].exec(match[3])) {
+                    // match[2] should be 'T' or space
+                    timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                    break;
+                }
+            }
+            if (timeFormat == null) {
+                config._isValid = false;
+                return;
+            }
+        }
+        if (!allowTime && timeFormat != null) {
+            config._isValid = false;
+            return;
+        }
+        if (match[4]) {
+            if (tzRegex.exec(match[4])) {
+                tzFormat = 'Z';
+            } else {
+                config._isValid = false;
+                return;
+            }
+        }
+        config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+        configFromStringAndFormat(config);
+    } else {
+        config._isValid = false;
+    }
+}
+
+// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
+var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
+
+function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
+    var result = [
+        untruncateYear(yearStr),
+        defaultLocaleMonthsShort.indexOf(monthStr),
+        parseInt(dayStr, 10),
+        parseInt(hourStr, 10),
+        parseInt(minuteStr, 10)
+    ];
+
+    if (secondStr) {
+        result.push(parseInt(secondStr, 10));
+    }
+
+    return result;
+}
+
+function untruncateYear(yearStr) {
+    var year = parseInt(yearStr, 10);
+    if (year <= 49) {
+        return 2000 + year;
+    } else if (year <= 999) {
+        return 1900 + year;
+    }
+    return year;
+}
+
+function preprocessRFC2822(s) {
+    // Remove comments and folding whitespace and replace multiple-spaces with a single space
+    return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim();
+}
+
+function checkWeekday(weekdayStr, parsedInput, config) {
+    if (weekdayStr) {
+        // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
+        var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
+            weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
+        if (weekdayProvided !== weekdayActual) {
+            getParsingFlags(config).weekdayMismatch = true;
+            config._isValid = false;
+            return false;
+        }
+    }
+    return true;
+}
+
+var obsOffsets = {
+    UT: 0,
+    GMT: 0,
+    EDT: -4 * 60,
+    EST: -5 * 60,
+    CDT: -5 * 60,
+    CST: -6 * 60,
+    MDT: -6 * 60,
+    MST: -7 * 60,
+    PDT: -7 * 60,
+    PST: -8 * 60
+};
+
+function calculateOffset(obsOffset, militaryOffset, numOffset) {
+    if (obsOffset) {
+        return obsOffsets[obsOffset];
+    } else if (militaryOffset) {
+        // the only allowed military tz is Z
+        return 0;
+    } else {
+        var hm = parseInt(numOffset, 10);
+        var m = hm % 100, h = (hm - m) / 100;
+        return h * 60 + m;
+    }
+}
+
+// date and time from ref 2822 format
+function configFromRFC2822(config) {
+    var match = rfc2822.exec(preprocessRFC2822(config._i));
+    if (match) {
+        var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
+        if (!checkWeekday(match[1], parsedArray, config)) {
+            return;
+        }
+
+        config._a = parsedArray;
+        config._tzm = calculateOffset(match[8], match[9], match[10]);
+
+        config._d = createUTCDate.apply(null, config._a);
+        config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+
+        getParsingFlags(config).rfc2822 = true;
+    } else {
+        config._isValid = false;
+    }
+}
+
+// date from iso format or fallback
+function configFromString(config) {
+    var matched = aspNetJsonRegex.exec(config._i);
+
+    if (matched !== null) {
+        config._d = new Date(+matched[1]);
+        return;
+    }
+
+    configFromISO(config);
+    if (config._isValid === false) {
+        delete config._isValid;
+    } else {
+        return;
+    }
+
+    configFromRFC2822(config);
+    if (config._isValid === false) {
+        delete config._isValid;
+    } else {
+        return;
+    }
+
+    // Final attempt, use Input Fallback
+    hooks.createFromInputFallback(config);
+}
+
+hooks.createFromInputFallback = deprecate(
+    'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
+    'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
+    'discouraged and will be removed in an upcoming major release. Please refer to ' +
+    'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
+    function (config) {
+        config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+    }
+);
+
+// constant that refers to the ISO standard
+hooks.ISO_8601 = function () {};
+
+// constant that refers to the RFC 2822 form
+hooks.RFC_2822 = function () {};
+
+// date from string and format string
+function configFromStringAndFormat(config) {
+    // TODO: Move this to another part of the creation flow to prevent circular deps
+    if (config._f === hooks.ISO_8601) {
+        configFromISO(config);
+        return;
+    }
+    if (config._f === hooks.RFC_2822) {
+        configFromRFC2822(config);
+        return;
+    }
+    config._a = [];
+    getParsingFlags(config).empty = true;
+
+    // This array is used to make a Date, either with `new Date` or `Date.UTC`
+    var string = '' + config._i,
+        i, parsedInput, tokens, token, skipped,
+        stringLength = string.length,
+        totalParsedInputLength = 0;
+
+    tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
+
+    for (i = 0; i < tokens.length; i++) {
+        token = tokens[i];
+        parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+        // console.log('token', token, 'parsedInput', parsedInput,
+        //         'regex', getParseRegexForToken(token, config));
+        if (parsedInput) {
+            skipped = string.substr(0, string.indexOf(parsedInput));
+            if (skipped.length > 0) {
+                getParsingFlags(config).unusedInput.push(skipped);
+            }
+            string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+            totalParsedInputLength += parsedInput.length;
+        }
+        // don't parse if it's not a known token
+        if (formatTokenFunctions[token]) {
+            if (parsedInput) {
+                getParsingFlags(config).empty = false;
+            }
+            else {
+                getParsingFlags(config).unusedTokens.push(token);
+            }
+            addTimeToArrayFromToken(token, parsedInput, config);
+        }
+        else if (config._strict && !parsedInput) {
+            getParsingFlags(config).unusedTokens.push(token);
+        }
+    }
+
+    // add remaining unparsed input length to the string
+    getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+    if (string.length > 0) {
+        getParsingFlags(config).unusedInput.push(string);
+    }
+
+    // clear _12h flag if hour is <= 12
+    if (config._a[HOUR] <= 12 &&
+        getParsingFlags(config).bigHour === true &&
+        config._a[HOUR] > 0) {
+        getParsingFlags(config).bigHour = undefined;
+    }
+
+    getParsingFlags(config).parsedDateParts = config._a.slice(0);
+    getParsingFlags(config).meridiem = config._meridiem;
+    // handle meridiem
+    config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
+
+    configFromArray(config);
+    checkOverflow(config);
+}
+
+
+function meridiemFixWrap (locale, hour, meridiem) {
+    var isPm;
+
+    if (meridiem == null) {
+        // nothing to do
+        return hour;
+    }
+    if (locale.meridiemHour != null) {
+        return locale.meridiemHour(hour, meridiem);
+    } else if (locale.isPM != null) {
+        // Fallback
+        isPm = locale.isPM(meridiem);
+        if (isPm && hour < 12) {
+            hour += 12;
+        }
+        if (!isPm && hour === 12) {
+            hour = 0;
+        }
+        return hour;
+    } else {
+        // this is not supposed to happen
+        return hour;
+    }
+}
+
+// date from string and array of format strings
+function configFromStringAndArray(config) {
+    var tempConfig,
+        bestMoment,
+
+        scoreToBeat,
+        i,
+        currentScore;
+
+    if (config._f.length === 0) {
+        getParsingFlags(config).invalidFormat = true;
+        config._d = new Date(NaN);
+        return;
+    }
+
+    for (i = 0; i < config._f.length; i++) {
+        currentScore = 0;
+        tempConfig = copyConfig({}, config);
+        if (config._useUTC != null) {
+            tempConfig._useUTC = config._useUTC;
+        }
+        tempConfig._f = config._f[i];
+        configFromStringAndFormat(tempConfig);
+
+        if (!isValid(tempConfig)) {
+            continue;
+        }
+
+        // if there is any input that was not parsed add a penalty for that format
+        currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+        //or tokens
+        currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+        getParsingFlags(tempConfig).score = currentScore;
+
+        if (scoreToBeat == null || currentScore < scoreToBeat) {
+            scoreToBeat = currentScore;
+            bestMoment = tempConfig;
+        }
+    }
+
+    extend(config, bestMoment || tempConfig);
+}
+
+function configFromObject(config) {
+    if (config._d) {
+        return;
+    }
+
+    var i = normalizeObjectUnits(config._i);
+    config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
+        return obj && parseInt(obj, 10);
+    });
+
+    configFromArray(config);
+}
+
+function createFromConfig (config) {
+    var res = new Moment(checkOverflow(prepareConfig(config)));
+    if (res._nextDay) {
+        // Adding is smart enough around DST
+        res.add(1, 'd');
+        res._nextDay = undefined;
+    }
+
+    return res;
+}
+
+function prepareConfig (config) {
+    var input = config._i,
+        format = config._f;
+
+    config._locale = config._locale || getLocale(config._l);
+
+    if (input === null || (format === undefined && input === '')) {
+        return createInvalid({nullInput: true});
+    }
+
+    if (typeof input === 'string') {
+        config._i = input = config._locale.preparse(input);
+    }
+
+    if (isMoment(input)) {
+        return new Moment(checkOverflow(input));
+    } else if (isDate(input)) {
+        config._d = input;
+    } else if (isArray(format)) {
+        configFromStringAndArray(config);
+    } else if (format) {
+        configFromStringAndFormat(config);
+    }  else {
+        configFromInput(config);
+    }
+
+    if (!isValid(config)) {
+        config._d = null;
+    }
+
+    return config;
+}
+
+function configFromInput(config) {
+    var input = config._i;
+    if (isUndefined(input)) {
+        config._d = new Date(hooks.now());
+    } else if (isDate(input)) {
+        config._d = new Date(input.valueOf());
+    } else if (typeof input === 'string') {
+        configFromString(config);
+    } else if (isArray(input)) {
+        config._a = map(input.slice(0), function (obj) {
+            return parseInt(obj, 10);
+        });
+        configFromArray(config);
+    } else if (isObject(input)) {
+        configFromObject(config);
+    } else if (isNumber(input)) {
+        // from milliseconds
+        config._d = new Date(input);
+    } else {
+        hooks.createFromInputFallback(config);
+    }
+}
+
+function createLocalOrUTC (input, format, locale, strict, isUTC) {
+    var c = {};
+
+    if (locale === true || locale === false) {
+        strict = locale;
+        locale = undefined;
+    }
+
+    if ((isObject(input) && isObjectEmpty(input)) ||
+            (isArray(input) && input.length === 0)) {
+        input = undefined;
+    }
+    // object construction must be done this way.
+    // https://github.com/moment/moment/issues/1423
+    c._isAMomentObject = true;
+    c._useUTC = c._isUTC = isUTC;
+    c._l = locale;
+    c._i = input;
+    c._f = format;
+    c._strict = strict;
+
+    return createFromConfig(c);
+}
+
+function createLocal (input, format, locale, strict) {
+    return createLocalOrUTC(input, format, locale, strict, false);
+}
+
+var prototypeMin = deprecate(
+    'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
+    function () {
+        var other = createLocal.apply(null, arguments);
+        if (this.isValid() && other.isValid()) {
+            return other < this ? this : other;
+        } else {
+            return createInvalid();
+        }
+    }
+);
+
+var prototypeMax = deprecate(
+    'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
+    function () {
+        var other = createLocal.apply(null, arguments);
+        if (this.isValid() && other.isValid()) {
+            return other > this ? this : other;
+        } else {
+            return createInvalid();
+        }
+    }
+);
+
+// Pick a moment m from moments so that m[fn](other) is true for all
+// other. This relies on the function fn to be transitive.
+//
+// moments should either be an array of moment objects or an array, whose
+// first element is an array of moment objects.
+function pickBy(fn, moments) {
+    var res, i;
+    if (moments.length === 1 && isArray(moments[0])) {
+        moments = moments[0];
+    }
+    if (!moments.length) {
+        return createLocal();
+    }
+    res = moments[0];
+    for (i = 1; i < moments.length; ++i) {
+        if (!moments[i].isValid() || moments[i][fn](res)) {
+            res = moments[i];
+        }
+    }
+    return res;
+}
+
+// TODO: Use [].sort instead?
+function min () {
+    var args = [].slice.call(arguments, 0);
+
+    return pickBy('isBefore', args);
+}
+
+function max () {
+    var args = [].slice.call(arguments, 0);
+
+    return pickBy('isAfter', args);
+}
+
+var now = function () {
+    return Date.now ? Date.now() : +(new Date());
+};
+
+var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
+
+function isDurationValid(m) {
+    for (var key in m) {
+        if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
+            return false;
+        }
+    }
+
+    var unitHasDecimal = false;
+    for (var i = 0; i < ordering.length; ++i) {
+        if (m[ordering[i]]) {
+            if (unitHasDecimal) {
+                return false; // only allow non-integers for smallest unit
+            }
+            if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
+                unitHasDecimal = true;
+            }
+        }
+    }
+
+    return true;
+}
+
+function isValid$1() {
+    return this._isValid;
+}
+
+function createInvalid$1() {
+    return createDuration(NaN);
+}
+
+function Duration (duration) {
+    var normalizedInput = normalizeObjectUnits(duration),
+        years = normalizedInput.year || 0,
+        quarters = normalizedInput.quarter || 0,
+        months = normalizedInput.month || 0,
+        weeks = normalizedInput.week || 0,
+        days = normalizedInput.day || 0,
+        hours = normalizedInput.hour || 0,
+        minutes = normalizedInput.minute || 0,
+        seconds = normalizedInput.second || 0,
+        milliseconds = normalizedInput.millisecond || 0;
+
+    this._isValid = isDurationValid(normalizedInput);
+
+    // representation for dateAddRemove
+    this._milliseconds = +milliseconds +
+        seconds * 1e3 + // 1000
+        minutes * 6e4 + // 1000 * 60
+        hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+    // Because of dateAddRemove treats 24 hours as different from a
+    // day when working around DST, we need to store them separately
+    this._days = +days +
+        weeks * 7;
+    // It is impossible to translate months into days without knowing
+    // which months you are are talking about, so we have to store
+    // it separately.
+    this._months = +months +
+        quarters * 3 +
+        years * 12;
+
+    this._data = {};
+
+    this._locale = getLocale();
+
+    this._bubble();
+}
+
+function isDuration (obj) {
+    return obj instanceof Duration;
+}
+
+function absRound (number) {
+    if (number < 0) {
+        return Math.round(-1 * number) * -1;
+    } else {
+        return Math.round(number);
+    }
+}
+
+// FORMATTING
+
+function offset (token, separator) {
+    addFormatToken(token, 0, 0, function () {
+        var offset = this.utcOffset();
+        var sign = '+';
+        if (offset < 0) {
+            offset = -offset;
+            sign = '-';
+        }
+        return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+    });
+}
+
+offset('Z', ':');
+offset('ZZ', '');
+
+// PARSING
+
+addRegexToken('Z',  matchShortOffset);
+addRegexToken('ZZ', matchShortOffset);
+addParseToken(['Z', 'ZZ'], function (input, array, config) {
+    config._useUTC = true;
+    config._tzm = offsetFromString(matchShortOffset, input);
+});
+
+// HELPERS
+
+// timezone chunker
+// '+10:00' > ['10',  '00']
+// '-1530'  > ['-15', '30']
+var chunkOffset = /([\+\-]|\d\d)/gi;
+
+function offsetFromString(matcher, string) {
+    var matches = (string || '').match(matcher);
+
+    if (matches === null) {
+        return null;
+    }
+
+    var chunk   = matches[matches.length - 1] || [];
+    var parts   = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+    var minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+    return minutes === 0 ?
+      0 :
+      parts[0] === '+' ? minutes : -minutes;
+}
+
+// Return a moment from input, that is local/utc/zone equivalent to model.
+function cloneWithOffset(input, model) {
+    var res, diff;
+    if (model._isUTC) {
+        res = model.clone();
+        diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
+        // Use low-level api, because this fn is low-level api.
+        res._d.setTime(res._d.valueOf() + diff);
+        hooks.updateOffset(res, false);
+        return res;
+    } else {
+        return createLocal(input).local();
+    }
+}
+
+function getDateOffset (m) {
+    // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+    // https://github.com/moment/moment/pull/1871
+    return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+}
+
+// HOOKS
+
+// This function will be called whenever a moment is mutated.
+// It is intended to keep the offset in sync with the timezone.
+hooks.updateOffset = function () {};
+
+// MOMENTS
+
+// keepLocalTime = true means only change the timezone, without
+// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+// +0200, so we adjust the time as needed, to be valid.
+//
+// Keeping the time actually adds/subtracts (one hour)
+// from the actual represented time. That is why we call updateOffset
+// a second time. In case it wants us to change the offset again
+// _changeInProgress == true case, then we have to adjust, because
+// there is no such time in the given timezone.
+function getSetOffset (input, keepLocalTime, keepMinutes) {
+    var offset = this._offset || 0,
+        localAdjust;
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    if (input != null) {
+        if (typeof input === 'string') {
+            input = offsetFromString(matchShortOffset, input);
+            if (input === null) {
+                return this;
+            }
+        } else if (Math.abs(input) < 16 && !keepMinutes) {
+            input = input * 60;
+        }
+        if (!this._isUTC && keepLocalTime) {
+            localAdjust = getDateOffset(this);
+        }
+        this._offset = input;
+        this._isUTC = true;
+        if (localAdjust != null) {
+            this.add(localAdjust, 'm');
+        }
+        if (offset !== input) {
+            if (!keepLocalTime || this._changeInProgress) {
+                addSubtract(this, createDuration(input - offset, 'm'), 1, false);
+            } else if (!this._changeInProgress) {
+                this._changeInProgress = true;
+                hooks.updateOffset(this, true);
+                this._changeInProgress = null;
+            }
+        }
+        return this;
+    } else {
+        return this._isUTC ? offset : getDateOffset(this);
+    }
+}
+
+function getSetZone (input, keepLocalTime) {
+    if (input != null) {
+        if (typeof input !== 'string') {
+            input = -input;
+        }
+
+        this.utcOffset(input, keepLocalTime);
+
+        return this;
+    } else {
+        return -this.utcOffset();
+    }
+}
+
+function setOffsetToUTC (keepLocalTime) {
+    return this.utcOffset(0, keepLocalTime);
+}
+
+function setOffsetToLocal (keepLocalTime) {
+    if (this._isUTC) {
+        this.utcOffset(0, keepLocalTime);
+        this._isUTC = false;
+
+        if (keepLocalTime) {
+            this.subtract(getDateOffset(this), 'm');
+        }
+    }
+    return this;
+}
+
+function setOffsetToParsedOffset () {
+    if (this._tzm != null) {
+        this.utcOffset(this._tzm, false, true);
+    } else if (typeof this._i === 'string') {
+        var tZone = offsetFromString(matchOffset, this._i);
+        if (tZone != null) {
+            this.utcOffset(tZone);
+        }
+        else {
+            this.utcOffset(0, true);
+        }
+    }
+    return this;
+}
+
+function hasAlignedHourOffset (input) {
+    if (!this.isValid()) {
+        return false;
+    }
+    input = input ? createLocal(input).utcOffset() : 0;
+
+    return (this.utcOffset() - input) % 60 === 0;
+}
+
+function isDaylightSavingTime () {
+    return (
+        this.utcOffset() > this.clone().month(0).utcOffset() ||
+        this.utcOffset() > this.clone().month(5).utcOffset()
+    );
+}
+
+function isDaylightSavingTimeShifted () {
+    if (!isUndefined(this._isDSTShifted)) {
+        return this._isDSTShifted;
+    }
+
+    var c = {};
+
+    copyConfig(c, this);
+    c = prepareConfig(c);
+
+    if (c._a) {
+        var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
+        this._isDSTShifted = this.isValid() &&
+            compareArrays(c._a, other.toArray()) > 0;
+    } else {
+        this._isDSTShifted = false;
+    }
+
+    return this._isDSTShifted;
+}
+
+function isLocal () {
+    return this.isValid() ? !this._isUTC : false;
+}
+
+function isUtcOffset () {
+    return this.isValid() ? this._isUTC : false;
+}
+
+function isUtc () {
+    return this.isValid() ? this._isUTC && this._offset === 0 : false;
+}
+
+// ASP.NET json date format regex
+var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
+
+// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+// and further modified to allow for strings containing both week and day
+var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
+
+function createDuration (input, key) {
+    var duration = input,
+        // matching against regexp is expensive, do it on demand
+        match = null,
+        sign,
+        ret,
+        diffRes;
+
+    if (isDuration(input)) {
+        duration = {
+            ms : input._milliseconds,
+            d  : input._days,
+            M  : input._months
+        };
+    } else if (isNumber(input)) {
+        duration = {};
+        if (key) {
+            duration[key] = input;
+        } else {
+            duration.milliseconds = input;
+        }
+    } else if (!!(match = aspNetRegex.exec(input))) {
+        sign = (match[1] === '-') ? -1 : 1;
+        duration = {
+            y  : 0,
+            d  : toInt(match[DATE])                         * sign,
+            h  : toInt(match[HOUR])                         * sign,
+            m  : toInt(match[MINUTE])                       * sign,
+            s  : toInt(match[SECOND])                       * sign,
+            ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
+        };
+    } else if (!!(match = isoRegex.exec(input))) {
+        sign = (match[1] === '-') ? -1 : (match[1] === '+') ? 1 : 1;
+        duration = {
+            y : parseIso(match[2], sign),
+            M : parseIso(match[3], sign),
+            w : parseIso(match[4], sign),
+            d : parseIso(match[5], sign),
+            h : parseIso(match[6], sign),
+            m : parseIso(match[7], sign),
+            s : parseIso(match[8], sign)
+        };
+    } else if (duration == null) {// checks for null or undefined
+        duration = {};
+    } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
+        diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
+
+        duration = {};
+        duration.ms = diffRes.milliseconds;
+        duration.M = diffRes.months;
+    }
+
+    ret = new Duration(duration);
+
+    if (isDuration(input) && hasOwnProp(input, '_locale')) {
+        ret._locale = input._locale;
+    }
+
+    return ret;
+}
+
+createDuration.fn = Duration.prototype;
+createDuration.invalid = createInvalid$1;
+
+function parseIso (inp, sign) {
+    // We'd normally use ~~inp for this, but unfortunately it also
+    // converts floats to ints.
+    // inp may be undefined, so careful calling replace on it.
+    var res = inp && parseFloat(inp.replace(',', '.'));
+    // apply sign while we're at it
+    return (isNaN(res) ? 0 : res) * sign;
+}
+
+function positiveMomentsDifference(base, other) {
+    var res = {milliseconds: 0, months: 0};
+
+    res.months = other.month() - base.month() +
+        (other.year() - base.year()) * 12;
+    if (base.clone().add(res.months, 'M').isAfter(other)) {
+        --res.months;
+    }
+
+    res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
+
+    return res;
+}
+
+function momentsDifference(base, other) {
+    var res;
+    if (!(base.isValid() && other.isValid())) {
+        return {milliseconds: 0, months: 0};
+    }
+
+    other = cloneWithOffset(other, base);
+    if (base.isBefore(other)) {
+        res = positiveMomentsDifference(base, other);
+    } else {
+        res = positiveMomentsDifference(other, base);
+        res.milliseconds = -res.milliseconds;
+        res.months = -res.months;
+    }
+
+    return res;
+}
+
+// TODO: remove 'name' arg after deprecation is removed
+function createAdder(direction, name) {
+    return function (val, period) {
+        var dur, tmp;
+        //invert the arguments, but complain about it
+        if (period !== null && !isNaN(+period)) {
+            deprecateSimple(name, 'moment().' + name  + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
+            'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
+            tmp = val; val = period; period = tmp;
+        }
+
+        val = typeof val === 'string' ? +val : val;
+        dur = createDuration(val, period);
+        addSubtract(this, dur, direction);
+        return this;
+    };
+}
+
+function addSubtract (mom, duration, isAdding, updateOffset) {
+    var milliseconds = duration._milliseconds,
+        days = absRound(duration._days),
+        months = absRound(duration._months);
+
+    if (!mom.isValid()) {
+        // No op
+        return;
+    }
+
+    updateOffset = updateOffset == null ? true : updateOffset;
+
+    if (months) {
+        setMonth(mom, get(mom, 'Month') + months * isAdding);
+    }
+    if (days) {
+        set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
+    }
+    if (milliseconds) {
+        mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
+    }
+    if (updateOffset) {
+        hooks.updateOffset(mom, days || months);
+    }
+}
+
+var add      = createAdder(1, 'add');
+var subtract = createAdder(-1, 'subtract');
+
+function getCalendarFormat(myMoment, now) {
+    var diff = myMoment.diff(now, 'days', true);
+    return diff < -6 ? 'sameElse' :
+            diff < -1 ? 'lastWeek' :
+            diff < 0 ? 'lastDay' :
+            diff < 1 ? 'sameDay' :
+            diff < 2 ? 'nextDay' :
+            diff < 7 ? 'nextWeek' : 'sameElse';
+}
+
+function calendar$1 (time, formats) {
+    // We want to compare the start of today, vs this.
+    // Getting start-of-today depends on whether we're local/utc/offset or not.
+    var now = time || createLocal(),
+        sod = cloneWithOffset(now, this).startOf('day'),
+        format = hooks.calendarFormat(this, sod) || 'sameElse';
+
+    var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
+
+    return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
+}
+
+function clone () {
+    return new Moment(this);
+}
+
+function isAfter (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input);
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() > localInput.valueOf();
+    } else {
+        return localInput.valueOf() < this.clone().startOf(units).valueOf();
+    }
+}
+
+function isBefore (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input);
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() < localInput.valueOf();
+    } else {
+        return this.clone().endOf(units).valueOf() < localInput.valueOf();
+    }
+}
+
+function isBetween (from, to, units, inclusivity) {
+    inclusivity = inclusivity || '()';
+    return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
+        (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
+}
+
+function isSame (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input),
+        inputMs;
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(units || 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() === localInput.valueOf();
+    } else {
+        inputMs = localInput.valueOf();
+        return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
+    }
+}
+
+function isSameOrAfter (input, units) {
+    return this.isSame(input, units) || this.isAfter(input,units);
+}
+
+function isSameOrBefore (input, units) {
+    return this.isSame(input, units) || this.isBefore(input,units);
+}
+
+function diff (input, units, asFloat) {
+    var that,
+        zoneDelta,
+        delta, output;
+
+    if (!this.isValid()) {
+        return NaN;
+    }
+
+    that = cloneWithOffset(input, this);
+
+    if (!that.isValid()) {
+        return NaN;
+    }
+
+    zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
+
+    units = normalizeUnits(units);
+
+    switch (units) {
+        case 'year': output = monthDiff(this, that) / 12; break;
+        case 'month': output = monthDiff(this, that); break;
+        case 'quarter': output = monthDiff(this, that) / 3; break;
+        case 'second': output = (this - that) / 1e3; break; // 1000
+        case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
+        case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
+        case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
+        case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
+        default: output = this - that;
+    }
+
+    return asFloat ? output : absFloor(output);
+}
+
+function monthDiff (a, b) {
+    // difference in months
+    var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+        // b is in (anchor - 1 month, anchor + 1 month)
+        anchor = a.clone().add(wholeMonthDiff, 'months'),
+        anchor2, adjust;
+
+    if (b - anchor < 0) {
+        anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+        // linear across the month
+        adjust = (b - anchor) / (anchor - anchor2);
+    } else {
+        anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+        // linear across the month
+        adjust = (b - anchor) / (anchor2 - anchor);
+    }
+
+    //check for negative zero, return zero if negative zero
+    return -(wholeMonthDiff + adjust) || 0;
+}
+
+hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
+
+function toString () {
+    return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+}
+
+function toISOString(keepOffset) {
+    if (!this.isValid()) {
+        return null;
+    }
+    var utc = keepOffset !== true;
+    var m = utc ? this.clone().utc() : this;
+    if (m.year() < 0 || m.year() > 9999) {
+        return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
+    }
+    if (isFunction(Date.prototype.toISOString)) {
+        // native implementation is ~50x faster, use it when we can
+        if (utc) {
+            return this.toDate().toISOString();
+        } else {
+            return new Date(this._d.valueOf()).toISOString().replace('Z', formatMoment(m, 'Z'));
+        }
+    }
+    return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
+}
+
+/**
+ * Return a human readable representation of a moment that can
+ * also be evaluated to get a new moment which is the same
+ *
+ * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
+ */
+function inspect () {
+    if (!this.isValid()) {
+        return 'moment.invalid(/* ' + this._i + ' */)';
+    }
+    var func = 'moment';
+    var zone = '';
+    if (!this.isLocal()) {
+        func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
+        zone = 'Z';
+    }
+    var prefix = '[' + func + '("]';
+    var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
+    var datetime = '-MM-DD[T]HH:mm:ss.SSS';
+    var suffix = zone + '[")]';
+
+    return this.format(prefix + year + datetime + suffix);
+}
+
+function format (inputString) {
+    if (!inputString) {
+        inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
+    }
+    var output = formatMoment(this, inputString);
+    return this.localeData().postformat(output);
+}
+
+function from (time, withoutSuffix) {
+    if (this.isValid() &&
+            ((isMoment(time) && time.isValid()) ||
+             createLocal(time).isValid())) {
+        return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+    } else {
+        return this.localeData().invalidDate();
+    }
+}
+
+function fromNow (withoutSuffix) {
+    return this.from(createLocal(), withoutSuffix);
+}
+
+function to (time, withoutSuffix) {
+    if (this.isValid() &&
+            ((isMoment(time) && time.isValid()) ||
+             createLocal(time).isValid())) {
+        return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+    } else {
+        return this.localeData().invalidDate();
+    }
+}
+
+function toNow (withoutSuffix) {
+    return this.to(createLocal(), withoutSuffix);
+}
+
+// If passed a locale key, it will set the locale for this
+// instance.  Otherwise, it will return the locale configuration
+// variables for this instance.
+function locale (key) {
+    var newLocaleData;
+
+    if (key === undefined) {
+        return this._locale._abbr;
+    } else {
+        newLocaleData = getLocale(key);
+        if (newLocaleData != null) {
+            this._locale = newLocaleData;
+        }
+        return this;
+    }
+}
+
+var lang = deprecate(
+    'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+    function (key) {
+        if (key === undefined) {
+            return this.localeData();
+        } else {
+            return this.locale(key);
+        }
+    }
+);
+
+function localeData () {
+    return this._locale;
+}
+
+function startOf (units) {
+    units = normalizeUnits(units);
+    // the following switch intentionally omits break keywords
+    // to utilize falling through the cases.
+    switch (units) {
+        case 'year':
+            this.month(0);
+            /* falls through */
+        case 'quarter':
+        case 'month':
+            this.date(1);
+            /* falls through */
+        case 'week':
+        case 'isoWeek':
+        case 'day':
+        case 'date':
+            this.hours(0);
+            /* falls through */
+        case 'hour':
+            this.minutes(0);
+            /* falls through */
+        case 'minute':
+            this.seconds(0);
+            /* falls through */
+        case 'second':
+            this.milliseconds(0);
+    }
+
+    // weeks are a special case
+    if (units === 'week') {
+        this.weekday(0);
+    }
+    if (units === 'isoWeek') {
+        this.isoWeekday(1);
+    }
+
+    // quarters are also special
+    if (units === 'quarter') {
+        this.month(Math.floor(this.month() / 3) * 3);
+    }
+
+    return this;
+}
+
+function endOf (units) {
+    units = normalizeUnits(units);
+    if (units === undefined || units === 'millisecond') {
+        return this;
+    }
+
+    // 'date' is an alias for 'day', so it should be considered as such.
+    if (units === 'date') {
+        units = 'day';
+    }
+
+    return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
+}
+
+function valueOf () {
+    return this._d.valueOf() - ((this._offset || 0) * 60000);
+}
+
+function unix () {
+    return Math.floor(this.valueOf() / 1000);
+}
+
+function toDate () {
+    return new Date(this.valueOf());
+}
+
+function toArray () {
+    var m = this;
+    return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
+}
+
+function toObject () {
+    var m = this;
+    return {
+        years: m.year(),
+        months: m.month(),
+        date: m.date(),
+        hours: m.hours(),
+        minutes: m.minutes(),
+        seconds: m.seconds(),
+        milliseconds: m.milliseconds()
+    };
+}
+
+function toJSON () {
+    // new Date(NaN).toJSON() === null
+    return this.isValid() ? this.toISOString() : null;
+}
+
+function isValid$2 () {
+    return isValid(this);
+}
+
+function parsingFlags () {
+    return extend({}, getParsingFlags(this));
+}
+
+function invalidAt () {
+    return getParsingFlags(this).overflow;
+}
+
+function creationData() {
+    return {
+        input: this._i,
+        format: this._f,
+        locale: this._locale,
+        isUTC: this._isUTC,
+        strict: this._strict
+    };
+}
+
+// FORMATTING
+
+addFormatToken(0, ['gg', 2], 0, function () {
+    return this.weekYear() % 100;
+});
+
+addFormatToken(0, ['GG', 2], 0, function () {
+    return this.isoWeekYear() % 100;
+});
+
+function addWeekYearFormatToken (token, getter) {
+    addFormatToken(0, [token, token.length], 0, getter);
+}
+
+addWeekYearFormatToken('gggg',     'weekYear');
+addWeekYearFormatToken('ggggg',    'weekYear');
+addWeekYearFormatToken('GGGG',  'isoWeekYear');
+addWeekYearFormatToken('GGGGG', 'isoWeekYear');
+
+// ALIASES
+
+addUnitAlias('weekYear', 'gg');
+addUnitAlias('isoWeekYear', 'GG');
+
+// PRIORITY
+
+addUnitPriority('weekYear', 1);
+addUnitPriority('isoWeekYear', 1);
+
+
+// PARSING
+
+addRegexToken('G',      matchSigned);
+addRegexToken('g',      matchSigned);
+addRegexToken('GG',     match1to2, match2);
+addRegexToken('gg',     match1to2, match2);
+addRegexToken('GGGG',   match1to4, match4);
+addRegexToken('gggg',   match1to4, match4);
+addRegexToken('GGGGG',  match1to6, match6);
+addRegexToken('ggggg',  match1to6, match6);
+
+addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
+    week[token.substr(0, 2)] = toInt(input);
+});
+
+addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+    week[token] = hooks.parseTwoDigitYear(input);
+});
+
+// MOMENTS
+
+function getSetWeekYear (input) {
+    return getSetWeekYearHelper.call(this,
+            input,
+            this.week(),
+            this.weekday(),
+            this.localeData()._week.dow,
+            this.localeData()._week.doy);
+}
+
+function getSetISOWeekYear (input) {
+    return getSetWeekYearHelper.call(this,
+            input, this.isoWeek(), this.isoWeekday(), 1, 4);
+}
+
+function getISOWeeksInYear () {
+    return weeksInYear(this.year(), 1, 4);
+}
+
+function getWeeksInYear () {
+    var weekInfo = this.localeData()._week;
+    return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+}
+
+function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+    var weeksTarget;
+    if (input == null) {
+        return weekOfYear(this, dow, doy).year;
+    } else {
+        weeksTarget = weeksInYear(input, dow, doy);
+        if (week > weeksTarget) {
+            week = weeksTarget;
+        }
+        return setWeekAll.call(this, input, week, weekday, dow, doy);
+    }
+}
+
+function setWeekAll(weekYear, week, weekday, dow, doy) {
+    var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+        date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
+
+    this.year(date.getUTCFullYear());
+    this.month(date.getUTCMonth());
+    this.date(date.getUTCDate());
+    return this;
+}
+
+// FORMATTING
+
+addFormatToken('Q', 0, 'Qo', 'quarter');
+
+// ALIASES
+
+addUnitAlias('quarter', 'Q');
+
+// PRIORITY
+
+addUnitPriority('quarter', 7);
+
+// PARSING
+
+addRegexToken('Q', match1);
+addParseToken('Q', function (input, array) {
+    array[MONTH] = (toInt(input) - 1) * 3;
+});
+
+// MOMENTS
+
+function getSetQuarter (input) {
+    return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+}
+
+// FORMATTING
+
+addFormatToken('D', ['DD', 2], 'Do', 'date');
+
+// ALIASES
+
+addUnitAlias('date', 'D');
+
+// PRIOROITY
+addUnitPriority('date', 9);
+
+// PARSING
+
+addRegexToken('D',  match1to2);
+addRegexToken('DD', match1to2, match2);
+addRegexToken('Do', function (isStrict, locale) {
+    // TODO: Remove "ordinalParse" fallback in next major release.
+    return isStrict ?
+      (locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
+      locale._dayOfMonthOrdinalParseLenient;
+});
+
+addParseToken(['D', 'DD'], DATE);
+addParseToken('Do', function (input, array) {
+    array[DATE] = toInt(input.match(match1to2)[0]);
+});
+
+// MOMENTS
+
+var getSetDayOfMonth = makeGetSet('Date', true);
+
+// FORMATTING
+
+addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+
+// ALIASES
+
+addUnitAlias('dayOfYear', 'DDD');
+
+// PRIORITY
+addUnitPriority('dayOfYear', 4);
+
+// PARSING
+
+addRegexToken('DDD',  match1to3);
+addRegexToken('DDDD', match3);
+addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+    config._dayOfYear = toInt(input);
+});
+
+// HELPERS
+
+// MOMENTS
+
+function getSetDayOfYear (input) {
+    var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
+    return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
+}
+
+// FORMATTING
+
+addFormatToken('m', ['mm', 2], 0, 'minute');
+
+// ALIASES
+
+addUnitAlias('minute', 'm');
+
+// PRIORITY
+
+addUnitPriority('minute', 14);
+
+// PARSING
+
+addRegexToken('m',  match1to2);
+addRegexToken('mm', match1to2, match2);
+addParseToken(['m', 'mm'], MINUTE);
+
+// MOMENTS
+
+var getSetMinute = makeGetSet('Minutes', false);
+
+// FORMATTING
+
+addFormatToken('s', ['ss', 2], 0, 'second');
+
+// ALIASES
+
+addUnitAlias('second', 's');
+
+// PRIORITY
+
+addUnitPriority('second', 15);
+
+// PARSING
+
+addRegexToken('s',  match1to2);
+addRegexToken('ss', match1to2, match2);
+addParseToken(['s', 'ss'], SECOND);
+
+// MOMENTS
+
+var getSetSecond = makeGetSet('Seconds', false);
+
+// FORMATTING
+
+addFormatToken('S', 0, 0, function () {
+    return ~~(this.millisecond() / 100);
+});
+
+addFormatToken(0, ['SS', 2], 0, function () {
+    return ~~(this.millisecond() / 10);
+});
+
+addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+addFormatToken(0, ['SSSS', 4], 0, function () {
+    return this.millisecond() * 10;
+});
+addFormatToken(0, ['SSSSS', 5], 0, function () {
+    return this.millisecond() * 100;
+});
+addFormatToken(0, ['SSSSSS', 6], 0, function () {
+    return this.millisecond() * 1000;
+});
+addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+    return this.millisecond() * 10000;
+});
+addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+    return this.millisecond() * 100000;
+});
+addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+    return this.millisecond() * 1000000;
+});
+
+
+// ALIASES
+
+addUnitAlias('millisecond', 'ms');
+
+// PRIORITY
+
+addUnitPriority('millisecond', 16);
+
+// PARSING
+
+addRegexToken('S',    match1to3, match1);
+addRegexToken('SS',   match1to3, match2);
+addRegexToken('SSS',  match1to3, match3);
+
+var token;
+for (token = 'SSSS'; token.length <= 9; token += 'S') {
+    addRegexToken(token, matchUnsigned);
+}
+
+function parseMs(input, array) {
+    array[MILLISECOND] = toInt(('0.' + input) * 1000);
+}
+
+for (token = 'S'; token.length <= 9; token += 'S') {
+    addParseToken(token, parseMs);
+}
+// MOMENTS
+
+var getSetMillisecond = makeGetSet('Milliseconds', false);
+
+// FORMATTING
+
+addFormatToken('z',  0, 0, 'zoneAbbr');
+addFormatToken('zz', 0, 0, 'zoneName');
+
+// MOMENTS
+
+function getZoneAbbr () {
+    return this._isUTC ? 'UTC' : '';
+}
+
+function getZoneName () {
+    return this._isUTC ? 'Coordinated Universal Time' : '';
+}
+
+var proto = Moment.prototype;
+
+proto.add               = add;
+proto.calendar          = calendar$1;
+proto.clone             = clone;
+proto.diff              = diff;
+proto.endOf             = endOf;
+proto.format            = format;
+proto.from              = from;
+proto.fromNow           = fromNow;
+proto.to                = to;
+proto.toNow             = toNow;
+proto.get               = stringGet;
+proto.invalidAt         = invalidAt;
+proto.isAfter           = isAfter;
+proto.isBefore          = isBefore;
+proto.isBetween         = isBetween;
+proto.isSame            = isSame;
+proto.isSameOrAfter     = isSameOrAfter;
+proto.isSameOrBefore    = isSameOrBefore;
+proto.isValid           = isValid$2;
+proto.lang              = lang;
+proto.locale            = locale;
+proto.localeData        = localeData;
+proto.max               = prototypeMax;
+proto.min               = prototypeMin;
+proto.parsingFlags      = parsingFlags;
+proto.set               = stringSet;
+proto.startOf           = startOf;
+proto.subtract          = subtract;
+proto.toArray           = toArray;
+proto.toObject          = toObject;
+proto.toDate            = toDate;
+proto.toISOString       = toISOString;
+proto.inspect           = inspect;
+proto.toJSON            = toJSON;
+proto.toString          = toString;
+proto.unix              = unix;
+proto.valueOf           = valueOf;
+proto.creationData      = creationData;
+
+// Year
+proto.year       = getSetYear;
+proto.isLeapYear = getIsLeapYear;
+
+// Week Year
+proto.weekYear    = getSetWeekYear;
+proto.isoWeekYear = getSetISOWeekYear;
+
+// Quarter
+proto.quarter = proto.quarters = getSetQuarter;
+
+// Month
+proto.month       = getSetMonth;
+proto.daysInMonth = getDaysInMonth;
+
+// Week
+proto.week           = proto.weeks        = getSetWeek;
+proto.isoWeek        = proto.isoWeeks     = getSetISOWeek;
+proto.weeksInYear    = getWeeksInYear;
+proto.isoWeeksInYear = getISOWeeksInYear;
+
+// Day
+proto.date       = getSetDayOfMonth;
+proto.day        = proto.days             = getSetDayOfWeek;
+proto.weekday    = getSetLocaleDayOfWeek;
+proto.isoWeekday = getSetISODayOfWeek;
+proto.dayOfYear  = getSetDayOfYear;
+
+// Hour
+proto.hour = proto.hours = getSetHour;
+
+// Minute
+proto.minute = proto.minutes = getSetMinute;
+
+// Second
+proto.second = proto.seconds = getSetSecond;
+
+// Millisecond
+proto.millisecond = proto.milliseconds = getSetMillisecond;
+
+// Offset
+proto.utcOffset            = getSetOffset;
+proto.utc                  = setOffsetToUTC;
+proto.local                = setOffsetToLocal;
+proto.parseZone            = setOffsetToParsedOffset;
+proto.hasAlignedHourOffset = hasAlignedHourOffset;
+proto.isDST                = isDaylightSavingTime;
+proto.isLocal              = isLocal;
+proto.isUtcOffset          = isUtcOffset;
+proto.isUtc                = isUtc;
+proto.isUTC                = isUtc;
+
+// Timezone
+proto.zoneAbbr = getZoneAbbr;
+proto.zoneName = getZoneName;
+
+// Deprecations
+proto.dates  = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
+proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
+proto.years  = deprecate('years accessor is deprecated. Use year instead', getSetYear);
+proto.zone   = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
+proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
+
+function createUnix (input) {
+    return createLocal(input * 1000);
+}
+
+function createInZone () {
+    return createLocal.apply(null, arguments).parseZone();
+}
+
+function preParsePostFormat (string) {
+    return string;
+}
+
+var proto$1 = Locale.prototype;
+
+proto$1.calendar        = calendar;
+proto$1.longDateFormat  = longDateFormat;
+proto$1.invalidDate     = invalidDate;
+proto$1.ordinal         = ordinal;
+proto$1.preparse        = preParsePostFormat;
+proto$1.postformat      = preParsePostFormat;
+proto$1.relativeTime    = relativeTime;
+proto$1.pastFuture      = pastFuture;
+proto$1.set             = set;
+
+// Month
+proto$1.months            =        localeMonths;
+proto$1.monthsShort       =        localeMonthsShort;
+proto$1.monthsParse       =        localeMonthsParse;
+proto$1.monthsRegex       = monthsRegex;
+proto$1.monthsShortRegex  = monthsShortRegex;
+
+// Week
+proto$1.week = localeWeek;
+proto$1.firstDayOfYear = localeFirstDayOfYear;
+proto$1.firstDayOfWeek = localeFirstDayOfWeek;
+
+// Day of Week
+proto$1.weekdays       =        localeWeekdays;
+proto$1.weekdaysMin    =        localeWeekdaysMin;
+proto$1.weekdaysShort  =        localeWeekdaysShort;
+proto$1.weekdaysParse  =        localeWeekdaysParse;
+
+proto$1.weekdaysRegex       =        weekdaysRegex;
+proto$1.weekdaysShortRegex  =        weekdaysShortRegex;
+proto$1.weekdaysMinRegex    =        weekdaysMinRegex;
+
+// Hours
+proto$1.isPM = localeIsPM;
+proto$1.meridiem = localeMeridiem;
+
+function get$1 (format, index, field, setter) {
+    var locale = getLocale();
+    var utc = createUTC().set(setter, index);
+    return locale[field](utc, format);
+}
+
+function listMonthsImpl (format, index, field) {
+    if (isNumber(format)) {
+        index = format;
+        format = undefined;
+    }
+
+    format = format || '';
+
+    if (index != null) {
+        return get$1(format, index, field, 'month');
+    }
+
+    var i;
+    var out = [];
+    for (i = 0; i < 12; i++) {
+        out[i] = get$1(format, i, field, 'month');
+    }
+    return out;
+}
+
+// ()
+// (5)
+// (fmt, 5)
+// (fmt)
+// (true)
+// (true, 5)
+// (true, fmt, 5)
+// (true, fmt)
+function listWeekdaysImpl (localeSorted, format, index, field) {
+    if (typeof localeSorted === 'boolean') {
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+    } else {
+        format = localeSorted;
+        index = format;
+        localeSorted = false;
+
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+    }
+
+    var locale = getLocale(),
+        shift = localeSorted ? locale._week.dow : 0;
+
+    if (index != null) {
+        return get$1(format, (index + shift) % 7, field, 'day');
+    }
+
+    var i;
+    var out = [];
+    for (i = 0; i < 7; i++) {
+        out[i] = get$1(format, (i + shift) % 7, field, 'day');
+    }
+    return out;
+}
+
+function listMonths (format, index) {
+    return listMonthsImpl(format, index, 'months');
+}
+
+function listMonthsShort (format, index) {
+    return listMonthsImpl(format, index, 'monthsShort');
+}
+
+function listWeekdays (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
+}
+
+function listWeekdaysShort (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
+}
+
+function listWeekdaysMin (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
+}
+
+getSetGlobalLocale('en', {
+    dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (toInt(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    }
+});
+
+// Side effect imports
+hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
+hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
+
+var mathAbs = Math.abs;
+
+function abs () {
+    var data           = this._data;
+
+    this._milliseconds = mathAbs(this._milliseconds);
+    this._days         = mathAbs(this._days);
+    this._months       = mathAbs(this._months);
+
+    data.milliseconds  = mathAbs(data.milliseconds);
+    data.seconds       = mathAbs(data.seconds);
+    data.minutes       = mathAbs(data.minutes);
+    data.hours         = mathAbs(data.hours);
+    data.months        = mathAbs(data.months);
+    data.years         = mathAbs(data.years);
+
+    return this;
+}
+
+function addSubtract$1 (duration, input, value, direction) {
+    var other = createDuration(input, value);
+
+    duration._milliseconds += direction * other._milliseconds;
+    duration._days         += direction * other._days;
+    duration._months       += direction * other._months;
+
+    return duration._bubble();
+}
+
+// supports only 2.0-style add(1, 's') or add(duration)
+function add$1 (input, value) {
+    return addSubtract$1(this, input, value, 1);
+}
+
+// supports only 2.0-style subtract(1, 's') or subtract(duration)
+function subtract$1 (input, value) {
+    return addSubtract$1(this, input, value, -1);
+}
+
+function absCeil (number) {
+    if (number < 0) {
+        return Math.floor(number);
+    } else {
+        return Math.ceil(number);
+    }
+}
+
+function bubble () {
+    var milliseconds = this._milliseconds;
+    var days         = this._days;
+    var months       = this._months;
+    var data         = this._data;
+    var seconds, minutes, hours, years, monthsFromDays;
+
+    // if we have a mix of positive and negative values, bubble down first
+    // check: https://github.com/moment/moment/issues/2166
+    if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
+            (milliseconds <= 0 && days <= 0 && months <= 0))) {
+        milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+        days = 0;
+        months = 0;
+    }
+
+    // The following code bubbles up values, see the tests for
+    // examples of what that means.
+    data.milliseconds = milliseconds % 1000;
+
+    seconds           = absFloor(milliseconds / 1000);
+    data.seconds      = seconds % 60;
+
+    minutes           = absFloor(seconds / 60);
+    data.minutes      = minutes % 60;
+
+    hours             = absFloor(minutes / 60);
+    data.hours        = hours % 24;
+
+    days += absFloor(hours / 24);
+
+    // convert days to months
+    monthsFromDays = absFloor(daysToMonths(days));
+    months += monthsFromDays;
+    days -= absCeil(monthsToDays(monthsFromDays));
+
+    // 12 months -> 1 year
+    years = absFloor(months / 12);
+    months %= 12;
+
+    data.days   = days;
+    data.months = months;
+    data.years  = years;
+
+    return this;
+}
+
+function daysToMonths (days) {
+    // 400 years have 146097 days (taking into account leap year rules)
+    // 400 years have 12 months === 4800
+    return days * 4800 / 146097;
+}
+
+function monthsToDays (months) {
+    // the reverse of daysToMonths
+    return months * 146097 / 4800;
+}
+
+function as (units) {
+    if (!this.isValid()) {
+        return NaN;
+    }
+    var days;
+    var months;
+    var milliseconds = this._milliseconds;
+
+    units = normalizeUnits(units);
+
+    if (units === 'month' || units === 'year') {
+        days   = this._days   + milliseconds / 864e5;
+        months = this._months + daysToMonths(days);
+        return units === 'month' ? months : months / 12;
+    } else {
+        // handle milliseconds separately because of floating point math errors (issue #1867)
+        days = this._days + Math.round(monthsToDays(this._months));
+        switch (units) {
+            case 'week'   : return days / 7     + milliseconds / 6048e5;
+            case 'day'    : return days         + milliseconds / 864e5;
+            case 'hour'   : return days * 24    + milliseconds / 36e5;
+            case 'minute' : return days * 1440  + milliseconds / 6e4;
+            case 'second' : return days * 86400 + milliseconds / 1000;
+            // Math.floor prevents floating point math errors here
+            case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
+            default: throw new Error('Unknown unit ' + units);
+        }
+    }
+}
+
+// TODO: Use this.as('ms')?
+function valueOf$1 () {
+    if (!this.isValid()) {
+        return NaN;
+    }
+    return (
+        this._milliseconds +
+        this._days * 864e5 +
+        (this._months % 12) * 2592e6 +
+        toInt(this._months / 12) * 31536e6
+    );
+}
+
+function makeAs (alias) {
+    return function () {
+        return this.as(alias);
+    };
+}
+
+var asMilliseconds = makeAs('ms');
+var asSeconds      = makeAs('s');
+var asMinutes      = makeAs('m');
+var asHours        = makeAs('h');
+var asDays         = makeAs('d');
+var asWeeks        = makeAs('w');
+var asMonths       = makeAs('M');
+var asYears        = makeAs('y');
+
+function clone$1 () {
+    return createDuration(this);
+}
+
+function get$2 (units) {
+    units = normalizeUnits(units);
+    return this.isValid() ? this[units + 's']() : NaN;
+}
+
+function makeGetter(name) {
+    return function () {
+        return this.isValid() ? this._data[name] : NaN;
+    };
+}
+
+var milliseconds = makeGetter('milliseconds');
+var seconds      = makeGetter('seconds');
+var minutes      = makeGetter('minutes');
+var hours        = makeGetter('hours');
+var days         = makeGetter('days');
+var months       = makeGetter('months');
+var years        = makeGetter('years');
+
+function weeks () {
+    return absFloor(this.days() / 7);
+}
+
+var round = Math.round;
+var thresholds = {
+    ss: 44,         // a few seconds to seconds
+    s : 45,         // seconds to minute
+    m : 45,         // minutes to hour
+    h : 22,         // hours to day
+    d : 26,         // days to month
+    M : 11          // months to year
+};
+
+// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+    return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+}
+
+function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
+    var duration = createDuration(posNegDuration).abs();
+    var seconds  = round(duration.as('s'));
+    var minutes  = round(duration.as('m'));
+    var hours    = round(duration.as('h'));
+    var days     = round(duration.as('d'));
+    var months   = round(duration.as('M'));
+    var years    = round(duration.as('y'));
+
+    var a = seconds <= thresholds.ss && ['s', seconds]  ||
+            seconds < thresholds.s   && ['ss', seconds] ||
+            minutes <= 1             && ['m']           ||
+            minutes < thresholds.m   && ['mm', minutes] ||
+            hours   <= 1             && ['h']           ||
+            hours   < thresholds.h   && ['hh', hours]   ||
+            days    <= 1             && ['d']           ||
+            days    < thresholds.d   && ['dd', days]    ||
+            months  <= 1             && ['M']           ||
+            months  < thresholds.M   && ['MM', months]  ||
+            years   <= 1             && ['y']           || ['yy', years];
+
+    a[2] = withoutSuffix;
+    a[3] = +posNegDuration > 0;
+    a[4] = locale;
+    return substituteTimeAgo.apply(null, a);
+}
+
+// This function allows you to set the rounding function for relative time strings
+function getSetRelativeTimeRounding (roundingFunction) {
+    if (roundingFunction === undefined) {
+        return round;
+    }
+    if (typeof(roundingFunction) === 'function') {
+        round = roundingFunction;
+        return true;
+    }
+    return false;
+}
+
+// This function allows you to set a threshold for relative time strings
+function getSetRelativeTimeThreshold (threshold, limit) {
+    if (thresholds[threshold] === undefined) {
+        return false;
+    }
+    if (limit === undefined) {
+        return thresholds[threshold];
+    }
+    thresholds[threshold] = limit;
+    if (threshold === 's') {
+        thresholds.ss = limit - 1;
+    }
+    return true;
+}
+
+function humanize (withSuffix) {
+    if (!this.isValid()) {
+        return this.localeData().invalidDate();
+    }
+
+    var locale = this.localeData();
+    var output = relativeTime$1(this, !withSuffix, locale);
+
+    if (withSuffix) {
+        output = locale.pastFuture(+this, output);
+    }
+
+    return locale.postformat(output);
+}
+
+var abs$1 = Math.abs;
+
+function sign(x) {
+    return ((x > 0) - (x < 0)) || +x;
+}
+
+function toISOString$1() {
+    // for ISO strings we do not use the normal bubbling rules:
+    //  * milliseconds bubble up until they become hours
+    //  * days do not bubble at all
+    //  * months bubble up until they become years
+    // This is because there is no context-free conversion between hours and days
+    // (think of clock changes)
+    // and also not between days and months (28-31 days per month)
+    if (!this.isValid()) {
+        return this.localeData().invalidDate();
+    }
+
+    var seconds = abs$1(this._milliseconds) / 1000;
+    var days         = abs$1(this._days);
+    var months       = abs$1(this._months);
+    var minutes, hours, years;
+
+    // 3600 seconds -> 60 minutes -> 1 hour
+    minutes           = absFloor(seconds / 60);
+    hours             = absFloor(minutes / 60);
+    seconds %= 60;
+    minutes %= 60;
+
+    // 12 months -> 1 year
+    years  = absFloor(months / 12);
+    months %= 12;
+
+
+    // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+    var Y = years;
+    var M = months;
+    var D = days;
+    var h = hours;
+    var m = minutes;
+    var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
+    var total = this.asSeconds();
+
+    if (!total) {
+        // this is the same as C#'s (Noda) and python (isodate)...
+        // but not other JS (goog.date)
+        return 'P0D';
+    }
+
+    var totalSign = total < 0 ? '-' : '';
+    var ymSign = sign(this._months) !== sign(total) ? '-' : '';
+    var daysSign = sign(this._days) !== sign(total) ? '-' : '';
+    var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
+
+    return totalSign + 'P' +
+        (Y ? ymSign + Y + 'Y' : '') +
+        (M ? ymSign + M + 'M' : '') +
+        (D ? daysSign + D + 'D' : '') +
+        ((h || m || s) ? 'T' : '') +
+        (h ? hmsSign + h + 'H' : '') +
+        (m ? hmsSign + m + 'M' : '') +
+        (s ? hmsSign + s + 'S' : '');
+}
+
+var proto$2 = Duration.prototype;
+
+proto$2.isValid        = isValid$1;
+proto$2.abs            = abs;
+proto$2.add            = add$1;
+proto$2.subtract       = subtract$1;
+proto$2.as             = as;
+proto$2.asMilliseconds = asMilliseconds;
+proto$2.asSeconds      = asSeconds;
+proto$2.asMinutes      = asMinutes;
+proto$2.asHours        = asHours;
+proto$2.asDays         = asDays;
+proto$2.asWeeks        = asWeeks;
+proto$2.asMonths       = asMonths;
+proto$2.asYears        = asYears;
+proto$2.valueOf        = valueOf$1;
+proto$2._bubble        = bubble;
+proto$2.clone          = clone$1;
+proto$2.get            = get$2;
+proto$2.milliseconds   = milliseconds;
+proto$2.seconds        = seconds;
+proto$2.minutes        = minutes;
+proto$2.hours          = hours;
+proto$2.days           = days;
+proto$2.weeks          = weeks;
+proto$2.months         = months;
+proto$2.years          = years;
+proto$2.humanize       = humanize;
+proto$2.toISOString    = toISOString$1;
+proto$2.toString       = toISOString$1;
+proto$2.toJSON         = toISOString$1;
+proto$2.locale         = locale;
+proto$2.localeData     = localeData;
+
+// Deprecations
+proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
+proto$2.lang = lang;
+
+// Side effect imports
+
+// FORMATTING
+
+addFormatToken('X', 0, 0, 'unix');
+addFormatToken('x', 0, 0, 'valueOf');
+
+// PARSING
+
+addRegexToken('x', matchSigned);
+addRegexToken('X', matchTimestamp);
+addParseToken('X', function (input, array, config) {
+    config._d = new Date(parseFloat(input, 10) * 1000);
+});
+addParseToken('x', function (input, array, config) {
+    config._d = new Date(toInt(input));
+});
+
+// Side effect imports
+
+
+hooks.version = '2.20.1';
+
+setHookCallback(createLocal);
+
+hooks.fn                    = proto;
+hooks.min                   = min;
+hooks.max                   = max;
+hooks.now                   = now;
+hooks.utc                   = createUTC;
+hooks.unix                  = createUnix;
+hooks.months                = listMonths;
+hooks.isDate                = isDate;
+hooks.locale                = getSetGlobalLocale;
+hooks.invalid               = createInvalid;
+hooks.duration              = createDuration;
+hooks.isMoment              = isMoment;
+hooks.weekdays              = listWeekdays;
+hooks.parseZone             = createInZone;
+hooks.localeData            = getLocale;
+hooks.isDuration            = isDuration;
+hooks.monthsShort           = listMonthsShort;
+hooks.weekdaysMin           = listWeekdaysMin;
+hooks.defineLocale          = defineLocale;
+hooks.updateLocale          = updateLocale;
+hooks.locales               = listLocales;
+hooks.weekdaysShort         = listWeekdaysShort;
+hooks.normalizeUnits        = normalizeUnits;
+hooks.relativeTimeRounding  = getSetRelativeTimeRounding;
+hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
+hooks.calendarFormat        = getCalendarFormat;
+hooks.prototype             = proto;
+
+// currently HTML5 input type only supports 24-hour formats
+hooks.HTML5_FMT = {
+    DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm',             // 
+    DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss',  // 
+    DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS',   // 
+    DATE: 'YYYY-MM-DD',                             // 
+    TIME: 'HH:mm',                                  // 
+    TIME_SECONDS: 'HH:mm:ss',                       // 
+    TIME_MS: 'HH:mm:ss.SSS',                        // 
+    WEEK: 'YYYY-[W]WW',                             // 
+    MONTH: 'YYYY-MM'                                // 
+};
+
+return hooks;
+
+})));
+
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)(module)))
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.setLogLevel = exports.logger = exports.LEVELS = undefined;
+
+var _moment = __webpack_require__(0);
+
+var _moment2 = _interopRequireDefault(_moment);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var LEVELS = exports.LEVELS = {
+  debug: 1,
+  info: 2,
+  warn: 3,
+  error: 4,
+  fatal: 5
+};
+
+var logger = exports.logger = {
+  debug: function debug() {},
+  info: function info() {},
+  warn: function warn() {},
+  error: function error() {},
+  fatal: function fatal() {}
+};
+
+var setLogLevel = exports.setLogLevel = function setLogLevel(level) {
+  logger.debug = function () {};
+  logger.info = function () {};
+  logger.warn = function () {};
+  logger.error = function () {};
+  logger.fatal = function () {};
+  if (level <= LEVELS.fatal) {
+    logger.fatal = console.log.bind(console, '\x1b[35m', format('FATAL'));
+  }
+  if (level <= LEVELS.error) {
+    logger.error = console.log.bind(console, '\x1b[31m', format('ERROR'));
+  }
+  if (level <= LEVELS.warn) {
+    logger.warn = console.log.bind(console, '\x1B[33m', format('WARN'));
+  }
+  if (level <= LEVELS.info) {
+    logger.info = console.log.bind(console, '\x1b[34m', format('INFO'));
+  }
+  if (level <= LEVELS.debug) {
+    logger.debug = console.log.bind(console, '\x1b[32m', format('DEBUG'));
+  }
+};
+
+var format = function format(level) {
+  var time = (0, _moment2.default)().format('HH:mm:ss.SSS');
+  return time + ' : ' + level + ' : ';
+};
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports) {
+
+/**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(document.body.children);
+ * // => false
+ *
+ * _.isArray('abc');
+ * // => false
+ *
+ * _.isArray(_.noop);
+ * // => false
+ */
+var isArray = Array.isArray;
+
+module.exports = isArray;
+
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports) {
+
+module.exports = function(module) {
+	if(!module.webpackPolyfill) {
+		module.deprecate = function() {};
+		module.paths = [];
+		// module.parent = undefined by default
+		if(!module.children) module.children = [];
+		Object.defineProperty(module, "loaded", {
+			enumerable: true,
+			get: function() {
+				return module.l;
+			}
+		});
+		Object.defineProperty(module, "id", {
+			enumerable: true,
+			get: function() {
+				return module.i;
+			}
+		});
+		module.webpackPolyfill = 1;
+	}
+	return module;
+};
+
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* global window */
+
+var lodash;
+
+if (true) {
+  try {
+    lodash = __webpack_require__(19);
+  } catch (e) {}
+}
+
+if (!lodash) {
+  lodash = window._;
+}
+
+module.exports = lodash;
+
+
+/***/ }),
+/* 5 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var freeGlobal = __webpack_require__(181);
+
+/** Detect free variable `self`. */
+var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+/** Used as a reference to the global object. */
+var root = freeGlobal || freeSelf || Function('return this')();
+
+module.exports = root;
+
+
+/***/ }),
+/* 6 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var _d = __webpack_require__(159);
+
+var _d2 = _interopRequireDefault(_d);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/*
+ D3 Text Wrap
+ By Vijith Assar
+ http://www.vijithassar.com
+ http://www.github.com/vijithassar
+ @vijithassar
+
+ Detailed instructions at http://www.github.com/vijithassar/d3textwrap
+ */
+
+(function () {
+  // set this variable to a string value to always force a particular
+  // wrap method for development purposes, for example to check tspan
+  // rendering using a foreignobject-enabled browser. set to 'tspan' to
+  // use tspans and 'foreignobject' to use foreignobject
+  var forceWrapMethod = false; // by default no wrap method is forced
+  forceWrapMethod = 'tspans'; // uncomment this statement to force tspans
+  // force_wrap_method = 'foreignobjects'; // uncomment this statement to force foreignobjects
+
+  // exit immediately if something in this location
+  // has already been defined; the plugin will defer to whatever
+  // else you're doing in your code
+  if (_d2.default.selection.prototype.textwrap) {
+    return false;
+  }
+
+  // double check the force_wrap_method flag
+  // and reset if someone screwed up the above
+  // settings
+  if (typeof forceWrapMethod === 'undefined') {
+    forceWrapMethod = false;
+  }
+
+  // create the plugin method twice, both for regular use
+  // and again for use inside the enter() selection
+  _d2.default.selection.prototype.textwrap = _d2.default.selection.enter.prototype.textwrap = function (bounds, padding) {
+    // default value of padding is zero if it's undefined
+    padding = parseInt(padding) || 0;
+
+    // save callee into a variable so we can continue to refer to it
+    // as the function scope changes
+    var selection = this;
+
+    // create a variable to store desired return values in
+    var returnValue;
+
+    // extract wrap boundaries from any d3-selected rect and return them
+    // in a format that matches the simpler object argument option
+    var extractBounds = function extractBounds(bounds) {
+      // discard the nested array wrappers added by d3
+      var boundingRect = bounds[0][0];
+      // sanitize the svg element name so we can test against it
+      var elementType = boundingRect.tagName.toString();
+      // if it's not a rect, exit
+      if (elementType !== 'rect') {
+        return false;
+        // if it's a rect, proceed to extracting the position attributes
+      } else {
+        var boundsExtracted = {};
+        boundsExtracted.x = _d2.default.select(boundingRect).attr('x') || 0;
+        boundsExtracted.y = _d2.default.select(boundingRect).attr('y') || 0;
+        boundsExtracted.width = _d2.default.select(boundingRect).attr('width') || 0;
+        boundsExtracted.height = _d2.default.select(boundingRect).attr('height') || 0;
+        // also pass along the getter function
+        boundsExtracted.attr = bounds.attr;
+      }
+      return boundsExtracted;
+    };
+
+    // double check the input argument for the wrapping
+    // boundaries to make sure it actually contains all
+    // the information we'll need in order to wrap successfully
+    var verifyBounds = function verifyBounds(bounds) {
+      // quickly add a simple getter method so you can use either
+      // bounds.x or bounds.attr('x') as your notation,
+      // the latter being a common convention among D3
+      // developers
+      if (!bounds.attr) {
+        bounds.attr = function (property) {
+          if (this[property]) {
+            return this[property];
+          }
+        };
+      }
+      // if it's an associative array, make sure it has all the
+      // necessary properties represented directly
+      if ((typeof bounds === 'undefined' ? 'undefined' : _typeof(bounds)) === 'object' && typeof bounds.x !== 'undefined' && typeof bounds.y !== 'undefined' && typeof bounds.width !== 'undefined' && typeof bounds.height !== 'undefined'
+      // if that's the case, then the bounds are fine
+      ) {
+          // return the lightly modified bounds
+          return bounds;
+          // if it's a numerically indexed array, assume it's a
+          // d3-selected rect and try to extract the positions
+        } else if (
+      // first try to make sure it's an array using Array.isArray
+      typeof Array.isArray === 'function' && Array.isArray(bounds) ||
+      // but since Array.isArray isn't always supported, fall
+      // back to casting to the object to string when it's not
+      Object.prototype.toString.call(bounds) === '[object Array]') {
+        // once you're sure it's an array, extract the boundaries
+        // from the rect
+        var extractedBounds = extractBounds(bounds);
+        return extractedBounds;
+      } else {
+        // but if the bounds are neither an object nor a numerical
+        // array, then the bounds argument is invalid and you'll
+        // need to fix it
+        return false;
+      }
+    };
+
+    var applyPadding = function applyPadding(bounds, padding) {
+      var paddedBounds = bounds;
+      if (padding !== 0) {
+        paddedBounds.x = parseInt(paddedBounds.x) + padding;
+        paddedBounds.y = parseInt(paddedBounds.y) + padding;
+        paddedBounds.width -= padding * 2;
+        paddedBounds.height -= padding * 2;
+      }
+      return paddedBounds;
+    };
+
+    // verify bounds
+    var verifiedBounds = verifyBounds(bounds);
+
+    // modify bounds if a padding value is provided
+    if (padding) {
+      verifiedBounds = applyPadding(verifiedBounds, padding);
+    }
+
+    // check that we have the necessary conditions for this function to operate properly
+    if (
+    // selection it's operating on cannot be not empty
+    selection.length === 0 ||
+    // d3 must be available
+    !_d2.default ||
+    // desired wrapping bounds must be provided as an input argument
+    !bounds ||
+    // input bounds must validate
+    !verifiedBounds) {
+      // try to return the calling selection if possible
+      // so as not to interfere with methods downstream in the
+      // chain
+      if (selection) {
+        return selection;
+        // if all else fails, just return false. if you hit this point then you're
+        // almost certainly trying to call the textwrap() method on something that
+        // doesn't make sense!
+      } else {
+        return false;
+      }
+      // if we've validated everything then we can finally proceed
+      // to the meat of this operation
+    } else {
+      // reassign the verified bounds as the set we want
+      // to work with from here on; this ensures that we're
+      // using the same data structure for our bounds regardless
+      // of whether the input argument was a simple object or
+      // a d3 selection
+      bounds = verifiedBounds;
+
+      // wrap using html and foreignObjects if they are supported
+      var wrapWithForeignobjects = function wrapWithForeignobjects(item) {
+        // establish variables to quickly reference target nodes later
+        var parent = _d2.default.select(item[0].parentNode);
+        var textNode = parent.select('text');
+        var styledLineHeight = textNode.style('line-height');
+        // extract our desired content from the single text element
+        var textToWrap = textNode.text();
+        // remove the text node and replace with a foreign object
+        textNode.remove();
+        var foreignObject = parent.append('foreignObject');
+        // add foreign object and set dimensions, position, etc
+        foreignObject.attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility').attr('x', bounds.x).attr('y', bounds.y).attr('width', bounds.width).attr('height', bounds.height);
+        // insert an HTML div
+        var wrapDiv = foreignObject.append('xhtml:div')
+        // this class is currently hardcoded
+        // probably not necessary but easy to
+        // override using .classed() and for now
+        // it's nice to avoid a litany of input
+        // arguments
+        .attr('class', 'wrapped');
+        // set div to same dimensions as foreign object
+        wrapDiv.style('height', bounds.height).style('width', bounds.width)
+        // insert text content
+        .html(textToWrap);
+        if (styledLineHeight) {
+          wrapDiv.style('line-height', styledLineHeight);
+        }
+        returnValue = parent.select('foreignObject');
+      };
+
+      // wrap with tspans if foreignObject is undefined
+      var wrapWithTspans = function wrapWithTspans(item) {
+        // operate on the first text item in the selection
+        var textNode = item[0];
+        var parent = textNode.parentNode;
+        var textNodeSelected = _d2.default.select(textNode);
+        // measure initial size of the text node as rendered
+        var textNodeHeight = textNode.getBBox().height;
+        var textNodeWidth = textNode.getBBox().width;
+        // figure out the line height, either from rendered height
+        // of the font or attached styling
+        var lineHeight;
+        var renderedLineHeight = textNodeHeight;
+        var styledLineHeight = textNodeSelected.style('line-height');
+        if (styledLineHeight && parseInt(styledLineHeight)) {
+          lineHeight = parseInt(styledLineHeight.replace('px', ''));
+        } else {
+          lineHeight = renderedLineHeight;
+        }
+        // only fire the rest of this if the text content
+        // overflows the desired dimensions
+        if (textNodeWidth > bounds.width) {
+          // store whatever is inside the text node
+          // in a variable and then zero out the
+          // initial content; we'll reinsert in a moment
+          // using tspan elements.
+          var textToWrap = textNodeSelected.text();
+          textNodeSelected.text('');
+          if (textToWrap) {
+            // keep track of whether we are splitting by spaces
+            // so we know whether to reinsert those spaces later
+            var breakDelimiter;
+            // split at spaces to create an array of individual words
+            var textToWrapArray;
+            if (textToWrap.indexOf(' ') !== -1) {
+              breakDelimiter = ' ';
+              textToWrapArray = textToWrap.split(' ');
+            } else {
+              // if there are no spaces, figure out the split
+              // points by comparing rendered text width against
+              // bounds and translating that into character position
+              // cuts
+              breakDelimiter = '';
+              var stringLength = textToWrap.length;
+              var numberOfSubstrings = Math.ceil(textNodeWidth / bounds.width);
+              var spliceInterval = Math.floor(stringLength / numberOfSubstrings);
+              if (!(spliceInterval * numberOfSubstrings >= stringLength)) {
+                numberOfSubstrings++;
+              }
+              textToWrapArray = [];
+              var substring;
+              var startPosition;
+              for (var i = 0; i < numberOfSubstrings; i++) {
+                startPosition = i * spliceInterval;
+                substring = textToWrap.substr(startPosition, spliceInterval);
+                textToWrapArray.push(substring);
+              }
+            }
+
+            // new array where we'll store the words re-assembled into
+            // substrings that have been tested against the desired
+            // maximum wrapping width
+            var substrings = [];
+            // computed text length is arguably incorrectly reported for
+            // all tspans after the first one, in that they will include
+            // the width of previous separate tspans. to compensate we need
+            // to manually track the computed text length of all those
+            // previous tspans and substrings, and then use that to offset
+            // the miscalculation. this then gives us the actual correct
+            // position we want to use in rendering the text in the SVG.
+            var totalOffset = 0;
+            // object for storing the results of text length computations later
+            var temp = {};
+            // loop through the words and test the computed text length
+            // of the string against the maximum desired wrapping width
+            for (i = 0; i < textToWrapArray.length; i++) {
+              var word = textToWrapArray[i];
+              var previousString = textNodeSelected.text();
+              var previousWidth = textNode.getComputedTextLength();
+              // initialize the current word as the first word
+              // or append to the previous string if one exists
+              var newstring;
+              if (previousString) {
+                newstring = previousString + breakDelimiter + word;
+              } else {
+                newstring = word;
+              }
+              // add the newest substring back to the text node and
+              // measure the length
+              textNodeSelected.text(newstring);
+              var newWidth = textNode.getComputedTextLength();
+              // adjust the length by the offset we've tracked
+              // due to the misreported length discussed above
+
+              // if our latest version of the string is too
+              // big for the bounds, use the previous
+              // version of the string (without the newest word
+              // added) and use the latest word to restart the
+              // process with a new tspan
+              if (newWidth > bounds.width) {
+                if (previousString && previousString !== '') {
+                  totalOffset = totalOffset + previousWidth;
+                  temp = { string: previousString, width: previousWidth, offset: totalOffset };
+                  substrings.push(temp);
+                  textNodeSelected.text('');
+                  textNodeSelected.text(word);
+                  // Handle case where there is just one more word to be wrapped
+                  if (i === textToWrapArray.length - 1) {
+                    newstring = word;
+                    textNodeSelected.text(newstring);
+                    newWidth = textNode.getComputedTextLength();
+                  }
+                }
+              }
+              // if we're up to the last word in the array,
+              // get the computed length as is without
+              // appending anything further to it
+              if (i === textToWrapArray.length - 1) {
+                textNodeSelected.text('');
+                var finalString = newstring;
+                if (finalString && finalString !== '') {
+                  if (newWidth - totalOffset > 0) {
+                    newWidth = newWidth - totalOffset;
+                  }
+                  temp = { string: finalString, width: newWidth, offset: totalOffset };
+                  substrings.push(temp);
+                }
+              }
+            }
+
+            // append each substring as a tspan
+            var currentTspan;
+            // var tspanCount
+            // double check that the text content has been removed
+            // before we start appending tspans
+            textNodeSelected.text('');
+            for (i = 0; i < substrings.length; i++) {
+              substring = substrings[i].string;
+              // only append if we're sure it won't make the tspans
+              // overflow the bounds.
+              if (i * lineHeight < bounds.height - lineHeight * 1.5) {
+                currentTspan = textNodeSelected.append('tspan').text(substring);
+                // vertical shift to all tspans after the first one
+                currentTspan.attr('dy', function (d) {
+                  if (i > 0) {
+                    return lineHeight;
+                  }
+                });
+                // shift left from default position, which
+                // is probably based on the full length of the
+                // text string until we make this adjustment
+                currentTspan.attr('x', function () {
+                  var xOffset = bounds.x;
+                  if (padding) {
+                    xOffset += padding;
+                  }
+                  return xOffset;
+                });
+              }
+            }
+          }
+        }
+        // position the overall text node, whether wrapped or not
+        textNodeSelected.attr('y', function () {
+          var yOffset = bounds.y;
+          // shift by line-height to move the baseline into
+          // the bounds – otherwise the text baseline would be
+          // at the top of the bounds
+          if (lineHeight) {
+            yOffset += lineHeight;
+          }
+          // shift by padding, if it's there
+          if (padding) {
+            yOffset += padding;
+          }
+          return yOffset;
+        });
+        // shift to the right by the padding value
+        textNodeSelected.attr('x', function () {
+          var xOffset = bounds.x;
+          if (padding) {
+            xOffset += padding;
+          }
+          return xOffset;
+        });
+
+        // assign our modified text node with tspans
+        // to the return value
+        returnValue = _d2.default.select(parent).selectAll('text');
+      };
+
+      // variable used to hold the functions that let us
+      // switch between the wrap methods
+      var wrapMethod;
+
+      // if a wrap method if being forced, assign that
+      // function
+      if (forceWrapMethod) {
+        if (forceWrapMethod === 'foreignobjects') {
+          wrapMethod = wrapWithForeignobjects;
+        } else if (forceWrapMethod === 'tspans') {
+          wrapMethod = wrapWithTspans;
+        }
+      }
+
+      // if no wrap method is being forced, then instead
+      // test for browser support of foreignobject and
+      // use whichever wrap method makes sense accordingly
+      if (!forceWrapMethod) {
+        if (typeof SVGForeignObjectElement !== 'undefined') {
+          wrapMethod = wrapWithForeignobjects;
+        } else {
+          wrapMethod = wrapWithTspans;
+        }
+      }
+
+      // run the desired wrap function for each item
+      // in the d3 selection that called .textwrap()
+      for (var i = 0; i < selection.length; i++) {
+        var item = selection[i];
+        wrapMethod(item);
+      }
+
+      // return the modified nodes so we can chain other
+      // methods to them.
+      return returnValue;
+    }
+  };
+})();
+
+exports.default = _d2.default;
+
+/***/ }),
+/* 7 */
+/***/ (function(module, exports) {
+
+// shim for using process in browser
+var process = module.exports = {};
+
+// cached from whatever global is present so that test runners that stub it
+// don't break things.  But we need to wrap it in a try catch in case it is
+// wrapped in strict mode code which doesn't define any globals.  It's inside a
+// function because try/catches deoptimize in certain engines.
+
+var cachedSetTimeout;
+var cachedClearTimeout;
+
+function defaultSetTimout() {
+    throw new Error('setTimeout has not been defined');
+}
+function defaultClearTimeout () {
+    throw new Error('clearTimeout has not been defined');
+}
+(function () {
+    try {
+        if (typeof setTimeout === 'function') {
+            cachedSetTimeout = setTimeout;
+        } else {
+            cachedSetTimeout = defaultSetTimout;
+        }
+    } catch (e) {
+        cachedSetTimeout = defaultSetTimout;
+    }
+    try {
+        if (typeof clearTimeout === 'function') {
+            cachedClearTimeout = clearTimeout;
+        } else {
+            cachedClearTimeout = defaultClearTimeout;
+        }
+    } catch (e) {
+        cachedClearTimeout = defaultClearTimeout;
+    }
+} ())
+function runTimeout(fun) {
+    if (cachedSetTimeout === setTimeout) {
+        //normal enviroments in sane situations
+        return setTimeout(fun, 0);
+    }
+    // if setTimeout wasn't available but was latter defined
+    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
+        cachedSetTimeout = setTimeout;
+        return setTimeout(fun, 0);
+    }
+    try {
+        // when when somebody has screwed with setTimeout but no I.E. maddness
+        return cachedSetTimeout(fun, 0);
+    } catch(e){
+        try {
+            // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
+            return cachedSetTimeout.call(null, fun, 0);
+        } catch(e){
+            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
+            return cachedSetTimeout.call(this, fun, 0);
+        }
+    }
+
+
+}
+function runClearTimeout(marker) {
+    if (cachedClearTimeout === clearTimeout) {
+        //normal enviroments in sane situations
+        return clearTimeout(marker);
+    }
+    // if clearTimeout wasn't available but was latter defined
+    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
+        cachedClearTimeout = clearTimeout;
+        return clearTimeout(marker);
+    }
+    try {
+        // when when somebody has screwed with setTimeout but no I.E. maddness
+        return cachedClearTimeout(marker);
+    } catch (e){
+        try {
+            // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
+            return cachedClearTimeout.call(null, marker);
+        } catch (e){
+            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
+            // Some versions of I.E. have different rules for clearTimeout vs setTimeout
+            return cachedClearTimeout.call(this, marker);
+        }
+    }
+
+
+
+}
+var queue = [];
+var draining = false;
+var currentQueue;
+var queueIndex = -1;
+
+function cleanUpNextTick() {
+    if (!draining || !currentQueue) {
+        return;
+    }
+    draining = false;
+    if (currentQueue.length) {
+        queue = currentQueue.concat(queue);
+    } else {
+        queueIndex = -1;
+    }
+    if (queue.length) {
+        drainQueue();
+    }
+}
+
+function drainQueue() {
+    if (draining) {
+        return;
+    }
+    var timeout = runTimeout(cleanUpNextTick);
+    draining = true;
+
+    var len = queue.length;
+    while(len) {
+        currentQueue = queue;
+        queue = [];
+        while (++queueIndex < len) {
+            if (currentQueue) {
+                currentQueue[queueIndex].run();
+            }
+        }
+        queueIndex = -1;
+        len = queue.length;
+    }
+    currentQueue = null;
+    draining = false;
+    runClearTimeout(timeout);
+}
+
+process.nextTick = function (fun) {
+    var args = new Array(arguments.length - 1);
+    if (arguments.length > 1) {
+        for (var i = 1; i < arguments.length; i++) {
+            args[i - 1] = arguments[i];
+        }
+    }
+    queue.push(new Item(fun, args));
+    if (queue.length === 1 && !draining) {
+        runTimeout(drainQueue);
+    }
+};
+
+// v8 likes predictible objects
+function Item(fun, array) {
+    this.fun = fun;
+    this.array = array;
+}
+Item.prototype.run = function () {
+    this.fun.apply(null, this.array);
+};
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+process.prependListener = noop;
+process.prependOnceListener = noop;
+
+process.listeners = function (name) { return [] }
+
+process.binding = function (name) {
+    throw new Error('process.binding is not supported');
+};
+
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+    throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+
+/***/ }),
+/* 8 */
+/***/ (function(module, exports) {
+
+
+
+/***/ }),
+/* 9 */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+  // if the path tries to go above the root, `up` ends up > 0
+  var up = 0;
+  for (var i = parts.length - 1; i >= 0; i--) {
+    var last = parts[i];
+    if (last === '.') {
+      parts.splice(i, 1);
+    } else if (last === '..') {
+      parts.splice(i, 1);
+      up++;
+    } else if (up) {
+      parts.splice(i, 1);
+      up--;
+    }
+  }
+
+  // if the path is allowed to go above the root, restore leading ..s
+  if (allowAboveRoot) {
+    for (; up--; up) {
+      parts.unshift('..');
+    }
+  }
+
+  return parts;
+}
+
+// Split a filename into [root, dir, basename, ext], unix version
+// 'root' is just a slash, or nothing.
+var splitPathRe =
+    /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+var splitPath = function(filename) {
+  return splitPathRe.exec(filename).slice(1);
+};
+
+// path.resolve([from ...], to)
+// posix version
+exports.resolve = function() {
+  var resolvedPath = '',
+      resolvedAbsolute = false;
+
+  for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+    var path = (i >= 0) ? arguments[i] : process.cwd();
+
+    // Skip empty and invalid entries
+    if (typeof path !== 'string') {
+      throw new TypeError('Arguments to path.resolve must be strings');
+    } else if (!path) {
+      continue;
+    }
+
+    resolvedPath = path + '/' + resolvedPath;
+    resolvedAbsolute = path.charAt(0) === '/';
+  }
+
+  // At this point the path should be resolved to a full absolute path, but
+  // handle relative paths to be safe (might happen when process.cwd() fails)
+
+  // Normalize the path
+  resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+    return !!p;
+  }), !resolvedAbsolute).join('/');
+
+  return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+};
+
+// path.normalize(path)
+// posix version
+exports.normalize = function(path) {
+  var isAbsolute = exports.isAbsolute(path),
+      trailingSlash = substr(path, -1) === '/';
+
+  // Normalize the path
+  path = normalizeArray(filter(path.split('/'), function(p) {
+    return !!p;
+  }), !isAbsolute).join('/');
+
+  if (!path && !isAbsolute) {
+    path = '.';
+  }
+  if (path && trailingSlash) {
+    path += '/';
+  }
+
+  return (isAbsolute ? '/' : '') + path;
+};
+
+// posix version
+exports.isAbsolute = function(path) {
+  return path.charAt(0) === '/';
+};
+
+// posix version
+exports.join = function() {
+  var paths = Array.prototype.slice.call(arguments, 0);
+  return exports.normalize(filter(paths, function(p, index) {
+    if (typeof p !== 'string') {
+      throw new TypeError('Arguments to path.join must be strings');
+    }
+    return p;
+  }).join('/'));
+};
+
+
+// path.relative(from, to)
+// posix version
+exports.relative = function(from, to) {
+  from = exports.resolve(from).substr(1);
+  to = exports.resolve(to).substr(1);
+
+  function trim(arr) {
+    var start = 0;
+    for (; start < arr.length; start++) {
+      if (arr[start] !== '') break;
+    }
+
+    var end = arr.length - 1;
+    for (; end >= 0; end--) {
+      if (arr[end] !== '') break;
+    }
+
+    if (start > end) return [];
+    return arr.slice(start, end - start + 1);
+  }
+
+  var fromParts = trim(from.split('/'));
+  var toParts = trim(to.split('/'));
+
+  var length = Math.min(fromParts.length, toParts.length);
+  var samePartsLength = length;
+  for (var i = 0; i < length; i++) {
+    if (fromParts[i] !== toParts[i]) {
+      samePartsLength = i;
+      break;
+    }
+  }
+
+  var outputParts = [];
+  for (var i = samePartsLength; i < fromParts.length; i++) {
+    outputParts.push('..');
+  }
+
+  outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+  return outputParts.join('/');
+};
+
+exports.sep = '/';
+exports.delimiter = ':';
+
+exports.dirname = function(path) {
+  var result = splitPath(path),
+      root = result[0],
+      dir = result[1];
+
+  if (!root && !dir) {
+    // No dirname whatsoever
+    return '.';
+  }
+
+  if (dir) {
+    // It has a dirname, strip trailing slash
+    dir = dir.substr(0, dir.length - 1);
+  }
+
+  return root + dir;
+};
+
+
+exports.basename = function(path, ext) {
+  var f = splitPath(path)[2];
+  // TODO: make this comparison case-insensitive on windows?
+  if (ext && f.substr(-1 * ext.length) === ext) {
+    f = f.substr(0, f.length - ext.length);
+  }
+  return f;
+};
+
+
+exports.extname = function(path) {
+  return splitPath(path)[3];
+};
+
+function filter (xs, f) {
+    if (xs.filter) return xs.filter(f);
+    var res = [];
+    for (var i = 0; i < xs.length; i++) {
+        if (f(xs[i], i, xs)) res.push(xs[i]);
+    }
+    return res;
+}
+
+// String.prototype.substr - negative index don't work in IE8
+var substr = 'ab'.substr(-1) === 'b'
+    ? function (str, start, len) { return str.substr(start, len) }
+    : function (str, start, len) {
+        if (start < 0) start = str.length + start;
+        return str.substr(start, len);
+    }
+;
+
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(7)))
+
+/***/ }),
+/* 10 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var baseIsNative = __webpack_require__(249),
+    getValue = __webpack_require__(252);
+
+/**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
+function getNative(object, key) {
+  var value = getValue(object, key);
+  return baseIsNative(value) ? value : undefined;
+}
+
+module.exports = getNative;
+
+
+/***/ }),
+/* 11 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var Symbol = __webpack_require__(21),
+    getRawTag = __webpack_require__(235),
+    objectToString = __webpack_require__(236);
+
+/** `Object#toString` result references. */
+var nullTag = '[object Null]',
+    undefinedTag = '[object Undefined]';
+
+/** Built-in value references. */
+var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
+
+/**
+ * The base implementation of `getTag` without fallbacks for buggy environments.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
+function baseGetTag(value) {
+  if (value == null) {
+    return value === undefined ? undefinedTag : nullTag;
+  }
+  return (symToStringTag && symToStringTag in Object(value))
+    ? getRawTag(value)
+    : objectToString(value);
+}
+
+module.exports = baseGetTag;
+
+
+/***/ }),
+/* 12 */
+/***/ (function(module, exports) {
+
+/**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+function isObjectLike(value) {
+  return value != null && typeof value == 'object';
+}
+
+module.exports = isObjectLike;
+
+
+/***/ }),
+/* 13 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var isFunction = __webpack_require__(186),
+    isLength = __webpack_require__(32);
+
+/**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
+function isArrayLike(value) {
+  return value != null && isLength(value.length) && !isFunction(value);
+}
+
+module.exports = isArrayLike;
+
+
+/***/ }),
+/* 14 */
+/***/ (function(module, exports) {
+
+/**
+ * Checks if `value` is the
+ * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
+ * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+function isObject(value) {
+  var type = typeof value;
+  return value != null && (type == 'object' || type == 'function');
+}
+
+module.exports = isObject;
+
+
+/***/ }),
+/* 15 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var baseMatches = __webpack_require__(267),
+    baseMatchesProperty = __webpack_require__(310),
+    identity = __webpack_require__(17),
+    isArray = __webpack_require__(2),
+    property = __webpack_require__(320);
+
+/**
+ * The base implementation of `_.iteratee`.
+ *
+ * @private
+ * @param {*} [value=_.identity] The value to convert to an iteratee.
+ * @returns {Function} Returns the iteratee.
+ */
+function baseIteratee(value) {
+  // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
+  // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
+  if (typeof value == 'function') {
+    return value;
+  }
+  if (value == null) {
+    return identity;
+  }
+  if (typeof value == 'object') {
+    return isArray(value)
+      ? baseMatchesProperty(value[0], value[1])
+      : baseMatches(value);
+  }
+  return property(value);
+}
+
+module.exports = baseIteratee;
+
+
+/***/ }),
+/* 16 */
+/***/ (function(module, exports, __webpack_require__) {
+
+var baseGetTag = __webpack_require__(11),
+    isObjectLike = __webpack_require__(12);
+
+/** `Object#toString` result references. */
+var symbolTag = '[object Symbol]';
+
+/**
+ * Checks if `value` is classified as a `Symbol` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
+ * @example
+ *
+ * _.isSymbol(Symbol.iterator);
+ * // => true
+ *
+ * _.isSymbol('abc');
+ * // => false
+ */
+function isSymbol(value) {
+  return typeof value == 'symbol' ||
+    (isObjectLike(value) && baseGetTag(value) == symbolTag);
+}
+
+module.exports = isSymbol;
+
+
+/***/ }),
+/* 17 */
+/***/ (function(module, exports) {
+
+/**
+ * This method returns the first argument it receives.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ *
+ * console.log(_.identity(object) === object);
+ * // => true
+ */
+function identity(value) {
+  return value;
+}
+
+module.exports = identity;
+
+
+/***/ }),
+/* 18 */
+/***/ (function(module, exports) {
+
+var g;
+
+// This works in non-strict mode
+g = (function() {
+	return this;
+})();
+
+try {
+	// This works if eval is allowed (see CSP)
+	g = g || Function("return this")() || (1,eval)("this");
+} catch(e) {
+	// This works if the window reference is available
+	if(typeof window === "object")
+		g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
+
+
+/***/ }),
+/* 19 */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* WEBPACK VAR INJECTION */(function(global, module) {/**
+ * @license
+ * Lodash 
+ * Copyright JS Foundation and other contributors 
+ * Released under MIT license 
+ * Based on Underscore.js 1.8.3 
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used as the semantic version number. */
+  var VERSION = '4.17.4';
+
+  /** Used as the size to enable large array optimizations. */
+  var LARGE_ARRAY_SIZE = 200;
+
+  /** Error message constants. */
+  var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
+      FUNC_ERROR_TEXT = 'Expected a function';
+
+  /** Used to stand-in for `undefined` hash values. */
+  var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+  /** Used as the maximum memoize cache size. */
+  var MAX_MEMOIZE_SIZE = 500;
+
+  /** Used as the internal argument placeholder. */
+  var PLACEHOLDER = '__lodash_placeholder__';
+
+  /** Used to compose bitmasks for cloning. */
+  var CLONE_DEEP_FLAG = 1,
+      CLONE_FLAT_FLAG = 2,
+      CLONE_SYMBOLS_FLAG = 4;
+
+  /** Used to compose bitmasks for value comparisons. */
+  var COMPARE_PARTIAL_FLAG = 1,
+      COMPARE_UNORDERED_FLAG = 2;
+
+  /** Used to compose bitmasks for function metadata. */
+  var WRAP_BIND_FLAG = 1,
+      WRAP_BIND_KEY_FLAG = 2,
+      WRAP_CURRY_BOUND_FLAG = 4,
+      WRAP_CURRY_FLAG = 8,
+      WRAP_CURRY_RIGHT_FLAG = 16,
+      WRAP_PARTIAL_FLAG = 32,
+      WRAP_PARTIAL_RIGHT_FLAG = 64,
+      WRAP_ARY_FLAG = 128,
+      WRAP_REARG_FLAG = 256,
+      WRAP_FLIP_FLAG = 512;
+
+  /** Used as default options for `_.truncate`. */
+  var DEFAULT_TRUNC_LENGTH = 30,
+      DEFAULT_TRUNC_OMISSION = '...';
+
+  /** Used to detect hot functions by number of calls within a span of milliseconds. */
+  var HOT_COUNT = 800,
+      HOT_SPAN = 16;
+
+  /** Used to indicate the type of lazy iteratees. */
+  var LAZY_FILTER_FLAG = 1,
+      LAZY_MAP_FLAG = 2,
+      LAZY_WHILE_FLAG = 3;
+
+  /** Used as references for various `Number` constants. */
+  var INFINITY = 1 / 0,
+      MAX_SAFE_INTEGER = 9007199254740991,
+      MAX_INTEGER = 1.7976931348623157e+308,
+      NAN = 0 / 0;
+
+  /** Used as references for the maximum length and index of an array. */
+  var MAX_ARRAY_LENGTH = 4294967295,
+      MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+      HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+
+  /** Used to associate wrap methods with their bit flags. */
+  var wrapFlags = [
+    ['ary', WRAP_ARY_FLAG],
+    ['bind', WRAP_BIND_FLAG],
+    ['bindKey', WRAP_BIND_KEY_FLAG],
+    ['curry', WRAP_CURRY_FLAG],
+    ['curryRight', WRAP_CURRY_RIGHT_FLAG],
+    ['flip', WRAP_FLIP_FLAG],
+    ['partial', WRAP_PARTIAL_FLAG],
+    ['partialRight', WRAP_PARTIAL_RIGHT_FLAG],
+    ['rearg', WRAP_REARG_FLAG]
+  ];
+
+  /** `Object#toString` result references. */
+  var argsTag = '[object Arguments]',
+      arrayTag = '[object Array]',
+      asyncTag = '[object AsyncFunction]',
+      boolTag = '[object Boolean]',
+      dateTag = '[object Date]',
+      domExcTag = '[object DOMException]',
+      errorTag = '[object Error]',
+      funcTag = '[object Function]',
+      genTag = '[object GeneratorFunction]',
+      mapTag = '[object Map]',
+      numberTag = '[object Number]',
+      nullTag = '[object Null]',
+      objectTag = '[object Object]',
+      promiseTag = '[object Promise]',
+      proxyTag = '[object Proxy]',
+      regexpTag = '[object RegExp]',
+      setTag = '[object Set]',
+      stringTag = '[object String]',
+      symbolTag = '[object Symbol]',
+      undefinedTag = '[object Undefined]',
+      weakMapTag = '[object WeakMap]',
+      weakSetTag = '[object WeakSet]';
+
+  var arrayBufferTag = '[object ArrayBuffer]',
+      dataViewTag = '[object DataView]',
+      float32Tag = '[object Float32Array]',
+      float64Tag = '[object Float64Array]',
+      int8Tag = '[object Int8Array]',
+      int16Tag = '[object Int16Array]',
+      int32Tag = '[object Int32Array]',
+      uint8Tag = '[object Uint8Array]',
+      uint8ClampedTag = '[object Uint8ClampedArray]',
+      uint16Tag = '[object Uint16Array]',
+      uint32Tag = '[object Uint32Array]';
+
+  /** Used to match empty string literals in compiled template source. */
+  var reEmptyStringLeading = /\b__p \+= '';/g,
+      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+  /** Used to match HTML entities and HTML characters. */
+  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
+      reUnescapedHtml = /[&<>"']/g,
+      reHasEscapedHtml = RegExp(reEscapedHtml.source),
+      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+  /** Used to match template delimiters. */
+  var reEscape = /<%-([\s\S]+?)%>/g,
+      reEvaluate = /<%([\s\S]+?)%>/g,
+      reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match property names within property paths. */
+  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+      reIsPlainProp = /^\w*$/,
+      reLeadingDot = /^\./,
+      rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
+
+  /**
+   * Used to match `RegExp`
+   * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
+   */
+  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+      reHasRegExpChar = RegExp(reRegExpChar.source);
+
+  /** Used to match leading and trailing whitespace. */
+  var reTrim = /^\s+|\s+$/g,
+      reTrimStart = /^\s+/,
+      reTrimEnd = /\s+$/;
+
+  /** Used to match wrap detail comments. */
+  var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
+      reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
+      reSplitDetails = /,? & /;
+
+  /** Used to match words composed of alphanumeric characters. */
+  var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
+
+  /** Used to match backslashes in property paths. */
+  var reEscapeChar = /\\(\\)?/g;
+
+  /**
+   * Used to match
+   * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
+   */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match `RegExp` flags from their coerced string values. */
+  var reFlags = /\w*$/;
+
+  /** Used to detect bad signed hexadecimal string values. */
+  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+
+  /** Used to detect binary string values. */
+  var reIsBinary = /^0b[01]+$/i;
+
+  /** Used to detect host constructors (Safari). */
+  var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+  /** Used to detect octal string values. */
+  var reIsOctal = /^0o[0-7]+$/i;
+
+  /** Used to detect unsigned integer values. */
+  var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+  /** Used to match Latin Unicode letters (excluding mathematical operators). */
+  var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
+
+  /** Used to ensure capturing order of template delimiters. */
+  var reNoMatch = /($^)/;
+
+  /** Used to match unescaped characters in compiled string literals. */
+  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+
+  /** Used to compose unicode character classes. */
+  var rsAstralRange = '\\ud800-\\udfff',
+      rsComboMarksRange = '\\u0300-\\u036f',
+      reComboHalfMarksRange = '\\ufe20-\\ufe2f',
+      rsComboSymbolsRange = '\\u20d0-\\u20ff',
+      rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
+      rsDingbatRange = '\\u2700-\\u27bf',
+      rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
+      rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
+      rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
+      rsPunctuationRange = '\\u2000-\\u206f',
+      rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
+      rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
+      rsVarRange = '\\ufe0e\\ufe0f',
+      rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
+
+  /** Used to compose unicode capture groups. */
+  var rsApos = "['\u2019]",
+      rsAstral = '[' + rsAstralRange + ']',
+      rsBreak = '[' + rsBreakRange + ']',
+      rsCombo = '[' + rsComboRange + ']',
+      rsDigits = '\\d+',
+      rsDingbat = '[' + rsDingbatRange + ']',
+      rsLower = '[' + rsLowerRange + ']',
+      rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
+      rsFitz = '\\ud83c[\\udffb-\\udfff]',
+      rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
+      rsNonAstral = '[^' + rsAstralRange + ']',
+      rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
+      rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
+      rsUpper = '[' + rsUpperRange + ']',
+      rsZWJ = '\\u200d';
+
+  /** Used to compose unicode regexes. */
+  var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')',
+      rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')',
+      rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
+      rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
+      reOptMod = rsModifier + '?',
+      rsOptVar = '[' + rsVarRange + ']?',
+      rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
+      rsOrdLower = '\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)',
+      rsOrdUpper = '\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)',
+      rsSeq = rsOptVar + reOptMod + rsOptJoin,
+      rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
+      rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
+
+  /** Used to match apostrophes. */
+  var reApos = RegExp(rsApos, 'g');
+
+  /**
+   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+   */
+  var reComboMark = RegExp(rsCombo, 'g');
+
+  /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
+  var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
+
+  /** Used to match complex or compound words. */
+  var reUnicodeWord = RegExp([
+    rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
+    rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')',
+    rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower,
+    rsUpper + '+' + rsOptContrUpper,
+    rsOrdUpper,
+    rsOrdLower,
+    rsDigits,
+    rsEmoji
+  ].join('|'), 'g');
+
+  /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
+  var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange  + rsComboRange + rsVarRange + ']');
+
+  /** Used to detect strings that need a more robust regexp to match words. */
+  var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
+
+  /** Used to assign default `context` object properties. */
+  var contextProps = [
+    'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array',
+    'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
+    'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array',
+    'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
+    '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
+  ];
+
+  /** Used to make template sourceURLs easier to identify. */
+  var templateCounter = -1;
+
+  /** Used to identify `toStringTag` values of typed arrays. */
+  var typedArrayTags = {};
+  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+  typedArrayTags[uint32Tag] = true;
+  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+  typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
+  typedArrayTags[errorTag] = typedArrayTags[funcTag] =
+  typedArrayTags[mapTag] = typedArrayTags[numberTag] =
+  typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
+  typedArrayTags[setTag] = typedArrayTags[stringTag] =
+  typedArrayTags[weakMapTag] = false;
+
+  /** Used to identify `toStringTag` values supported by `_.clone`. */
+  var cloneableTags = {};
+  cloneableTags[argsTag] = cloneableTags[arrayTag] =
+  cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
+  cloneableTags[boolTag] = cloneableTags[dateTag] =
+  cloneableTags[float32Tag] = cloneableTags[float64Tag] =
+  cloneableTags[int8Tag] = cloneableTags[int16Tag] =
+  cloneableTags[int32Tag] = cloneableTags[mapTag] =
+  cloneableTags[numberTag] = cloneableTags[objectTag] =
+  cloneableTags[regexpTag] = cloneableTags[setTag] =
+  cloneableTags[stringTag] = cloneableTags[symbolTag] =
+  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
+  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+  cloneableTags[errorTag] = cloneableTags[funcTag] =
+  cloneableTags[weakMapTag] = false;
+
+  /** Used to map Latin Unicode letters to basic Latin letters. */
+  var deburredLetters = {
+    // Latin-1 Supplement block.
+    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+    '\xc7': 'C',  '\xe7': 'c',
+    '\xd0': 'D',  '\xf0': 'd',
+    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+    '\xcc': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+    '\xec': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
+    '\xd1': 'N',  '\xf1': 'n',
+    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
+    '\xc6': 'Ae', '\xe6': 'ae',
+    '\xde': 'Th', '\xfe': 'th',
+    '\xdf': 'ss',
+    // Latin Extended-A block.
+    '\u0100': 'A',  '\u0102': 'A', '\u0104': 'A',
+    '\u0101': 'a',  '\u0103': 'a', '\u0105': 'a',
+    '\u0106': 'C',  '\u0108': 'C', '\u010a': 'C', '\u010c': 'C',
+    '\u0107': 'c',  '\u0109': 'c', '\u010b': 'c', '\u010d': 'c',
+    '\u010e': 'D',  '\u0110': 'D', '\u010f': 'd', '\u0111': 'd',
+    '\u0112': 'E',  '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E',
+    '\u0113': 'e',  '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e',
+    '\u011c': 'G',  '\u011e': 'G', '\u0120': 'G', '\u0122': 'G',
+    '\u011d': 'g',  '\u011f': 'g', '\u0121': 'g', '\u0123': 'g',
+    '\u0124': 'H',  '\u0126': 'H', '\u0125': 'h', '\u0127': 'h',
+    '\u0128': 'I',  '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I',
+    '\u0129': 'i',  '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i',
+    '\u0134': 'J',  '\u0135': 'j',
+    '\u0136': 'K',  '\u0137': 'k', '\u0138': 'k',
+    '\u0139': 'L',  '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L',
+    '\u013a': 'l',  '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l',
+    '\u0143': 'N',  '\u0145': 'N', '\u0147': 'N', '\u014a': 'N',
+    '\u0144': 'n',  '\u0146': 'n', '\u0148': 'n', '\u014b': 'n',
+    '\u014c': 'O',  '\u014e': 'O', '\u0150': 'O',
+    '\u014d': 'o',  '\u014f': 'o', '\u0151': 'o',
+    '\u0154': 'R',  '\u0156': 'R', '\u0158': 'R',
+    '\u0155': 'r',  '\u0157': 'r', '\u0159': 'r',
+    '\u015a': 'S',  '\u015c': 'S', '\u015e': 'S', '\u0160': 'S',
+    '\u015b': 's',  '\u015d': 's', '\u015f': 's', '\u0161': 's',
+    '\u0162': 'T',  '\u0164': 'T', '\u0166': 'T',
+    '\u0163': 't',  '\u0165': 't', '\u0167': 't',
+    '\u0168': 'U',  '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U',
+    '\u0169': 'u',  '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u',
+    '\u0174': 'W',  '\u0175': 'w',
+    '\u0176': 'Y',  '\u0177': 'y', '\u0178': 'Y',
+    '\u0179': 'Z',  '\u017b': 'Z', '\u017d': 'Z',
+    '\u017a': 'z',  '\u017c': 'z', '\u017e': 'z',
+    '\u0132': 'IJ', '\u0133': 'ij',
+    '\u0152': 'Oe', '\u0153': 'oe',
+    '\u0149': "'n", '\u017f': 's'
+  };
+
+  /** Used to map characters to HTML entities. */
+  var htmlEscapes = {
+    '&': '&',
+    '<': '<',
+    '>': '>',
+    '"': '"',
+    "'": '''
+  };
+
+  /** Used to map HTML entities to characters. */
+  var htmlUnescapes = {
+    '&': '&',
+    '<': '<',
+    '>': '>',
+    '"': '"',
+    ''': "'"
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals. */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Built-in method references without a dependency on `root`. */
+  var freeParseFloat = parseFloat,
+      freeParseInt = parseInt;
+
+  /** Detect free variable `global` from Node.js. */
+  var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
+
+  /** Detect free variable `self`. */
+  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+  /** Used as a reference to the global object. */
+  var root = freeGlobal || freeSelf || Function('return this')();
+
+  /** Detect free variable `exports`. */
+  var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
+
+  /** Detect free variable `module`. */
+  var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
+
+  /** Detect the popular CommonJS extension `module.exports`. */
+  var moduleExports = freeModule && freeModule.exports === freeExports;
+
+  /** Detect free variable `process` from Node.js. */
+  var freeProcess = moduleExports && freeGlobal.process;
+
+  /** Used to access faster Node.js helpers. */
+  var nodeUtil = (function() {
+    try {
+      return freeProcess && freeProcess.binding && freeProcess.binding('util');
+    } catch (e) {}
+  }());
+
+  /* Node.js helper references. */
+  var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
+      nodeIsDate = nodeUtil && nodeUtil.isDate,
+      nodeIsMap = nodeUtil && nodeUtil.isMap,
+      nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
+      nodeIsSet = nodeUtil && nodeUtil.isSet,
+      nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Adds the key-value `pair` to `map`.
+   *
+   * @private
+   * @param {Object} map The map to modify.
+   * @param {Array} pair The key-value pair to add.
+   * @returns {Object} Returns `map`.
+   */
+  function addMapEntry(map, pair) {
+    // Don't return `map.set` because it's not chainable in IE 11.
+    map.set(pair[0], pair[1]);
+    return map;
+  }
+
+  /**
+   * Adds `value` to `set`.
+   *
+   * @private
+   * @param {Object} set The set to modify.
+   * @param {*} value The value to add.
+   * @returns {Object} Returns `set`.
+   */
+  function addSetEntry(set, value) {
+    // Don't return `set.add` because it's not chainable in IE 11.
+    set.add(value);
+    return set;
+  }
+
+  /**
+   * A faster alternative to `Function#apply`, this function invokes `func`
+   * with the `this` binding of `thisArg` and the arguments of `args`.
+   *
+   * @private
+   * @param {Function} func The function to invoke.
+   * @param {*} thisArg The `this` binding of `func`.
+   * @param {Array} args The arguments to invoke `func` with.
+   * @returns {*} Returns the result of `func`.
+   */
+  function apply(func, thisArg, args) {
+    switch (args.length) {
+      case 0: return func.call(thisArg);
+      case 1: return func.call(thisArg, args[0]);
+      case 2: return func.call(thisArg, args[0], args[1]);
+      case 3: return func.call(thisArg, args[0], args[1], args[2]);
+    }
+    return func.apply(thisArg, args);
+  }
+
+  /**
+   * A specialized version of `baseAggregator` for arrays.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} setter The function to set `accumulator` values.
+   * @param {Function} iteratee The iteratee to transform keys.
+   * @param {Object} accumulator The initial aggregated object.
+   * @returns {Function} Returns `accumulator`.
+   */
+  function arrayAggregator(array, setter, iteratee, accumulator) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      var value = array[index];
+      setter(accumulator, value, iteratee(value), array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.forEach` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEach(array, iteratee) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (iteratee(array[index], index, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.forEachRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEachRight(array, iteratee) {
+    var length = array == null ? 0 : array.length;
+
+    while (length--) {
+      if (iteratee(array[length], length, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.every` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if all elements pass the predicate check,
+   *  else `false`.
+   */
+  function arrayEvery(array, predicate) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (!predicate(array[index], index, array)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * A specialized version of `_.filter` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {Array} Returns the new filtered array.
+   */
+  function arrayFilter(array, predicate) {
+    var index = -1,
+        length = array == null ? 0 : array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (predicate(value, index, array)) {
+        result[resIndex++] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.includes` for arrays without support for
+   * specifying an index to search from.
+   *
+   * @private
+   * @param {Array} [array] The array to inspect.
+   * @param {*} target The value to search for.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludes(array, value) {
+    var length = array == null ? 0 : array.length;
+    return !!length && baseIndexOf(array, value, 0) > -1;
+  }
+
+  /**
+   * This function is like `arrayIncludes` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} [array] The array to inspect.
+   * @param {*} target The value to search for.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludesWith(array, value, comparator) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (comparator(value, array[index])) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A specialized version of `_.map` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the new mapped array.
+   */
+  function arrayMap(array, iteratee) {
+    var index = -1,
+        length = array == null ? 0 : array.length,
+        result = Array(length);
+
+    while (++index < length) {
+      result[index] = iteratee(array[index], index, array);
+    }
+    return result;
+  }
+
+  /**
+   * Appends the elements of `values` to `array`.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {Array} values The values to append.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayPush(array, values) {
+    var index = -1,
+        length = values.length,
+        offset = array.length;
+
+    while (++index < length) {
+      array[offset + index] = values[index];
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.reduce` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the first element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduce(array, iteratee, accumulator, initAccum) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    if (initAccum && length) {
+      accumulator = array[++index];
+    }
+    while (++index < length) {
+      accumulator = iteratee(accumulator, array[index], index, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.reduceRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the last element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+    var length = array == null ? 0 : array.length;
+    if (initAccum && length) {
+      accumulator = array[--length];
+    }
+    while (length--) {
+      accumulator = iteratee(accumulator, array[length], length, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.some` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if any element passes the predicate check,
+   *  else `false`.
+   */
+  function arraySome(array, predicate) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (predicate(array[index], index, array)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Gets the size of an ASCII `string`.
+   *
+   * @private
+   * @param {string} string The string inspect.
+   * @returns {number} Returns the string size.
+   */
+  var asciiSize = baseProperty('length');
+
+  /**
+   * Converts an ASCII `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function asciiToArray(string) {
+    return string.split('');
+  }
+
+  /**
+   * Splits an ASCII `string` into an array of its words.
+   *
+   * @private
+   * @param {string} The string to inspect.
+   * @returns {Array} Returns the words of `string`.
+   */
+  function asciiWords(string) {
+    return string.match(reAsciiWord) || [];
+  }
+
+  /**
+   * The base implementation of methods like `_.findKey` and `_.findLastKey`,
+   * without support for iteratee shorthands, which iterates over `collection`
+   * using `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to inspect.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @returns {*} Returns the found element or its key, else `undefined`.
+   */
+  function baseFindKey(collection, predicate, eachFunc) {
+    var result;
+    eachFunc(collection, function(value, key, collection) {
+      if (predicate(value, key, collection)) {
+        result = key;
+        return false;
+      }
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.findIndex` and `_.findLastIndex` without
+   * support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {number} fromIndex The index to search from.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseFindIndex(array, predicate, fromIndex, fromRight) {
+    var length = array.length,
+        index = fromIndex + (fromRight ? 1 : -1);
+
+    while ((fromRight ? index-- : ++index < length)) {
+      if (predicate(array[index], index, array)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    return value === value
+      ? strictIndexOf(array, value, fromIndex)
+      : baseFindIndex(array, baseIsNaN, fromIndex);
+  }
+
+  /**
+   * This function is like `baseIndexOf` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOfWith(array, value, fromIndex, comparator) {
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (comparator(array[index], value)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.isNaN` without support for number objects.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+   */
+  function baseIsNaN(value) {
+    return value !== value;
+  }
+
+  /**
+   * The base implementation of `_.mean` and `_.meanBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the mean.
+   */
+  function baseMean(array, iteratee) {
+    var length = array == null ? 0 : array.length;
+    return length ? (baseSum(array, iteratee) / length) : NAN;
+  }
+
+  /**
+   * The base implementation of `_.property` without support for deep paths.
+   *
+   * @private
+   * @param {string} key The key of the property to get.
+   * @returns {Function} Returns the new accessor function.
+   */
+  function baseProperty(key) {
+    return function(object) {
+      return object == null ? undefined : object[key];
+    };
+  }
+
+  /**
+   * The base implementation of `_.propertyOf` without support for deep paths.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @returns {Function} Returns the new accessor function.
+   */
+  function basePropertyOf(object) {
+    return function(key) {
+      return object == null ? undefined : object[key];
+    };
+  }
+
+  /**
+   * The base implementation of `_.reduce` and `_.reduceRight`, without support
+   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} accumulator The initial value.
+   * @param {boolean} initAccum Specify using the first or last element of
+   *  `collection` as the initial value.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @returns {*} Returns the accumulated value.
+   */
+  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+    eachFunc(collection, function(value, index, collection) {
+      accumulator = initAccum
+        ? (initAccum = false, value)
+        : iteratee(accumulator, value, index, collection);
+    });
+    return accumulator;
+  }
+
+  /**
+   * The base implementation of `_.sortBy` which uses `comparer` to define the
+   * sort order of `array` and replaces criteria objects with their corresponding
+   * values.
+   *
+   * @private
+   * @param {Array} array The array to sort.
+   * @param {Function} comparer The function to define sort order.
+   * @returns {Array} Returns `array`.
+   */
+  function baseSortBy(array, comparer) {
+    var length = array.length;
+
+    array.sort(comparer);
+    while (length--) {
+      array[length] = array[length].value;
+    }
+    return array;
+  }
+
+  /**
+   * The base implementation of `_.sum` and `_.sumBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the sum.
+   */
+  function baseSum(array, iteratee) {
+    var result,
+        index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      var current = iteratee(array[index]);
+      if (current !== undefined) {
+        result = result === undefined ? current : (result + current);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.times` without support for iteratee shorthands
+   * or max array length checks.
+   *
+   * @private
+   * @param {number} n The number of times to invoke `iteratee`.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the array of results.
+   */
+  function baseTimes(n, iteratee) {
+    var index = -1,
+        result = Array(n);
+
+    while (++index < n) {
+      result[index] = iteratee(index);
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+   * of key-value pairs for `object` corresponding to the property names of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the key-value pairs.
+   */
+  function baseToPairs(object, props) {
+    return arrayMap(props, function(key) {
+      return [key, object[key]];
+    });
+  }
+
+  /**
+   * The base implementation of `_.unary` without support for storing metadata.
+   *
+   * @private
+   * @param {Function} func The function to cap arguments for.
+   * @returns {Function} Returns the new capped function.
+   */
+  function baseUnary(func) {
+    return function(value) {
+      return func(value);
+    };
+  }
+
+  /**
+   * The base implementation of `_.values` and `_.valuesIn` which creates an
+   * array of `object` property values corresponding to the property names
+   * of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the array of property values.
+   */
+  function baseValues(object, props) {
+    return arrayMap(props, function(key) {
+      return object[key];
+    });
+  }
+
+  /**
+   * Checks if a `cache` value for `key` exists.
+   *
+   * @private
+   * @param {Object} cache The cache to query.
+   * @param {string} key The key of the entry to check.
+   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+   */
+  function cacheHas(cache, key) {
+    return cache.has(key);
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the first unmatched string symbol.
+   */
+  function charsStartIndex(strSymbols, chrSymbols) {
+    var index = -1,
+        length = strSymbols.length;
+
+    while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the last unmatched string symbol.
+   */
+  function charsEndIndex(strSymbols, chrSymbols) {
+    var index = strSymbols.length;
+
+    while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Gets the number of `placeholder` occurrences in `array`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} placeholder The placeholder to search for.
+   * @returns {number} Returns the placeholder count.
+   */
+  function countHolders(array, placeholder) {
+    var length = array.length,
+        result = 0;
+
+    while (length--) {
+      if (array[length] === placeholder) {
+        ++result;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
+   * letters to basic Latin letters.
+   *
+   * @private
+   * @param {string} letter The matched letter to deburr.
+   * @returns {string} Returns the deburred letter.
+   */
+  var deburrLetter = basePropertyOf(deburredLetters);
+
+  /**
+   * Used by `_.escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  var escapeHtmlChar = basePropertyOf(htmlEscapes);
+
+  /**
+   * Used by `_.template` to escape characters for inclusion in compiled string literals.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(chr) {
+    return '\\' + stringEscapes[chr];
+  }
+
+  /**
+   * Gets the value at `key` of `object`.
+   *
+   * @private
+   * @param {Object} [object] The object to query.
+   * @param {string} key The key of the property to get.
+   * @returns {*} Returns the property value.
+   */
+  function getValue(object, key) {
+    return object == null ? undefined : object[key];
+  }
+
+  /**
+   * Checks if `string` contains Unicode symbols.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {boolean} Returns `true` if a symbol is found, else `false`.
+   */
+  function hasUnicode(string) {
+    return reHasUnicode.test(string);
+  }
+
+  /**
+   * Checks if `string` contains a word composed of Unicode symbols.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {boolean} Returns `true` if a word is found, else `false`.
+   */
+  function hasUnicodeWord(string) {
+    return reHasUnicodeWord.test(string);
+  }
+
+  /**
+   * Converts `iterator` to an array.
+   *
+   * @private
+   * @param {Object} iterator The iterator to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function iteratorToArray(iterator) {
+    var data,
+        result = [];
+
+    while (!(data = iterator.next()).done) {
+      result.push(data.value);
+    }
+    return result;
+  }
+
+  /**
+   * Converts `map` to its key-value pairs.
+   *
+   * @private
+   * @param {Object} map The map to convert.
+   * @returns {Array} Returns the key-value pairs.
+   */
+  function mapToArray(map) {
+    var index = -1,
+        result = Array(map.size);
+
+    map.forEach(function(value, key) {
+      result[++index] = [key, value];
+    });
+    return result;
+  }
+
+  /**
+   * Creates a unary function that invokes `func` with its argument transformed.
+   *
+   * @private
+   * @param {Function} func The function to wrap.
+   * @param {Function} transform The argument transform.
+   * @returns {Function} Returns the new function.
+   */
+  function overArg(func, transform) {
+    return function(arg) {
+      return func(transform(arg));
+    };
+  }
+
+  /**
+   * Replaces all `placeholder` elements in `array` with an internal placeholder
+   * and returns an array of their indexes.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {*} placeholder The placeholder to replace.
+   * @returns {Array} Returns the new array of placeholder indexes.
+   */
+  function replaceHolders(array, placeholder) {
+    var index = -1,
+        length = array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (value === placeholder || value === PLACEHOLDER) {
+        array[index] = PLACEHOLDER;
+        result[resIndex++] = index;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Converts `set` to an array of its values.
+   *
+   * @private
+   * @param {Object} set The set to convert.
+   * @returns {Array} Returns the values.
+   */
+  function setToArray(set) {
+    var index = -1,
+        result = Array(set.size);
+
+    set.forEach(function(value) {
+      result[++index] = value;
+    });
+    return result;
+  }
+
+  /**
+   * Converts `set` to its value-value pairs.
+   *
+   * @private
+   * @param {Object} set The set to convert.
+   * @returns {Array} Returns the value-value pairs.
+   */
+  function setToPairs(set) {
+    var index = -1,
+        result = Array(set.size);
+
+    set.forEach(function(value) {
+      result[++index] = [value, value];
+    });
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.indexOf` which performs strict equality
+   * comparisons of values, i.e. `===`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function strictIndexOf(array, value, fromIndex) {
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * A specialized version of `_.lastIndexOf` which performs strict equality
+   * comparisons of values, i.e. `===`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function strictLastIndexOf(array, value, fromIndex) {
+    var index = fromIndex + 1;
+    while (index--) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return index;
+  }
+
+  /**
+   * Gets the number of symbols in `string`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {number} Returns the string size.
+   */
+  function stringSize(string) {
+    return hasUnicode(string)
+      ? unicodeSize(string)
+      : asciiSize(string);
+  }
+
+  /**
+   * Converts `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function stringToArray(string) {
+    return hasUnicode(string)
+      ? unicodeToArray(string)
+      : asciiToArray(string);
+  }
+
+  /**
+   * Used by `_.unescape` to convert HTML entities to characters.
+   *
+   * @private
+   * @param {string} chr The matched character to unescape.
+   * @returns {string} Returns the unescaped character.
+   */
+  var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
+
+  /**
+   * Gets the size of a Unicode `string`.
+   *
+   * @private
+   * @param {string} string The string inspect.
+   * @returns {number} Returns the string size.
+   */
+  function unicodeSize(string) {
+    var result = reUnicode.lastIndex = 0;
+    while (reUnicode.test(string)) {
+      ++result;
+    }
+    return result;
+  }
+
+  /**
+   * Converts a Unicode `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function unicodeToArray(string) {
+    return string.match(reUnicode) || [];
+  }
+
+  /**
+   * Splits a Unicode `string` into an array of its words.
+   *
+   * @private
+   * @param {string} The string to inspect.
+   * @returns {Array} Returns the words of `string`.
+   */
+  function unicodeWords(string) {
+    return string.match(reUnicodeWord) || [];
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new pristine `lodash` function using the `context` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 1.1.0
+   * @category Util
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns a new `lodash` function.
+   * @example
+   *
+   * _.mixin({ 'foo': _.constant('foo') });
+   *
+   * var lodash = _.runInContext();
+   * lodash.mixin({ 'bar': lodash.constant('bar') });
+   *
+   * _.isFunction(_.foo);
+   * // => true
+   * _.isFunction(_.bar);
+   * // => false
+   *
+   * lodash.isFunction(lodash.foo);
+   * // => false
+   * lodash.isFunction(lodash.bar);
+   * // => true
+   *
+   * // Create a suped-up `defer` in Node.js.
+   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+   */
+  var runInContext = (function runInContext(context) {
+    context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
+
+    /** Built-in constructor references. */
+    var Array = context.Array,
+        Date = context.Date,
+        Error = context.Error,
+        Function = context.Function,
+        Math = context.Math,
+        Object = context.Object,
+        RegExp = context.RegExp,
+        String = context.String,
+        TypeError = context.TypeError;
+
+    /** Used for built-in method references. */
+    var arrayProto = Array.prototype,
+        funcProto = Function.prototype,
+        objectProto = Object.prototype;
+
+    /** Used to detect overreaching core-js shims. */
+    var coreJsData = context['__core-js_shared__'];
+
+    /** Used to resolve the decompiled source of functions. */
+    var funcToString = funcProto.toString;
+
+    /** Used to check objects for own properties. */
+    var hasOwnProperty = objectProto.hasOwnProperty;
+
+    /** Used to generate unique IDs. */
+    var idCounter = 0;
+
+    /** Used to detect methods masquerading as native. */
+    var maskSrcKey = (function() {
+      var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
+      return uid ? ('Symbol(src)_1.' + uid) : '';
+    }());
+
+    /**
+     * Used to resolve the
+     * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+     * of values.
+     */
+    var nativeObjectToString = objectProto.toString;
+
+    /** Used to infer the `Object` constructor. */
+    var objectCtorString = funcToString.call(Object);
+
+    /** Used to restore the original `_` reference in `_.noConflict`. */
+    var oldDash = root._;
+
+    /** Used to detect if a method is native. */
+    var reIsNative = RegExp('^' +
+      funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+    );
+
+    /** Built-in value references. */
+    var Buffer = moduleExports ? context.Buffer : undefined,
+        Symbol = context.Symbol,
+        Uint8Array = context.Uint8Array,
+        allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
+        getPrototype = overArg(Object.getPrototypeOf, Object),
+        objectCreate = Object.create,
+        propertyIsEnumerable = objectProto.propertyIsEnumerable,
+        splice = arrayProto.splice,
+        spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined,
+        symIterator = Symbol ? Symbol.iterator : undefined,
+        symToStringTag = Symbol ? Symbol.toStringTag : undefined;
+
+    var defineProperty = (function() {
+      try {
+        var func = getNative(Object, 'defineProperty');
+        func({}, '', {});
+        return func;
+      } catch (e) {}
+    }());
+
+    /** Mocked built-ins. */
+    var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
+        ctxNow = Date && Date.now !== root.Date.now && Date.now,
+        ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
+
+    /* Built-in method references for those with the same name as other `lodash` methods. */
+    var nativeCeil = Math.ceil,
+        nativeFloor = Math.floor,
+        nativeGetSymbols = Object.getOwnPropertySymbols,
+        nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
+        nativeIsFinite = context.isFinite,
+        nativeJoin = arrayProto.join,
+        nativeKeys = overArg(Object.keys, Object),
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeNow = Date.now,
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random,
+        nativeReverse = arrayProto.reverse;
+
+    /* Built-in method references that are verified to be native. */
+    var DataView = getNative(context, 'DataView'),
+        Map = getNative(context, 'Map'),
+        Promise = getNative(context, 'Promise'),
+        Set = getNative(context, 'Set'),
+        WeakMap = getNative(context, 'WeakMap'),
+        nativeCreate = getNative(Object, 'create');
+
+    /** Used to store function metadata. */
+    var metaMap = WeakMap && new WeakMap;
+
+    /** Used to lookup unminified function names. */
+    var realNames = {};
+
+    /** Used to detect maps, sets, and weakmaps. */
+    var dataViewCtorString = toSource(DataView),
+        mapCtorString = toSource(Map),
+        promiseCtorString = toSource(Promise),
+        setCtorString = toSource(Set),
+        weakMapCtorString = toSource(WeakMap);
+
+    /** Used to convert symbols to primitives and strings. */
+    var symbolProto = Symbol ? Symbol.prototype : undefined,
+        symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
+        symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps `value` to enable implicit method
+     * chain sequences. Methods that operate on and return arrays, collections,
+     * and functions can be chained together. Methods that retrieve a single value
+     * or may return a primitive value will automatically end the chain sequence
+     * and return the unwrapped value. Otherwise, the value must be unwrapped
+     * with `_#value`.
+     *
+     * Explicit chain sequences, which must be unwrapped with `_#value`, may be
+     * enabled using `_.chain`.
+     *
+     * The execution of chained methods is lazy, that is, it's deferred until
+     * `_#value` is implicitly or explicitly called.
+     *
+     * Lazy evaluation allows several methods to support shortcut fusion.
+     * Shortcut fusion is an optimization to merge iteratee calls; this avoids
+     * the creation of intermediate arrays and can greatly reduce the number of
+     * iteratee executions. Sections of a chain sequence qualify for shortcut
+     * fusion if the section is applied to an array and iteratees accept only
+     * one argument. The heuristic for whether a section qualifies for shortcut
+     * fusion is subject to change.
+     *
+     * Chaining is supported in custom builds as long as the `_#value` method is
+     * directly or indirectly included in the build.
+     *
+     * In addition to lodash methods, wrappers have `Array` and `String` methods.
+     *
+     * The wrapper `Array` methods are:
+     * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+     *
+     * The wrapper `String` methods are:
+     * `replace` and `split`
+     *
+     * The wrapper methods that support shortcut fusion are:
+     * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+     * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+     * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+     *
+     * The chainable wrapper methods are:
+     * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+     * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+     * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+     * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+     * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+     * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+     * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+     * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+     * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+     * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+     * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+     * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+     * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+     * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+     * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+     * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+     * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+     * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+     * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+     * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+     * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+     * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+     * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+     * `zipObject`, `zipObjectDeep`, and `zipWith`
+     *
+     * The wrapper methods that are **not** chainable by default are:
+     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+     * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
+     * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
+     * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
+     * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
+     * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
+     * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
+     * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
+     * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
+     * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
+     * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
+     * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
+     * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
+     * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
+     * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
+     * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
+     * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
+     * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
+     * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
+     * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
+     * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
+     * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
+     * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
+     * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
+     * `upperFirst`, `value`, and `words`
+     *
+     * @name _
+     * @constructor
+     * @category Seq
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // Returns an unwrapped value.
+     * wrapped.reduce(_.add);
+     * // => 6
+     *
+     * // Returns a wrapped value.
+     * var squares = wrapped.map(square);
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+        if (value instanceof LodashWrapper) {
+          return value;
+        }
+        if (hasOwnProperty.call(value, '__wrapped__')) {
+          return wrapperClone(value);
+        }
+      }
+      return new LodashWrapper(value);
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} proto The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    var baseCreate = (function() {
+      function object() {}
+      return function(proto) {
+        if (!isObject(proto)) {
+          return {};
+        }
+        if (objectCreate) {
+          return objectCreate(proto);
+        }
+        object.prototype = proto;
+        var result = new object;
+        object.prototype = undefined;
+        return result;
+      };
+    }());
+
+    /**
+     * The function whose prototype chain sequence wrappers inherit from.
+     *
+     * @private
+     */
+    function baseLodash() {
+      // No operation performed.
+    }
+
+    /**
+     * The base constructor for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap.
+     * @param {boolean} [chainAll] Enable explicit method chain sequences.
+     */
+    function LodashWrapper(value, chainAll) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__chain__ = !!chainAll;
+      this.__index__ = 0;
+      this.__values__ = undefined;
+    }
+
+    /**
+     * By default, the template delimiters used by lodash are like those in
+     * embedded Ruby (ERB) as well as ES2015 template strings. Change the
+     * following template settings to use alternative delimiters.
+     *
+     * @static
+     * @memberOf _
+     * @type {Object}
+     */
+    lodash.templateSettings = {
+
+      /**
+       * Used to detect `data` property values to be HTML-escaped.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'escape': reEscape,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'evaluate': reEvaluate,
+
+      /**
+       * Used to detect `data` property values to inject.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'interpolate': reInterpolate,
+
+      /**
+       * Used to reference the data object in the template text.
+       *
+       * @memberOf _.templateSettings
+       * @type {string}
+       */
+      'variable': '',
+
+      /**
+       * Used to import variables into the compiled template.
+       *
+       * @memberOf _.templateSettings
+       * @type {Object}
+       */
+      'imports': {
+
+        /**
+         * A reference to the `lodash` function.
+         *
+         * @memberOf _.templateSettings.imports
+         * @type {Function}
+         */
+        '_': lodash
+      }
+    };
+
+    // Ensure wrappers are instances of `baseLodash`.
+    lodash.prototype = baseLodash.prototype;
+    lodash.prototype.constructor = lodash;
+
+    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+    LodashWrapper.prototype.constructor = LodashWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+     *
+     * @private
+     * @constructor
+     * @param {*} value The value to wrap.
+     */
+    function LazyWrapper(value) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__dir__ = 1;
+      this.__filtered__ = false;
+      this.__iteratees__ = [];
+      this.__takeCount__ = MAX_ARRAY_LENGTH;
+      this.__views__ = [];
+    }
+
+    /**
+     * Creates a clone of the lazy wrapper object.
+     *
+     * @private
+     * @name clone
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the cloned `LazyWrapper` object.
+     */
+    function lazyClone() {
+      var result = new LazyWrapper(this.__wrapped__);
+      result.__actions__ = copyArray(this.__actions__);
+      result.__dir__ = this.__dir__;
+      result.__filtered__ = this.__filtered__;
+      result.__iteratees__ = copyArray(this.__iteratees__);
+      result.__takeCount__ = this.__takeCount__;
+      result.__views__ = copyArray(this.__views__);
+      return result;
+    }
+
+    /**
+     * Reverses the direction of lazy iteration.
+     *
+     * @private
+     * @name reverse
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the new reversed `LazyWrapper` object.
+     */
+    function lazyReverse() {
+      if (this.__filtered__) {
+        var result = new LazyWrapper(this);
+        result.__dir__ = -1;
+        result.__filtered__ = true;
+      } else {
+        result = this.clone();
+        result.__dir__ *= -1;
+      }
+      return result;
+    }
+
+    /**
+     * Extracts the unwrapped value from its lazy wrapper.
+     *
+     * @private
+     * @name value
+     * @memberOf LazyWrapper
+     * @returns {*} Returns the unwrapped value.
+     */
+    function lazyValue() {
+      var array = this.__wrapped__.value(),
+          dir = this.__dir__,
+          isArr = isArray(array),
+          isRight = dir < 0,
+          arrLength = isArr ? array.length : 0,
+          view = getView(0, arrLength, this.__views__),
+          start = view.start,
+          end = view.end,
+          length = end - start,
+          index = isRight ? end : (start - 1),
+          iteratees = this.__iteratees__,
+          iterLength = iteratees.length,
+          resIndex = 0,
+          takeCount = nativeMin(length, this.__takeCount__);
+
+      if (!isArr || (!isRight && arrLength == length && takeCount == length)) {
+        return baseWrapperValue(array, this.__actions__);
+      }
+      var result = [];
+
+      outer:
+      while (length-- && resIndex < takeCount) {
+        index += dir;
+
+        var iterIndex = -1,
+            value = array[index];
+
+        while (++iterIndex < iterLength) {
+          var data = iteratees[iterIndex],
+              iteratee = data.iteratee,
+              type = data.type,
+              computed = iteratee(value);
+
+          if (type == LAZY_MAP_FLAG) {
+            value = computed;
+          } else if (!computed) {
+            if (type == LAZY_FILTER_FLAG) {
+              continue outer;
+            } else {
+              break outer;
+            }
+          }
+        }
+        result[resIndex++] = value;
+      }
+      return result;
+    }
+
+    // Ensure `LazyWrapper` is an instance of `baseLodash`.
+    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+    LazyWrapper.prototype.constructor = LazyWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a hash object.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function Hash(entries) {
+      var index = -1,
+          length = entries == null ? 0 : entries.length;
+
+      this.clear();
+      while (++index < length) {
+        var entry = entries[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the hash.
+     *
+     * @private
+     * @name clear
+     * @memberOf Hash
+     */
+    function hashClear() {
+      this.__data__ = nativeCreate ? nativeCreate(null) : {};
+      this.size = 0;
+    }
+
+    /**
+     * Removes `key` and its value from the hash.
+     *
+     * @private
+     * @name delete
+     * @memberOf Hash
+     * @param {Object} hash The hash to modify.
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function hashDelete(key) {
+      var result = this.has(key) && delete this.__data__[key];
+      this.size -= result ? 1 : 0;
+      return result;
+    }
+
+    /**
+     * Gets the hash value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf Hash
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function hashGet(key) {
+      var data = this.__data__;
+      if (nativeCreate) {
+        var result = data[key];
+        return result === HASH_UNDEFINED ? undefined : result;
+      }
+      return hasOwnProperty.call(data, key) ? data[key] : undefined;
+    }
+
+    /**
+     * Checks if a hash value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf Hash
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function hashHas(key) {
+      var data = this.__data__;
+      return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
+    }
+
+    /**
+     * Sets the hash `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf Hash
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the hash instance.
+     */
+    function hashSet(key, value) {
+      var data = this.__data__;
+      this.size += this.has(key) ? 0 : 1;
+      data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+      return this;
+    }
+
+    // Add methods to `Hash`.
+    Hash.prototype.clear = hashClear;
+    Hash.prototype['delete'] = hashDelete;
+    Hash.prototype.get = hashGet;
+    Hash.prototype.has = hashHas;
+    Hash.prototype.set = hashSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an list cache object.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function ListCache(entries) {
+      var index = -1,
+          length = entries == null ? 0 : entries.length;
+
+      this.clear();
+      while (++index < length) {
+        var entry = entries[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the list cache.
+     *
+     * @private
+     * @name clear
+     * @memberOf ListCache
+     */
+    function listCacheClear() {
+      this.__data__ = [];
+      this.size = 0;
+    }
+
+    /**
+     * Removes `key` and its value from the list cache.
+     *
+     * @private
+     * @name delete
+     * @memberOf ListCache
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function listCacheDelete(key) {
+      var data = this.__data__,
+          index = assocIndexOf(data, key);
+
+      if (index < 0) {
+        return false;
+      }
+      var lastIndex = data.length - 1;
+      if (index == lastIndex) {
+        data.pop();
+      } else {
+        splice.call(data, index, 1);
+      }
+      --this.size;
+      return true;
+    }
+
+    /**
+     * Gets the list cache value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf ListCache
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function listCacheGet(key) {
+      var data = this.__data__,
+          index = assocIndexOf(data, key);
+
+      return index < 0 ? undefined : data[index][1];
+    }
+
+    /**
+     * Checks if a list cache value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf ListCache
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function listCacheHas(key) {
+      return assocIndexOf(this.__data__, key) > -1;
+    }
+
+    /**
+     * Sets the list cache `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf ListCache
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the list cache instance.
+     */
+    function listCacheSet(key, value) {
+      var data = this.__data__,
+          index = assocIndexOf(data, key);
+
+      if (index < 0) {
+        ++this.size;
+        data.push([key, value]);
+      } else {
+        data[index][1] = value;
+      }
+      return this;
+    }
+
+    // Add methods to `ListCache`.
+    ListCache.prototype.clear = listCacheClear;
+    ListCache.prototype['delete'] = listCacheDelete;
+    ListCache.prototype.get = listCacheGet;
+    ListCache.prototype.has = listCacheHas;
+    ListCache.prototype.set = listCacheSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a map cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function MapCache(entries) {
+      var index = -1,
+          length = entries == null ? 0 : entries.length;
+
+      this.clear();
+      while (++index < length) {
+        var entry = entries[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the map.
+     *
+     * @private
+     * @name clear
+     * @memberOf MapCache
+     */
+    function mapCacheClear() {
+      this.size = 0;
+      this.__data__ = {
+        'hash': new Hash,
+        'map': new (Map || ListCache),
+        'string': new Hash
+      };
+    }
+
+    /**
+     * Removes `key` and its value from the map.
+     *
+     * @private
+     * @name delete
+     * @memberOf MapCache
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function mapCacheDelete(key) {
+      var result = getMapData(this, key)['delete'](key);
+      this.size -= result ? 1 : 0;
+      return result;
+    }
+
+    /**
+     * Gets the map value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf MapCache
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function mapCacheGet(key) {
+      return getMapData(this, key).get(key);
+    }
+
+    /**
+     * Checks if a map value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf MapCache
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function mapCacheHas(key) {
+      return getMapData(this, key).has(key);
+    }
+
+    /**
+     * Sets the map `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf MapCache
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the map cache instance.
+     */
+    function mapCacheSet(key, value) {
+      var data = getMapData(this, key),
+          size = data.size;
+
+      data.set(key, value);
+      this.size += data.size == size ? 0 : 1;
+      return this;
+    }
+
+    // Add methods to `MapCache`.
+    MapCache.prototype.clear = mapCacheClear;
+    MapCache.prototype['delete'] = mapCacheDelete;
+    MapCache.prototype.get = mapCacheGet;
+    MapCache.prototype.has = mapCacheHas;
+    MapCache.prototype.set = mapCacheSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     *
+     * Creates an array cache object to store unique values.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function SetCache(values) {
+      var index = -1,
+          length = values == null ? 0 : values.length;
+
+      this.__data__ = new MapCache;
+      while (++index < length) {
+        this.add(values[index]);
+      }
+    }
+
+    /**
+     * Adds `value` to the array cache.
+     *
+     * @private
+     * @name add
+     * @memberOf SetCache
+     * @alias push
+     * @param {*} value The value to cache.
+     * @returns {Object} Returns the cache instance.
+     */
+    function setCacheAdd(value) {
+      this.__data__.set(value, HASH_UNDEFINED);
+      return this;
+    }
+
+    /**
+     * Checks if `value` is in the array cache.
+     *
+     * @private
+     * @name has
+     * @memberOf SetCache
+     * @param {*} value The value to search for.
+     * @returns {number} Returns `true` if `value` is found, else `false`.
+     */
+    function setCacheHas(value) {
+      return this.__data__.has(value);
+    }
+
+    // Add methods to `SetCache`.
+    SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
+    SetCache.prototype.has = setCacheHas;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a stack cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function Stack(entries) {
+      var data = this.__data__ = new ListCache(entries);
+      this.size = data.size;
+    }
+
+    /**
+     * Removes all key-value entries from the stack.
+     *
+     * @private
+     * @name clear
+     * @memberOf Stack
+     */
+    function stackClear() {
+      this.__data__ = new ListCache;
+      this.size = 0;
+    }
+
+    /**
+     * Removes `key` and its value from the stack.
+     *
+     * @private
+     * @name delete
+     * @memberOf Stack
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function stackDelete(key) {
+      var data = this.__data__,
+          result = data['delete'](key);
+
+      this.size = data.size;
+      return result;
+    }
+
+    /**
+     * Gets the stack value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf Stack
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function stackGet(key) {
+      return this.__data__.get(key);
+    }
+
+    /**
+     * Checks if a stack value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf Stack
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function stackHas(key) {
+      return this.__data__.has(key);
+    }
+
+    /**
+     * Sets the stack `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf Stack
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the stack cache instance.
+     */
+    function stackSet(key, value) {
+      var data = this.__data__;
+      if (data instanceof ListCache) {
+        var pairs = data.__data__;
+        if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
+          pairs.push([key, value]);
+          this.size = ++data.size;
+          return this;
+        }
+        data = this.__data__ = new MapCache(pairs);
+      }
+      data.set(key, value);
+      this.size = data.size;
+      return this;
+    }
+
+    // Add methods to `Stack`.
+    Stack.prototype.clear = stackClear;
+    Stack.prototype['delete'] = stackDelete;
+    Stack.prototype.get = stackGet;
+    Stack.prototype.has = stackHas;
+    Stack.prototype.set = stackSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of the enumerable property names of the array-like `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @param {boolean} inherited Specify returning inherited property names.
+     * @returns {Array} Returns the array of property names.
+     */
+    function arrayLikeKeys(value, inherited) {
+      var isArr = isArray(value),
+          isArg = !isArr && isArguments(value),
+          isBuff = !isArr && !isArg && isBuffer(value),
+          isType = !isArr && !isArg && !isBuff && isTypedArray(value),
+          skipIndexes = isArr || isArg || isBuff || isType,
+          result = skipIndexes ? baseTimes(value.length, String) : [],
+          length = result.length;
+
+      for (var key in value) {
+        if ((inherited || hasOwnProperty.call(value, key)) &&
+            !(skipIndexes && (
+               // Safari 9 has enumerable `arguments.length` in strict mode.
+               key == 'length' ||
+               // Node.js 0.10 has enumerable non-index properties on buffers.
+               (isBuff && (key == 'offset' || key == 'parent')) ||
+               // PhantomJS 2 has enumerable non-index properties on typed arrays.
+               (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
+               // Skip index properties.
+               isIndex(key, length)
+            ))) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * A specialized version of `_.sample` for arrays.
+     *
+     * @private
+     * @param {Array} array The array to sample.
+     * @returns {*} Returns the random element.
+     */
+    function arraySample(array) {
+      var length = array.length;
+      return length ? array[baseRandom(0, length - 1)] : undefined;
+    }
+
+    /**
+     * A specialized version of `_.sampleSize` for arrays.
+     *
+     * @private
+     * @param {Array} array The array to sample.
+     * @param {number} n The number of elements to sample.
+     * @returns {Array} Returns the random elements.
+     */
+    function arraySampleSize(array, n) {
+      return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
+    }
+
+    /**
+     * A specialized version of `_.shuffle` for arrays.
+     *
+     * @private
+     * @param {Array} array The array to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     */
+    function arrayShuffle(array) {
+      return shuffleSelf(copyArray(array));
+    }
+
+    /**
+     * This function is like `assignValue` except that it doesn't assign
+     * `undefined` values.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignMergeValue(object, key, value) {
+      if ((value !== undefined && !eq(object[key], value)) ||
+          (value === undefined && !(key in object))) {
+        baseAssignValue(object, key, value);
+      }
+    }
+
+    /**
+     * Assigns `value` to `key` of `object` if the existing value is not equivalent
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignValue(object, key, value) {
+      var objValue = object[key];
+      if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+          (value === undefined && !(key in object))) {
+        baseAssignValue(object, key, value);
+      }
+    }
+
+    /**
+     * Gets the index at which the `key` is found in `array` of key-value pairs.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {*} key The key to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     */
+    function assocIndexOf(array, key) {
+      var length = array.length;
+      while (length--) {
+        if (eq(array[length][0], key)) {
+          return length;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Aggregates elements of `collection` on `accumulator` with keys transformed
+     * by `iteratee` and values set by `setter`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform keys.
+     * @param {Object} accumulator The initial aggregated object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseAggregator(collection, setter, iteratee, accumulator) {
+      baseEach(collection, function(value, key, collection) {
+        setter(accumulator, value, iteratee(value), collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.assign` without support for multiple sources
+     * or `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @returns {Object} Returns `object`.
+     */
+    function baseAssign(object, source) {
+      return object && copyObject(source, keys(source), object);
+    }
+
+    /**
+     * The base implementation of `_.assignIn` without support for multiple sources
+     * or `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @returns {Object} Returns `object`.
+     */
+    function baseAssignIn(object, source) {
+      return object && copyObject(source, keysIn(source), object);
+    }
+
+    /**
+     * The base implementation of `assignValue` and `assignMergeValue` without
+     * value checks.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function baseAssignValue(object, key, value) {
+      if (key == '__proto__' && defineProperty) {
+        defineProperty(object, key, {
+          'configurable': true,
+          'enumerable': true,
+          'value': value,
+          'writable': true
+        });
+      } else {
+        object[key] = value;
+      }
+    }
+
+    /**
+     * The base implementation of `_.at` without support for individual paths.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {string[]} paths The property paths to pick.
+     * @returns {Array} Returns the picked elements.
+     */
+    function baseAt(object, paths) {
+      var index = -1,
+          length = paths.length,
+          result = Array(length),
+          skip = object == null;
+
+      while (++index < length) {
+        result[index] = skip ? undefined : get(object, paths[index]);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.clamp` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {number} number The number to clamp.
+     * @param {number} [lower] The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the clamped number.
+     */
+    function baseClamp(number, lower, upper) {
+      if (number === number) {
+        if (upper !== undefined) {
+          number = number <= upper ? number : upper;
+        }
+        if (lower !== undefined) {
+          number = number >= lower ? number : lower;
+        }
+      }
+      return number;
+    }
+
+    /**
+     * The base implementation of `_.clone` and `_.cloneDeep` which tracks
+     * traversed objects.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} bitmask The bitmask flags.
+     *  1 - Deep clone
+     *  2 - Flatten inherited properties
+     *  4 - Clone symbols
+     * @param {Function} [customizer] The function to customize cloning.
+     * @param {string} [key] The key of `value`.
+     * @param {Object} [object] The parent object of `value`.
+     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, bitmask, customizer, key, object, stack) {
+      var result,
+          isDeep = bitmask & CLONE_DEEP_FLAG,
+          isFlat = bitmask & CLONE_FLAT_FLAG,
+          isFull = bitmask & CLONE_SYMBOLS_FLAG;
+
+      if (customizer) {
+        result = object ? customizer(value, key, object, stack) : customizer(value);
+      }
+      if (result !== undefined) {
+        return result;
+      }
+      if (!isObject(value)) {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isArr) {
+        result = initCloneArray(value);
+        if (!isDeep) {
+          return copyArray(value, result);
+        }
+      } else {
+        var tag = getTag(value),
+            isFunc = tag == funcTag || tag == genTag;
+
+        if (isBuffer(value)) {
+          return cloneBuffer(value, isDeep);
+        }
+        if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+          result = (isFlat || isFunc) ? {} : initCloneObject(value);
+          if (!isDeep) {
+            return isFlat
+              ? copySymbolsIn(value, baseAssignIn(result, value))
+              : copySymbols(value, baseAssign(result, value));
+          }
+        } else {
+          if (!cloneableTags[tag]) {
+            return object ? value : {};
+          }
+          result = initCloneByTag(value, tag, baseClone, isDeep);
+        }
+      }
+      // Check for circular references and return its corresponding clone.
+      stack || (stack = new Stack);
+      var stacked = stack.get(value);
+      if (stacked) {
+        return stacked;
+      }
+      stack.set(value, result);
+
+      var keysFunc = isFull
+        ? (isFlat ? getAllKeysIn : getAllKeys)
+        : (isFlat ? keysIn : keys);
+
+      var props = isArr ? undefined : keysFunc(value);
+      arrayEach(props || value, function(subValue, key) {
+        if (props) {
+          key = subValue;
+          subValue = value[key];
+        }
+        // Recursively populate clone (susceptible to call stack limits).
+        assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.conforms` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {Function} Returns the new spec function.
+     */
+    function baseConforms(source) {
+      var props = keys(source);
+      return function(object) {
+        return baseConformsTo(object, source, props);
+      };
+    }
+
+    /**
+     * The base implementation of `_.conformsTo` which accepts `props` to check.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {boolean} Returns `true` if `object` conforms, else `false`.
+     */
+    function baseConformsTo(object, source, props) {
+      var length = props.length;
+      if (object == null) {
+        return !length;
+      }
+      object = Object(object);
+      while (length--) {
+        var key = props[length],
+            predicate = source[key],
+            value = object[key];
+
+        if ((value === undefined && !(key in object)) || !predicate(value)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * The base implementation of `_.delay` and `_.defer` which accepts `args`
+     * to provide to `func`.
+     *
+     * @private
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {Array} args The arguments to provide to `func`.
+     * @returns {number|Object} Returns the timer id or timeout object.
+     */
+    function baseDelay(func, wait, args) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * The base implementation of methods like `_.difference` without support
+     * for excluding multiple arrays or iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Array} values The values to exclude.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     */
+    function baseDifference(array, values, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          isCommon = true,
+          length = array.length,
+          result = [],
+          valuesLength = values.length;
+
+      if (!length) {
+        return result;
+      }
+      if (iteratee) {
+        values = arrayMap(values, baseUnary(iteratee));
+      }
+      if (comparator) {
+        includes = arrayIncludesWith;
+        isCommon = false;
+      }
+      else if (values.length >= LARGE_ARRAY_SIZE) {
+        includes = cacheHas;
+        isCommon = false;
+        values = new SetCache(values);
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee == null ? value : iteratee(value);
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var valuesIndex = valuesLength;
+          while (valuesIndex--) {
+            if (values[valuesIndex] === computed) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+        else if (!includes(values, computed, comparator)) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.forEach` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEach = createBaseEach(baseForOwn);
+
+    /**
+     * The base implementation of `_.forEachRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEachRight = createBaseEach(baseForOwnRight, true);
+
+    /**
+     * The base implementation of `_.every` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`
+     */
+    function baseEvery(collection, predicate) {
+      var result = true;
+      baseEach(collection, function(value, index, collection) {
+        result = !!predicate(value, index, collection);
+        return result;
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of methods like `_.max` and `_.min` which accepts a
+     * `comparator` to determine the extremum value.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The iteratee invoked per iteration.
+     * @param {Function} comparator The comparator used to compare values.
+     * @returns {*} Returns the extremum value.
+     */
+    function baseExtremum(array, iteratee, comparator) {
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        var value = array[index],
+            current = iteratee(value);
+
+        if (current != null && (computed === undefined
+              ? (current === current && !isSymbol(current))
+              : comparator(current, computed)
+            )) {
+          var computed = current,
+              result = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.fill` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     */
+    function baseFill(array, value, start, end) {
+      var length = array.length;
+
+      start = toInteger(start);
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = (end === undefined || end > length) ? length : toInteger(end);
+      if (end < 0) {
+        end += length;
+      }
+      end = start > end ? 0 : toLength(end);
+      while (start < end) {
+        array[start++] = value;
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.filter` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     */
+    function baseFilter(collection, predicate) {
+      var result = [];
+      baseEach(collection, function(value, index, collection) {
+        if (predicate(value, index, collection)) {
+          result.push(value);
+        }
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` with support for restricting flattening.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {number} depth The maximum recursion depth.
+     * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
+     * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
+     * @param {Array} [result=[]] The initial result value.
+     * @returns {Array} Returns the new flattened array.
+     */
+    function baseFlatten(array, depth, predicate, isStrict, result) {
+      var index = -1,
+          length = array.length;
+
+      predicate || (predicate = isFlattenable);
+      result || (result = []);
+
+      while (++index < length) {
+        var value = array[index];
+        if (depth > 0 && predicate(value)) {
+          if (depth > 1) {
+            // Recursively flatten arrays (susceptible to call stack limits).
+            baseFlatten(value, depth - 1, predicate, isStrict, result);
+          } else {
+            arrayPush(result, value);
+          }
+        } else if (!isStrict) {
+          result[result.length] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `baseForOwn` which iterates over `object`
+     * properties returned by `keysFunc` and invokes `iteratee` for each property.
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseFor = createBaseFor();
+
+    /**
+     * This function is like `baseFor` except that it iterates over properties
+     * in the opposite order.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseForRight = createBaseFor(true);
+
+    /**
+     * The base implementation of `_.forOwn` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwn(object, iteratee) {
+      return object && baseFor(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwnRight(object, iteratee) {
+      return object && baseForRight(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.functions` which creates an array of
+     * `object` function property names filtered from `props`.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Array} props The property names to filter.
+     * @returns {Array} Returns the function names.
+     */
+    function baseFunctions(object, props) {
+      return arrayFilter(props, function(key) {
+        return isFunction(object[key]);
+      });
+    }
+
+    /**
+     * The base implementation of `_.get` without support for default values.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseGet(object, path) {
+      path = castPath(path, object);
+
+      var index = 0,
+          length = path.length;
+
+      while (object != null && index < length) {
+        object = object[toKey(path[index++])];
+      }
+      return (index && index == length) ? object : undefined;
+    }
+
+    /**
+     * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
+     * `keysFunc` and `symbolsFunc` to get the enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @param {Function} symbolsFunc The function to get the symbols of `object`.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function baseGetAllKeys(object, keysFunc, symbolsFunc) {
+      var result = keysFunc(object);
+      return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
+    }
+
+    /**
+     * The base implementation of `getTag` without fallbacks for buggy environments.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the `toStringTag`.
+     */
+    function baseGetTag(value) {
+      if (value == null) {
+        return value === undefined ? undefinedTag : nullTag;
+      }
+      return (symToStringTag && symToStringTag in Object(value))
+        ? getRawTag(value)
+        : objectToString(value);
+    }
+
+    /**
+     * The base implementation of `_.gt` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`,
+     *  else `false`.
+     */
+    function baseGt(value, other) {
+      return value > other;
+    }
+
+    /**
+     * The base implementation of `_.has` without support for deep paths.
+     *
+     * @private
+     * @param {Object} [object] The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHas(object, key) {
+      return object != null && hasOwnProperty.call(object, key);
+    }
+
+    /**
+     * The base implementation of `_.hasIn` without support for deep paths.
+     *
+     * @private
+     * @param {Object} [object] The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHasIn(object, key) {
+      return object != null && key in Object(object);
+    }
+
+    /**
+     * The base implementation of `_.inRange` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {number} number The number to check.
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+     */
+    function baseInRange(number, start, end) {
+      return number >= nativeMin(start, end) && number < nativeMax(start, end);
+    }
+
+    /**
+     * The base implementation of methods like `_.intersection`, without support
+     * for iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of shared values.
+     */
+    function baseIntersection(arrays, iteratee, comparator) {
+      var includes = comparator ? arrayIncludesWith : arrayIncludes,
+          length = arrays[0].length,
+          othLength = arrays.length,
+          othIndex = othLength,
+          caches = Array(othLength),
+          maxLength = Infinity,
+          result = [];
+
+      while (othIndex--) {
+        var array = arrays[othIndex];
+        if (othIndex && iteratee) {
+          array = arrayMap(array, baseUnary(iteratee));
+        }
+        maxLength = nativeMin(array.length, maxLength);
+        caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
+          ? new SetCache(othIndex && array)
+          : undefined;
+      }
+      array = arrays[0];
+
+      var index = -1,
+          seen = caches[0];
+
+      outer:
+      while (++index < length && result.length < maxLength) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (!(seen
+              ? cacheHas(seen, computed)
+              : includes(result, computed, comparator)
+            )) {
+          othIndex = othLength;
+          while (--othIndex) {
+            var cache = caches[othIndex];
+            if (!(cache
+                  ? cacheHas(cache, computed)
+                  : includes(arrays[othIndex], computed, comparator))
+                ) {
+              continue outer;
+            }
+          }
+          if (seen) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.invert` and `_.invertBy` which inverts
+     * `object` with values transformed by `iteratee` and set by `setter`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform values.
+     * @param {Object} accumulator The initial inverted object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseInverter(object, setter, iteratee, accumulator) {
+      baseForOwn(object, function(value, key, object) {
+        setter(accumulator, iteratee(value), key, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.invoke` without support for individual
+     * method arguments.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {Array} args The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     */
+    function baseInvoke(object, path, args) {
+      path = castPath(path, object);
+      object = parent(object, path);
+      var func = object == null ? object : object[toKey(last(path))];
+      return func == null ? undefined : apply(func, object, args);
+    }
+
+    /**
+     * The base implementation of `_.isArguments`.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+     */
+    function baseIsArguments(value) {
+      return isObjectLike(value) && baseGetTag(value) == argsTag;
+    }
+
+    /**
+     * The base implementation of `_.isArrayBuffer` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
+     */
+    function baseIsArrayBuffer(value) {
+      return isObjectLike(value) && baseGetTag(value) == arrayBufferTag;
+    }
+
+    /**
+     * The base implementation of `_.isDate` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
+     */
+    function baseIsDate(value) {
+      return isObjectLike(value) && baseGetTag(value) == dateTag;
+    }
+
+    /**
+     * The base implementation of `_.isEqual` which supports partial comparisons
+     * and tracks traversed objects.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {boolean} bitmask The bitmask flags.
+     *  1 - Unordered comparison
+     *  2 - Partial comparison
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(value, other, bitmask, customizer, stack) {
+      if (value === other) {
+        return true;
+      }
+      if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {
+        return value !== value && other !== other;
+      }
+      return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
+    }
+
+    /**
+     * A specialized version of `baseIsEqual` for arrays and objects which performs
+     * deep comparisons and tracks traversed objects enabling objects with circular
+     * references to be compared.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
+      var objIsArr = isArray(object),
+          othIsArr = isArray(other),
+          objTag = objIsArr ? arrayTag : getTag(object),
+          othTag = othIsArr ? arrayTag : getTag(other);
+
+      objTag = objTag == argsTag ? objectTag : objTag;
+      othTag = othTag == argsTag ? objectTag : othTag;
+
+      var objIsObj = objTag == objectTag,
+          othIsObj = othTag == objectTag,
+          isSameTag = objTag == othTag;
+
+      if (isSameTag && isBuffer(object)) {
+        if (!isBuffer(other)) {
+          return false;
+        }
+        objIsArr = true;
+        objIsObj = false;
+      }
+      if (isSameTag && !objIsObj) {
+        stack || (stack = new Stack);
+        return (objIsArr || isTypedArray(object))
+          ? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
+          : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
+      }
+      if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
+        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+        if (objIsWrapped || othIsWrapped) {
+          var objUnwrapped = objIsWrapped ? object.value() : object,
+              othUnwrapped = othIsWrapped ? other.value() : other;
+
+          stack || (stack = new Stack);
+          return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
+        }
+      }
+      if (!isSameTag) {
+        return false;
+      }
+      stack || (stack = new Stack);
+      return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
+    }
+
+    /**
+     * The base implementation of `_.isMap` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a map, else `false`.
+     */
+    function baseIsMap(value) {
+      return isObjectLike(value) && getTag(value) == mapTag;
+    }
+
+    /**
+     * The base implementation of `_.isMatch` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Array} matchData The property names, values, and compare flags to match.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     */
+    function baseIsMatch(object, source, matchData, customizer) {
+      var index = matchData.length,
+          length = index,
+          noCustomizer = !customizer;
+
+      if (object == null) {
+        return !length;
+      }
+      object = Object(object);
+      while (index--) {
+        var data = matchData[index];
+        if ((noCustomizer && data[2])
+              ? data[1] !== object[data[0]]
+              : !(data[0] in object)
+            ) {
+          return false;
+        }
+      }
+      while (++index < length) {
+        data = matchData[index];
+        var key = data[0],
+            objValue = object[key],
+            srcValue = data[1];
+
+        if (noCustomizer && data[2]) {
+          if (objValue === undefined && !(key in object)) {
+            return false;
+          }
+        } else {
+          var stack = new Stack;
+          if (customizer) {
+            var result = customizer(objValue, srcValue, key, object, source, stack);
+          }
+          if (!(result === undefined
+                ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack)
+                : result
+              )) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * The base implementation of `_.isNative` without bad shim checks.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a native function,
+     *  else `false`.
+     */
+    function baseIsNative(value) {
+      if (!isObject(value) || isMasked(value)) {
+        return false;
+      }
+      var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
+      return pattern.test(toSource(value));
+    }
+
+    /**
+     * The base implementation of `_.isRegExp` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
+     */
+    function baseIsRegExp(value) {
+      return isObjectLike(value) && baseGetTag(value) == regexpTag;
+    }
+
+    /**
+     * The base implementation of `_.isSet` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a set, else `false`.
+     */
+    function baseIsSet(value) {
+      return isObjectLike(value) && getTag(value) == setTag;
+    }
+
+    /**
+     * The base implementation of `_.isTypedArray` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+     */
+    function baseIsTypedArray(value) {
+      return isObjectLike(value) &&
+        isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
+    }
+
+    /**
+     * The base implementation of `_.iteratee`.
+     *
+     * @private
+     * @param {*} [value=_.identity] The value to convert to an iteratee.
+     * @returns {Function} Returns the iteratee.
+     */
+    function baseIteratee(value) {
+      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
+      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
+      if (typeof value == 'function') {
+        return value;
+      }
+      if (value == null) {
+        return identity;
+      }
+      if (typeof value == 'object') {
+        return isArray(value)
+          ? baseMatchesProperty(value[0], value[1])
+          : baseMatches(value);
+      }
+      return property(value);
+    }
+
+    /**
+     * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeys(object) {
+      if (!isPrototype(object)) {
+        return nativeKeys(object);
+      }
+      var result = [];
+      for (var key in Object(object)) {
+        if (hasOwnProperty.call(object, key) && key != 'constructor') {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeysIn(object) {
+      if (!isObject(object)) {
+        return nativeKeysIn(object);
+      }
+      var isProto = isPrototype(object),
+          result = [];
+
+      for (var key in object) {
+        if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.lt` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`,
+     *  else `false`.
+     */
+    function baseLt(value, other) {
+      return value < other;
+    }
+
+    /**
+     * The base implementation of `_.map` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     */
+    function baseMap(collection, iteratee) {
+      var index = -1,
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value, key, collection) {
+        result[++index] = iteratee(value, key, collection);
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.matches` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new spec function.
+     */
+    function baseMatches(source) {
+      var matchData = getMatchData(source);
+      if (matchData.length == 1 && matchData[0][2]) {
+        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
+      }
+      return function(object) {
+        return object === source || baseIsMatch(object, source, matchData);
+      };
+    }
+
+    /**
+     * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
+     *
+     * @private
+     * @param {string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new spec function.
+     */
+    function baseMatchesProperty(path, srcValue) {
+      if (isKey(path) && isStrictComparable(srcValue)) {
+        return matchesStrictComparable(toKey(path), srcValue);
+      }
+      return function(object) {
+        var objValue = get(object, path);
+        return (objValue === undefined && objValue === srcValue)
+          ? hasIn(object, path)
+          : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
+      };
+    }
+
+    /**
+     * The base implementation of `_.merge` without support for multiple sources.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} [customizer] The function to customize merged values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMerge(object, source, srcIndex, customizer, stack) {
+      if (object === source) {
+        return;
+      }
+      baseFor(source, function(srcValue, key) {
+        if (isObject(srcValue)) {
+          stack || (stack = new Stack);
+          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+        }
+        else {
+          var newValue = customizer
+            ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+            : undefined;
+
+          if (newValue === undefined) {
+            newValue = srcValue;
+          }
+          assignMergeValue(object, key, newValue);
+        }
+      }, keysIn);
+    }
+
+    /**
+     * A specialized version of `baseMerge` for arrays and objects which performs
+     * deep merges and tracks traversed objects enabling objects with circular
+     * references to be merged.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {string} key The key of the value to merge.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} mergeFunc The function to merge values.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+      var objValue = object[key],
+          srcValue = source[key],
+          stacked = stack.get(srcValue);
+
+      if (stacked) {
+        assignMergeValue(object, key, stacked);
+        return;
+      }
+      var newValue = customizer
+        ? customizer(objValue, srcValue, (key + ''), object, source, stack)
+        : undefined;
+
+      var isCommon = newValue === undefined;
+
+      if (isCommon) {
+        var isArr = isArray(srcValue),
+            isBuff = !isArr && isBuffer(srcValue),
+            isTyped = !isArr && !isBuff && isTypedArray(srcValue);
+
+        newValue = srcValue;
+        if (isArr || isBuff || isTyped) {
+          if (isArray(objValue)) {
+            newValue = objValue;
+          }
+          else if (isArrayLikeObject(objValue)) {
+            newValue = copyArray(objValue);
+          }
+          else if (isBuff) {
+            isCommon = false;
+            newValue = cloneBuffer(srcValue, true);
+          }
+          else if (isTyped) {
+            isCommon = false;
+            newValue = cloneTypedArray(srcValue, true);
+          }
+          else {
+            newValue = [];
+          }
+        }
+        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+          newValue = objValue;
+          if (isArguments(objValue)) {
+            newValue = toPlainObject(objValue);
+          }
+          else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
+            newValue = initCloneObject(srcValue);
+          }
+        }
+        else {
+          isCommon = false;
+        }
+      }
+      if (isCommon) {
+        // Recursively merge objects and arrays (susceptible to call stack limits).
+        stack.set(srcValue, newValue);
+        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+        stack['delete'](srcValue);
+      }
+      assignMergeValue(object, key, newValue);
+    }
+
+    /**
+     * The base implementation of `_.nth` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {number} n The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     */
+    function baseNth(array, n) {
+      var length = array.length;
+      if (!length) {
+        return;
+      }
+      n += n < 0 ? length : 0;
+      return isIndex(n, length) ? array[n] : undefined;
+    }
+
+    /**
+     * The base implementation of `_.orderBy` without param guards.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+     * @param {string[]} orders The sort orders of `iteratees`.
+     * @returns {Array} Returns the new sorted array.
+     */
+    function baseOrderBy(collection, iteratees, orders) {
+      var index = -1;
+      iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee()));
+
+      var result = baseMap(collection, function(value, key, collection) {
+        var criteria = arrayMap(iteratees, function(iteratee) {
+          return iteratee(value);
+        });
+        return { 'criteria': criteria, 'index': ++index, 'value': value };
+      });
+
+      return baseSortBy(result, function(object, other) {
+        return compareMultiple(object, other, orders);
+      });
+    }
+
+    /**
+     * The base implementation of `_.pick` without support for individual
+     * property identifiers.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {string[]} paths The property paths to pick.
+     * @returns {Object} Returns the new object.
+     */
+    function basePick(object, paths) {
+      return basePickBy(object, paths, function(value, path) {
+        return hasIn(object, path);
+      });
+    }
+
+    /**
+     * The base implementation of  `_.pickBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {string[]} paths The property paths to pick.
+     * @param {Function} predicate The function invoked per property.
+     * @returns {Object} Returns the new object.
+     */
+    function basePickBy(object, paths, predicate) {
+      var index = -1,
+          length = paths.length,
+          result = {};
+
+      while (++index < length) {
+        var path = paths[index],
+            value = baseGet(object, path);
+
+        if (predicate(value, path)) {
+          baseSet(result, castPath(path, object), value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseProperty` which supports deep paths.
+     *
+     * @private
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new accessor function.
+     */
+    function basePropertyDeep(path) {
+      return function(object) {
+        return baseGet(object, path);
+      };
+    }
+
+    /**
+     * The base implementation of `_.pullAllBy` without support for iteratee
+     * shorthands.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAll(array, values, iteratee, comparator) {
+      var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+          index = -1,
+          length = values.length,
+          seen = array;
+
+      if (array === values) {
+        values = copyArray(values);
+      }
+      if (iteratee) {
+        seen = arrayMap(array, baseUnary(iteratee));
+      }
+      while (++index < length) {
+        var fromIndex = 0,
+            value = values[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+          if (seen !== array) {
+            splice.call(seen, fromIndex, 1);
+          }
+          splice.call(array, fromIndex, 1);
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.pullAt` without support for individual
+     * indexes or capturing the removed elements.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {number[]} indexes The indexes of elements to remove.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAt(array, indexes) {
+      var length = array ? indexes.length : 0,
+          lastIndex = length - 1;
+
+      while (length--) {
+        var index = indexes[length];
+        if (length == lastIndex || index !== previous) {
+          var previous = index;
+          if (isIndex(index)) {
+            splice.call(array, index, 1);
+          } else {
+            baseUnset(array, index);
+          }
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.random` without support for returning
+     * floating-point numbers.
+     *
+     * @private
+     * @param {number} lower The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the random number.
+     */
+    function baseRandom(lower, upper) {
+      return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
+    }
+
+    /**
+     * The base implementation of `_.range` and `_.rangeRight` which doesn't
+     * coerce arguments.
+     *
+     * @private
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} step The value to increment or decrement by.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the range of numbers.
+     */
+    function baseRange(start, end, step, fromRight) {
+      var index = -1,
+          length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+          result = Array(length);
+
+      while (length--) {
+        result[fromRight ? length : ++index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.repeat` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {string} string The string to repeat.
+     * @param {number} n The number of times to repeat the string.
+     * @returns {string} Returns the repeated string.
+     */
+    function baseRepeat(string, n) {
+      var result = '';
+      if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
+        return result;
+      }
+      // Leverage the exponentiation by squaring algorithm for a faster repeat.
+      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
+      do {
+        if (n % 2) {
+          result += string;
+        }
+        n = nativeFloor(n / 2);
+        if (n) {
+          string += string;
+        }
+      } while (n);
+
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.rest` which doesn't validate or coerce arguments.
+     *
+     * @private
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @returns {Function} Returns the new function.
+     */
+    function baseRest(func, start) {
+      return setToString(overRest(func, start, identity), func + '');
+    }
+
+    /**
+     * The base implementation of `_.sample`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to sample.
+     * @returns {*} Returns the random element.
+     */
+    function baseSample(collection) {
+      return arraySample(values(collection));
+    }
+
+    /**
+     * The base implementation of `_.sampleSize` without param guards.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to sample.
+     * @param {number} n The number of elements to sample.
+     * @returns {Array} Returns the random elements.
+     */
+    function baseSampleSize(collection, n) {
+      var array = values(collection);
+      return shuffleSelf(array, baseClamp(n, 0, array.length));
+    }
+
+    /**
+     * The base implementation of `_.set`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseSet(object, path, value, customizer) {
+      if (!isObject(object)) {
+        return object;
+      }
+      path = castPath(path, object);
+
+      var index = -1,
+          length = path.length,
+          lastIndex = length - 1,
+          nested = object;
+
+      while (nested != null && ++index < length) {
+        var key = toKey(path[index]),
+            newValue = value;
+
+        if (index != lastIndex) {
+          var objValue = nested[key];
+          newValue = customizer ? customizer(objValue, key, nested) : undefined;
+          if (newValue === undefined) {
+            newValue = isObject(objValue)
+              ? objValue
+              : (isIndex(path[index + 1]) ? [] : {});
+          }
+        }
+        assignValue(nested, key, newValue);
+        nested = nested[key];
+      }
+      return object;
+    }
+
+    /**
+     * The base implementation of `setData` without support for hot loop shorting.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var baseSetData = !metaMap ? identity : function(func, data) {
+      metaMap.set(func, data);
+      return func;
+    };
+
+    /**
+     * The base implementation of `setToString` without support for hot loop shorting.
+     *
+     * @private
+     * @param {Function} func The function to modify.
+     * @param {Function} string The `toString` result.
+     * @returns {Function} Returns `func`.
+     */
+    var baseSetToString = !defineProperty ? identity : function(func, string) {
+      return defineProperty(func, 'toString', {
+        'configurable': true,
+        'enumerable': false,
+        'value': constant(string),
+        'writable': true
+      });
+    };
+
+    /**
+     * The base implementation of `_.shuffle`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     */
+    function baseShuffle(collection) {
+      return shuffleSelf(values(collection));
+    }
+
+    /**
+     * The base implementation of `_.slice` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseSlice(array, start, end) {
+      var index = -1,
+          length = array.length;
+
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = end > length ? length : end;
+      if (end < 0) {
+        end += length;
+      }
+      length = start > end ? 0 : ((end - start) >>> 0);
+      start >>>= 0;
+
+      var result = Array(length);
+      while (++index < length) {
+        result[index] = array[index + start];
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.some` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     */
+    function baseSome(collection, predicate) {
+      var result;
+
+      baseEach(collection, function(value, index, collection) {
+        result = predicate(value, index, collection);
+        return !result;
+      });
+      return !!result;
+    }
+
+    /**
+     * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
+     * performs a binary search of `array` to determine the index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndex(array, value, retHighest) {
+      var low = 0,
+          high = array == null ? low : array.length;
+
+      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+        while (low < high) {
+          var mid = (low + high) >>> 1,
+              computed = array[mid];
+
+          if (computed !== null && !isSymbol(computed) &&
+              (retHighest ? (computed <= value) : (computed < value))) {
+            low = mid + 1;
+          } else {
+            high = mid;
+          }
+        }
+        return high;
+      }
+      return baseSortedIndexBy(array, value, identity, retHighest);
+    }
+
+    /**
+     * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
+     * which invokes `iteratee` for `value` and each element of `array` to compute
+     * their sort ranking. The iteratee is invoked with one argument; (value).
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} iteratee The iteratee invoked per element.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndexBy(array, value, iteratee, retHighest) {
+      value = iteratee(value);
+
+      var low = 0,
+          high = array == null ? 0 : array.length,
+          valIsNaN = value !== value,
+          valIsNull = value === null,
+          valIsSymbol = isSymbol(value),
+          valIsUndefined = value === undefined;
+
+      while (low < high) {
+        var mid = nativeFloor((low + high) / 2),
+            computed = iteratee(array[mid]),
+            othIsDefined = computed !== undefined,
+            othIsNull = computed === null,
+            othIsReflexive = computed === computed,
+            othIsSymbol = isSymbol(computed);
+
+        if (valIsNaN) {
+          var setLow = retHighest || othIsReflexive;
+        } else if (valIsUndefined) {
+          setLow = othIsReflexive && (retHighest || othIsDefined);
+        } else if (valIsNull) {
+          setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
+        } else if (valIsSymbol) {
+          setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
+        } else if (othIsNull || othIsSymbol) {
+          setLow = false;
+        } else {
+          setLow = retHighest ? (computed <= value) : (computed < value);
+        }
+        if (setLow) {
+          low = mid + 1;
+        } else {
+          high = mid;
+        }
+      }
+      return nativeMin(high, MAX_ARRAY_INDEX);
+    }
+
+    /**
+     * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
+     * support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseSortedUniq(array, iteratee) {
+      var index = -1,
+          length = array.length,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        if (!index || !eq(computed, seen)) {
+          var seen = computed;
+          result[resIndex++] = value === 0 ? 0 : value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.toNumber` which doesn't ensure correct
+     * conversions of binary, hexadecimal, or octal string values.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {number} Returns the number.
+     */
+    function baseToNumber(value) {
+      if (typeof value == 'number') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return NAN;
+      }
+      return +value;
+    }
+
+    /**
+     * The base implementation of `_.toString` which doesn't convert nullish
+     * values to empty strings.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {string} Returns the string.
+     */
+    function baseToString(value) {
+      // Exit early for strings to avoid a performance hit in some environments.
+      if (typeof value == 'string') {
+        return value;
+      }
+      if (isArray(value)) {
+        // Recursively convert values (susceptible to call stack limits).
+        return arrayMap(value, baseToString) + '';
+      }
+      if (isSymbol(value)) {
+        return symbolToString ? symbolToString.call(value) : '';
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * The base implementation of `_.uniqBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseUniq(array, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          length = array.length,
+          isCommon = true,
+          result = [],
+          seen = result;
+
+      if (comparator) {
+        isCommon = false;
+        includes = arrayIncludesWith;
+      }
+      else if (length >= LARGE_ARRAY_SIZE) {
+        var set = iteratee ? null : createSet(array);
+        if (set) {
+          return setToArray(set);
+        }
+        isCommon = false;
+        includes = cacheHas;
+        seen = new SetCache;
+      }
+      else {
+        seen = iteratee ? [] : result;
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var seenIndex = seen.length;
+          while (seenIndex--) {
+            if (seen[seenIndex] === computed) {
+              continue outer;
+            }
+          }
+          if (iteratee) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+        else if (!includes(seen, computed, comparator)) {
+          if (seen !== result) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.unset`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The property path to unset.
+     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+     */
+    function baseUnset(object, path) {
+      path = castPath(path, object);
+      object = parent(object, path);
+      return object == null || delete object[toKey(last(path))];
+    }
+
+    /**
+     * The base implementation of `_.update`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to update.
+     * @param {Function} updater The function to produce the updated value.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseUpdate(object, path, updater, customizer) {
+      return baseSet(object, path, updater(baseGet(object, path)), customizer);
+    }
+
+    /**
+     * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
+     * without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {Function} predicate The function invoked per iteration.
+     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseWhile(array, predicate, isDrop, fromRight) {
+      var length = array.length,
+          index = fromRight ? length : -1;
+
+      while ((fromRight ? index-- : ++index < length) &&
+        predicate(array[index], index, array)) {}
+
+      return isDrop
+        ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+        : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+    }
+
+    /**
+     * The base implementation of `wrapperValue` which returns the result of
+     * performing a sequence of actions on the unwrapped `value`, where each
+     * successive action is supplied the return value of the previous.
+     *
+     * @private
+     * @param {*} value The unwrapped value.
+     * @param {Array} actions Actions to perform to resolve the unwrapped value.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseWrapperValue(value, actions) {
+      var result = value;
+      if (result instanceof LazyWrapper) {
+        result = result.value();
+      }
+      return arrayReduce(actions, function(result, action) {
+        return action.func.apply(action.thisArg, arrayPush([result], action.args));
+      }, result);
+    }
+
+    /**
+     * The base implementation of methods like `_.xor`, without support for
+     * iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of values.
+     */
+    function baseXor(arrays, iteratee, comparator) {
+      var length = arrays.length;
+      if (length < 2) {
+        return length ? baseUniq(arrays[0]) : [];
+      }
+      var index = -1,
+          result = Array(length);
+
+      while (++index < length) {
+        var array = arrays[index],
+            othIndex = -1;
+
+        while (++othIndex < length) {
+          if (othIndex != index) {
+            result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
+          }
+        }
+      }
+      return baseUniq(baseFlatten(result, 1), iteratee, comparator);
+    }
+
+    /**
+     * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
+     *
+     * @private
+     * @param {Array} props The property identifiers.
+     * @param {Array} values The property values.
+     * @param {Function} assignFunc The function to assign values.
+     * @returns {Object} Returns the new object.
+     */
+    function baseZipObject(props, values, assignFunc) {
+      var index = -1,
+          length = props.length,
+          valsLength = values.length,
+          result = {};
+
+      while (++index < length) {
+        var value = index < valsLength ? values[index] : undefined;
+        assignFunc(result, props[index], value);
+      }
+      return result;
+    }
+
+    /**
+     * Casts `value` to an empty array if it's not an array like object.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Array|Object} Returns the cast array-like object.
+     */
+    function castArrayLikeObject(value) {
+      return isArrayLikeObject(value) ? value : [];
+    }
+
+    /**
+     * Casts `value` to `identity` if it's not a function.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Function} Returns cast function.
+     */
+    function castFunction(value) {
+      return typeof value == 'function' ? value : identity;
+    }
+
+    /**
+     * Casts `value` to a path array if it's not one.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @param {Object} [object] The object to query keys on.
+     * @returns {Array} Returns the cast property path array.
+     */
+    function castPath(value, object) {
+      if (isArray(value)) {
+        return value;
+      }
+      return isKey(value, object) ? [value] : stringToPath(toString(value));
+    }
+
+    /**
+     * A `baseRest` alias which can be replaced with `identity` by module
+     * replacement plugins.
+     *
+     * @private
+     * @type {Function}
+     * @param {Function} func The function to apply a rest parameter to.
+     * @returns {Function} Returns the new function.
+     */
+    var castRest = baseRest;
+
+    /**
+     * Casts `array` to a slice if it's needed.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {number} start The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the cast slice.
+     */
+    function castSlice(array, start, end) {
+      var length = array.length;
+      end = end === undefined ? length : end;
+      return (!start && end >= length) ? array : baseSlice(array, start, end);
+    }
+
+    /**
+     * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
+     *
+     * @private
+     * @param {number|Object} id The timer id or timeout object of the timer to clear.
+     */
+    var clearTimeout = ctxClearTimeout || function(id) {
+      return root.clearTimeout(id);
+    };
+
+    /**
+     * Creates a clone of  `buffer`.
+     *
+     * @private
+     * @param {Buffer} buffer The buffer to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Buffer} Returns the cloned buffer.
+     */
+    function cloneBuffer(buffer, isDeep) {
+      if (isDeep) {
+        return buffer.slice();
+      }
+      var length = buffer.length,
+          result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
+
+      buffer.copy(result);
+      return result;
+    }
+
+    /**
+     * Creates a clone of `arrayBuffer`.
+     *
+     * @private
+     * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+     * @returns {ArrayBuffer} Returns the cloned array buffer.
+     */
+    function cloneArrayBuffer(arrayBuffer) {
+      var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+      new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+      return result;
+    }
+
+    /**
+     * Creates a clone of `dataView`.
+     *
+     * @private
+     * @param {Object} dataView The data view to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned data view.
+     */
+    function cloneDataView(dataView, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
+      return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
+    }
+
+    /**
+     * Creates a clone of `map`.
+     *
+     * @private
+     * @param {Object} map The map to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned map.
+     */
+    function cloneMap(map, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(mapToArray(map), CLONE_DEEP_FLAG) : mapToArray(map);
+      return arrayReduce(array, addMapEntry, new map.constructor);
+    }
+
+    /**
+     * Creates a clone of `regexp`.
+     *
+     * @private
+     * @param {Object} regexp The regexp to clone.
+     * @returns {Object} Returns the cloned regexp.
+     */
+    function cloneRegExp(regexp) {
+      var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+      result.lastIndex = regexp.lastIndex;
+      return result;
+    }
+
+    /**
+     * Creates a clone of `set`.
+     *
+     * @private
+     * @param {Object} set The set to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned set.
+     */
+    function cloneSet(set, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(setToArray(set), CLONE_DEEP_FLAG) : setToArray(set);
+      return arrayReduce(array, addSetEntry, new set.constructor);
+    }
+
+    /**
+     * Creates a clone of the `symbol` object.
+     *
+     * @private
+     * @param {Object} symbol The symbol object to clone.
+     * @returns {Object} Returns the cloned symbol object.
+     */
+    function cloneSymbol(symbol) {
+      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
+    }
+
+    /**
+     * Creates a clone of `typedArray`.
+     *
+     * @private
+     * @param {Object} typedArray The typed array to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned typed array.
+     */
+    function cloneTypedArray(typedArray, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+      return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+    }
+
+    /**
+     * Compares values to sort them in ascending order.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {number} Returns the sort order indicator for `value`.
+     */
+    function compareAscending(value, other) {
+      if (value !== other) {
+        var valIsDefined = value !== undefined,
+            valIsNull = value === null,
+            valIsReflexive = value === value,
+            valIsSymbol = isSymbol(value);
+
+        var othIsDefined = other !== undefined,
+            othIsNull = other === null,
+            othIsReflexive = other === other,
+            othIsSymbol = isSymbol(other);
+
+        if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
+            (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
+            (valIsNull && othIsDefined && othIsReflexive) ||
+            (!valIsDefined && othIsReflexive) ||
+            !valIsReflexive) {
+          return 1;
+        }
+        if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
+            (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
+            (othIsNull && valIsDefined && valIsReflexive) ||
+            (!othIsDefined && valIsReflexive) ||
+            !othIsReflexive) {
+          return -1;
+        }
+      }
+      return 0;
+    }
+
+    /**
+     * Used by `_.orderBy` to compare multiple properties of a value to another
+     * and stable sort them.
+     *
+     * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
+     * specify an order of "desc" for descending or "asc" for ascending sort order
+     * of corresponding values.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {boolean[]|string[]} orders The order to sort by for each property.
+     * @returns {number} Returns the sort order indicator for `object`.
+     */
+    function compareMultiple(object, other, orders) {
+      var index = -1,
+          objCriteria = object.criteria,
+          othCriteria = other.criteria,
+          length = objCriteria.length,
+          ordersLength = orders.length;
+
+      while (++index < length) {
+        var result = compareAscending(objCriteria[index], othCriteria[index]);
+        if (result) {
+          if (index >= ordersLength) {
+            return result;
+          }
+          var order = orders[index];
+          return result * (order == 'desc' ? -1 : 1);
+        }
+      }
+      // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+      // that causes it, under certain circumstances, to provide the same value for
+      // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+      // for more details.
+      //
+      // This also ensures a stable sort in V8 and other engines.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
+      return object.index - other.index;
+    }
+
+    /**
+     * Creates an array that is the composition of partially applied arguments,
+     * placeholders, and provided arguments into a single array of arguments.
+     *
+     * @private
+     * @param {Array} args The provided arguments.
+     * @param {Array} partials The arguments to prepend to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgs(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersLength = holders.length,
+          leftIndex = -1,
+          leftLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(leftLength + rangeLength),
+          isUncurried = !isCurried;
+
+      while (++leftIndex < leftLength) {
+        result[leftIndex] = partials[leftIndex];
+      }
+      while (++argsIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[holders[argsIndex]] = args[argsIndex];
+        }
+      }
+      while (rangeLength--) {
+        result[leftIndex++] = args[argsIndex++];
+      }
+      return result;
+    }
+
+    /**
+     * This function is like `composeArgs` except that the arguments composition
+     * is tailored for `_.partialRight`.
+     *
+     * @private
+     * @param {Array} args The provided arguments.
+     * @param {Array} partials The arguments to append to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgsRight(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersIndex = -1,
+          holdersLength = holders.length,
+          rightIndex = -1,
+          rightLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(rangeLength + rightLength),
+          isUncurried = !isCurried;
+
+      while (++argsIndex < rangeLength) {
+        result[argsIndex] = args[argsIndex];
+      }
+      var offset = argsIndex;
+      while (++rightIndex < rightLength) {
+        result[offset + rightIndex] = partials[rightIndex];
+      }
+      while (++holdersIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[offset + holders[holdersIndex]] = args[argsIndex++];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Copies the values of `source` to `array`.
+     *
+     * @private
+     * @param {Array} source The array to copy values from.
+     * @param {Array} [array=[]] The array to copy values to.
+     * @returns {Array} Returns `array`.
+     */
+    function copyArray(source, array) {
+      var index = -1,
+          length = source.length;
+
+      array || (array = Array(length));
+      while (++index < length) {
+        array[index] = source[index];
+      }
+      return array;
+    }
+
+    /**
+     * Copies properties of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy properties from.
+     * @param {Array} props The property identifiers to copy.
+     * @param {Object} [object={}] The object to copy properties to.
+     * @param {Function} [customizer] The function to customize copied values.
+     * @returns {Object} Returns `object`.
+     */
+    function copyObject(source, props, object, customizer) {
+      var isNew = !object;
+      object || (object = {});
+
+      var index = -1,
+          length = props.length;
+
+      while (++index < length) {
+        var key = props[index];
+
+        var newValue = customizer
+          ? customizer(object[key], source[key], key, object, source)
+          : undefined;
+
+        if (newValue === undefined) {
+          newValue = source[key];
+        }
+        if (isNew) {
+          baseAssignValue(object, key, newValue);
+        } else {
+          assignValue(object, key, newValue);
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Copies own symbols of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy symbols from.
+     * @param {Object} [object={}] The object to copy symbols to.
+     * @returns {Object} Returns `object`.
+     */
+    function copySymbols(source, object) {
+      return copyObject(source, getSymbols(source), object);
+    }
+
+    /**
+     * Copies own and inherited symbols of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy symbols from.
+     * @param {Object} [object={}] The object to copy symbols to.
+     * @returns {Object} Returns `object`.
+     */
+    function copySymbolsIn(source, object) {
+      return copyObject(source, getSymbolsIn(source), object);
+    }
+
+    /**
+     * Creates a function like `_.groupBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} [initializer] The accumulator object initializer.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter, initializer) {
+      return function(collection, iteratee) {
+        var func = isArray(collection) ? arrayAggregator : baseAggregator,
+            accumulator = initializer ? initializer() : {};
+
+        return func(collection, setter, getIteratee(iteratee, 2), accumulator);
+      };
+    }
+
+    /**
+     * Creates a function like `_.assign`.
+     *
+     * @private
+     * @param {Function} assigner The function to assign values.
+     * @returns {Function} Returns the new assigner function.
+     */
+    function createAssigner(assigner) {
+      return baseRest(function(object, sources) {
+        var index = -1,
+            length = sources.length,
+            customizer = length > 1 ? sources[length - 1] : undefined,
+            guard = length > 2 ? sources[2] : undefined;
+
+        customizer = (assigner.length > 3 && typeof customizer == 'function')
+          ? (length--, customizer)
+          : undefined;
+
+        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+          customizer = length < 3 ? undefined : customizer;
+          length = 1;
+        }
+        object = Object(object);
+        while (++index < length) {
+          var source = sources[index];
+          if (source) {
+            assigner(object, source, index, customizer);
+          }
+        }
+        return object;
+      });
+    }
+
+    /**
+     * Creates a `baseEach` or `baseEachRight` function.
+     *
+     * @private
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseEach(eachFunc, fromRight) {
+      return function(collection, iteratee) {
+        if (collection == null) {
+          return collection;
+        }
+        if (!isArrayLike(collection)) {
+          return eachFunc(collection, iteratee);
+        }
+        var length = collection.length,
+            index = fromRight ? length : -1,
+            iterable = Object(collection);
+
+        while ((fromRight ? index-- : ++index < length)) {
+          if (iteratee(iterable[index], index, iterable) === false) {
+            break;
+          }
+        }
+        return collection;
+      };
+    }
+
+    /**
+     * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseFor(fromRight) {
+      return function(object, iteratee, keysFunc) {
+        var index = -1,
+            iterable = Object(object),
+            props = keysFunc(object),
+            length = props.length;
+
+        while (length--) {
+          var key = props[fromRight ? length : ++index];
+          if (iteratee(iterable[key], key, iterable) === false) {
+            break;
+          }
+        }
+        return object;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the optional `this`
+     * binding of `thisArg`.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createBind(func, bitmask, thisArg) {
+      var isBind = bitmask & WRAP_BIND_FLAG,
+          Ctor = createCtor(func);
+
+      function wrapper() {
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return fn.apply(isBind ? thisArg : this, arguments);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.lowerFirst`.
+     *
+     * @private
+     * @param {string} methodName The name of the `String` case method to use.
+     * @returns {Function} Returns the new case function.
+     */
+    function createCaseFirst(methodName) {
+      return function(string) {
+        string = toString(string);
+
+        var strSymbols = hasUnicode(string)
+          ? stringToArray(string)
+          : undefined;
+
+        var chr = strSymbols
+          ? strSymbols[0]
+          : string.charAt(0);
+
+        var trailing = strSymbols
+          ? castSlice(strSymbols, 1).join('')
+          : string.slice(1);
+
+        return chr[methodName]() + trailing;
+      };
+    }
+
+    /**
+     * Creates a function like `_.camelCase`.
+     *
+     * @private
+     * @param {Function} callback The function to combine each word.
+     * @returns {Function} Returns the new compounder function.
+     */
+    function createCompounder(callback) {
+      return function(string) {
+        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
+      };
+    }
+
+    /**
+     * Creates a function that produces an instance of `Ctor` regardless of
+     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+     *
+     * @private
+     * @param {Function} Ctor The constructor to wrap.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCtor(Ctor) {
+      return function() {
+        // Use a `switch` statement to work with class constructors. See
+        // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+        // for more details.
+        var args = arguments;
+        switch (args.length) {
+          case 0: return new Ctor;
+          case 1: return new Ctor(args[0]);
+          case 2: return new Ctor(args[0], args[1]);
+          case 3: return new Ctor(args[0], args[1], args[2]);
+          case 4: return new Ctor(args[0], args[1], args[2], args[3]);
+          case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+          case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
+          case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+        }
+        var thisBinding = baseCreate(Ctor.prototype),
+            result = Ctor.apply(thisBinding, args);
+
+        // Mimic the constructor's `return` behavior.
+        // See https://es5.github.io/#x13.2.2 for more details.
+        return isObject(result) ? result : thisBinding;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to enable currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {number} arity The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCurry(func, bitmask, arity) {
+      var Ctor = createCtor(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            args = Array(length),
+            index = length,
+            placeholder = getHolder(wrapper);
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
+          ? []
+          : replaceHolders(args, placeholder);
+
+        length -= holders.length;
+        if (length < arity) {
+          return createRecurry(
+            func, bitmask, createHybrid, wrapper.placeholder, undefined,
+            args, holders, undefined, undefined, arity - length);
+        }
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return apply(fn, this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.find` or `_.findLast` function.
+     *
+     * @private
+     * @param {Function} findIndexFunc The function to find the collection index.
+     * @returns {Function} Returns the new find function.
+     */
+    function createFind(findIndexFunc) {
+      return function(collection, predicate, fromIndex) {
+        var iterable = Object(collection);
+        if (!isArrayLike(collection)) {
+          var iteratee = getIteratee(predicate, 3);
+          collection = keys(collection);
+          predicate = function(key) { return iteratee(iterable[key], key, iterable); };
+        }
+        var index = findIndexFunc(collection, predicate, fromIndex);
+        return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
+      };
+    }
+
+    /**
+     * Creates a `_.flow` or `_.flowRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new flow function.
+     */
+    function createFlow(fromRight) {
+      return flatRest(function(funcs) {
+        var length = funcs.length,
+            index = length,
+            prereq = LodashWrapper.prototype.thru;
+
+        if (fromRight) {
+          funcs.reverse();
+        }
+        while (index--) {
+          var func = funcs[index];
+          if (typeof func != 'function') {
+            throw new TypeError(FUNC_ERROR_TEXT);
+          }
+          if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+            var wrapper = new LodashWrapper([], true);
+          }
+        }
+        index = wrapper ? index : length;
+        while (++index < length) {
+          func = funcs[index];
+
+          var funcName = getFuncName(func),
+              data = funcName == 'wrapper' ? getData(func) : undefined;
+
+          if (data && isLaziable(data[0]) &&
+                data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) &&
+                !data[4].length && data[9] == 1
+              ) {
+            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+          } else {
+            wrapper = (func.length == 1 && isLaziable(func))
+              ? wrapper[funcName]()
+              : wrapper.thru(func);
+          }
+        }
+        return function() {
+          var args = arguments,
+              value = args[0];
+
+          if (wrapper && args.length == 1 && isArray(value)) {
+            return wrapper.plant(value).value();
+          }
+          var index = 0,
+              result = length ? funcs[index].apply(this, args) : value;
+
+          while (++index < length) {
+            result = funcs[index].call(this, result);
+          }
+          return result;
+        };
+      });
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with optional `this`
+     * binding of `thisArg`, partial application, and currying.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [partialsRight] The arguments to append to those provided
+     *  to the new function.
+     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+      var isAry = bitmask & WRAP_ARY_FLAG,
+          isBind = bitmask & WRAP_BIND_FLAG,
+          isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
+          isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
+          isFlip = bitmask & WRAP_FLIP_FLAG,
+          Ctor = isBindKey ? undefined : createCtor(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            args = Array(length),
+            index = length;
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        if (isCurried) {
+          var placeholder = getHolder(wrapper),
+              holdersCount = countHolders(args, placeholder);
+        }
+        if (partials) {
+          args = composeArgs(args, partials, holders, isCurried);
+        }
+        if (partialsRight) {
+          args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+        }
+        length -= holdersCount;
+        if (isCurried && length < arity) {
+          var newHolders = replaceHolders(args, placeholder);
+          return createRecurry(
+            func, bitmask, createHybrid, wrapper.placeholder, thisArg,
+            args, newHolders, argPos, ary, arity - length
+          );
+        }
+        var thisBinding = isBind ? thisArg : this,
+            fn = isBindKey ? thisBinding[func] : func;
+
+        length = args.length;
+        if (argPos) {
+          args = reorder(args, argPos);
+        } else if (isFlip && length > 1) {
+          args.reverse();
+        }
+        if (isAry && ary < length) {
+          args.length = ary;
+        }
+        if (this && this !== root && this instanceof wrapper) {
+          fn = Ctor || createCtor(fn);
+        }
+        return fn.apply(thisBinding, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.invertBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} toIteratee The function to resolve iteratees.
+     * @returns {Function} Returns the new inverter function.
+     */
+    function createInverter(setter, toIteratee) {
+      return function(object, iteratee) {
+        return baseInverter(object, setter, toIteratee(iteratee), {});
+      };
+    }
+
+    /**
+     * Creates a function that performs a mathematical operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @param {number} [defaultValue] The value used for `undefined` arguments.
+     * @returns {Function} Returns the new mathematical operation function.
+     */
+    function createMathOperation(operator, defaultValue) {
+      return function(value, other) {
+        var result;
+        if (value === undefined && other === undefined) {
+          return defaultValue;
+        }
+        if (value !== undefined) {
+          result = value;
+        }
+        if (other !== undefined) {
+          if (result === undefined) {
+            return other;
+          }
+          if (typeof value == 'string' || typeof other == 'string') {
+            value = baseToString(value);
+            other = baseToString(other);
+          } else {
+            value = baseToNumber(value);
+            other = baseToNumber(other);
+          }
+          result = operator(value, other);
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function like `_.over`.
+     *
+     * @private
+     * @param {Function} arrayFunc The function to iterate over iteratees.
+     * @returns {Function} Returns the new over function.
+     */
+    function createOver(arrayFunc) {
+      return flatRest(function(iteratees) {
+        iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
+        return baseRest(function(args) {
+          var thisArg = this;
+          return arrayFunc(iteratees, function(iteratee) {
+            return apply(iteratee, thisArg, args);
+          });
+        });
+      });
+    }
+
+    /**
+     * Creates the padding for `string` based on `length`. The `chars` string
+     * is truncated if the number of characters exceeds `length`.
+     *
+     * @private
+     * @param {number} length The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padding for `string`.
+     */
+    function createPadding(length, chars) {
+      chars = chars === undefined ? ' ' : baseToString(chars);
+
+      var charsLength = chars.length;
+      if (charsLength < 2) {
+        return charsLength ? baseRepeat(chars, length) : chars;
+      }
+      var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
+      return hasUnicode(chars)
+        ? castSlice(stringToArray(result), 0, length).join('')
+        : result.slice(0, length);
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the `this` binding
+     * of `thisArg` and `partials` prepended to the arguments it receives.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {Array} partials The arguments to prepend to those provided to
+     *  the new function.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createPartial(func, bitmask, thisArg, partials) {
+      var isBind = bitmask & WRAP_BIND_FLAG,
+          Ctor = createCtor(func);
+
+      function wrapper() {
+        var argsIndex = -1,
+            argsLength = arguments.length,
+            leftIndex = -1,
+            leftLength = partials.length,
+            args = Array(leftLength + argsLength),
+            fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+
+        while (++leftIndex < leftLength) {
+          args[leftIndex] = partials[leftIndex];
+        }
+        while (argsLength--) {
+          args[leftIndex++] = arguments[++argsIndex];
+        }
+        return apply(fn, isBind ? thisArg : this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.range` or `_.rangeRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new range function.
+     */
+    function createRange(fromRight) {
+      return function(start, end, step) {
+        if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
+          end = step = undefined;
+        }
+        // Ensure the sign of `-0` is preserved.
+        start = toFinite(start);
+        if (end === undefined) {
+          end = start;
+          start = 0;
+        } else {
+          end = toFinite(end);
+        }
+        step = step === undefined ? (start < end ? 1 : -1) : toFinite(step);
+        return baseRange(start, end, step, fromRight);
+      };
+    }
+
+    /**
+     * Creates a function that performs a relational operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @returns {Function} Returns the new relational operation function.
+     */
+    function createRelationalOperation(operator) {
+      return function(value, other) {
+        if (!(typeof value == 'string' && typeof other == 'string')) {
+          value = toNumber(value);
+          other = toNumber(other);
+        }
+        return operator(value, other);
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to continue currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {Function} wrapFunc The function to create the `func` wrapper.
+     * @param {*} placeholder The placeholder value.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
+      var isCurry = bitmask & WRAP_CURRY_FLAG,
+          newHolders = isCurry ? holders : undefined,
+          newHoldersRight = isCurry ? undefined : holders,
+          newPartials = isCurry ? partials : undefined,
+          newPartialsRight = isCurry ? undefined : partials;
+
+      bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG);
+      bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);
+
+      if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
+        bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
+      }
+      var newData = [
+        func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
+        newHoldersRight, argPos, ary, arity
+      ];
+
+      var result = wrapFunc.apply(undefined, newData);
+      if (isLaziable(func)) {
+        setData(result, newData);
+      }
+      result.placeholder = placeholder;
+      return setWrapToString(result, func, bitmask);
+    }
+
+    /**
+     * Creates a function like `_.round`.
+     *
+     * @private
+     * @param {string} methodName The name of the `Math` method to use when rounding.
+     * @returns {Function} Returns the new round function.
+     */
+    function createRound(methodName) {
+      var func = Math[methodName];
+      return function(number, precision) {
+        number = toNumber(number);
+        precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
+        if (precision) {
+          // Shift with exponential notation to avoid floating-point issues.
+          // See [MDN](https://mdn.io/round#Examples) for more details.
+          var pair = (toString(number) + 'e').split('e'),
+              value = func(pair[0] + 'e' + (+pair[1] + precision));
+
+          pair = (toString(value) + 'e').split('e');
+          return +(pair[0] + 'e' + (+pair[1] - precision));
+        }
+        return func(number);
+      };
+    }
+
+    /**
+     * Creates a set object of `values`.
+     *
+     * @private
+     * @param {Array} values The values to add to the set.
+     * @returns {Object} Returns the new set.
+     */
+    var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {
+      return new Set(values);
+    };
+
+    /**
+     * Creates a `_.toPairs` or `_.toPairsIn` function.
+     *
+     * @private
+     * @param {Function} keysFunc The function to get the keys of a given object.
+     * @returns {Function} Returns the new pairs function.
+     */
+    function createToPairs(keysFunc) {
+      return function(object) {
+        var tag = getTag(object);
+        if (tag == mapTag) {
+          return mapToArray(object);
+        }
+        if (tag == setTag) {
+          return setToPairs(object);
+        }
+        return baseToPairs(object, keysFunc(object));
+      };
+    }
+
+    /**
+     * Creates a function that either curries or invokes `func` with optional
+     * `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask flags.
+     *    1 - `_.bind`
+     *    2 - `_.bindKey`
+     *    4 - `_.curry` or `_.curryRight` of a bound function
+     *    8 - `_.curry`
+     *   16 - `_.curryRight`
+     *   32 - `_.partial`
+     *   64 - `_.partialRight`
+     *  128 - `_.rearg`
+     *  256 - `_.ary`
+     *  512 - `_.flip`
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to be partially applied.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+      var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
+      if (!isBindKey && typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var length = partials ? partials.length : 0;
+      if (!length) {
+        bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
+        partials = holders = undefined;
+      }
+      ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
+      arity = arity === undefined ? arity : toInteger(arity);
+      length -= holders ? holders.length : 0;
+
+      if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
+        var partialsRight = partials,
+            holdersRight = holders;
+
+        partials = holders = undefined;
+      }
+      var data = isBindKey ? undefined : getData(func);
+
+      var newData = [
+        func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
+        argPos, ary, arity
+      ];
+
+      if (data) {
+        mergeData(newData, data);
+      }
+      func = newData[0];
+      bitmask = newData[1];
+      thisArg = newData[2];
+      partials = newData[3];
+      holders = newData[4];
+      arity = newData[9] = newData[9] === undefined
+        ? (isBindKey ? 0 : func.length)
+        : nativeMax(newData[9] - length, 0);
+
+      if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
+        bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
+      }
+      if (!bitmask || bitmask == WRAP_BIND_FLAG) {
+        var result = createBind(func, bitmask, thisArg);
+      } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
+        result = createCurry(func, bitmask, arity);
+      } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
+        result = createPartial(func, bitmask, thisArg, partials);
+      } else {
+        result = createHybrid.apply(undefined, newData);
+      }
+      var setter = data ? baseSetData : setData;
+      return setWrapToString(setter(result, newData), func, bitmask);
+    }
+
+    /**
+     * Used by `_.defaults` to customize its `_.assignIn` use to assign properties
+     * of source objects to the destination object for all destination properties
+     * that resolve to `undefined`.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to assign.
+     * @param {Object} object The parent object of `objValue`.
+     * @returns {*} Returns the value to assign.
+     */
+    function customDefaultsAssignIn(objValue, srcValue, key, object) {
+      if (objValue === undefined ||
+          (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
+        return srcValue;
+      }
+      return objValue;
+    }
+
+    /**
+     * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source
+     * objects into destination objects that are passed thru.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to merge.
+     * @param {Object} object The parent object of `objValue`.
+     * @param {Object} source The parent object of `srcValue`.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     * @returns {*} Returns the value to assign.
+     */
+    function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
+      if (isObject(objValue) && isObject(srcValue)) {
+        // Recursively merge objects and arrays (susceptible to call stack limits).
+        stack.set(srcValue, objValue);
+        baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack);
+        stack['delete'](srcValue);
+      }
+      return objValue;
+    }
+
+    /**
+     * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain
+     * objects.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @param {string} key The key of the property to inspect.
+     * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`.
+     */
+    function customOmitClone(value) {
+      return isPlainObject(value) ? undefined : value;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for arrays with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Array} array The array to compare.
+     * @param {Array} other The other array to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} stack Tracks traversed `array` and `other` objects.
+     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+     */
+    function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
+      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
+          arrLength = array.length,
+          othLength = other.length;
+
+      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+        return false;
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(array);
+      if (stacked && stack.get(other)) {
+        return stacked == other;
+      }
+      var index = -1,
+          result = true,
+          seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;
+
+      stack.set(array, other);
+      stack.set(other, array);
+
+      // Ignore non-index properties.
+      while (++index < arrLength) {
+        var arrValue = array[index],
+            othValue = other[index];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, arrValue, index, other, array, stack)
+            : customizer(arrValue, othValue, index, array, other, stack);
+        }
+        if (compared !== undefined) {
+          if (compared) {
+            continue;
+          }
+          result = false;
+          break;
+        }
+        // Recursively compare arrays (susceptible to call stack limits).
+        if (seen) {
+          if (!arraySome(other, function(othValue, othIndex) {
+                if (!cacheHas(seen, othIndex) &&
+                    (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
+                  return seen.push(othIndex);
+                }
+              })) {
+            result = false;
+            break;
+          }
+        } else if (!(
+              arrValue === othValue ||
+                equalFunc(arrValue, othValue, bitmask, customizer, stack)
+            )) {
+          result = false;
+          break;
+        }
+      }
+      stack['delete'](array);
+      stack['delete'](other);
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for comparing objects of
+     * the same `toStringTag`.
+     *
+     * **Note:** This function only supports comparing values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {string} tag The `toStringTag` of the objects to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
+      switch (tag) {
+        case dataViewTag:
+          if ((object.byteLength != other.byteLength) ||
+              (object.byteOffset != other.byteOffset)) {
+            return false;
+          }
+          object = object.buffer;
+          other = other.buffer;
+
+        case arrayBufferTag:
+          if ((object.byteLength != other.byteLength) ||
+              !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+            return false;
+          }
+          return true;
+
+        case boolTag:
+        case dateTag:
+        case numberTag:
+          // Coerce booleans to `1` or `0` and dates to milliseconds.
+          // Invalid dates are coerced to `NaN`.
+          return eq(+object, +other);
+
+        case errorTag:
+          return object.name == other.name && object.message == other.message;
+
+        case regexpTag:
+        case stringTag:
+          // Coerce regexes to strings and treat strings, primitives and objects,
+          // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
+          // for more details.
+          return object == (other + '');
+
+        case mapTag:
+          var convert = mapToArray;
+
+        case setTag:
+          var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
+          convert || (convert = setToArray);
+
+          if (object.size != other.size && !isPartial) {
+            return false;
+          }
+          // Assume cyclic values are equal.
+          var stacked = stack.get(object);
+          if (stacked) {
+            return stacked == other;
+          }
+          bitmask |= COMPARE_UNORDERED_FLAG;
+
+          // Recursively compare objects (susceptible to call stack limits).
+          stack.set(object, other);
+          var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
+          stack['delete'](object);
+          return result;
+
+        case symbolTag:
+          if (symbolValueOf) {
+            return symbolValueOf.call(object) == symbolValueOf.call(other);
+          }
+      }
+      return false;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for objects with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
+      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
+          objProps = getAllKeys(object),
+          objLength = objProps.length,
+          othProps = getAllKeys(other),
+          othLength = othProps.length;
+
+      if (objLength != othLength && !isPartial) {
+        return false;
+      }
+      var index = objLength;
+      while (index--) {
+        var key = objProps[index];
+        if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
+          return false;
+        }
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(object);
+      if (stacked && stack.get(other)) {
+        return stacked == other;
+      }
+      var result = true;
+      stack.set(object, other);
+      stack.set(other, object);
+
+      var skipCtor = isPartial;
+      while (++index < objLength) {
+        key = objProps[index];
+        var objValue = object[key],
+            othValue = other[key];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, objValue, key, other, object, stack)
+            : customizer(objValue, othValue, key, object, other, stack);
+        }
+        // Recursively compare objects (susceptible to call stack limits).
+        if (!(compared === undefined
+              ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
+              : compared
+            )) {
+          result = false;
+          break;
+        }
+        skipCtor || (skipCtor = key == 'constructor');
+      }
+      if (result && !skipCtor) {
+        var objCtor = object.constructor,
+            othCtor = other.constructor;
+
+        // Non `Object` object instances with different constructors are not equal.
+        if (objCtor != othCtor &&
+            ('constructor' in object && 'constructor' in other) &&
+            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+          result = false;
+        }
+      }
+      stack['delete'](object);
+      stack['delete'](other);
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseRest` which flattens the rest array.
+     *
+     * @private
+     * @param {Function} func The function to apply a rest parameter to.
+     * @returns {Function} Returns the new function.
+     */
+    function flatRest(func) {
+      return setToString(overRest(func, undefined, flatten), func + '');
+    }
+
+    /**
+     * Creates an array of own enumerable property names and symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeys(object) {
+      return baseGetAllKeys(object, keys, getSymbols);
+    }
+
+    /**
+     * Creates an array of own and inherited enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeysIn(object) {
+      return baseGetAllKeys(object, keysIn, getSymbolsIn);
+    }
+
+    /**
+     * Gets metadata for `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {*} Returns the metadata for `func`.
+     */
+    var getData = !metaMap ? noop : function(func) {
+      return metaMap.get(func);
+    };
+
+    /**
+     * Gets the name of `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {string} Returns the function name.
+     */
+    function getFuncName(func) {
+      var result = (func.name + ''),
+          array = realNames[result],
+          length = hasOwnProperty.call(realNames, result) ? array.length : 0;
+
+      while (length--) {
+        var data = array[length],
+            otherFunc = data.func;
+        if (otherFunc == null || otherFunc == func) {
+          return data.name;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Gets the argument placeholder value for `func`.
+     *
+     * @private
+     * @param {Function} func The function to inspect.
+     * @returns {*} Returns the placeholder value.
+     */
+    function getHolder(func) {
+      var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
+      return object.placeholder;
+    }
+
+    /**
+     * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
+     * this function returns the custom method, otherwise it returns `baseIteratee`.
+     * If arguments are provided, the chosen function is invoked with them and
+     * its result is returned.
+     *
+     * @private
+     * @param {*} [value] The value to convert to an iteratee.
+     * @param {number} [arity] The arity of the created iteratee.
+     * @returns {Function} Returns the chosen function or its result.
+     */
+    function getIteratee() {
+      var result = lodash.iteratee || iteratee;
+      result = result === iteratee ? baseIteratee : result;
+      return arguments.length ? result(arguments[0], arguments[1]) : result;
+    }
+
+    /**
+     * Gets the data for `map`.
+     *
+     * @private
+     * @param {Object} map The map to query.
+     * @param {string} key The reference key.
+     * @returns {*} Returns the map data.
+     */
+    function getMapData(map, key) {
+      var data = map.__data__;
+      return isKeyable(key)
+        ? data[typeof key == 'string' ? 'string' : 'hash']
+        : data.map;
+    }
+
+    /**
+     * Gets the property names, values, and compare flags of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the match data of `object`.
+     */
+    function getMatchData(object) {
+      var result = keys(object),
+          length = result.length;
+
+      while (length--) {
+        var key = result[length],
+            value = object[key];
+
+        result[length] = [key, value, isStrictComparable(value)];
+      }
+      return result;
+    }
+
+    /**
+     * Gets the native function at `key` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {string} key The key of the method to get.
+     * @returns {*} Returns the function if it's native, else `undefined`.
+     */
+    function getNative(object, key) {
+      var value = getValue(object, key);
+      return baseIsNative(value) ? value : undefined;
+    }
+
+    /**
+     * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the raw `toStringTag`.
+     */
+    function getRawTag(value) {
+      var isOwn = hasOwnProperty.call(value, symToStringTag),
+          tag = value[symToStringTag];
+
+      try {
+        value[symToStringTag] = undefined;
+        var unmasked = true;
+      } catch (e) {}
+
+      var result = nativeObjectToString.call(value);
+      if (unmasked) {
+        if (isOwn) {
+          value[symToStringTag] = tag;
+        } else {
+          delete value[symToStringTag];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array of the own enumerable symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    var getSymbols = !nativeGetSymbols ? stubArray : function(object) {
+      if (object == null) {
+        return [];
+      }
+      object = Object(object);
+      return arrayFilter(nativeGetSymbols(object), function(symbol) {
+        return propertyIsEnumerable.call(object, symbol);
+      });
+    };
+
+    /**
+     * Creates an array of the own and inherited enumerable symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) {
+      var result = [];
+      while (object) {
+        arrayPush(result, getSymbols(object));
+        object = getPrototype(object);
+      }
+      return result;
+    };
+
+    /**
+     * Gets the `toStringTag` of `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the `toStringTag`.
+     */
+    var getTag = baseGetTag;
+
+    // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
+    if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
+        (Map && getTag(new Map) != mapTag) ||
+        (Promise && getTag(Promise.resolve()) != promiseTag) ||
+        (Set && getTag(new Set) != setTag) ||
+        (WeakMap && getTag(new WeakMap) != weakMapTag)) {
+      getTag = function(value) {
+        var result = baseGetTag(value),
+            Ctor = result == objectTag ? value.constructor : undefined,
+            ctorString = Ctor ? toSource(Ctor) : '';
+
+        if (ctorString) {
+          switch (ctorString) {
+            case dataViewCtorString: return dataViewTag;
+            case mapCtorString: return mapTag;
+            case promiseCtorString: return promiseTag;
+            case setCtorString: return setTag;
+            case weakMapCtorString: return weakMapTag;
+          }
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Gets the view, applying any `transforms` to the `start` and `end` positions.
+     *
+     * @private
+     * @param {number} start The start of the view.
+     * @param {number} end The end of the view.
+     * @param {Array} transforms The transformations to apply to the view.
+     * @returns {Object} Returns an object containing the `start` and `end`
+     *  positions of the view.
+     */
+    function getView(start, end, transforms) {
+      var index = -1,
+          length = transforms.length;
+
+      while (++index < length) {
+        var data = transforms[index],
+            size = data.size;
+
+        switch (data.type) {
+          case 'drop':      start += size; break;
+          case 'dropRight': end -= size; break;
+          case 'take':      end = nativeMin(end, start + size); break;
+          case 'takeRight': start = nativeMax(start, end - size); break;
+        }
+      }
+      return { 'start': start, 'end': end };
+    }
+
+    /**
+     * Extracts wrapper details from the `source` body comment.
+     *
+     * @private
+     * @param {string} source The source to inspect.
+     * @returns {Array} Returns the wrapper details.
+     */
+    function getWrapDetails(source) {
+      var match = source.match(reWrapDetails);
+      return match ? match[1].split(reSplitDetails) : [];
+    }
+
+    /**
+     * Checks if `path` exists on `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @param {Function} hasFunc The function to check properties.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     */
+    function hasPath(object, path, hasFunc) {
+      path = castPath(path, object);
+
+      var index = -1,
+          length = path.length,
+          result = false;
+
+      while (++index < length) {
+        var key = toKey(path[index]);
+        if (!(result = object != null && hasFunc(object, key))) {
+          break;
+        }
+        object = object[key];
+      }
+      if (result || ++index != length) {
+        return result;
+      }
+      length = object == null ? 0 : object.length;
+      return !!length && isLength(length) && isIndex(key, length) &&
+        (isArray(object) || isArguments(object));
+    }
+
+    /**
+     * Initializes an array clone.
+     *
+     * @private
+     * @param {Array} array The array to clone.
+     * @returns {Array} Returns the initialized clone.
+     */
+    function initCloneArray(array) {
+      var length = array.length,
+          result = array.constructor(length);
+
+      // Add properties assigned by `RegExp#exec`.
+      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+        result.index = array.index;
+        result.input = array.input;
+      }
+      return result;
+    }
+
+    /**
+     * Initializes an object clone.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneObject(object) {
+      return (typeof object.constructor == 'function' && !isPrototype(object))
+        ? baseCreate(getPrototype(object))
+        : {};
+    }
+
+    /**
+     * Initializes an object clone based on its `toStringTag`.
+     *
+     * **Note:** This function only supports cloning values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @param {string} tag The `toStringTag` of the object to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneByTag(object, tag, cloneFunc, isDeep) {
+      var Ctor = object.constructor;
+      switch (tag) {
+        case arrayBufferTag:
+          return cloneArrayBuffer(object);
+
+        case boolTag:
+        case dateTag:
+          return new Ctor(+object);
+
+        case dataViewTag:
+          return cloneDataView(object, isDeep);
+
+        case float32Tag: case float64Tag:
+        case int8Tag: case int16Tag: case int32Tag:
+        case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+          return cloneTypedArray(object, isDeep);
+
+        case mapTag:
+          return cloneMap(object, isDeep, cloneFunc);
+
+        case numberTag:
+        case stringTag:
+          return new Ctor(object);
+
+        case regexpTag:
+          return cloneRegExp(object);
+
+        case setTag:
+          return cloneSet(object, isDeep, cloneFunc);
+
+        case symbolTag:
+          return cloneSymbol(object);
+      }
+    }
+
+    /**
+     * Inserts wrapper `details` in a comment at the top of the `source` body.
+     *
+     * @private
+     * @param {string} source The source to modify.
+     * @returns {Array} details The details to insert.
+     * @returns {string} Returns the modified source.
+     */
+    function insertWrapDetails(source, details) {
+      var length = details.length;
+      if (!length) {
+        return source;
+      }
+      var lastIndex = length - 1;
+      details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
+      details = details.join(length > 2 ? ', ' : ' ');
+      return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
+    }
+
+    /**
+     * Checks if `value` is a flattenable `arguments` object or array.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+     */
+    function isFlattenable(value) {
+      return isArray(value) || isArguments(value) ||
+        !!(spreadableSymbol && value && value[spreadableSymbol]);
+    }
+
+    /**
+     * Checks if `value` is a valid array-like index.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+     */
+    function isIndex(value, length) {
+      length = length == null ? MAX_SAFE_INTEGER : length;
+      return !!length &&
+        (typeof value == 'number' || reIsUint.test(value)) &&
+        (value > -1 && value % 1 == 0 && value < length);
+    }
+
+    /**
+     * Checks if the given arguments are from an iteratee call.
+     *
+     * @private
+     * @param {*} value The potential iteratee value argument.
+     * @param {*} index The potential iteratee index or key argument.
+     * @param {*} object The potential iteratee object argument.
+     * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
+     *  else `false`.
+     */
+    function isIterateeCall(value, index, object) {
+      if (!isObject(object)) {
+        return false;
+      }
+      var type = typeof index;
+      if (type == 'number'
+            ? (isArrayLike(object) && isIndex(index, object.length))
+            : (type == 'string' && index in object)
+          ) {
+        return eq(object[index], value);
+      }
+      return false;
+    }
+
+    /**
+     * Checks if `value` is a property name and not a property path.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {Object} [object] The object to query keys on.
+     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+     */
+    function isKey(value, object) {
+      if (isArray(value)) {
+        return false;
+      }
+      var type = typeof value;
+      if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+          value == null || isSymbol(value)) {
+        return true;
+      }
+      return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+        (object != null && value in Object(object));
+    }
+
+    /**
+     * Checks if `value` is suitable for use as unique object key.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+     */
+    function isKeyable(value) {
+      var type = typeof value;
+      return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
+        ? (value !== '__proto__')
+        : (value === null);
+    }
+
+    /**
+     * Checks if `func` has a lazy counterpart.
+     *
+     * @private
+     * @param {Function} func The function to check.
+     * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
+     *  else `false`.
+     */
+    function isLaziable(func) {
+      var funcName = getFuncName(func),
+          other = lodash[funcName];
+
+      if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
+        return false;
+      }
+      if (func === other) {
+        return true;
+      }
+      var data = getData(other);
+      return !!data && func === data[0];
+    }
+
+    /**
+     * Checks if `func` has its source masked.
+     *
+     * @private
+     * @param {Function} func The function to check.
+     * @returns {boolean} Returns `true` if `func` is masked, else `false`.
+     */
+    function isMasked(func) {
+      return !!maskSrcKey && (maskSrcKey in func);
+    }
+
+    /**
+     * Checks if `func` is capable of being masked.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `func` is maskable, else `false`.
+     */
+    var isMaskable = coreJsData ? isFunction : stubFalse;
+
+    /**
+     * Checks if `value` is likely a prototype object.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+     */
+    function isPrototype(value) {
+      var Ctor = value && value.constructor,
+          proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+      return value === proto;
+    }
+
+    /**
+     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` if suitable for strict
+     *  equality comparisons, else `false`.
+     */
+    function isStrictComparable(value) {
+      return value === value && !isObject(value);
+    }
+
+    /**
+     * A specialized version of `matchesProperty` for source values suitable
+     * for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {string} key The key of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new spec function.
+     */
+    function matchesStrictComparable(key, srcValue) {
+      return function(object) {
+        if (object == null) {
+          return false;
+        }
+        return object[key] === srcValue &&
+          (srcValue !== undefined || (key in Object(object)));
+      };
+    }
+
+    /**
+     * A specialized version of `_.memoize` which clears the memoized function's
+     * cache when it exceeds `MAX_MEMOIZE_SIZE`.
+     *
+     * @private
+     * @param {Function} func The function to have its output memoized.
+     * @returns {Function} Returns the new memoized function.
+     */
+    function memoizeCapped(func) {
+      var result = memoize(func, function(key) {
+        if (cache.size === MAX_MEMOIZE_SIZE) {
+          cache.clear();
+        }
+        return key;
+      });
+
+      var cache = result.cache;
+      return result;
+    }
+
+    /**
+     * Merges the function metadata of `source` into `data`.
+     *
+     * Merging metadata reduces the number of wrappers used to invoke a function.
+     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+     * may be applied regardless of execution order. Methods like `_.ary` and
+     * `_.rearg` modify function arguments, making the order in which they are
+     * executed important, preventing the merging of metadata. However, we make
+     * an exception for a safe combined case where curried functions have `_.ary`
+     * and or `_.rearg` applied.
+     *
+     * @private
+     * @param {Array} data The destination metadata.
+     * @param {Array} source The source metadata.
+     * @returns {Array} Returns `data`.
+     */
+    function mergeData(data, source) {
+      var bitmask = data[1],
+          srcBitmask = source[1],
+          newBitmask = bitmask | srcBitmask,
+          isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG);
+
+      var isCombo =
+        ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) ||
+        ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) ||
+        ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG));
+
+      // Exit early if metadata can't be merged.
+      if (!(isCommon || isCombo)) {
+        return data;
+      }
+      // Use source `thisArg` if available.
+      if (srcBitmask & WRAP_BIND_FLAG) {
+        data[2] = source[2];
+        // Set when currying a bound function.
+        newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
+      }
+      // Compose partial arguments.
+      var value = source[3];
+      if (value) {
+        var partials = data[3];
+        data[3] = partials ? composeArgs(partials, value, source[4]) : value;
+        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
+      }
+      // Compose partial right arguments.
+      value = source[5];
+      if (value) {
+        partials = data[5];
+        data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
+        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
+      }
+      // Use source `argPos` if available.
+      value = source[7];
+      if (value) {
+        data[7] = value;
+      }
+      // Use source `ary` if it's smaller.
+      if (srcBitmask & WRAP_ARY_FLAG) {
+        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+      }
+      // Use source `arity` if one is not provided.
+      if (data[9] == null) {
+        data[9] = source[9];
+      }
+      // Use source `func` and merge bitmasks.
+      data[0] = source[0];
+      data[1] = newBitmask;
+
+      return data;
+    }
+
+    /**
+     * This function is like
+     * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+     * except that it includes inherited enumerable properties.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function nativeKeysIn(object) {
+      var result = [];
+      if (object != null) {
+        for (var key in Object(object)) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Converts `value` to a string using `Object.prototype.toString`.
+     *
+     * @private
+     * @param {*} value The value to convert.
+     * @returns {string} Returns the converted string.
+     */
+    function objectToString(value) {
+      return nativeObjectToString.call(value);
+    }
+
+    /**
+     * A specialized version of `baseRest` which transforms the rest array.
+     *
+     * @private
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @param {Function} transform The rest array transform.
+     * @returns {Function} Returns the new function.
+     */
+    function overRest(func, start, transform) {
+      start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
+      return function() {
+        var args = arguments,
+            index = -1,
+            length = nativeMax(args.length - start, 0),
+            array = Array(length);
+
+        while (++index < length) {
+          array[index] = args[start + index];
+        }
+        index = -1;
+        var otherArgs = Array(start + 1);
+        while (++index < start) {
+          otherArgs[index] = args[index];
+        }
+        otherArgs[start] = transform(array);
+        return apply(func, this, otherArgs);
+      };
+    }
+
+    /**
+     * Gets the parent value at `path` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array} path The path to get the parent value of.
+     * @returns {*} Returns the parent value.
+     */
+    function parent(object, path) {
+      return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
+    }
+
+    /**
+     * Reorder `array` according to the specified indexes where the element at
+     * the first index is assigned as the first element, the element at
+     * the second index is assigned as the second element, and so on.
+     *
+     * @private
+     * @param {Array} array The array to reorder.
+     * @param {Array} indexes The arranged array indexes.
+     * @returns {Array} Returns `array`.
+     */
+    function reorder(array, indexes) {
+      var arrLength = array.length,
+          length = nativeMin(indexes.length, arrLength),
+          oldArray = copyArray(array);
+
+      while (length--) {
+        var index = indexes[length];
+        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
+      }
+      return array;
+    }
+
+    /**
+     * Sets metadata for `func`.
+     *
+     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+     * period of time, it will trip its breaker and transition to an identity
+     * function to avoid garbage collection pauses in V8. See
+     * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
+     * for more details.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var setData = shortOut(baseSetData);
+
+    /**
+     * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
+     *
+     * @private
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @returns {number|Object} Returns the timer id or timeout object.
+     */
+    var setTimeout = ctxSetTimeout || function(func, wait) {
+      return root.setTimeout(func, wait);
+    };
+
+    /**
+     * Sets the `toString` method of `func` to return `string`.
+     *
+     * @private
+     * @param {Function} func The function to modify.
+     * @param {Function} string The `toString` result.
+     * @returns {Function} Returns `func`.
+     */
+    var setToString = shortOut(baseSetToString);
+
+    /**
+     * Sets the `toString` method of `wrapper` to mimic the source of `reference`
+     * with wrapper details in a comment at the top of the source body.
+     *
+     * @private
+     * @param {Function} wrapper The function to modify.
+     * @param {Function} reference The reference function.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @returns {Function} Returns `wrapper`.
+     */
+    function setWrapToString(wrapper, reference, bitmask) {
+      var source = (reference + '');
+      return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
+    }
+
+    /**
+     * Creates a function that'll short out and invoke `identity` instead
+     * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
+     * milliseconds.
+     *
+     * @private
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new shortable function.
+     */
+    function shortOut(func) {
+      var count = 0,
+          lastCalled = 0;
+
+      return function() {
+        var stamp = nativeNow(),
+            remaining = HOT_SPAN - (stamp - lastCalled);
+
+        lastCalled = stamp;
+        if (remaining > 0) {
+          if (++count >= HOT_COUNT) {
+            return arguments[0];
+          }
+        } else {
+          count = 0;
+        }
+        return func.apply(undefined, arguments);
+      };
+    }
+
+    /**
+     * A specialized version of `_.shuffle` which mutates and sets the size of `array`.
+     *
+     * @private
+     * @param {Array} array The array to shuffle.
+     * @param {number} [size=array.length] The size of `array`.
+     * @returns {Array} Returns `array`.
+     */
+    function shuffleSelf(array, size) {
+      var index = -1,
+          length = array.length,
+          lastIndex = length - 1;
+
+      size = size === undefined ? length : size;
+      while (++index < size) {
+        var rand = baseRandom(index, lastIndex),
+            value = array[rand];
+
+        array[rand] = array[index];
+        array[index] = value;
+      }
+      array.length = size;
+      return array;
+    }
+
+    /**
+     * Converts `string` to a property path array.
+     *
+     * @private
+     * @param {string} string The string to convert.
+     * @returns {Array} Returns the property path array.
+     */
+    var stringToPath = memoizeCapped(function(string) {
+      var result = [];
+      if (reLeadingDot.test(string)) {
+        result.push('');
+      }
+      string.replace(rePropName, function(match, number, quote, string) {
+        result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+      });
+      return result;
+    });
+
+    /**
+     * Converts `value` to a string key if it's not a string or symbol.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {string|symbol} Returns the key.
+     */
+    function toKey(value) {
+      if (typeof value == 'string' || isSymbol(value)) {
+        return value;
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * Converts `func` to its source code.
+     *
+     * @private
+     * @param {Function} func The function to convert.
+     * @returns {string} Returns the source code.
+     */
+    function toSource(func) {
+      if (func != null) {
+        try {
+          return funcToString.call(func);
+        } catch (e) {}
+        try {
+          return (func + '');
+        } catch (e) {}
+      }
+      return '';
+    }
+
+    /**
+     * Updates wrapper `details` based on `bitmask` flags.
+     *
+     * @private
+     * @returns {Array} details The details to modify.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @returns {Array} Returns `details`.
+     */
+    function updateWrapDetails(details, bitmask) {
+      arrayEach(wrapFlags, function(pair) {
+        var value = '_.' + pair[0];
+        if ((bitmask & pair[1]) && !arrayIncludes(details, value)) {
+          details.push(value);
+        }
+      });
+      return details.sort();
+    }
+
+    /**
+     * Creates a clone of `wrapper`.
+     *
+     * @private
+     * @param {Object} wrapper The wrapper to clone.
+     * @returns {Object} Returns the cloned wrapper.
+     */
+    function wrapperClone(wrapper) {
+      if (wrapper instanceof LazyWrapper) {
+        return wrapper.clone();
+      }
+      var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+      result.__actions__ = copyArray(wrapper.__actions__);
+      result.__index__  = wrapper.__index__;
+      result.__values__ = wrapper.__values__;
+      return result;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements split into groups the length of `size`.
+     * If `array` can't be split evenly, the final chunk will be the remaining
+     * elements.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to process.
+     * @param {number} [size=1] The length of each chunk
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the new array of chunks.
+     * @example
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 2);
+     * // => [['a', 'b'], ['c', 'd']]
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 3);
+     * // => [['a', 'b', 'c'], ['d']]
+     */
+    function chunk(array, size, guard) {
+      if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) {
+        size = 1;
+      } else {
+        size = nativeMax(toInteger(size), 0);
+      }
+      var length = array == null ? 0 : array.length;
+      if (!length || size < 1) {
+        return [];
+      }
+      var index = 0,
+          resIndex = 0,
+          result = Array(nativeCeil(length / size));
+
+      while (index < length) {
+        result[resIndex++] = baseSlice(array, index, (index += size));
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are falsey.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array == null ? 0 : array.length,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result[resIndex++] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates a new array concatenating `array` with any additional arrays
+     * and/or values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to concatenate.
+     * @param {...*} [values] The values to concatenate.
+     * @returns {Array} Returns the new concatenated array.
+     * @example
+     *
+     * var array = [1];
+     * var other = _.concat(array, 2, [3], [[4]]);
+     *
+     * console.log(other);
+     * // => [1, 2, 3, [4]]
+     *
+     * console.log(array);
+     * // => [1]
+     */
+    function concat() {
+      var length = arguments.length;
+      if (!length) {
+        return [];
+      }
+      var args = Array(length - 1),
+          array = arguments[0],
+          index = length;
+
+      while (index--) {
+        args[index - 1] = arguments[index];
+      }
+      return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
+    }
+
+    /**
+     * Creates an array of `array` values not included in the other given arrays
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons. The order and references of result values are
+     * determined by the first array.
+     *
+     * **Note:** Unlike `_.pullAll`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.without, _.xor
+     * @example
+     *
+     * _.difference([2, 1], [2, 3]);
+     * // => [1]
+     */
+    var difference = baseRest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `iteratee` which
+     * is invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. The order and references of result values are
+     * determined by the first array. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+     * // => [1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var differenceBy = baseRest(function(array, values) {
+      var iteratee = last(values);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `comparator`
+     * which is invoked to compare elements of `array` to `values`. The order and
+     * references of result values are determined by the first array. The comparator
+     * is invoked with two arguments: (arrVal, othVal).
+     *
+     * **Note:** Unlike `_.pullAllWith`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     *
+     * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }]
+     */
+    var differenceWith = baseRest(function(array, values) {
+      var comparator = last(values);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.drop([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.drop([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.drop([1, 2, 3], 5);
+     * // => []
+     *
+     * _.drop([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function drop(array, n, guard) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.dropRight([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.dropRight([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.dropRight([1, 2, 3], 5);
+     * // => []
+     *
+     * _.dropRight([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function dropRight(array, n, guard) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the end.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.dropRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropRightWhile(users, ['active', false]);
+     * // => objects for ['barney']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropRightWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the beginning.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.dropWhile(users, function(o) { return !o.active; });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropWhile(users, ['active', false]);
+     * // => objects for ['pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true)
+        : [];
+    }
+
+    /**
+     * Fills elements of `array` with `value` from `start` up to, but not
+     * including, `end`.
+     *
+     * **Note:** This method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Array
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.fill(array, 'a');
+     * console.log(array);
+     * // => ['a', 'a', 'a']
+     *
+     * _.fill(Array(3), 2);
+     * // => [2, 2, 2]
+     *
+     * _.fill([4, 6, 8, 10], '*', 1, 3);
+     * // => [4, '*', '*', 10]
+     */
+    function fill(array, value, start, end) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+        start = 0;
+        end = length;
+      }
+      return baseFill(array, value, start, end);
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.findIndex(users, function(o) { return o.user == 'barney'; });
+     * // => 0
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findIndex(users, { 'user': 'fred', 'active': false });
+     * // => 1
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findIndex(users, ['active', false]);
+     * // => 0
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findIndex(users, 'active');
+     * // => 2
+     */
+    function findIndex(array, predicate, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = fromIndex == null ? 0 : toInteger(fromIndex);
+      if (index < 0) {
+        index = nativeMax(length + index, 0);
+      }
+      return baseFindIndex(array, getIteratee(predicate, 3), index);
+    }
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=array.length-1] The index to search from.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+     * // => 2
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+     * // => 0
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findLastIndex(users, ['active', false]);
+     * // => 2
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findLastIndex(users, 'active');
+     * // => 0
+     */
+    function findLastIndex(array, predicate, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = length - 1;
+      if (fromIndex !== undefined) {
+        index = toInteger(fromIndex);
+        index = fromIndex < 0
+          ? nativeMax(length + index, 0)
+          : nativeMin(index, length - 1);
+      }
+      return baseFindIndex(array, getIteratee(predicate, 3), index, true);
+    }
+
+    /**
+     * Flattens `array` a single level deep.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, [3, [4]], 5]
+     */
+    function flatten(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseFlatten(array, 1) : [];
+    }
+
+    /**
+     * Recursively flattens `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flattenDeep([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, 3, 4, 5]
+     */
+    function flattenDeep(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseFlatten(array, INFINITY) : [];
+    }
+
+    /**
+     * Recursively flatten `array` up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.4.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * var array = [1, [2, [3, [4]], 5]];
+     *
+     * _.flattenDepth(array, 1);
+     * // => [1, 2, [3, [4]], 5]
+     *
+     * _.flattenDepth(array, 2);
+     * // => [1, 2, 3, [4], 5]
+     */
+    function flattenDepth(array, depth) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(array, depth);
+    }
+
+    /**
+     * The inverse of `_.toPairs`; this method returns an object composed
+     * from key-value `pairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} pairs The key-value pairs.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.fromPairs([['a', 1], ['b', 2]]);
+     * // => { 'a': 1, 'b': 2 }
+     */
+    function fromPairs(pairs) {
+      var index = -1,
+          length = pairs == null ? 0 : pairs.length,
+          result = {};
+
+      while (++index < length) {
+        var pair = pairs[index];
+        result[pair[0]] = pair[1];
+      }
+      return result;
+    }
+
+    /**
+     * Gets the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias first
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the first element of `array`.
+     * @example
+     *
+     * _.head([1, 2, 3]);
+     * // => 1
+     *
+     * _.head([]);
+     * // => undefined
+     */
+    function head(array) {
+      return (array && array.length) ? array[0] : undefined;
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found in `array`
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons. If `fromIndex` is negative, it's used as the
+     * offset from the end of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 1, 2], 2);
+     * // => 1
+     *
+     * // Search from the `fromIndex`.
+     * _.indexOf([1, 2, 1, 2], 2, 2);
+     * // => 3
+     */
+    function indexOf(array, value, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = fromIndex == null ? 0 : toInteger(fromIndex);
+      if (index < 0) {
+        index = nativeMax(length + index, 0);
+      }
+      return baseIndexOf(array, value, index);
+    }
+
+    /**
+     * Gets all but the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     */
+    function initial(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseSlice(array, 0, -1) : [];
+    }
+
+    /**
+     * Creates an array of unique values that are included in all given arrays
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons. The order and references of result values are
+     * determined by the first array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersection([2, 1], [2, 3]);
+     * // => [2]
+     */
+    var intersection = baseRest(function(arrays) {
+      var mapped = arrayMap(arrays, castArrayLikeObject);
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped)
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `iteratee`
+     * which is invoked for each element of each `arrays` to generate the criterion
+     * by which they're compared. The order and references of result values are
+     * determined by the first array. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+     * // => [2.1]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }]
+     */
+    var intersectionBy = baseRest(function(arrays) {
+      var iteratee = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      if (iteratee === last(mapped)) {
+        iteratee = undefined;
+      } else {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, getIteratee(iteratee, 2))
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `comparator`
+     * which is invoked to compare elements of `arrays`. The order and references
+     * of result values are determined by the first array. The comparator is
+     * invoked with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.intersectionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }]
+     */
+    var intersectionWith = baseRest(function(arrays) {
+      var comparator = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      if (comparator) {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Converts all elements in `array` into a string separated by `separator`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to convert.
+     * @param {string} [separator=','] The element separator.
+     * @returns {string} Returns the joined string.
+     * @example
+     *
+     * _.join(['a', 'b', 'c'], '~');
+     * // => 'a~b~c'
+     */
+    function join(array, separator) {
+      return array == null ? '' : nativeJoin.call(array, separator);
+    }
+
+    /**
+     * Gets the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the last element of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     */
+    function last(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? array[length - 1] : undefined;
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it iterates over elements of
+     * `array` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=array.length-1] The index to search from.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 1, 2], 2);
+     * // => 3
+     *
+     * // Search from the `fromIndex`.
+     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+     * // => 1
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = length;
+      if (fromIndex !== undefined) {
+        index = toInteger(fromIndex);
+        index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
+      }
+      return value === value
+        ? strictLastIndexOf(array, value, index)
+        : baseFindIndex(array, baseIsNaN, index, true);
+    }
+
+    /**
+     * Gets the element at index `n` of `array`. If `n` is negative, the nth
+     * element from the end is returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.11.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=0] The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'd'];
+     *
+     * _.nth(array, 1);
+     * // => 'b'
+     *
+     * _.nth(array, -2);
+     * // => 'c';
+     */
+    function nth(array, n) {
+      return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;
+    }
+
+    /**
+     * Removes all given values from `array` using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+     * to remove elements from an array by predicate.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...*} [values] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+     *
+     * _.pull(array, 'a', 'c');
+     * console.log(array);
+     * // => ['b', 'b']
+     */
+    var pull = baseRest(pullAll);
+
+    /**
+     * This method is like `_.pull` except that it accepts an array of values to remove.
+     *
+     * **Note:** Unlike `_.difference`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+     *
+     * _.pullAll(array, ['a', 'c']);
+     * console.log(array);
+     * // => ['b', 'b']
+     */
+    function pullAll(array, values) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values)
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `iteratee` which is
+     * invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. The iteratee is invoked with one argument: (value).
+     *
+     * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+     *
+     * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+     * console.log(array);
+     * // => [{ 'x': 2 }]
+     */
+    function pullAllBy(array, values, iteratee) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, getIteratee(iteratee, 2))
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `comparator` which
+     * is invoked to compare elements of `array` to `values`. The comparator is
+     * invoked with two arguments: (arrVal, othVal).
+     *
+     * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+     *
+     * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+     * console.log(array);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+     */
+    function pullAllWith(array, values, comparator) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, undefined, comparator)
+        : array;
+    }
+
+    /**
+     * Removes elements from `array` corresponding to `indexes` and returns an
+     * array of removed elements.
+     *
+     * **Note:** Unlike `_.at`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...(number|number[])} [indexes] The indexes of elements to remove.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'd'];
+     * var pulled = _.pullAt(array, [1, 3]);
+     *
+     * console.log(array);
+     * // => ['a', 'c']
+     *
+     * console.log(pulled);
+     * // => ['b', 'd']
+     */
+    var pullAt = flatRest(function(array, indexes) {
+      var length = array == null ? 0 : array.length,
+          result = baseAt(array, indexes);
+
+      basePullAt(array, arrayMap(indexes, function(index) {
+        return isIndex(index, length) ? +index : index;
+      }).sort(compareAscending));
+
+      return result;
+    });
+
+    /**
+     * Removes all elements from `array` that `predicate` returns truthy for
+     * and returns an array of the removed elements. The predicate is invoked
+     * with three arguments: (value, index, array).
+     *
+     * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+     * to pull elements from an array by value.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4];
+     * var evens = _.remove(array, function(n) {
+     *   return n % 2 == 0;
+     * });
+     *
+     * console.log(array);
+     * // => [1, 3]
+     *
+     * console.log(evens);
+     * // => [2, 4]
+     */
+    function remove(array, predicate) {
+      var result = [];
+      if (!(array && array.length)) {
+        return result;
+      }
+      var index = -1,
+          indexes = [],
+          length = array.length;
+
+      predicate = getIteratee(predicate, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (predicate(value, index, array)) {
+          result.push(value);
+          indexes.push(index);
+        }
+      }
+      basePullAt(array, indexes);
+      return result;
+    }
+
+    /**
+     * Reverses `array` so that the first element becomes the last, the second
+     * element becomes the second to last, and so on.
+     *
+     * **Note:** This method mutates `array` and is based on
+     * [`Array#reverse`](https://mdn.io/Array/reverse).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.reverse(array);
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function reverse(array) {
+      return array == null ? array : nativeReverse.call(array);
+    }
+
+    /**
+     * Creates a slice of `array` from `start` up to, but not including, `end`.
+     *
+     * **Note:** This method is used instead of
+     * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+     * returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function slice(array, start, end) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+        start = 0;
+        end = length;
+      }
+      else {
+        start = start == null ? 0 : toInteger(start);
+        end = end === undefined ? length : toInteger(end);
+      }
+      return baseSlice(array, start, end);
+    }
+
+    /**
+     * Uses a binary search to determine the lowest index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([30, 50], 40);
+     * // => 1
+     */
+    function sortedIndex(array, value) {
+      return baseSortedIndex(array, value);
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * var objects = [{ 'x': 4 }, { 'x': 5 }];
+     *
+     * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+     * // => 0
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedIndexBy(objects, { 'x': 4 }, 'x');
+     * // => 0
+     */
+    function sortedIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedIndexOf([4, 5, 5, 5, 6], 5);
+     * // => 1
+     */
+    function sortedIndexOf(array, value) {
+      var length = array == null ? 0 : array.length;
+      if (length) {
+        var index = baseSortedIndex(array, value);
+        if (index < length && eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it returns the highest
+     * index at which `value` should be inserted into `array` in order to
+     * maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedLastIndex([4, 5, 5, 5, 6], 5);
+     * // => 4
+     */
+    function sortedLastIndex(array, value) {
+      return baseSortedIndex(array, value, true);
+    }
+
+    /**
+     * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * var objects = [{ 'x': 4 }, { 'x': 5 }];
+     *
+     * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+     * // => 1
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
+     * // => 1
+     */
+    function sortedLastIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
+    }
+
+    /**
+     * This method is like `_.lastIndexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
+     * // => 3
+     */
+    function sortedLastIndexOf(array, value) {
+      var length = array == null ? 0 : array.length;
+      if (length) {
+        var index = baseSortedIndex(array, value, true) - 1;
+        if (eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.uniq` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniq([1, 1, 2]);
+     * // => [1, 2]
+     */
+    function sortedUniq(array) {
+      return (array && array.length)
+        ? baseSortedUniq(array)
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniqBy` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+     * // => [1.1, 2.3]
+     */
+    function sortedUniqBy(array, iteratee) {
+      return (array && array.length)
+        ? baseSortedUniq(array, getIteratee(iteratee, 2))
+        : [];
+    }
+
+    /**
+     * Gets all but the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.tail([1, 2, 3]);
+     * // => [2, 3]
+     */
+    function tail(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseSlice(array, 1, length) : [];
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.take([1, 2, 3]);
+     * // => [1]
+     *
+     * _.take([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.take([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.take([1, 2, 3], 0);
+     * // => []
+     */
+    function take(array, n, guard) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.takeRight([1, 2, 3]);
+     * // => [3]
+     *
+     * _.takeRight([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.takeRight([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.takeRight([1, 2, 3], 0);
+     * // => []
+     */
+    function takeRight(array, n, guard) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the end. Elements are
+     * taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.takeRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeRightWhile(users, ['active', false]);
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeRightWhile(users, 'active');
+     * // => []
+     */
+    function takeRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), false, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the beginning. Elements
+     * are taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.takeWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeWhile(users, ['active', false]);
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeWhile(users, 'active');
+     * // => []
+     */
+    function takeWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3))
+        : [];
+    }
+
+    /**
+     * Creates an array of unique values, in order, from all given arrays using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.union([2], [1, 2]);
+     * // => [2, 1]
+     */
+    var union = baseRest(function(arrays) {
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which uniqueness is computed. Result values are chosen from the first
+     * array in which the value occurs. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.unionBy([2.1], [1.2, 2.3], Math.floor);
+     * // => [2.1, 1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    var unionBy = baseRest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `comparator` which
+     * is invoked to compare elements of `arrays`. Result values are chosen from
+     * the first array in which the value occurs. The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.unionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var unionWith = baseRest(function(arrays) {
+      var comparator = last(arrays);
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
+    });
+
+    /**
+     * Creates a duplicate-free version of an array, using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons, in which only the first occurrence of each element
+     * is kept. The order of result values is determined by the order they occur
+     * in the array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniq([2, 1, 2]);
+     * // => [2, 1]
+     */
+    function uniq(array) {
+      return (array && array.length) ? baseUniq(array) : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * uniqueness is computed. The order of result values is determined by the
+     * order they occur in the array. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
+     * // => [2.1, 1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniqBy(array, iteratee) {
+      return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `comparator` which
+     * is invoked to compare elements of `array`. The order of result values is
+     * determined by the order they occur in the array.The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.uniqWith(objects, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+     */
+    function uniqWith(array, comparator) {
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      return (array && array.length) ? baseUniq(array, undefined, comparator) : [];
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts an array of grouped
+     * elements and creates an array regrouping the elements to their pre-zip
+     * configuration.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.2.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
+     * // => [['a', 1, true], ['b', 2, false]]
+     *
+     * _.unzip(zipped);
+     * // => [['a', 'b'], [1, 2], [true, false]]
+     */
+    function unzip(array) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var length = 0;
+      array = arrayFilter(array, function(group) {
+        if (isArrayLikeObject(group)) {
+          length = nativeMax(group.length, length);
+          return true;
+        }
+      });
+      return baseTimes(length, function(index) {
+        return arrayMap(array, baseProperty(index));
+      });
+    }
+
+    /**
+     * This method is like `_.unzip` except that it accepts `iteratee` to specify
+     * how regrouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @param {Function} [iteratee=_.identity] The function to combine
+     *  regrouped values.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+     * // => [[1, 10, 100], [2, 20, 200]]
+     *
+     * _.unzipWith(zipped, _.add);
+     * // => [3, 30, 300]
+     */
+    function unzipWith(array, iteratee) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var result = unzip(array);
+      if (iteratee == null) {
+        return result;
+      }
+      return arrayMap(result, function(group) {
+        return apply(iteratee, undefined, group);
+      });
+    }
+
+    /**
+     * Creates an array excluding all given values using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * **Note:** Unlike `_.pull`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...*} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.difference, _.xor
+     * @example
+     *
+     * _.without([2, 1, 2, 3], 1, 2);
+     * // => [3]
+     */
+    var without = baseRest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, values)
+        : [];
+    });
+
+    /**
+     * Creates an array of unique values that is the
+     * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+     * of the given arrays. The order of result values is determined by the order
+     * they occur in the arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.difference, _.without
+     * @example
+     *
+     * _.xor([2, 1], [2, 3]);
+     * // => [1, 3]
+     */
+    var xor = baseRest(function(arrays) {
+      return baseXor(arrayFilter(arrays, isArrayLikeObject));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which by which they're compared. The order of result values is determined
+     * by the order they occur in the arrays. The iteratee is invoked with one
+     * argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+     * // => [1.2, 3.4]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var xorBy = baseRest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `comparator` which is
+     * invoked to compare elements of `arrays`. The order of result values is
+     * determined by the order they occur in the arrays. The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.xorWith(objects, others, _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var xorWith = baseRest(function(arrays) {
+      var comparator = last(arrays);
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
+    });
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the
+     * first elements of the given arrays, the second of which contains the
+     * second elements of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zip(['a', 'b'], [1, 2], [true, false]);
+     * // => [['a', 1, true], ['b', 2, false]]
+     */
+    var zip = baseRest(unzip);
+
+    /**
+     * This method is like `_.fromPairs` except that it accepts two arrays,
+     * one of property identifiers and one of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.4.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObject(['a', 'b'], [1, 2]);
+     * // => { 'a': 1, 'b': 2 }
+     */
+    function zipObject(props, values) {
+      return baseZipObject(props || [], values || [], assignValue);
+    }
+
+    /**
+     * This method is like `_.zipObject` except that it supports property paths.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.1.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+     * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+     */
+    function zipObjectDeep(props, values) {
+      return baseZipObject(props || [], values || [], baseSet);
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts `iteratee` to specify
+     * how grouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @param {Function} [iteratee=_.identity] The function to combine
+     *  grouped values.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+     *   return a + b + c;
+     * });
+     * // => [111, 222]
+     */
+    var zipWith = baseRest(function(arrays) {
+      var length = arrays.length,
+          iteratee = length > 1 ? arrays[length - 1] : undefined;
+
+      iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
+      return unzipWith(arrays, iteratee);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` wrapper instance that wraps `value` with explicit method
+     * chain sequences enabled. The result of such sequences must be unwrapped
+     * with `_#value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.3.0
+     * @category Seq
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36 },
+     *   { 'user': 'fred',    'age': 40 },
+     *   { 'user': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _
+     *   .chain(users)
+     *   .sortBy('age')
+     *   .map(function(o) {
+     *     return o.user + ' is ' + o.age;
+     *   })
+     *   .head()
+     *   .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      var result = lodash(value);
+      result.__chain__ = true;
+      return result;
+    }
+
+    /**
+     * This method invokes `interceptor` and returns `value`. The interceptor
+     * is invoked with one argument; (value). The purpose of this method is to
+     * "tap into" a method chain sequence in order to modify intermediate results.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3])
+     *  .tap(function(array) {
+     *    // Mutate input array.
+     *    array.pop();
+     *  })
+     *  .reverse()
+     *  .value();
+     * // => [2, 1]
+     */
+    function tap(value, interceptor) {
+      interceptor(value);
+      return value;
+    }
+
+    /**
+     * This method is like `_.tap` except that it returns the result of `interceptor`.
+     * The purpose of this method is to "pass thru" values replacing intermediate
+     * results in a method chain sequence.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns the result of `interceptor`.
+     * @example
+     *
+     * _('  abc  ')
+     *  .chain()
+     *  .trim()
+     *  .thru(function(value) {
+     *    return [value];
+     *  })
+     *  .value();
+     * // => ['abc']
+     */
+    function thru(value, interceptor) {
+      return interceptor(value);
+    }
+
+    /**
+     * This method is the wrapper version of `_.at`.
+     *
+     * @name at
+     * @memberOf _
+     * @since 1.0.0
+     * @category Seq
+     * @param {...(string|string[])} [paths] The property paths to pick.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+     *
+     * _(object).at(['a[0].b.c', 'a[1]']).value();
+     * // => [3, 4]
+     */
+    var wrapperAt = flatRest(function(paths) {
+      var length = paths.length,
+          start = length ? paths[0] : 0,
+          value = this.__wrapped__,
+          interceptor = function(object) { return baseAt(object, paths); };
+
+      if (length > 1 || this.__actions__.length ||
+          !(value instanceof LazyWrapper) || !isIndex(start)) {
+        return this.thru(interceptor);
+      }
+      value = value.slice(start, +start + (length ? 1 : 0));
+      value.__actions__.push({
+        'func': thru,
+        'args': [interceptor],
+        'thisArg': undefined
+      });
+      return new LodashWrapper(value, this.__chain__).thru(function(array) {
+        if (length && !array.length) {
+          array.push(undefined);
+        }
+        return array;
+      });
+    });
+
+    /**
+     * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+     *
+     * @name chain
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * // A sequence without explicit chaining.
+     * _(users).head();
+     * // => { 'user': 'barney', 'age': 36 }
+     *
+     * // A sequence with explicit chaining.
+     * _(users)
+     *   .chain()
+     *   .head()
+     *   .pick('user')
+     *   .value();
+     * // => { 'user': 'barney' }
+     */
+    function wrapperChain() {
+      return chain(this);
+    }
+
+    /**
+     * Executes the chain sequence and returns the wrapped result.
+     *
+     * @name commit
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2];
+     * var wrapped = _(array).push(3);
+     *
+     * console.log(array);
+     * // => [1, 2]
+     *
+     * wrapped = wrapped.commit();
+     * console.log(array);
+     * // => [1, 2, 3]
+     *
+     * wrapped.last();
+     * // => 3
+     *
+     * console.log(array);
+     * // => [1, 2, 3]
+     */
+    function wrapperCommit() {
+      return new LodashWrapper(this.value(), this.__chain__);
+    }
+
+    /**
+     * Gets the next value on a wrapped object following the
+     * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
+     *
+     * @name next
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the next iterator value.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 1 }
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 2 }
+     *
+     * wrapped.next();
+     * // => { 'done': true, 'value': undefined }
+     */
+    function wrapperNext() {
+      if (this.__values__ === undefined) {
+        this.__values__ = toArray(this.value());
+      }
+      var done = this.__index__ >= this.__values__.length,
+          value = done ? undefined : this.__values__[this.__index__++];
+
+      return { 'done': done, 'value': value };
+    }
+
+    /**
+     * Enables the wrapper to be iterable.
+     *
+     * @name Symbol.iterator
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the wrapper object.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped[Symbol.iterator]() === wrapped;
+     * // => true
+     *
+     * Array.from(wrapped);
+     * // => [1, 2]
+     */
+    function wrapperToIterator() {
+      return this;
+    }
+
+    /**
+     * Creates a clone of the chain sequence planting `value` as the wrapped value.
+     *
+     * @name plant
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @param {*} value The value to plant.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2]).map(square);
+     * var other = wrapped.plant([3, 4]);
+     *
+     * other.value();
+     * // => [9, 16]
+     *
+     * wrapped.value();
+     * // => [1, 4]
+     */
+    function wrapperPlant(value) {
+      var result,
+          parent = this;
+
+      while (parent instanceof baseLodash) {
+        var clone = wrapperClone(parent);
+        clone.__index__ = 0;
+        clone.__values__ = undefined;
+        if (result) {
+          previous.__wrapped__ = clone;
+        } else {
+          result = clone;
+        }
+        var previous = clone;
+        parent = parent.__wrapped__;
+      }
+      previous.__wrapped__ = value;
+      return result;
+    }
+
+    /**
+     * This method is the wrapper version of `_.reverse`.
+     *
+     * **Note:** This method mutates the wrapped array.
+     *
+     * @name reverse
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _(array).reverse().value()
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function wrapperReverse() {
+      var value = this.__wrapped__;
+      if (value instanceof LazyWrapper) {
+        var wrapped = value;
+        if (this.__actions__.length) {
+          wrapped = new LazyWrapper(this);
+        }
+        wrapped = wrapped.reverse();
+        wrapped.__actions__.push({
+          'func': thru,
+          'args': [reverse],
+          'thisArg': undefined
+        });
+        return new LodashWrapper(wrapped, this.__chain__);
+      }
+      return this.thru(reverse);
+    }
+
+    /**
+     * Executes the chain sequence to resolve the unwrapped value.
+     *
+     * @name value
+     * @memberOf _
+     * @since 0.1.0
+     * @alias toJSON, valueOf
+     * @category Seq
+     * @returns {*} Returns the resolved unwrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).value();
+     * // => [1, 2, 3]
+     */
+    function wrapperValue() {
+      return baseWrapperValue(this.__wrapped__, this.__actions__);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the number of times the key was returned by `iteratee`. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': 1, '6': 2 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      if (hasOwnProperty.call(result, key)) {
+        ++result[key];
+      } else {
+        baseAssignValue(result, key, 1);
+      }
+    });
+
+    /**
+     * Checks if `predicate` returns truthy for **all** elements of `collection`.
+     * Iteration is stopped once `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * **Note:** This method returns `true` for
+     * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
+     * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
+     * elements of empty collections.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes'], Boolean);
+     * // => false
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.every(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.every(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.every(users, 'active');
+     * // => false
+     */
+    function every(collection, predicate, guard) {
+      var func = isArray(collection) ? arrayEvery : baseEvery;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning an array of all elements
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * **Note:** Unlike `_.remove`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.reject
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.filter(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.filter(users, { 'age': 36, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.filter(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.filter(users, 'active');
+     * // => objects for ['barney']
+     */
+    function filter(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning the first element
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': true },
+     *   { 'user': 'fred',    'age': 40, 'active': false },
+     *   { 'user': 'pebbles', 'age': 1,  'active': true }
+     * ];
+     *
+     * _.find(users, function(o) { return o.age < 40; });
+     * // => object for 'barney'
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.find(users, { 'age': 1, 'active': true });
+     * // => object for 'pebbles'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.find(users, ['active', false]);
+     * // => object for 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.find(users, 'active');
+     * // => object for 'barney'
+     */
+    var find = createFind(findIndex);
+
+    /**
+     * This method is like `_.find` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=collection.length-1] The index to search from.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(n) {
+     *   return n % 2 == 1;
+     * });
+     * // => 3
+     */
+    var findLast = createFind(findLastIndex);
+
+    /**
+     * Creates a flattened array of values by running each element in `collection`
+     * thru `iteratee` and flattening the mapped results. The iteratee is invoked
+     * with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [n, n];
+     * }
+     *
+     * _.flatMap([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMap(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), 1);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDeep([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMapDeep(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), INFINITY);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDepth([1, 2], duplicate, 2);
+     * // => [[1, 1], [2, 2]]
+     */
+    function flatMapDepth(collection, iteratee, depth) {
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(map(collection, iteratee), depth);
+    }
+
+    /**
+     * Iterates over elements of `collection` and invokes `iteratee` for each element.
+     * The iteratee is invoked with three arguments: (value, index|key, collection).
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * **Note:** As with other "Collections" methods, objects with a "length"
+     * property are iterated like arrays. To avoid this behavior use `_.forIn`
+     * or `_.forOwn` for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias each
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEachRight
+     * @example
+     *
+     * _.forEach([1, 2], function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `1` then `2`.
+     *
+     * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+     */
+    function forEach(collection, iteratee) {
+      var func = isArray(collection) ? arrayEach : baseEach;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @alias eachRight
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEach
+     * @example
+     *
+     * _.forEachRight([1, 2], function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `2` then `1`.
+     */
+    function forEachRight(collection, iteratee) {
+      var func = isArray(collection) ? arrayEachRight : baseEachRight;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The order of grouped values
+     * is determined by the order they occur in `collection`. The corresponding
+     * value of each key is an array of elements responsible for generating the
+     * key. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': [4.2], '6': [6.1, 6.3] }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      if (hasOwnProperty.call(result, key)) {
+        result[key].push(value);
+      } else {
+        baseAssignValue(result, key, [value]);
+      }
+    });
+
+    /**
+     * Checks if `value` is in `collection`. If `collection` is a string, it's
+     * checked for a substring of `value`, otherwise
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * is used for equality comparisons. If `fromIndex` is negative, it's used as
+     * the offset from the end of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {boolean} Returns `true` if `value` is found, else `false`.
+     * @example
+     *
+     * _.includes([1, 2, 3], 1);
+     * // => true
+     *
+     * _.includes([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.includes({ 'a': 1, 'b': 2 }, 1);
+     * // => true
+     *
+     * _.includes('abcd', 'bc');
+     * // => true
+     */
+    function includes(collection, value, fromIndex, guard) {
+      collection = isArrayLike(collection) ? collection : values(collection);
+      fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
+
+      var length = collection.length;
+      if (fromIndex < 0) {
+        fromIndex = nativeMax(length + fromIndex, 0);
+      }
+      return isString(collection)
+        ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
+        : (!!length && baseIndexOf(collection, value, fromIndex) > -1);
+    }
+
+    /**
+     * Invokes the method at `path` of each element in `collection`, returning
+     * an array of the results of each invoked method. Any additional arguments
+     * are provided to each invoked method. If `path` is a function, it's invoked
+     * for, and `this` bound to, each element in `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|string} path The path of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [args] The arguments to invoke each method with.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invokeMap([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    var invokeMap = baseRest(function(collection, path, args) {
+      var index = -1,
+          isFunc = typeof path == 'function',
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value) {
+        result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args);
+      });
+      return result;
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the last element responsible for generating the key. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var array = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.keyBy(array, function(o) {
+     *   return String.fromCharCode(o.code);
+     * });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.keyBy(array, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     */
+    var keyBy = createAggregator(function(result, value, key) {
+      baseAssignValue(result, key, value);
+    });
+
+    /**
+     * Creates an array of values by running each element in `collection` thru
+     * `iteratee`. The iteratee is invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+     *
+     * The guarded methods are:
+     * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+     * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+     * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+     * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * _.map([4, 8], square);
+     * // => [16, 64]
+     *
+     * _.map({ 'a': 4, 'b': 8 }, square);
+     * // => [16, 64] (iteration order is not guaranteed)
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.map(users, 'user');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, iteratee) {
+      var func = isArray(collection) ? arrayMap : baseMap;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * This method is like `_.sortBy` except that it allows specifying the sort
+     * orders of the iteratees to sort by. If `orders` is unspecified, all values
+     * are sorted in ascending order. Otherwise, specify an order of "desc" for
+     * descending or "asc" for ascending sort order of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
+     *  The iteratees to sort by.
+     * @param {string[]} [orders] The sort orders of `iteratees`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 34 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 36 }
+     * ];
+     *
+     * // Sort by `user` in ascending order and by `age` in descending order.
+     * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     */
+    function orderBy(collection, iteratees, orders, guard) {
+      if (collection == null) {
+        return [];
+      }
+      if (!isArray(iteratees)) {
+        iteratees = iteratees == null ? [] : [iteratees];
+      }
+      orders = guard ? undefined : orders;
+      if (!isArray(orders)) {
+        orders = orders == null ? [] : [orders];
+      }
+      return baseOrderBy(collection, iteratees, orders);
+    }
+
+    /**
+     * Creates an array of elements split into two groups, the first of which
+     * contains elements `predicate` returns truthy for, the second of which
+     * contains elements `predicate` returns falsey for. The predicate is
+     * invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the array of grouped elements.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': false },
+     *   { 'user': 'fred',    'age': 40, 'active': true },
+     *   { 'user': 'pebbles', 'age': 1,  'active': false }
+     * ];
+     *
+     * _.partition(users, function(o) { return o.active; });
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.partition(users, { 'age': 1, 'active': false });
+     * // => objects for [['pebbles'], ['barney', 'fred']]
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.partition(users, ['active', false]);
+     * // => objects for [['barney', 'pebbles'], ['fred']]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.partition(users, 'active');
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     */
+    var partition = createAggregator(function(result, value, key) {
+      result[key ? 0 : 1].push(value);
+    }, function() { return [[], []]; });
+
+    /**
+     * Reduces `collection` to a value which is the accumulated result of running
+     * each element in `collection` thru `iteratee`, where each successive
+     * invocation is supplied the return value of the previous. If `accumulator`
+     * is not given, the first element of `collection` is used as the initial
+     * value. The iteratee is invoked with four arguments:
+     * (accumulator, value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.reduce`, `_.reduceRight`, and `_.transform`.
+     *
+     * The guarded methods are:
+     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+     * and `sortBy`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduceRight
+     * @example
+     *
+     * _.reduce([1, 2], function(sum, n) {
+     *   return sum + n;
+     * }, 0);
+     * // => 3
+     *
+     * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+     *   (result[value] || (result[value] = [])).push(key);
+     *   return result;
+     * }, {});
+     * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+     */
+    function reduce(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduce : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
+    }
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduce
+     * @example
+     *
+     * var array = [[0, 1], [2, 3], [4, 5]];
+     *
+     * _.reduceRight(array, function(flattened, other) {
+     *   return flattened.concat(other);
+     * }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    function reduceRight(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduceRight : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
+    }
+
+    /**
+     * The opposite of `_.filter`; this method returns the elements of `collection`
+     * that `predicate` does **not** return truthy for.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.filter
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': true }
+     * ];
+     *
+     * _.reject(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.reject(users, { 'age': 40, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.reject(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.reject(users, 'active');
+     * // => objects for ['barney']
+     */
+    function reject(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      return func(collection, negate(getIteratee(predicate, 3)));
+    }
+
+    /**
+     * Gets a random element from `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @returns {*} Returns the random element.
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     */
+    function sample(collection) {
+      var func = isArray(collection) ? arraySample : baseSample;
+      return func(collection);
+    }
+
+    /**
+     * Gets `n` random elements at unique keys from `collection` up to the
+     * size of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @param {number} [n=1] The number of elements to sample.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the random elements.
+     * @example
+     *
+     * _.sampleSize([1, 2, 3], 2);
+     * // => [3, 1]
+     *
+     * _.sampleSize([1, 2, 3], 4);
+     * // => [2, 3, 1]
+     */
+    function sampleSize(collection, n, guard) {
+      if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) {
+        n = 1;
+      } else {
+        n = toInteger(n);
+      }
+      var func = isArray(collection) ? arraySampleSize : baseSampleSize;
+      return func(collection, n);
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the
+     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4]);
+     * // => [4, 1, 3, 2]
+     */
+    function shuffle(collection) {
+      var func = isArray(collection) ? arrayShuffle : baseShuffle;
+      return func(collection);
+    }
+
+    /**
+     * Gets the size of `collection` by returning its length for array-like
+     * values or the number of own enumerable string keyed properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @returns {number} Returns the collection size.
+     * @example
+     *
+     * _.size([1, 2, 3]);
+     * // => 3
+     *
+     * _.size({ 'a': 1, 'b': 2 });
+     * // => 2
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      if (collection == null) {
+        return 0;
+      }
+      if (isArrayLike(collection)) {
+        return isString(collection) ? stringSize(collection) : collection.length;
+      }
+      var tag = getTag(collection);
+      if (tag == mapTag || tag == setTag) {
+        return collection.size;
+      }
+      return baseKeys(collection).length;
+    }
+
+    /**
+     * Checks if `predicate` returns truthy for **any** element of `collection`.
+     * Iteration is stopped once `predicate` returns truthy. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var users = [
+     *   { 'user': 'barney', 'active': true },
+     *   { 'user': 'fred',   'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.some(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.some(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.some(users, 'active');
+     * // => true
+     */
+    function some(collection, predicate, guard) {
+      var func = isArray(collection) ? arraySome : baseSome;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection thru each iteratee. This method
+     * performs a stable sort, that is, it preserves the original sort order of
+     * equal elements. The iteratees are invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {...(Function|Function[])} [iteratees=[_.identity]]
+     *  The iteratees to sort by.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 34 }
+     * ];
+     *
+     * _.sortBy(users, [function(o) { return o.user; }]);
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     *
+     * _.sortBy(users, ['user', 'age']);
+     * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
+     */
+    var sortBy = baseRest(function(collection, iteratees) {
+      if (collection == null) {
+        return [];
+      }
+      var length = iteratees.length;
+      if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
+        iteratees = [];
+      } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
+        iteratees = [iteratees[0]];
+      }
+      return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Gets the timestamp of the number of milliseconds that have elapsed since
+     * the Unix epoch (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Date
+     * @returns {number} Returns the timestamp.
+     * @example
+     *
+     * _.defer(function(stamp) {
+     *   console.log(_.now() - stamp);
+     * }, _.now());
+     * // => Logs the number of milliseconds it took for the deferred invocation.
+     */
+    var now = ctxNow || function() {
+      return root.Date.now();
+    };
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The opposite of `_.before`; this method creates a function that invokes
+     * `func` once it's called `n` or more times.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {number} n The number of calls before `func` is invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => Logs 'done saving!' after the two async saves have completed.
+     */
+    function after(n, func) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func`, with up to `n` arguments,
+     * ignoring any additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @param {number} [n=func.length] The arity cap.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new capped function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+     * // => [6, 8, 10]
+     */
+    function ary(func, n, guard) {
+      n = guard ? undefined : n;
+      n = (func && n == null) ? func.length : n;
+      return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n);
+    }
+
+    /**
+     * Creates a function that invokes `func`, with the `this` binding and arguments
+     * of the created function, while it's called less than `n` times. Subsequent
+     * calls to the created function return the result of the last `func` invocation.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {number} n The number of calls at which `func` is no longer invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * jQuery(element).on('click', _.before(5, addContactToList));
+     * // => Allows adding up to 4 contacts to the list.
+     */
+    function before(n, func) {
+      var result;
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n > 0) {
+          result = func.apply(this, arguments);
+        }
+        if (n <= 1) {
+          func = undefined;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of `thisArg`
+     * and `partials` prepended to the arguments it receives.
+     *
+     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
+     * property of bound functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to bind.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * function greet(greeting, punctuation) {
+     *   return greeting + ' ' + this.user + punctuation;
+     * }
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * var bound = _.bind(greet, object, 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bind(greet, object, _, '!');
+     * bound('hi');
+     * // => 'hi fred!'
+     */
+    var bind = baseRest(function(func, thisArg, partials) {
+      var bitmask = WRAP_BIND_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getHolder(bind));
+        bitmask |= WRAP_PARTIAL_FLAG;
+      }
+      return createWrap(func, bitmask, thisArg, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes the method at `object[key]` with `partials`
+     * prepended to the arguments it receives.
+     *
+     * This method differs from `_.bind` by allowing bound functions to reference
+     * methods that may be redefined or don't yet exist. See
+     * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+     * for more details.
+     *
+     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.10.0
+     * @category Function
+     * @param {Object} object The object to invoke the method on.
+     * @param {string} key The key of the method.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'user': 'fred',
+     *   'greet': function(greeting, punctuation) {
+     *     return greeting + ' ' + this.user + punctuation;
+     *   }
+     * };
+     *
+     * var bound = _.bindKey(object, 'greet', 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * object.greet = function(greeting, punctuation) {
+     *   return greeting + 'ya ' + this.user + punctuation;
+     * };
+     *
+     * bound('!');
+     * // => 'hiya fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bindKey(object, 'greet', _, '!');
+     * bound('hi');
+     * // => 'hiya fred!'
+     */
+    var bindKey = baseRest(function(object, key, partials) {
+      var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getHolder(bindKey));
+        bitmask |= WRAP_PARTIAL_FLAG;
+      }
+      return createWrap(key, bitmask, object, partials, holders);
+    });
+
+    /**
+     * Creates a function that accepts arguments of `func` and either invokes
+     * `func` returning its result, if at least `arity` number of arguments have
+     * been provided, or returns a function that accepts the remaining `func`
+     * arguments, and so on. The arity of `func` may be specified if `func.length`
+     * is not sufficient.
+     *
+     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curry(abc);
+     *
+     * curried(1)(2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(1)(_, 3)(2);
+     * // => [1, 2, 3]
+     */
+    function curry(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curry.placeholder;
+      return result;
+    }
+
+    /**
+     * This method is like `_.curry` except that arguments are applied to `func`
+     * in the manner of `_.partialRight` instead of `_.partial`.
+     *
+     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curryRight(abc);
+     *
+     * curried(3)(2)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(2, 3)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(3)(1, _)(2);
+     * // => [1, 2, 3]
+     */
+    function curryRight(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curryRight.placeholder;
+      return result;
+    }
+
+    /**
+     * Creates a debounced function that delays invoking `func` until after `wait`
+     * milliseconds have elapsed since the last time the debounced function was
+     * invoked. The debounced function comes with a `cancel` method to cancel
+     * delayed `func` invocations and a `flush` method to immediately invoke them.
+     * Provide `options` to indicate whether `func` should be invoked on the
+     * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+     * with the last arguments provided to the debounced function. Subsequent
+     * calls to the debounced function return the result of the last `func`
+     * invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is
+     * invoked on the trailing edge of the timeout only if the debounced function
+     * is invoked more than once during the `wait` timeout.
+     *
+     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.debounce` and `_.throttle`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to debounce.
+     * @param {number} [wait=0] The number of milliseconds to delay.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=false]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {number} [options.maxWait]
+     *  The maximum time `func` is allowed to be delayed before it's invoked.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // Avoid costly calculations while the window size is in flux.
+     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+     *
+     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+     * jQuery(element).on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * }));
+     *
+     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+     * var source = new EventSource('/stream');
+     * jQuery(source).on('message', debounced);
+     *
+     * // Cancel the trailing debounced invocation.
+     * jQuery(window).on('popstate', debounced.cancel);
+     */
+    function debounce(func, wait, options) {
+      var lastArgs,
+          lastThis,
+          maxWait,
+          result,
+          timerId,
+          lastCallTime,
+          lastInvokeTime = 0,
+          leading = false,
+          maxing = false,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      wait = toNumber(wait) || 0;
+      if (isObject(options)) {
+        leading = !!options.leading;
+        maxing = 'maxWait' in options;
+        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+
+      function invokeFunc(time) {
+        var args = lastArgs,
+            thisArg = lastThis;
+
+        lastArgs = lastThis = undefined;
+        lastInvokeTime = time;
+        result = func.apply(thisArg, args);
+        return result;
+      }
+
+      function leadingEdge(time) {
+        // Reset any `maxWait` timer.
+        lastInvokeTime = time;
+        // Start the timer for the trailing edge.
+        timerId = setTimeout(timerExpired, wait);
+        // Invoke the leading edge.
+        return leading ? invokeFunc(time) : result;
+      }
+
+      function remainingWait(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime,
+            result = wait - timeSinceLastCall;
+
+        return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
+      }
+
+      function shouldInvoke(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime;
+
+        // Either this is the first call, activity has stopped and we're at the
+        // trailing edge, the system time has gone backwards and we're treating
+        // it as the trailing edge, or we've hit the `maxWait` limit.
+        return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
+          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
+      }
+
+      function timerExpired() {
+        var time = now();
+        if (shouldInvoke(time)) {
+          return trailingEdge(time);
+        }
+        // Restart the timer.
+        timerId = setTimeout(timerExpired, remainingWait(time));
+      }
+
+      function trailingEdge(time) {
+        timerId = undefined;
+
+        // Only invoke if we have `lastArgs` which means `func` has been
+        // debounced at least once.
+        if (trailing && lastArgs) {
+          return invokeFunc(time);
+        }
+        lastArgs = lastThis = undefined;
+        return result;
+      }
+
+      function cancel() {
+        if (timerId !== undefined) {
+          clearTimeout(timerId);
+        }
+        lastInvokeTime = 0;
+        lastArgs = lastCallTime = lastThis = timerId = undefined;
+      }
+
+      function flush() {
+        return timerId === undefined ? result : trailingEdge(now());
+      }
+
+      function debounced() {
+        var time = now(),
+            isInvoking = shouldInvoke(time);
+
+        lastArgs = arguments;
+        lastThis = this;
+        lastCallTime = time;
+
+        if (isInvoking) {
+          if (timerId === undefined) {
+            return leadingEdge(lastCallTime);
+          }
+          if (maxing) {
+            // Handle invocations in a tight loop.
+            timerId = setTimeout(timerExpired, wait);
+            return invokeFunc(lastCallTime);
+          }
+        }
+        if (timerId === undefined) {
+          timerId = setTimeout(timerExpired, wait);
+        }
+        return result;
+      }
+      debounced.cancel = cancel;
+      debounced.flush = flush;
+      return debounced;
+    }
+
+    /**
+     * Defers invoking the `func` until the current call stack has cleared. Any
+     * additional arguments are provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to defer.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) {
+     *   console.log(text);
+     * }, 'deferred');
+     * // => Logs 'deferred' after one millisecond.
+     */
+    var defer = baseRest(function(func, args) {
+      return baseDelay(func, 1, args);
+    });
+
+    /**
+     * Invokes `func` after `wait` milliseconds. Any additional arguments are
+     * provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) {
+     *   console.log(text);
+     * }, 1000, 'later');
+     * // => Logs 'later' after one second.
+     */
+    var delay = baseRest(function(func, wait, args) {
+      return baseDelay(func, toNumber(wait) || 0, args);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments reversed.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to flip arguments for.
+     * @returns {Function} Returns the new flipped function.
+     * @example
+     *
+     * var flipped = _.flip(function() {
+     *   return _.toArray(arguments);
+     * });
+     *
+     * flipped('a', 'b', 'c', 'd');
+     * // => ['d', 'c', 'b', 'a']
+     */
+    function flip(func) {
+      return createWrap(func, WRAP_FLIP_FLAG);
+    }
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided, it determines the cache key for storing the result based on the
+     * arguments provided to the memoized function. By default, the first argument
+     * provided to the memoized function is used as the map cache key. The `func`
+     * is invoked with the `this` binding of the memoized function.
+     *
+     * **Note:** The cache is exposed as the `cache` property on the memoized
+     * function. Its creation may be customized by replacing the `_.memoize.Cache`
+     * constructor with one whose instances implement the
+     * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
+     * method interface of `clear`, `delete`, `get`, `has`, and `set`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] The function to resolve the cache key.
+     * @returns {Function} Returns the new memoized function.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2 };
+     * var other = { 'c': 3, 'd': 4 };
+     *
+     * var values = _.memoize(_.values);
+     * values(object);
+     * // => [1, 2]
+     *
+     * values(other);
+     * // => [3, 4]
+     *
+     * object.a = 2;
+     * values(object);
+     * // => [1, 2]
+     *
+     * // Modify the result cache.
+     * values.cache.set(object, ['a', 'b']);
+     * values(object);
+     * // => ['a', 'b']
+     *
+     * // Replace `_.memoize.Cache`.
+     * _.memoize.Cache = WeakMap;
+     */
+    function memoize(func, resolver) {
+      if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var memoized = function() {
+        var args = arguments,
+            key = resolver ? resolver.apply(this, args) : args[0],
+            cache = memoized.cache;
+
+        if (cache.has(key)) {
+          return cache.get(key);
+        }
+        var result = func.apply(this, args);
+        memoized.cache = cache.set(key, result) || cache;
+        return result;
+      };
+      memoized.cache = new (memoize.Cache || MapCache);
+      return memoized;
+    }
+
+    // Expose `MapCache`.
+    memoize.Cache = MapCache;
+
+    /**
+     * Creates a function that negates the result of the predicate `func`. The
+     * `func` predicate is invoked with the `this` binding and arguments of the
+     * created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} predicate The predicate to negate.
+     * @returns {Function} Returns the new negated function.
+     * @example
+     *
+     * function isEven(n) {
+     *   return n % 2 == 0;
+     * }
+     *
+     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+     * // => [1, 3, 5]
+     */
+    function negate(predicate) {
+      if (typeof predicate != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return function() {
+        var args = arguments;
+        switch (args.length) {
+          case 0: return !predicate.call(this);
+          case 1: return !predicate.call(this, args[0]);
+          case 2: return !predicate.call(this, args[0], args[1]);
+          case 3: return !predicate.call(this, args[0], args[1], args[2]);
+        }
+        return !predicate.apply(this, args);
+      };
+    }
+
+    /**
+     * Creates a function that is restricted to invoking `func` once. Repeat calls
+     * to the function return the value of the first invocation. The `func` is
+     * invoked with the `this` binding and arguments of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // => `createApplication` is invoked once
+     */
+    function once(func) {
+      return before(2, func);
+    }
+
+    /**
+     * Creates a function that invokes `func` with its arguments transformed.
+     *
+     * @static
+     * @since 4.0.0
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to wrap.
+     * @param {...(Function|Function[])} [transforms=[_.identity]]
+     *  The argument transforms.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function doubled(n) {
+     *   return n * 2;
+     * }
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var func = _.overArgs(function(x, y) {
+     *   return [x, y];
+     * }, [square, doubled]);
+     *
+     * func(9, 3);
+     * // => [81, 6]
+     *
+     * func(10, 5);
+     * // => [100, 10]
+     */
+    var overArgs = castRest(function(func, transforms) {
+      transforms = (transforms.length == 1 && isArray(transforms[0]))
+        ? arrayMap(transforms[0], baseUnary(getIteratee()))
+        : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));
+
+      var funcsLength = transforms.length;
+      return baseRest(function(args) {
+        var index = -1,
+            length = nativeMin(args.length, funcsLength);
+
+        while (++index < length) {
+          args[index] = transforms[index].call(this, args[index]);
+        }
+        return apply(func, this, args);
+      });
+    });
+
+    /**
+     * Creates a function that invokes `func` with `partials` prepended to the
+     * arguments it receives. This method is like `_.bind` except it does **not**
+     * alter the `this` binding.
+     *
+     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.2.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * function greet(greeting, name) {
+     *   return greeting + ' ' + name;
+     * }
+     *
+     * var sayHelloTo = _.partial(greet, 'hello');
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     *
+     * // Partially applied with placeholders.
+     * var greetFred = _.partial(greet, _, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     */
+    var partial = baseRest(function(func, partials) {
+      var holders = replaceHolders(partials, getHolder(partial));
+      return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * This method is like `_.partial` except that partially applied arguments
+     * are appended to the arguments it receives.
+     *
+     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * function greet(greeting, name) {
+     *   return greeting + ' ' + name;
+     * }
+     *
+     * var greetFred = _.partialRight(greet, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     *
+     * // Partially applied with placeholders.
+     * var sayHelloTo = _.partialRight(greet, 'hello', _);
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     */
+    var partialRight = baseRest(function(func, partials) {
+      var holders = replaceHolders(partials, getHolder(partialRight));
+      return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments arranged according
+     * to the specified `indexes` where the argument value at the first index is
+     * provided as the first argument, the argument value at the second index is
+     * provided as the second argument, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to rearrange arguments for.
+     * @param {...(number|number[])} indexes The arranged argument indexes.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var rearged = _.rearg(function(a, b, c) {
+     *   return [a, b, c];
+     * }, [2, 0, 1]);
+     *
+     * rearged('b', 'c', 'a')
+     * // => ['a', 'b', 'c']
+     */
+    var rearg = flatRest(function(func, indexes) {
+      return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes);
+    });
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * created function and arguments from `start` and beyond provided as
+     * an array.
+     *
+     * **Note:** This method is based on the
+     * [rest parameter](https://mdn.io/rest_parameters).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.rest(function(what, names) {
+     *   return what + ' ' + _.initial(names).join(', ') +
+     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+     * });
+     *
+     * say('hello', 'fred', 'barney', 'pebbles');
+     * // => 'hello fred, barney, & pebbles'
+     */
+    function rest(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = start === undefined ? start : toInteger(start);
+      return baseRest(func, start);
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * create function and an array of arguments much like
+     * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
+     *
+     * **Note:** This method is based on the
+     * [spread operator](https://mdn.io/spread_operator).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Function
+     * @param {Function} func The function to spread arguments over.
+     * @param {number} [start=0] The start position of the spread.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.spread(function(who, what) {
+     *   return who + ' says ' + what;
+     * });
+     *
+     * say(['fred', 'hello']);
+     * // => 'fred says hello'
+     *
+     * var numbers = Promise.all([
+     *   Promise.resolve(40),
+     *   Promise.resolve(36)
+     * ]);
+     *
+     * numbers.then(_.spread(function(x, y) {
+     *   return x + y;
+     * }));
+     * // => a Promise of 76
+     */
+    function spread(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = start == null ? 0 : nativeMax(toInteger(start), 0);
+      return baseRest(function(args) {
+        var array = args[start],
+            otherArgs = castSlice(args, 0, start);
+
+        if (array) {
+          arrayPush(otherArgs, array);
+        }
+        return apply(func, this, otherArgs);
+      });
+    }
+
+    /**
+     * Creates a throttled function that only invokes `func` at most once per
+     * every `wait` milliseconds. The throttled function comes with a `cancel`
+     * method to cancel delayed `func` invocations and a `flush` method to
+     * immediately invoke them. Provide `options` to indicate whether `func`
+     * should be invoked on the leading and/or trailing edge of the `wait`
+     * timeout. The `func` is invoked with the last arguments provided to the
+     * throttled function. Subsequent calls to the throttled function return the
+     * result of the last `func` invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is
+     * invoked on the trailing edge of the timeout only if the throttled function
+     * is invoked more than once during the `wait` timeout.
+     *
+     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.throttle` and `_.debounce`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to throttle.
+     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=true]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // Avoid excessively updating the position while scrolling.
+     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+     *
+     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+     * jQuery(element).on('click', throttled);
+     *
+     * // Cancel the trailing throttled invocation.
+     * jQuery(window).on('popstate', throttled.cancel);
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      if (isObject(options)) {
+        leading = 'leading' in options ? !!options.leading : leading;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+      return debounce(func, wait, {
+        'leading': leading,
+        'maxWait': wait,
+        'trailing': trailing
+      });
+    }
+
+    /**
+     * Creates a function that accepts up to one argument, ignoring any
+     * additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @returns {Function} Returns the new capped function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.unary(parseInt));
+     * // => [6, 8, 10]
+     */
+    function unary(func) {
+      return ary(func, 1);
+    }
+
+    /**
+     * Creates a function that provides `value` to `wrapper` as its first
+     * argument. Any additional arguments provided to the function are appended
+     * to those provided to the `wrapper`. The wrapper is invoked with the `this`
+     * binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {*} value The value to wrap.
+     * @param {Function} [wrapper=identity] The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '

' + func(text) + '

'; + * }); + * + * p('fred, barney, & pebbles'); + * // => '

fred, barney, & pebbles

' + */ + function wrap(value, wrapper) { + return partial(castFunction(wrapper), value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Casts `value` as an array if it's not one. + * + * @static + * @memberOf _ + * @since 4.4.0 + * @category Lang + * @param {*} value The value to inspect. + * @returns {Array} Returns the cast array. + * @example + * + * _.castArray(1); + * // => [1] + * + * _.castArray({ 'a': 1 }); + * // => [{ 'a': 1 }] + * + * _.castArray('abc'); + * // => ['abc'] + * + * _.castArray(null); + * // => [null] + * + * _.castArray(undefined); + * // => [undefined] + * + * _.castArray(); + * // => [] + * + * var array = [1, 2, 3]; + * console.log(_.castArray(array) === array); + * // => true + */ + function castArray() { + if (!arguments.length) { + return []; + } + var value = arguments[0]; + return isArray(value) ? value : [value]; + } + + /** + * Creates a shallow clone of `value`. + * + * **Note:** This method is loosely based on the + * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) + * and supports cloning arrays, array buffers, booleans, date objects, maps, + * numbers, `Object` objects, regexes, sets, strings, symbols, and typed + * arrays. The own enumerable properties of `arguments` objects are cloned + * as plain objects. An empty object is returned for uncloneable values such + * as error objects, functions, DOM nodes, and WeakMaps. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to clone. + * @returns {*} Returns the cloned value. + * @see _.cloneDeep + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var shallow = _.clone(objects); + * console.log(shallow[0] === objects[0]); + * // => true + */ + function clone(value) { + return baseClone(value, CLONE_SYMBOLS_FLAG); + } + + /** + * This method is like `_.clone` except that it accepts `customizer` which + * is invoked to produce the cloned value. If `customizer` returns `undefined`, + * cloning is handled by the method instead. The `customizer` is invoked with + * up to four arguments; (value [, index|key, object, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the cloned value. + * @see _.cloneDeepWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(false); + * } + * } + * + * var el = _.cloneWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 0 + */ + function cloneWith(value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseClone(value, CLONE_SYMBOLS_FLAG, customizer); + } + + /** + * This method is like `_.clone` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @returns {*} Returns the deep cloned value. + * @see _.clone + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var deep = _.cloneDeep(objects); + * console.log(deep[0] === objects[0]); + * // => false + */ + function cloneDeep(value) { + return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG); + } + + /** + * This method is like `_.cloneWith` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the deep cloned value. + * @see _.cloneWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(true); + * } + * } + * + * var el = _.cloneDeepWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 20 + */ + function cloneDeepWith(value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer); + } + + /** + * Checks if `object` conforms to `source` by invoking the predicate + * properties of `source` with the corresponding property values of `object`. + * + * **Note:** This method is equivalent to `_.conforms` when `source` is + * partially applied. + * + * @static + * @memberOf _ + * @since 4.14.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property predicates to conform to. + * @returns {boolean} Returns `true` if `object` conforms, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); + * // => true + * + * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); + * // => false + */ + function conformsTo(object, source) { + return source == null || baseConformsTo(object, source, keys(source)); + } + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || (value !== value && other !== other); + } + + /** + * Checks if `value` is greater than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than `other`, + * else `false`. + * @see _.lt + * @example + * + * _.gt(3, 1); + * // => true + * + * _.gt(3, 3); + * // => false + * + * _.gt(1, 3); + * // => false + */ + var gt = createRelationalOperation(baseGt); + + /** + * Checks if `value` is greater than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than or equal to + * `other`, else `false`. + * @see _.lte + * @example + * + * _.gte(3, 1); + * // => true + * + * _.gte(3, 3); + * // => true + * + * _.gte(1, 3); + * // => false + */ + var gte = createRelationalOperation(function(value, other) { + return value >= other; + }); + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { + return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && + !propertyIsEnumerable.call(value, 'callee'); + }; + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is classified as an `ArrayBuffer` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. + * @example + * + * _.isArrayBuffer(new ArrayBuffer(2)); + * // => true + * + * _.isArrayBuffer(new Array(2)); + * // => false + */ + var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is classified as a boolean primitive or object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. + * @example + * + * _.isBoolean(false); + * // => true + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return value === true || value === false || + (isObjectLike(value) && baseGetTag(value) == boolTag); + } + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse; + + /** + * Checks if `value` is classified as a `Date` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + * + * _.isDate('Mon April 23 2012'); + * // => false + */ + var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; + + /** + * Checks if `value` is likely a DOM element. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + * + * _.isElement(''); + * // => false + */ + function isElement(value) { + return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value); + } + + /** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ + function isEmpty(value) { + if (value == null) { + return true; + } + if (isArrayLike(value) && + (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || + isBuffer(value) || isTypedArray(value) || isArguments(value))) { + return !value.length; + } + var tag = getTag(value); + if (tag == mapTag || tag == setTag) { + return !value.size; + } + if (isPrototype(value)) { + return !baseKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty.call(value, key)) { + return false; + } + } + return true; + } + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent. + * + * **Note:** This method supports comparing arrays, array buffers, booleans, + * date objects, error objects, maps, numbers, `Object` objects, regexes, + * sets, strings, symbols, and typed arrays. `Object` objects are compared + * by their own, not inherited, enumerable properties. Functions and DOM + * nodes are compared by strict equality, i.e. `===`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.isEqual(object, other); + * // => true + * + * object === other; + * // => false + */ + function isEqual(value, other) { + return baseIsEqual(value, other); + } + + /** + * This method is like `_.isEqual` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with up to + * six arguments: (objValue, othValue [, index|key, object, other, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, othValue) { + * if (isGreeting(objValue) && isGreeting(othValue)) { + * return true; + * } + * } + * + * var array = ['hello', 'goodbye']; + * var other = ['hi', 'goodbye']; + * + * _.isEqualWith(array, other, customizer); + * // => true + */ + function isEqualWith(value, other, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + var result = customizer ? customizer(value, other) : undefined; + return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result; + } + + /** + * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, + * `SyntaxError`, `TypeError`, or `URIError` object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an error object, else `false`. + * @example + * + * _.isError(new Error); + * // => true + * + * _.isError(Error); + * // => false + */ + function isError(value) { + if (!isObjectLike(value)) { + return false; + } + var tag = baseGetTag(value); + return tag == errorTag || tag == domExcTag || + (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value)); + } + + /** + * Checks if `value` is a finite primitive number. + * + * **Note:** This method is based on + * [`Number.isFinite`](https://mdn.io/Number/isFinite). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. + * @example + * + * _.isFinite(3); + * // => true + * + * _.isFinite(Number.MIN_VALUE); + * // => true + * + * _.isFinite(Infinity); + * // => false + * + * _.isFinite('3'); + * // => false + */ + function isFinite(value) { + return typeof value == 'number' && nativeIsFinite(value); + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + if (!isObject(value)) { + return false; + } + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 9 which returns 'object' for typed arrays and other constructors. + var tag = baseGetTag(value); + return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; + } + + /** + * Checks if `value` is an integer. + * + * **Note:** This method is based on + * [`Number.isInteger`](https://mdn.io/Number/isInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an integer, else `false`. + * @example + * + * _.isInteger(3); + * // => true + * + * _.isInteger(Number.MIN_VALUE); + * // => false + * + * _.isInteger(Infinity); + * // => false + * + * _.isInteger('3'); + * // => false + */ + function isInteger(value) { + return typeof value == 'number' && value == toInteger(value); + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return value != null && typeof value == 'object'; + } + + /** + * Checks if `value` is classified as a `Map` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a map, else `false`. + * @example + * + * _.isMap(new Map); + * // => true + * + * _.isMap(new WeakMap); + * // => false + */ + var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; + + /** + * Performs a partial deep comparison between `object` and `source` to + * determine if `object` contains equivalent property values. + * + * **Note:** This method is equivalent to `_.matches` when `source` is + * partially applied. + * + * Partial comparisons will match empty array and empty object `source` + * values against any array or object value, respectively. See `_.isEqual` + * for a list of supported value comparisons. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.isMatch(object, { 'b': 2 }); + * // => true + * + * _.isMatch(object, { 'b': 1 }); + * // => false + */ + function isMatch(object, source) { + return object === source || baseIsMatch(object, source, getMatchData(source)); + } + + /** + * This method is like `_.isMatch` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with five + * arguments: (objValue, srcValue, index|key, object, source). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, srcValue) { + * if (isGreeting(objValue) && isGreeting(srcValue)) { + * return true; + * } + * } + * + * var object = { 'greeting': 'hello' }; + * var source = { 'greeting': 'hi' }; + * + * _.isMatchWith(object, source, customizer); + * // => true + */ + function isMatchWith(object, source, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseIsMatch(object, source, getMatchData(source), customizer); + } + + /** + * Checks if `value` is `NaN`. + * + * **Note:** This method is based on + * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as + * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for + * `undefined` and other non-number values. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // An `NaN` primitive is the only value that is not equal to itself. + // Perform the `toStringTag` check first to avoid errors with some + // ActiveX objects in IE. + return isNumber(value) && value != +value; + } + + /** + * Checks if `value` is a pristine native function. + * + * **Note:** This method can't reliably detect native functions in the presence + * of the core-js package because core-js circumvents this kind of detection. + * Despite multiple requests, the core-js maintainer has made it clear: any + * attempt to fix the detection will be obstructed. As a result, we're left + * with little choice but to throw an error. Unfortunately, this also affects + * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), + * which rely on core-js. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ + function isNative(value) { + if (isMaskable(value)) { + throw new Error(CORE_ERROR_TEXT); + } + return baseIsNative(value); + } + + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(void 0); + * // => false + */ + function isNull(value) { + return value === null; + } + + /** + * Checks if `value` is `null` or `undefined`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is nullish, else `false`. + * @example + * + * _.isNil(null); + * // => true + * + * _.isNil(void 0); + * // => true + * + * _.isNil(NaN); + * // => false + */ + function isNil(value) { + return value == null; + } + + /** + * Checks if `value` is classified as a `Number` primitive or object. + * + * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are + * classified as numbers, use the `_.isFinite` method. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a number, else `false`. + * @example + * + * _.isNumber(3); + * // => true + * + * _.isNumber(Number.MIN_VALUE); + * // => true + * + * _.isNumber(Infinity); + * // => true + * + * _.isNumber('3'); + * // => false + */ + function isNumber(value) { + return typeof value == 'number' || + (isObjectLike(value) && baseGetTag(value) == numberTag); + } + + /** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * @static + * @memberOf _ + * @since 0.8.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ + function isPlainObject(value) { + if (!isObjectLike(value) || baseGetTag(value) != objectTag) { + return false; + } + var proto = getPrototype(value); + if (proto === null) { + return true; + } + var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; + return typeof Ctor == 'function' && Ctor instanceof Ctor && + funcToString.call(Ctor) == objectCtorString; + } + + /** + * Checks if `value` is classified as a `RegExp` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. + * @example + * + * _.isRegExp(/abc/); + * // => true + * + * _.isRegExp('/abc/'); + * // => false + */ + var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; + + /** + * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 + * double precision number which isn't the result of a rounded unsafe integer. + * + * **Note:** This method is based on + * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. + * @example + * + * _.isSafeInteger(3); + * // => true + * + * _.isSafeInteger(Number.MIN_VALUE); + * // => false + * + * _.isSafeInteger(Infinity); + * // => false + * + * _.isSafeInteger('3'); + * // => false + */ + function isSafeInteger(value) { + return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is classified as a `Set` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a set, else `false`. + * @example + * + * _.isSet(new Set); + * // => true + * + * _.isSet(new WeakSet); + * // => false + */ + var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; + + /** + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a string, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false + */ + function isString(value) { + return typeof value == 'string' || + (!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag); + } + + /** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ + function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike(value) && baseGetTag(value) == symbolTag); + } + + /** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ + var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; + + /** + * Checks if `value` is `undefined`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + * + * _.isUndefined(null); + * // => false + */ + function isUndefined(value) { + return value === undefined; + } + + /** + * Checks if `value` is classified as a `WeakMap` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. + * @example + * + * _.isWeakMap(new WeakMap); + * // => true + * + * _.isWeakMap(new Map); + * // => false + */ + function isWeakMap(value) { + return isObjectLike(value) && getTag(value) == weakMapTag; + } + + /** + * Checks if `value` is classified as a `WeakSet` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. + * @example + * + * _.isWeakSet(new WeakSet); + * // => true + * + * _.isWeakSet(new Set); + * // => false + */ + function isWeakSet(value) { + return isObjectLike(value) && baseGetTag(value) == weakSetTag; + } + + /** + * Checks if `value` is less than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than `other`, + * else `false`. + * @see _.gt + * @example + * + * _.lt(1, 3); + * // => true + * + * _.lt(3, 3); + * // => false + * + * _.lt(3, 1); + * // => false + */ + var lt = createRelationalOperation(baseLt); + + /** + * Checks if `value` is less than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than or equal to + * `other`, else `false`. + * @see _.gte + * @example + * + * _.lte(1, 3); + * // => true + * + * _.lte(3, 3); + * // => true + * + * _.lte(3, 1); + * // => false + */ + var lte = createRelationalOperation(function(value, other) { + return value <= other; + }); + + /** + * Converts `value` to an array. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Array} Returns the converted array. + * @example + * + * _.toArray({ 'a': 1, 'b': 2 }); + * // => [1, 2] + * + * _.toArray('abc'); + * // => ['a', 'b', 'c'] + * + * _.toArray(1); + * // => [] + * + * _.toArray(null); + * // => [] + */ + function toArray(value) { + if (!value) { + return []; + } + if (isArrayLike(value)) { + return isString(value) ? stringToArray(value) : copyArray(value); + } + if (symIterator && value[symIterator]) { + return iteratorToArray(value[symIterator]()); + } + var tag = getTag(value), + func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values); + + return func(value); + } + + /** + * Converts `value` to a finite number. + * + * @static + * @memberOf _ + * @since 4.12.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted number. + * @example + * + * _.toFinite(3.2); + * // => 3.2 + * + * _.toFinite(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toFinite(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toFinite('3.2'); + * // => 3.2 + */ + function toFinite(value) { + if (!value) { + return value === 0 ? value : 0; + } + value = toNumber(value); + if (value === INFINITY || value === -INFINITY) { + var sign = (value < 0 ? -1 : 1); + return sign * MAX_INTEGER; + } + return value === value ? value : 0; + } + + /** + * Converts `value` to an integer. + * + * **Note:** This method is loosely based on + * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toInteger(3.2); + * // => 3 + * + * _.toInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toInteger(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toInteger('3.2'); + * // => 3 + */ + function toInteger(value) { + var result = toFinite(value), + remainder = result % 1; + + return result === result ? (remainder ? result - remainder : result) : 0; + } + + /** + * Converts `value` to an integer suitable for use as the length of an + * array-like object. + * + * **Note:** This method is based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toLength(3.2); + * // => 3 + * + * _.toLength(Number.MIN_VALUE); + * // => 0 + * + * _.toLength(Infinity); + * // => 4294967295 + * + * _.toLength('3.2'); + * // => 3 + */ + function toLength(value) { + return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; + } + + /** + * Converts `value` to a number. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to process. + * @returns {number} Returns the number. + * @example + * + * _.toNumber(3.2); + * // => 3.2 + * + * _.toNumber(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toNumber(Infinity); + * // => Infinity + * + * _.toNumber('3.2'); + * // => 3.2 + */ + function toNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + if (isObject(value)) { + var other = typeof value.valueOf == 'function' ? value.valueOf() : value; + value = isObject(other) ? (other + '') : other; + } + if (typeof value != 'string') { + return value === 0 ? value : +value; + } + value = value.replace(reTrim, ''); + var isBinary = reIsBinary.test(value); + return (isBinary || reIsOctal.test(value)) + ? freeParseInt(value.slice(2), isBinary ? 2 : 8) + : (reIsBadHex.test(value) ? NAN : +value); + } + + /** + * Converts `value` to a plain object flattening inherited enumerable string + * keyed properties of `value` to own properties of the plain object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {Object} Returns the converted plain object. + * @example + * + * function Foo() { + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.assign({ 'a': 1 }, new Foo); + * // => { 'a': 1, 'b': 2 } + * + * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); + * // => { 'a': 1, 'b': 2, 'c': 3 } + */ + function toPlainObject(value) { + return copyObject(value, keysIn(value)); + } + + /** + * Converts `value` to a safe integer. A safe integer can be compared and + * represented correctly. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toSafeInteger(3.2); + * // => 3 + * + * _.toSafeInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toSafeInteger(Infinity); + * // => 9007199254740991 + * + * _.toSafeInteger('3.2'); + * // => 3 + */ + function toSafeInteger(value) { + return value + ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) + : (value === 0 ? value : 0); + } + + /** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ + function toString(value) { + return value == null ? '' : baseToString(value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable string keyed properties of source objects to the + * destination object. Source objects are applied from left to right. + * Subsequent sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object` and is loosely based on + * [`Object.assign`](https://mdn.io/Object/assign). + * + * @static + * @memberOf _ + * @since 0.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assignIn + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assign({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'c': 3 } + */ + var assign = createAssigner(function(object, source) { + if (isPrototype(source) || isArrayLike(source)) { + copyObject(source, keys(source), object); + return; + } + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + assignValue(object, key, source[key]); + } + } + }); + + /** + * This method is like `_.assign` except that it iterates over own and + * inherited source properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extend + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assign + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assignIn({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } + */ + var assignIn = createAssigner(function(object, source) { + copyObject(source, keysIn(source), object); + }); + + /** + * This method is like `_.assignIn` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extendWith + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignInWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { + copyObject(source, keysIn(source), object, customizer); + }); + + /** + * This method is like `_.assign` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignInWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignWith = createAssigner(function(object, source, srcIndex, customizer) { + copyObject(source, keys(source), object, customizer); + }); + + /** + * Creates an array of values corresponding to `paths` of `object`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {...(string|string[])} [paths] The property paths to pick. + * @returns {Array} Returns the picked values. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; + * + * _.at(object, ['a[0].b.c', 'a[1]']); + * // => [3, 4] + */ + var at = flatRest(baseAt); + + /** + * Creates an object that inherits from the `prototype` object. If a + * `properties` object is given, its own enumerable string keyed properties + * are assigned to the created object. + * + * @static + * @memberOf _ + * @since 2.3.0 + * @category Object + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { + * 'constructor': Circle + * }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ + function create(prototype, properties) { + var result = baseCreate(prototype); + return properties == null ? result : baseAssign(result, properties); + } + + /** + * Assigns own and inherited enumerable string keyed properties of source + * objects to the destination object for all destination properties that + * resolve to `undefined`. Source objects are applied from left to right. + * Once a property is set, additional values of the same property are ignored. + * + * **Note:** This method mutates `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaultsDeep + * @example + * + * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var defaults = baseRest(function(args) { + args.push(undefined, customDefaultsAssignIn); + return apply(assignInWith, undefined, args); + }); + + /** + * This method is like `_.defaults` except that it recursively assigns + * default properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaults + * @example + * + * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); + * // => { 'a': { 'b': 2, 'c': 3 } } + */ + var defaultsDeep = baseRest(function(args) { + args.push(undefined, customDefaultsMerge); + return apply(mergeWith, undefined, args); + }); + + /** + * This method is like `_.find` except that it returns the key of the first + * element `predicate` returns truthy for instead of the element itself. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findKey(users, function(o) { return o.age < 40; }); + * // => 'barney' (iteration order is not guaranteed) + * + * // The `_.matches` iteratee shorthand. + * _.findKey(users, { 'age': 1, 'active': true }); + * // => 'pebbles' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findKey(users, 'active'); + * // => 'barney' + */ + function findKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); + } + + /** + * This method is like `_.findKey` except that it iterates over elements of + * a collection in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findLastKey(users, function(o) { return o.age < 40; }); + * // => returns 'pebbles' assuming `_.findKey` returns 'barney' + * + * // The `_.matches` iteratee shorthand. + * _.findLastKey(users, { 'age': 36, 'active': true }); + * // => 'barney' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findLastKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findLastKey(users, 'active'); + * // => 'pebbles' + */ + function findLastKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); + } + + /** + * Iterates over own and inherited enumerable string keyed properties of an + * object and invokes `iteratee` for each property. The iteratee is invoked + * with three arguments: (value, key, object). Iteratee functions may exit + * iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forInRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forIn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). + */ + function forIn(object, iteratee) { + return object == null + ? object + : baseFor(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * This method is like `_.forIn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forIn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forInRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. + */ + function forInRight(object, iteratee) { + return object == null + ? object + : baseForRight(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * Iterates over own enumerable string keyed properties of an object and + * invokes `iteratee` for each property. The iteratee is invoked with three + * arguments: (value, key, object). Iteratee functions may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwnRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a' then 'b' (iteration order is not guaranteed). + */ + function forOwn(object, iteratee) { + return object && baseForOwn(object, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.forOwn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwnRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. + */ + function forOwnRight(object, iteratee) { + return object && baseForOwnRight(object, getIteratee(iteratee, 3)); + } + + /** + * Creates an array of function property names from own enumerable properties + * of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functionsIn + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functions(new Foo); + * // => ['a', 'b'] + */ + function functions(object) { + return object == null ? [] : baseFunctions(object, keys(object)); + } + + /** + * Creates an array of function property names from own and inherited + * enumerable properties of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functions + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functionsIn(new Foo); + * // => ['a', 'b', 'c'] + */ + function functionsIn(object) { + return object == null ? [] : baseFunctions(object, keysIn(object)); + } + + /** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.get(object, 'a[0].b.c'); + * // => 3 + * + * _.get(object, ['a', '0', 'b', 'c']); + * // => 3 + * + * _.get(object, 'a.b.c', 'default'); + * // => 'default' + */ + function get(object, path, defaultValue) { + var result = object == null ? undefined : baseGet(object, path); + return result === undefined ? defaultValue : result; + } + + /** + * Checks if `path` is a direct property of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = { 'a': { 'b': 2 } }; + * var other = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.has(object, 'a'); + * // => true + * + * _.has(object, 'a.b'); + * // => true + * + * _.has(object, ['a', 'b']); + * // => true + * + * _.has(other, 'a'); + * // => false + */ + function has(object, path) { + return object != null && hasPath(object, path, baseHas); + } + + /** + * Checks if `path` is a direct or inherited property of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.hasIn(object, 'a'); + * // => true + * + * _.hasIn(object, 'a.b'); + * // => true + * + * _.hasIn(object, ['a', 'b']); + * // => true + * + * _.hasIn(object, 'b'); + * // => false + */ + function hasIn(object, path) { + return object != null && hasPath(object, path, baseHasIn); + } + + /** + * Creates an object composed of the inverted keys and values of `object`. + * If `object` contains duplicate values, subsequent values overwrite + * property assignments of previous values. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Object + * @param {Object} object The object to invert. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invert(object); + * // => { '1': 'c', '2': 'b' } + */ + var invert = createInverter(function(result, value, key) { + result[value] = key; + }, constant(identity)); + + /** + * This method is like `_.invert` except that the inverted object is generated + * from the results of running each element of `object` thru `iteratee`. The + * corresponding inverted value of each inverted key is an array of keys + * responsible for generating the inverted value. The iteratee is invoked + * with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.1.0 + * @category Object + * @param {Object} object The object to invert. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invertBy(object); + * // => { '1': ['a', 'c'], '2': ['b'] } + * + * _.invertBy(object, function(value) { + * return 'group' + value; + * }); + * // => { 'group1': ['a', 'c'], 'group2': ['b'] } + */ + var invertBy = createInverter(function(result, value, key) { + if (hasOwnProperty.call(result, value)) { + result[value].push(key); + } else { + result[value] = [key]; + } + }, getIteratee); + + /** + * Invokes the method at `path` of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the method to invoke. + * @param {...*} [args] The arguments to invoke the method with. + * @returns {*} Returns the result of the invoked method. + * @example + * + * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; + * + * _.invoke(object, 'a[0].b.c.slice', 1, 3); + * // => [2, 3] + */ + var invoke = baseRest(baseInvoke); + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + /** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ + function keysIn(object) { + return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); + } + + /** + * The opposite of `_.mapValues`; this method creates an object with the + * same values as `object` and keys generated by running each own enumerable + * string keyed property of `object` thru `iteratee`. The iteratee is invoked + * with three arguments: (value, key, object). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapValues + * @example + * + * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { + * return key + value; + * }); + * // => { 'a1': 1, 'b2': 2 } + */ + function mapKeys(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + + baseForOwn(object, function(value, key, object) { + baseAssignValue(result, iteratee(value, key, object), value); + }); + return result; + } + + /** + * Creates an object with the same keys as `object` and values generated + * by running each own enumerable string keyed property of `object` thru + * `iteratee`. The iteratee is invoked with three arguments: + * (value, key, object). + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapKeys + * @example + * + * var users = { + * 'fred': { 'user': 'fred', 'age': 40 }, + * 'pebbles': { 'user': 'pebbles', 'age': 1 } + * }; + * + * _.mapValues(users, function(o) { return o.age; }); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + * + * // The `_.property` iteratee shorthand. + * _.mapValues(users, 'age'); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + */ + function mapValues(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + + baseForOwn(object, function(value, key, object) { + baseAssignValue(result, key, iteratee(value, key, object)); + }); + return result; + } + + /** + * This method is like `_.assign` except that it recursively merges own and + * inherited enumerable string keyed properties of source objects into the + * destination object. Source properties that resolve to `undefined` are + * skipped if a destination value exists. Array and plain object properties + * are merged recursively. Other objects and value types are overridden by + * assignment. Source objects are applied from left to right. Subsequent + * sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @example + * + * var object = { + * 'a': [{ 'b': 2 }, { 'd': 4 }] + * }; + * + * var other = { + * 'a': [{ 'c': 3 }, { 'e': 5 }] + * }; + * + * _.merge(object, other); + * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } + */ + var merge = createAssigner(function(object, source, srcIndex) { + baseMerge(object, source, srcIndex); + }); + + /** + * This method is like `_.merge` except that it accepts `customizer` which + * is invoked to produce the merged values of the destination and source + * properties. If `customizer` returns `undefined`, merging is handled by the + * method instead. The `customizer` is invoked with six arguments: + * (objValue, srcValue, key, object, source, stack). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} customizer The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * function customizer(objValue, srcValue) { + * if (_.isArray(objValue)) { + * return objValue.concat(srcValue); + * } + * } + * + * var object = { 'a': [1], 'b': [2] }; + * var other = { 'a': [3], 'b': [4] }; + * + * _.mergeWith(object, other, customizer); + * // => { 'a': [1, 3], 'b': [2, 4] } + */ + var mergeWith = createAssigner(function(object, source, srcIndex, customizer) { + baseMerge(object, source, srcIndex, customizer); + }); + + /** + * The opposite of `_.pick`; this method creates an object composed of the + * own and inherited enumerable property paths of `object` that are not omitted. + * + * **Note:** This method is considerably slower than `_.pick`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [paths] The property paths to omit. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omit(object, ['a', 'c']); + * // => { 'b': '2' } + */ + var omit = flatRest(function(object, paths) { + var result = {}; + if (object == null) { + return result; + } + var isDeep = false; + paths = arrayMap(paths, function(path) { + path = castPath(path, object); + isDeep || (isDeep = path.length > 1); + return path; + }); + copyObject(object, getAllKeysIn(object), result); + if (isDeep) { + result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone); + } + var length = paths.length; + while (length--) { + baseUnset(result, paths[length]); + } + return result; + }); + + /** + * The opposite of `_.pickBy`; this method creates an object composed of + * the own and inherited enumerable string keyed properties of `object` that + * `predicate` doesn't return truthy for. The predicate is invoked with two + * arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omitBy(object, _.isNumber); + * // => { 'b': '2' } + */ + function omitBy(object, predicate) { + return pickBy(object, negate(getIteratee(predicate))); + } + + /** + * Creates an object composed of the picked `object` properties. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [paths] The property paths to pick. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pick(object, ['a', 'c']); + * // => { 'a': 1, 'c': 3 } + */ + var pick = flatRest(function(object, paths) { + return object == null ? {} : basePick(object, paths); + }); + + /** + * Creates an object composed of the `object` properties `predicate` returns + * truthy for. The predicate is invoked with two arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pickBy(object, _.isNumber); + * // => { 'a': 1, 'c': 3 } + */ + function pickBy(object, predicate) { + if (object == null) { + return {}; + } + var props = arrayMap(getAllKeysIn(object), function(prop) { + return [prop]; + }); + predicate = getIteratee(predicate); + return basePickBy(object, props, function(value, path) { + return predicate(value, path[0]); + }); + } + + /** + * This method is like `_.get` except that if the resolved value is a + * function it's invoked with the `this` binding of its parent object and + * its result is returned. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to resolve. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; + * + * _.result(object, 'a[0].b.c1'); + * // => 3 + * + * _.result(object, 'a[0].b.c2'); + * // => 4 + * + * _.result(object, 'a[0].b.c3', 'default'); + * // => 'default' + * + * _.result(object, 'a[0].b.c3', _.constant('default')); + * // => 'default' + */ + function result(object, path, defaultValue) { + path = castPath(path, object); + + var index = -1, + length = path.length; + + // Ensure the loop is entered when path is empty. + if (!length) { + length = 1; + object = undefined; + } + while (++index < length) { + var value = object == null ? undefined : object[toKey(path[index])]; + if (value === undefined) { + index = length; + value = defaultValue; + } + object = isFunction(value) ? value.call(object) : value; + } + return object; + } + + /** + * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, + * it's created. Arrays are created for missing index properties while objects + * are created for all other missing properties. Use `_.setWith` to customize + * `path` creation. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.set(object, 'a[0].b.c', 4); + * console.log(object.a[0].b.c); + * // => 4 + * + * _.set(object, ['x', '0', 'y', 'z'], 5); + * console.log(object.x[0].y.z); + * // => 5 + */ + function set(object, path, value) { + return object == null ? object : baseSet(object, path, value); + } + + /** + * This method is like `_.set` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.setWith(object, '[0][1]', 'a', Object); + * // => { '0': { '1': 'a' } } + */ + function setWith(object, path, value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseSet(object, path, value, customizer); + } + + /** + * Creates an array of own enumerable string keyed-value pairs for `object` + * which can be consumed by `_.fromPairs`. If `object` is a map or set, its + * entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entries + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairs(new Foo); + * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) + */ + var toPairs = createToPairs(keys); + + /** + * Creates an array of own and inherited enumerable string keyed-value pairs + * for `object` which can be consumed by `_.fromPairs`. If `object` is a map + * or set, its entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entriesIn + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairsIn(new Foo); + * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) + */ + var toPairsIn = createToPairs(keysIn); + + /** + * An alternative to `_.reduce`; this method transforms `object` to a new + * `accumulator` object which is the result of running each of its own + * enumerable string keyed properties thru `iteratee`, with each invocation + * potentially mutating the `accumulator` object. If `accumulator` is not + * provided, a new object with the same `[[Prototype]]` will be used. The + * iteratee is invoked with four arguments: (accumulator, value, key, object). + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 1.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The custom accumulator value. + * @returns {*} Returns the accumulated value. + * @example + * + * _.transform([2, 3, 4], function(result, n) { + * result.push(n *= n); + * return n % 2 == 0; + * }, []); + * // => [4, 9] + * + * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { + * (result[value] || (result[value] = [])).push(key); + * }, {}); + * // => { '1': ['a', 'c'], '2': ['b'] } + */ + function transform(object, iteratee, accumulator) { + var isArr = isArray(object), + isArrLike = isArr || isBuffer(object) || isTypedArray(object); + + iteratee = getIteratee(iteratee, 4); + if (accumulator == null) { + var Ctor = object && object.constructor; + if (isArrLike) { + accumulator = isArr ? new Ctor : []; + } + else if (isObject(object)) { + accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; + } + else { + accumulator = {}; + } + } + (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) { + return iteratee(accumulator, value, index, object); + }); + return accumulator; + } + + /** + * Removes the property at `path` of `object`. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to unset. + * @returns {boolean} Returns `true` if the property is deleted, else `false`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 7 } }] }; + * _.unset(object, 'a[0].b.c'); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + * + * _.unset(object, ['a', '0', 'b', 'c']); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + */ + function unset(object, path) { + return object == null ? true : baseUnset(object, path); + } + + /** + * This method is like `_.set` except that accepts `updater` to produce the + * value to set. Use `_.updateWith` to customize `path` creation. The `updater` + * is invoked with one argument: (value). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.update(object, 'a[0].b.c', function(n) { return n * n; }); + * console.log(object.a[0].b.c); + * // => 9 + * + * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); + * console.log(object.x[0].y.z); + * // => 0 + */ + function update(object, path, updater) { + return object == null ? object : baseUpdate(object, path, castFunction(updater)); + } + + /** + * This method is like `_.update` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.updateWith(object, '[0][1]', _.constant('a'), Object); + * // => { '0': { '1': 'a' } } + */ + function updateWith(object, path, updater, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); + } + + /** + * Creates an array of the own enumerable string keyed property values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.values(new Foo); + * // => [1, 2] (iteration order is not guaranteed) + * + * _.values('hi'); + * // => ['h', 'i'] + */ + function values(object) { + return object == null ? [] : baseValues(object, keys(object)); + } + + /** + * Creates an array of the own and inherited enumerable string keyed property + * values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.valuesIn(new Foo); + * // => [1, 2, 3] (iteration order is not guaranteed) + */ + function valuesIn(object) { + return object == null ? [] : baseValues(object, keysIn(object)); + } + + /*------------------------------------------------------------------------*/ + + /** + * Clamps `number` within the inclusive `lower` and `upper` bounds. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Number + * @param {number} number The number to clamp. + * @param {number} [lower] The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the clamped number. + * @example + * + * _.clamp(-10, -5, 5); + * // => -5 + * + * _.clamp(10, -5, 5); + * // => 5 + */ + function clamp(number, lower, upper) { + if (upper === undefined) { + upper = lower; + lower = undefined; + } + if (upper !== undefined) { + upper = toNumber(upper); + upper = upper === upper ? upper : 0; + } + if (lower !== undefined) { + lower = toNumber(lower); + lower = lower === lower ? lower : 0; + } + return baseClamp(toNumber(number), lower, upper); + } + + /** + * Checks if `n` is between `start` and up to, but not including, `end`. If + * `end` is not specified, it's set to `start` with `start` then set to `0`. + * If `start` is greater than `end` the params are swapped to support + * negative ranges. + * + * @static + * @memberOf _ + * @since 3.3.0 + * @category Number + * @param {number} number The number to check. + * @param {number} [start=0] The start of the range. + * @param {number} end The end of the range. + * @returns {boolean} Returns `true` if `number` is in the range, else `false`. + * @see _.range, _.rangeRight + * @example + * + * _.inRange(3, 2, 4); + * // => true + * + * _.inRange(4, 8); + * // => true + * + * _.inRange(4, 2); + * // => false + * + * _.inRange(2, 2); + * // => false + * + * _.inRange(1.2, 2); + * // => true + * + * _.inRange(5.2, 4); + * // => false + * + * _.inRange(-3, -2, -6); + * // => true + */ + function inRange(number, start, end) { + start = toFinite(start); + if (end === undefined) { + end = start; + start = 0; + } else { + end = toFinite(end); + } + number = toNumber(number); + return baseInRange(number, start, end); + } + + /** + * Produces a random number between the inclusive `lower` and `upper` bounds. + * If only one argument is provided a number between `0` and the given number + * is returned. If `floating` is `true`, or either `lower` or `upper` are + * floats, a floating-point number is returned instead of an integer. + * + * **Note:** JavaScript follows the IEEE-754 standard for resolving + * floating-point values which can produce unexpected results. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Number + * @param {number} [lower=0] The lower bound. + * @param {number} [upper=1] The upper bound. + * @param {boolean} [floating] Specify returning a floating-point number. + * @returns {number} Returns the random number. + * @example + * + * _.random(0, 5); + * // => an integer between 0 and 5 + * + * _.random(5); + * // => also an integer between 0 and 5 + * + * _.random(5, true); + * // => a floating-point number between 0 and 5 + * + * _.random(1.2, 5.2); + * // => a floating-point number between 1.2 and 5.2 + */ + function random(lower, upper, floating) { + if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { + upper = floating = undefined; + } + if (floating === undefined) { + if (typeof upper == 'boolean') { + floating = upper; + upper = undefined; + } + else if (typeof lower == 'boolean') { + floating = lower; + lower = undefined; + } + } + if (lower === undefined && upper === undefined) { + lower = 0; + upper = 1; + } + else { + lower = toFinite(lower); + if (upper === undefined) { + upper = lower; + lower = 0; + } else { + upper = toFinite(upper); + } + } + if (lower > upper) { + var temp = lower; + lower = upper; + upper = temp; + } + if (floating || lower % 1 || upper % 1) { + var rand = nativeRandom(); + return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper); + } + return baseRandom(lower, upper); + } + + /*------------------------------------------------------------------------*/ + + /** + * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the camel cased string. + * @example + * + * _.camelCase('Foo Bar'); + * // => 'fooBar' + * + * _.camelCase('--foo-bar--'); + * // => 'fooBar' + * + * _.camelCase('__FOO_BAR__'); + * // => 'fooBar' + */ + var camelCase = createCompounder(function(result, word, index) { + word = word.toLowerCase(); + return result + (index ? capitalize(word) : word); + }); + + /** + * Converts the first character of `string` to upper case and the remaining + * to lower case. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to capitalize. + * @returns {string} Returns the capitalized string. + * @example + * + * _.capitalize('FRED'); + * // => 'Fred' + */ + function capitalize(string) { + return upperFirst(toString(string).toLowerCase()); + } + + /** + * Deburrs `string` by converting + * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) + * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) + * letters to basic Latin letters and removing + * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to deburr. + * @returns {string} Returns the deburred string. + * @example + * + * _.deburr('déjà vu'); + * // => 'deja vu' + */ + function deburr(string) { + string = toString(string); + return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); + } + + /** + * Checks if `string` ends with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=string.length] The position to search up to. + * @returns {boolean} Returns `true` if `string` ends with `target`, + * else `false`. + * @example + * + * _.endsWith('abc', 'c'); + * // => true + * + * _.endsWith('abc', 'b'); + * // => false + * + * _.endsWith('abc', 'b', 2); + * // => true + */ + function endsWith(string, target, position) { + string = toString(string); + target = baseToString(target); + + var length = string.length; + position = position === undefined + ? length + : baseClamp(toInteger(position), 0, length); + + var end = position; + position -= target.length; + return position >= 0 && string.slice(position, end) == target; + } + + /** + * Converts the characters "&", "<", ">", '"', and "'" in `string` to their + * corresponding HTML entities. + * + * **Note:** No other characters are escaped. To escape additional + * characters use a third-party library like [_he_](https://mths.be/he). + * + * Though the ">" character is escaped for symmetry, characters like + * ">" and "/" don't need escaping in HTML and have no special meaning + * unless they're part of a tag or unquoted attribute value. See + * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) + * (under "semi-related fun fact") for more details. + * + * When working with HTML you should always + * [quote attribute values](http://wonko.com/post/html-escaping) to reduce + * XSS vectors. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escape('fred, barney, & pebbles'); + * // => 'fred, barney, & pebbles' + */ + function escape(string) { + string = toString(string); + return (string && reHasUnescapedHtml.test(string)) + ? string.replace(reUnescapedHtml, escapeHtmlChar) + : string; + } + + /** + * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", + * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escapeRegExp('[lodash](https://lodash.com/)'); + * // => '\[lodash\]\(https://lodash\.com/\)' + */ + function escapeRegExp(string) { + string = toString(string); + return (string && reHasRegExpChar.test(string)) + ? string.replace(reRegExpChar, '\\$&') + : string; + } + + /** + * Converts `string` to + * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the kebab cased string. + * @example + * + * _.kebabCase('Foo Bar'); + * // => 'foo-bar' + * + * _.kebabCase('fooBar'); + * // => 'foo-bar' + * + * _.kebabCase('__FOO_BAR__'); + * // => 'foo-bar' + */ + var kebabCase = createCompounder(function(result, word, index) { + return result + (index ? '-' : '') + word.toLowerCase(); + }); + + /** + * Converts `string`, as space separated words, to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the lower cased string. + * @example + * + * _.lowerCase('--Foo-Bar--'); + * // => 'foo bar' + * + * _.lowerCase('fooBar'); + * // => 'foo bar' + * + * _.lowerCase('__FOO_BAR__'); + * // => 'foo bar' + */ + var lowerCase = createCompounder(function(result, word, index) { + return result + (index ? ' ' : '') + word.toLowerCase(); + }); + + /** + * Converts the first character of `string` to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.lowerFirst('Fred'); + * // => 'fred' + * + * _.lowerFirst('FRED'); + * // => 'fRED' + */ + var lowerFirst = createCaseFirst('toLowerCase'); + + /** + * Pads `string` on the left and right sides if it's shorter than `length`. + * Padding characters are truncated if they can't be evenly divided by `length`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.pad('abc', 8); + * // => ' abc ' + * + * _.pad('abc', 8, '_-'); + * // => '_-abc_-_' + * + * _.pad('abc', 3); + * // => 'abc' + */ + function pad(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + if (!length || strLength >= length) { + return string; + } + var mid = (length - strLength) / 2; + return ( + createPadding(nativeFloor(mid), chars) + + string + + createPadding(nativeCeil(mid), chars) + ); + } + + /** + * Pads `string` on the right side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padEnd('abc', 6); + * // => 'abc ' + * + * _.padEnd('abc', 6, '_-'); + * // => 'abc_-_' + * + * _.padEnd('abc', 3); + * // => 'abc' + */ + function padEnd(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + return (length && strLength < length) + ? (string + createPadding(length - strLength, chars)) + : string; + } + + /** + * Pads `string` on the left side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padStart('abc', 6); + * // => ' abc' + * + * _.padStart('abc', 6, '_-'); + * // => '_-_abc' + * + * _.padStart('abc', 3); + * // => 'abc' + */ + function padStart(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + return (length && strLength < length) + ? (createPadding(length - strLength, chars) + string) + : string; + } + + /** + * Converts `string` to an integer of the specified radix. If `radix` is + * `undefined` or `0`, a `radix` of `10` is used unless `value` is a + * hexadecimal, in which case a `radix` of `16` is used. + * + * **Note:** This method aligns with the + * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category String + * @param {string} string The string to convert. + * @param {number} [radix=10] The radix to interpret `value` by. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {number} Returns the converted integer. + * @example + * + * _.parseInt('08'); + * // => 8 + * + * _.map(['6', '08', '10'], _.parseInt); + * // => [6, 8, 10] + */ + function parseInt(string, radix, guard) { + if (guard || radix == null) { + radix = 0; + } else if (radix) { + radix = +radix; + } + return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); + } + + /** + * Repeats the given string `n` times. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to repeat. + * @param {number} [n=1] The number of times to repeat the string. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {string} Returns the repeated string. + * @example + * + * _.repeat('*', 3); + * // => '***' + * + * _.repeat('abc', 2); + * // => 'abcabc' + * + * _.repeat('abc', 0); + * // => '' + */ + function repeat(string, n, guard) { + if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) { + n = 1; + } else { + n = toInteger(n); + } + return baseRepeat(toString(string), n); + } + + /** + * Replaces matches for `pattern` in `string` with `replacement`. + * + * **Note:** This method is based on + * [`String#replace`](https://mdn.io/String/replace). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to modify. + * @param {RegExp|string} pattern The pattern to replace. + * @param {Function|string} replacement The match replacement. + * @returns {string} Returns the modified string. + * @example + * + * _.replace('Hi Fred', 'Fred', 'Barney'); + * // => 'Hi Barney' + */ + function replace() { + var args = arguments, + string = toString(args[0]); + + return args.length < 3 ? string : string.replace(args[1], args[2]); + } + + /** + * Converts `string` to + * [snake case](https://en.wikipedia.org/wiki/Snake_case). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the snake cased string. + * @example + * + * _.snakeCase('Foo Bar'); + * // => 'foo_bar' + * + * _.snakeCase('fooBar'); + * // => 'foo_bar' + * + * _.snakeCase('--FOO-BAR--'); + * // => 'foo_bar' + */ + var snakeCase = createCompounder(function(result, word, index) { + return result + (index ? '_' : '') + word.toLowerCase(); + }); + + /** + * Splits `string` by `separator`. + * + * **Note:** This method is based on + * [`String#split`](https://mdn.io/String/split). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to split. + * @param {RegExp|string} separator The separator pattern to split by. + * @param {number} [limit] The length to truncate results to. + * @returns {Array} Returns the string segments. + * @example + * + * _.split('a-b-c', '-', 2); + * // => ['a', 'b'] + */ + function split(string, separator, limit) { + if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { + separator = limit = undefined; + } + limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; + if (!limit) { + return []; + } + string = toString(string); + if (string && ( + typeof separator == 'string' || + (separator != null && !isRegExp(separator)) + )) { + separator = baseToString(separator); + if (!separator && hasUnicode(string)) { + return castSlice(stringToArray(string), 0, limit); + } + } + return string.split(separator, limit); + } + + /** + * Converts `string` to + * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). + * + * @static + * @memberOf _ + * @since 3.1.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the start cased string. + * @example + * + * _.startCase('--foo-bar--'); + * // => 'Foo Bar' + * + * _.startCase('fooBar'); + * // => 'Foo Bar' + * + * _.startCase('__FOO_BAR__'); + * // => 'FOO BAR' + */ + var startCase = createCompounder(function(result, word, index) { + return result + (index ? ' ' : '') + upperFirst(word); + }); + + /** + * Checks if `string` starts with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=0] The position to search from. + * @returns {boolean} Returns `true` if `string` starts with `target`, + * else `false`. + * @example + * + * _.startsWith('abc', 'a'); + * // => true + * + * _.startsWith('abc', 'b'); + * // => false + * + * _.startsWith('abc', 'b', 1); + * // => true + */ + function startsWith(string, target, position) { + string = toString(string); + position = position == null + ? 0 + : baseClamp(toInteger(position), 0, string.length); + + target = baseToString(target); + return string.slice(position, position + target.length) == target; + } + + /** + * Creates a compiled template function that can interpolate data properties + * in "interpolate" delimiters, HTML-escape interpolated data properties in + * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data + * properties may be accessed as free variables in the template. If a setting + * object is given, it takes precedence over `_.templateSettings` values. + * + * **Note:** In the development build `_.template` utilizes + * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) + * for easier debugging. + * + * For more information on precompiling templates see + * [lodash's custom builds documentation](https://lodash.com/custom-builds). + * + * For more information on Chrome extension sandboxes see + * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The template string. + * @param {Object} [options={}] The options object. + * @param {RegExp} [options.escape=_.templateSettings.escape] + * The HTML "escape" delimiter. + * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] + * The "evaluate" delimiter. + * @param {Object} [options.imports=_.templateSettings.imports] + * An object to import into the template as free variables. + * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] + * The "interpolate" delimiter. + * @param {string} [options.sourceURL='lodash.templateSources[n]'] + * The sourceURL of the compiled template. + * @param {string} [options.variable='obj'] + * The data object variable name. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the compiled template function. + * @example + * + * // Use the "interpolate" delimiter to create a compiled template. + * var compiled = _.template('hello <%= user %>!'); + * compiled({ 'user': 'fred' }); + * // => 'hello fred!' + * + * // Use the HTML "escape" delimiter to escape data property values. + * var compiled = _.template('<%- value %>'); + * compiled({ 'value': '