text:   function typeOf(obj) {
      return toString.call(obj).slice(8, -1).toLowerCase();
    }
  
    function isNumber(num) {
      return typeof num === 'number' && !isNaN(num);
    }
  
    function isUndefined(obj) {
      return typeof obj === 'undefined';
    }
  
    function isObject(obj) {
      return typeof obj === 'object' && obj !== null;
    }
  
    function isPlainObject(obj) {
      var constructor;
      var prototype;
  
      if (!isObject(obj)) {
        return false;
      }
  
      try {
        constructor = obj.constructor;
        prototype = constructor.prototype;
  
        return constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
      } catch (e) {
        return false;
      }
    }
  
    function isFunction(fn) {
      return typeOf(fn) === 'function';
    }
  
    function isArray(arr) {
      return Array.isArray ? Array.isArray(arr) : typeOf(arr) === 'array';
    }
  
    function toArray(obj, offset) {
      offset = offset >= 0 ? offset : 0;
  
      if (Array.from) {
        return Array.from(obj).slice(offset);
      }
  
      return slice.call(obj, offset);
    }
  
    function trim(str) {
      if (typeof str === 'string') {
        str = str.trim ? str.trim() : str.replace(REGEXP_TRIM, '$1');
      }
  
      return str;
    }
  
    function each(obj, callback) {
      var length;
      var i;
  
      if (obj && isFunction(callback)) {
        if (isArray(obj) || isNumber(obj.length)/* array-like */) {
          for (i = 0, length = obj.length; i < length; i++) {
            if (callback.call(obj, obj[i], i, obj) === false) {
              break;
            }
          }
        } else if (isObject(obj)) {
          for (i in obj) {
            if (obj.hasOwnProperty(i)) {
              if (callback.call(obj, obj[i], i, obj) === false) {
                break;
              }
            }
          }
        }
      }
  
      return obj;
    }
  
    function extend(obj) {
      var args;
  
      if (arguments.length > 1) {
        args = toArray(arguments);
  
        if (Object.assign) {
          return Object.assign.apply(Object, args);
        }
  
        args.shift();
  
        each(args, function (arg) {
          each(arg, function (prop, i) {
            obj[i] = prop;
          });
        });
      }
  
      return obj;
    }
  
    function proxy(fn, context) {
      var args = toArray(arguments, 2);
  
      return function () {
        return fn.apply(context, args.concat(toArray(arguments)));
      };
    }
  
    function setStyle(element, styles) {
      var style = element.style;
  
      each(styles, function (value, property) {
        if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
          value += 'px';
        }
  
        style[property] = value;
      });
    }
  
    function hasClass(element, value) {
      return element.classList ?
        element.classList.contains(value) :
        element.className.indexOf(value) > -1;
    }
  
    function addClass(element, value) {
      var className;
  
      if (isNumber(element.length)) {
        return each(element, function (elem) {
          addClass(elem, value);
        });
      }
  
      if (element.classList) {
        return element.classList.add(value);
      }
  
      className = trim(element.className);
  
      if (!className) {
        element.className = value;
      } else if (className.indexOf(value) < 0) {
        element.className = className + ' ' + value;
      }
    }
  
    function removeClass(element, value) {
      if (isNumber(element.length)) {
        return each(element, function (elem) {
          removeClass(elem, value);
        });
      }
  
      if (element.classList) {
        return element.classList.remove(value);
      }
  
      if (element.className.indexOf(value) >= 0) {
        element.className = element.className.replace(value, '');
      }
    }
  
    function toggleClass(element, value, added) {
      if (isNumber(element.length)) {
        return each(element, function (elem) {
          toggleClass(elem, value, added);
        });
      }
  
      // IE10-11 doesn't support the second parameter of `classList.toggle`
      if (added) {
        addClass(element, value);
      } else {
        removeClass(element, value);
      }
    }
  
    function getData(element, name) {
      return isObject(element[name]) ?
        element[name] :
        element.dataset ?
          element.dataset[name] :
          element.getAttribute('data-' + name);
    }
  
    function setData(element, name, data) {
      if (isObject(data) && isUndefined(element[name])) {
        element[name] = data;
      } else if (element.dataset) {
        element.dataset[name] = data;
      } else {
        element.setAttribute('data-' + name, data);
      }
    }
  
    function removeData(element, name) {
      if (isObject(element[name])) {
        delete element[name];
      } else if (element.dataset) {
        delete element.dataset[name];
      } else {
        element.removeAttribute('data-' + name);
      }
    }
  
    function addListener(element, type, handler, once) {
      var types = trim(type).split(REGEXP_SPACES);
      var originalHandler = handler;
  
      if (types.length > 1) {
        return each(types, function (type) {
          addListener(element, type, handler);
        });
      }
  
      if (once) {
        handler = function () {
          removeListener(element, type, handler);
  
          return originalHandler.apply(element, arguments);
        };
      }
  
      if (element.addEventListener) {
        element.addEventListener(type, handler, false);
      } else if (element.attachEvent) {
        element.attachEvent('on' + type, handler);
      }
    }
  
    function removeListener(element, type, handler) {
      var types = trim(type).split(REGEXP_SPACES);
  
      if (types.length > 1) {
        return each(types, function (type) {
          removeListener(element, type, handler);
        });
      }
  
      if (element.removeEventListener) {
        element.removeEventListener(type, handler, false);
      } else if (element.detachEvent) {
        element.detachEvent('on' + type, handler);
      }
    }
  
    function dispatchEvent(element, type, data) {
      var event;
  
      if (element.dispatchEvent) {
  
        // Event and CustomEvent on IE9-11 are global objects, not constructors
        if (isFunction(Event) && isFunction(CustomEvent)) {
          if (isUndefined(data)) {
            event = new Event(type, {
              bubbles: true,
              cancelable: true
            });
          } else {
            event = new CustomEvent(type, {
              detail: data,
              bubbles: true,
              cancelable: true
            });
          }
        } else {
          // IE9-11
          if (isUndefined(data)) {
            event = document.createEvent('Event');
            event.initEvent(type, true, true);
          } else {
            event = document.createEvent('CustomEvent');
            event.initCustomEvent(type, true, true, data);
          }
        }
  
        // IE9+
        return element.dispatchEvent(event);
      } else if (element.fireEvent) {
  
        // IE6-10 (native events only)
        return element.fireEvent('on' + type);
      }
    }
  
    function preventDefault(e) {
      if (e.preventDefault) {
        e.preventDefault();
      } else {
        e.returnValue = false;
      }
    }
  
    function getEvent(event) {
      var e = event || window.event;
      var doc;
  
      // Fix target property (IE8)
      if (!e.target) {
        e.target = e.srcElement || document;
      }
  
      if (!isNumber(e.pageX)) {
        doc = document.documentElement;
        e.pageX = e.clientX + (window.scrollX || doc && doc.scrollLeft || 0) - (doc && doc.clientLeft || 0);
        e.pageY = e.clientY + (window.scrollY || doc && doc.scrollTop || 0) - (doc && doc.clientTop || 0);
      }
  
      return e;
    }
  
    function getOffset(element) {
      var doc = document.documentElement;
      var box = element.getBoundingClientRect();
  
      return {
        left: box.left + (window.scrollX || doc && doc.scrollLeft || 0) - (doc && doc.clientLeft || 0),
        top: box.top + (window.scrollY || doc && doc.scrollTop || 0) - (doc && doc.clientTop || 0)
      };
    }
  
    function getTouchesCenter(touches) {
      var length = touches.length;
      var pageX = 0;
      var pageY = 0;
  
      if (length) {
        each(touches, function (touch) {
          pageX += touch.pageX;
          pageY += touch.pageY;
        });
  
        pageX /= length;
        pageY /= length;
      }
  
      return {
        pageX: pageX,
        pageY: pageY
      };
    }
  
    function getByTag(element, tagName) {
      return element.getElementsByTagName(tagName);
    }
  
    function getByClass(element, className) {
      return element.getElementsByClassName ?
        element.getElementsByClassName(className) :
        element.querySelectorAll('.' + className);
    }
  
    function createElement(tagName) {
      return document.createElement(tagName);
    }
  
    function appendChild(element, elem) {
      element.appendChild(elem);
    }
  
    function removeChild(element) {
      if (element.parentNode) {
        element.parentNode.removeChild(element);
      }
    }
  
    function empty(element) {
      while (element.firstChild) {
        element.removeChild(element.firstChild);
      }
    }
  
    function isCrossOriginURL(url) {
      var parts = url.match(REGEXP_ORIGINS);
  
      return parts && (
        parts[1] !== location.protocol ||
        parts[2] !== location.hostname ||
        parts[3] !== location.port
      );
    }
  
    function addTimestamp(url) {
      var timestamp = 'timestamp=' + (new Date()).getTime();
  
      return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
    }
  
    function getImageSize(image, callback) {
      var newImage;
  
      // Modern browsers (ignore Safari)
      if (image.naturalWidth && !IS_SAFARI) {
        return callback(image.naturalWidth, image.naturalHeight);
      }
  
      // IE8: Don't use `new Image()` here
      newImage = createElement('img');
  
      newImage.onload = function () {
        callback(this.width, this.height);
      };
  
      newImage.src = image.src;
    }
  
    function getTransform(data) {
      var transforms = [];
      var rotate = data.rotate;
      var scaleX = data.scaleX;
      var scaleY = data.scaleY;
  
      if (isNumber(rotate)) {
        transforms.push('rotate(' + rotate + 'deg)');
      }
  
      if (isNumber(scaleX) && isNumber(scaleY)) {
        transforms.push('scale(' + scaleX + ',' + scaleY + ')');
      }
  
      return transforms.length ? transforms.join(' ') : 'none';
    }
  
    function getRotatedSizes(data, reversed) {
      var deg = abs(data.degree) % 180;
      var arc = (deg > 90 ? (180 - deg) : deg) * PI / 180;
      var sinArc = sin(arc);
      var cosArc = cos(arc);
      var width = data.width;
      var height = data.height;
      var aspectRatio = data.aspectRatio;
      var newWidth;
      var newHeight;
  
      if (!reversed) {
        newWidth = width * cosArc + height * sinArc;
        newHeight = width * sinArc + height * cosArc;
      } else {
        newWidth = width / (cosArc + sinArc / aspectRatio);
        newHeight = newWidth / aspectRatio;
      }
  
      return {
        width: newWidth,
        height: newHeight
      };
    }
  
    function getSourceCanvas(image, data) {
      var canvas = createElement('canvas');
      var context = canvas.getContext('2d');
      var dstX = 0;
      var dstY = 0;
      var dstWidth = data.naturalWidth;
      var dstHeight = data.naturalHeight;
      var rotate = data.rotate;
      var scaleX = data.scaleX;
      var scaleY = data.scaleY;
      var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1);
      var rotatable = isNumber(rotate) && rotate !== 0;
      var advanced = rotatable || scalable;
      var canvasWidth = dstWidth * abs(scaleX || 1);
      var canvasHeight = dstHeight * abs(scaleY || 1);
      var translateX;
      var translateY;
      var rotated;
  
      if (scalable) {
        translateX = canvasWidth / 2;
        translateY = canvasHeight / 2;
      }
  
      if (rotatable) {
        rotated = getRotatedSizes({
          width: canvasWidth,
          height: canvasHeight,
          degree: rotate
        });
  
        canvasWidth = rotated.width;
        canvasHeight = rotated.height;
        translateX = canvasWidth / 2;
        translateY = canvasHeight / 2;
      }
  
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;
  
      if (advanced) {
        dstX = -dstWidth / 2;
        dstY = -dstHeight / 2;
  
        context.save();
        context.translate(translateX, translateY);
      }
  
      if (rotatable) {
        context.rotate(rotate * PI / 180);
      }
  
      // Should call `scale` after rotated
      if (scalable) {
        context.scale(scaleX, scaleY);
      }
  
      context.drawImage(image, floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
  
      if (advanced) {
        context.restore();
      }
  
      return canvas;
    }
  
    function getStringFromCharCode(dataView, start, length) {
      var str = '';
      var i = start;
  
      for (length += start; i < length; i++) {
        str += fromCharCode(dataView.getUint8(i));
      }
  
      return str;
    }
  
    function getOrientation(arrayBuffer) {
      var dataView = new DataView(arrayBuffer);
      var length = dataView.byteLength;
      var orientation;
      var exifIDCode;
      var tiffOffset;
      var firstIFDOffset;
      var littleEndian;
      var endianness;
      var app1Start;
      var ifdStart;
      var offset;
      var i;
  
      // Only handle JPEG image (start by 0xFFD8)
      if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
        offset = 2;
  
        while (offset < length) {
          if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
            app1Start = offset;
            break;
          }
  
          offset++;
        }
      }
  
      if (app1Start) {
        exifIDCode = app1Start + 4;
        tiffOffset = app1Start + 10;
  
        if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
          endianness = dataView.getUint16(tiffOffset);
          littleEndian = endianness === 0x4949;
  
          if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
            if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
              firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
  
              if (firstIFDOffset >= 0x00000008) {
                ifdStart = tiffOffset + firstIFDOffset;
              }
            }
          }
        }
      }
  
      if (ifdStart) {
        length = dataView.getUint16(ifdStart, littleEndian);
  
        for (i = 0; i < length; i++) {
          offset = ifdStart + i * 12 + 2;
  
          if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
  
            // 8 is the offset of the current tag's value
            offset += 8;
  
            // Get the original orientation value
            orientation = dataView.getUint16(offset, littleEndian);
  
            // Override the orientation with its default value for Safari
            if (IS_SAFARI) {
              dataView.setUint16(offset, 1, littleEndian);
            }
  
            break;
          }
        }
      }
  
      return orientation;
    }
  
    function dataURLToArrayBuffer(dataURL) {
      var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
      var binary = atob(base64);
      var length = binary.length;
      var arrayBuffer = new ArrayBuffer(length);
      var dataView = new Uint8Array(arrayBuffer);
      var i;
  
      for (i = 0; i < length; i++) {
        dataView[i] = binary.charCodeAt(i);
      }
  
      return arrayBuffer;
    }
  
    // Only available for JPEG image
    function arrayBufferToDataURL(arrayBuffer) {
      var dataView = new Uint8Array(arrayBuffer);
      var length = dataView.length;
      var base64 = '';
      var i;
  
      for (i = 0; i < length; i++) {
        base64 += fromCharCode(dataView[i]);
      }
  
      return 'data:image/jpeg;base64,' + btoa(base64);
    }
  