text: /**
   * vivus - JavaScript library to make drawing animation on SVG
   * @version v0.4.2
   * @link https://github.com/maxwellito/vivus
   * @license MIT
   */
  
  'use strict';
  
  (function () {
  
    'use strict';
  
  /**
   * Pathformer
   * Beta version
   *
   * Take any SVG version 1.1 and transform
   * child elements to 'path' elements
   *
   * This code is purely forked from
   * https://github.com/Waest/SVGPathConverter
   */
  
  /**
   * Class constructor
   *
   * @param {DOM|String} element Dom element of the SVG or id of it
   */
  function Pathformer(element) {
    // Test params
    if (typeof element === 'undefined') {
      throw new Error('Pathformer [constructor]: "element" parameter is required');
    }
  
    // Set the element
    if (element.constructor === String) {
      element = document.getElementById(element);
      if (!element) {
        throw new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID');
      }
    }
    if (element instanceof window.SVGElement || 
        element instanceof window.SVGGElement ||
        /^svg$/i.test(element.nodeName)) {
      this.el = element;
    } else {
      throw new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement');
    }
  
    // Start
    this.scan(element);
  }
  
  /**
   * List of tags which can be transformed
   * to path elements
   *
   * @type {Array}
   */
  Pathformer.prototype.TYPES = ['line', 'ellipse', 'circle', 'polygon', 'polyline', 'rect'];
  
  /**
   * List of attribute names which contain
   * data. This array list them to check if
   * they contain bad values, like percentage.
   *
   * @type {Array}
   */
  Pathformer.prototype.ATTR_WATCH = ['cx', 'cy', 'points', 'r', 'rx', 'ry', 'x', 'x1', 'x2', 'y', 'y1', 'y2'];
  
  /**
   * Finds the elements compatible for transform
   * and apply the liked method
   *
   * @param  {object} options Object from the constructor
   */
  Pathformer.prototype.scan = function (svg) {
    var fn, element, pathData, pathDom,
        elements = svg.querySelectorAll(this.TYPES.join(','));
  
    for (var i = 0; i < elements.length; i++) {
      element = elements[i];
      fn = this[element.tagName.toLowerCase() + 'ToPath'];
      pathData = fn(this.parseAttr(element.attributes));
      pathDom = this.pathMaker(element, pathData);
      element.parentNode.replaceChild(pathDom, element);
    }
  };
  
  
  /**
   * Read `line` element to extract and transform
   * data, to make it ready for a `path` object.
   *
   * @param  {DOMelement} element Line element to transform
   * @return {object}             Data for a `path` element
   */
  Pathformer.prototype.lineToPath = function (element) {
    var newElement = {},
        x1 = element.x1 || 0,
        y1 = element.y1 || 0,
        x2 = element.x2 || 0,
        y2 = element.y2 || 0;
  
    newElement.d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
    return newElement;
  };
  
  /**
   * Read `rect` element to extract and transform
   * data, to make it ready for a `path` object.
   * The radius-border is not taken in charge yet.
   * (your help is more than welcomed)
   *
   * @param  {DOMelement} element Rect element to transform
   * @return {object}             Data for a `path` element
   */
  Pathformer.prototype.rectToPath = function (element) {
    var newElement = {},
        x      = parseFloat(element.x)      || 0,
        y      = parseFloat(element.y)      || 0,
        width  = parseFloat(element.width)  || 0,
        height = parseFloat(element.height) || 0;
  
    if (element.rx || element.ry) {
      var rx = parseInt(element.rx, 10) || -1,
          ry = parseInt(element.ry, 10) || -1;
      rx = Math.min(Math.max(rx < 0 ? ry : rx, 0), width/2);
      ry = Math.min(Math.max(ry < 0 ? rx : ry, 0), height/2);
  
      newElement.d = 'M ' + (x + rx) + ',' + y + ' ' +
                     'L ' + (x + width - rx) + ',' + y + ' ' +
                     'A ' + rx + ',' + ry + ',0,0,1,' + (x + width) + ',' + (y + ry) + ' ' +
                     'L ' + (x + width) + ',' + (y + height - ry) + ' ' +
                     'A ' + rx + ',' + ry + ',0,0,1,' + (x + width - rx) + ',' + (y + height) + ' ' +
                     'L ' + (x + rx) + ',' + (y + height) + ' ' +
                     'A ' + rx + ',' + ry + ',0,0,1,' + x + ',' + (y + height - ry) + ' ' +
                     'L ' + x + ',' + (y + ry) + ' ' +
                     'A ' + rx + ',' + ry + ',0,0,1,' + (x + rx) + ',' + y;
    }
    else {
      newElement.d = 'M' + x + ' ' + y + ' ' +
                     'L' + (x + width) + ' ' + y + ' ' +
                     'L' + (x + width) + ' ' + (y + height) + ' ' +
                     'L' + x + ' ' + (y + height) + ' Z';
    }
    return newElement;
  };
  
  /**
   * Read `polyline` element to extract and transform
   * data, to make it ready for a `path` object.
   *
   * @param  {DOMelement} element Polyline element to transform
   * @return {object}             Data for a `path` element
   */
  Pathformer.prototype.polylineToPath = function (element) {
    var newElement = {},
        points = element.points.trim().split(' '),
        i, path;
  
    // Reformatting if points are defined without commas
    if (element.points.indexOf(',') === -1) {
      var formattedPoints = [];
      for (i = 0; i < points.length; i+=2) {
        formattedPoints.push(points[i] + ',' + points[i+1]);
      }
      points = formattedPoints;
    }
  
    // Generate the path.d value
    path = 'M' + points[0];
    for(i = 1; i < points.length; i++) {
      if (points[i].indexOf(',') !== -1) {
        path += 'L' + points[i];
      }
    }
    newElement.d = path;
    return newElement;
  };
  
  /**
   * Read `polygon` element to extract and transform
   * data, to make it ready for a `path` object.
   * This method rely on polylineToPath, because the
   * logic is similar. The path created is just closed,
   * so it needs an 'Z' at the end.
   *
   * @param  {DOMelement} element Polygon element to transform
   * @return {object}             Data for a `path` element
   */
  Pathformer.prototype.polygonToPath = function (element) {
    var newElement = Pathformer.prototype.polylineToPath(element);
  
    newElement.d += 'Z';
    return newElement;
  };
  
  /**
   * Read `ellipse` element to extract and transform
   * data, to make it ready for a `path` object.
   *
   * @param  {DOMelement} element ellipse element to transform
   * @return {object}             Data for a `path` element
   */
  Pathformer.prototype.ellipseToPath = function (element) {
    var newElement = {},
        rx = parseFloat(element.rx) || 0,
        ry = parseFloat(element.ry) || 0,
        cx = parseFloat(element.cx) || 0,
        cy = parseFloat(element.cy) || 0,
        startX = cx - rx,
        startY = cy,
        endX = parseFloat(cx) + parseFloat(rx),
        endY = cy;
  
    newElement.d = 'M' + startX + ',' + startY +
                   'A' + rx + ',' + ry + ' 0,1,1 ' + endX + ',' + endY +
                   'A' + rx + ',' + ry + ' 0,1,1 ' + startX + ',' + endY;
    return newElement;
  };
  
  /**
   * Read `circle` element to extract and transform
   * data, to make it ready for a `path` object.
   *
   * @param  {DOMelement} element Circle element to transform
   * @return {object}             Data for a `path` element
   */
  Pathformer.prototype.circleToPath = function (element) {
    var newElement = {},
        r  = parseFloat(element.r)  || 0,
        cx = parseFloat(element.cx) || 0,
        cy = parseFloat(element.cy) || 0,
        startX = cx - r,
        startY = cy,
        endX = parseFloat(cx) + parseFloat(r),
        endY = cy;
        
    newElement.d =  'M' + startX + ',' + startY +
                    'A' + r + ',' + r + ' 0,1,1 ' + endX + ',' + endY +
                    'A' + r + ',' + r + ' 0,1,1 ' + startX + ',' + endY;
    return newElement;
  };
  
  /**
   * Create `path` elements form original element
   * and prepared objects
   *
   * @param  {DOMelement} element  Original element to transform
   * @param  {object} pathData     Path data (from `toPath` methods)
   * @return {DOMelement}          Path element
   */
  Pathformer.prototype.pathMaker = function (element, pathData) {
    var i, attr, pathTag = document.createElementNS('http://www.w3.org/2000/svg','path');
    for(i = 0; i < element.attributes.length; i++) {
      attr = element.attributes[i];
      if (this.ATTR_WATCH.indexOf(attr.name) === -1) {
        pathTag.setAttribute(attr.name, attr.value);
      }
    }
    for(i in pathData) {
      pathTag.setAttribute(i, pathData[i]);
    }
    return pathTag;
  };
  
  /**
   * Parse attributes of a DOM element to
   * get an object of attribute => value
   *
   * @param  {NamedNodeMap} attributes Attributes object from DOM element to parse
   * @return {object}                  Object of attributes
   */
  Pathformer.prototype.parseAttr = function (element) {
    var attr, output = {};
    for (var i = 0; i < element.length; i++) {
      attr = element[i];
      // Check if no data attribute contains '%', or the transformation is impossible
      if (this.ATTR_WATCH.indexOf(attr.name) !== -1 && attr.value.indexOf('%') !== -1) {
        throw new Error('Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into \'path\' tags. Please use \'viewBox\'.');
      }
      output[attr.name] = attr.value;
    }
    return output;
  };
  
    'use strict';
  
  var setupEnv, requestAnimFrame, cancelAnimFrame, parsePositiveInt;
  
  /**
   * Vivus
   * Beta version
   *
   * Take any SVG and make the animation
   * to give give the impression of live drawing
   *
   * This in more than just inspired from codrops
   * At that point, it's a pure fork.
   */
  
  /**
   * Class constructor
   * option structure
   *   type: 'delayed'|'sync'|'oneByOne'|'script' (to know if the items must be drawn synchronously or not, default: delayed)
   *   duration: <int> (in frames)
   *   start: 'inViewport'|'manual'|'autostart' (start automatically the animation, default: inViewport)
   *   delay: <int> (delay between the drawing of first and last path)
   *   dashGap <integer> whitespace extra margin between dashes
   *   pathTimingFunction <function> timing animation function for each path element of the SVG
   *   animTimingFunction <function> timing animation function for the complete SVG
   *   forceRender <boolean> force the browser to re-render all updated path items
   *   selfDestroy <boolean> removes all extra styling on the SVG, and leaves it as original
   *
   * The attribute 'type' is by default on 'delayed'.
   *  - 'delayed'
   *    all paths are draw at the same time but with a
   *    little delay between them before start
   *  - 'sync'
   *    all path are start and finish at the same time
   *  - 'oneByOne'
   *    only one path is draw at the time
   *    the end of the first one will trigger the draw
   *    of the next one
   *
   * All these values can be overwritten individually
   * for each path item in the SVG
   * The value of frames will always take the advantage of
   * the duration value.
   * If you fail somewhere, an error will be thrown.
   * Good luck.
   *
   * @constructor
   * @this {Vivus}
   * @param {DOM|String}   element  Dom element of the SVG or id of it
   * @param {Object}       options  Options about the animation
   * @param {Function}     callback Callback for the end of the animation
   */
  function Vivus (element, options, callback) {
  
    setupEnv();
  
    // Setup
    this.isReady = false;
    this.setElement(element, options);
    this.setOptions(options);
    this.setCallback(callback);
  
    if (this.isReady) {
      this.init();
    }
  }
  
  /**
   * Timing functions
   **************************************
   *
   * Default functions to help developers.
   * It always take a number as parameter (between 0 to 1) then
   * return a number (between 0 and 1)
   */
  Vivus.LINEAR          = function (x) {return x;};
  Vivus.EASE            = function (x) {return -Math.cos(x * Math.PI) / 2 + 0.5;};
  Vivus.EASE_OUT        = function (x) {return 1 - Math.pow(1-x, 3);};
  Vivus.EASE_IN         = function (x) {return Math.pow(x, 3);};
  Vivus.EASE_OUT_BOUNCE = function (x) {
    var base = -Math.cos(x * (0.5 * Math.PI)) + 1,
      rate = Math.pow(base,1.5),
      rateR = Math.pow(1 - x, 2),
      progress = -Math.abs(Math.cos(rate * (2.5 * Math.PI) )) + 1;
    return (1- rateR) + (progress * rateR);
  };
  
  
  /**
   * Setters
   **************************************
   */
  
  /**
   * Check and set the element in the instance
   * The method will not return anything, but will throw an
   * error if the parameter is invalid
   *
   * @param {DOM|String}   element  SVG Dom element or id of it
   */
  Vivus.prototype.setElement = function (element, options) {
    // Basic check
    if (typeof element === 'undefined') {
      throw new Error('Vivus [constructor]: "element" parameter is required');
    }
  
    // Set the element
    if (element.constructor === String) {
      element = document.getElementById(element);
      if (!element) {
        throw new Error('Vivus [constructor]: "element" parameter is not related to an existing ID');
      }
    }
    this.parentEl = element;
  
    // Create the object element if the property `file` exists in the options object
    if (options && options.file) {
      var objElm = document.createElement('object');
      objElm.setAttribute('type', 'image/svg+xml');
      objElm.setAttribute('data', options.file);
      objElm.setAttribute('built-by-vivus', 'true');
      element.appendChild(objElm);
      element = objElm;
    }
  
    switch (element.constructor) {
    case window.SVGSVGElement:
    case window.SVGElement:
    case window.SVGGElement:
      this.el = element;
      this.isReady = true;
      break;
  
    case window.HTMLObjectElement:
      // If we have to wait for it
      var onLoad, self;
  
      self = this;
      onLoad = function (e) {
        if (self.isReady) {
          return;
        }
        self.el = element.contentDocument && element.contentDocument.querySelector('svg');
        if (!self.el && e) {
          throw new Error('Vivus [constructor]: object loaded does not contain any SVG');
        }
        else if (self.el) {
          if (element.getAttribute('built-by-vivus')) {
            self.parentEl.insertBefore(self.el, element);
            self.parentEl.removeChild(element);
            self.el.setAttribute('width', '100%');
            self.el.setAttribute('height', '100%');
          }
          self.isReady = true;
          self.init();
          return true;
        }
      };
  
      if (!onLoad()) {
        element.addEventListener('load', onLoad);
      }
      break;
  
    default:
      throw new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)');
    }
  };
  
  /**
   * Set up user option to the instance
   * The method will not return anything, but will throw an
   * error if the parameter is invalid
   *
   * @param  {object} options Object from the constructor
   */
  Vivus.prototype.setOptions = function (options) {
    var allowedTypes = ['delayed', 'sync', 'async', 'nsync', 'oneByOne', 'scenario', 'scenario-sync'];
    var allowedStarts =  ['inViewport', 'manual', 'autostart'];
  
    // Basic check
    if (options !== undefined && options.constructor !== Object) {
      throw new Error('Vivus [constructor]: "options" parameter must be an object');
    }
    else {
      options = options || {};
    }
  
    // Set the animation type
    if (options.type && allowedTypes.indexOf(options.type) === -1) {
      throw new Error('Vivus [constructor]: ' + options.type + ' is not an existing animation `type`');
    }
    else {
      this.type = options.type || allowedTypes[0];
    }
  
    // Set the start type
    if (options.start && allowedStarts.indexOf(options.start) === -1) {
      throw new Error('Vivus [constructor]: ' + options.start + ' is not an existing `start` option');
    }
    else {
      this.start = options.start || allowedStarts[0];
    }
  
    this.isIE         = (window.navigator.userAgent.indexOf('MSIE') !== -1 || window.navigator.userAgent.indexOf('Trident/') !== -1 || window.navigator.userAgent.indexOf('Edge/') !== -1 );
    this.duration     = parsePositiveInt(options.duration, 120);
    this.delay        = parsePositiveInt(options.delay, null);
    this.dashGap      = parsePositiveInt(options.dashGap, 1);
    this.forceRender  = options.hasOwnProperty('forceRender') ? !!options.forceRender : this.isIE;
    this.reverseStack = !!options.reverseStack;
    this.selfDestroy  = !!options.selfDestroy;
    this.onReady      = options.onReady;
    this.map          = [];
    this.frameLength  = this.currentFrame = this.delayUnit = this.speed = this.handle = null;
  
    this.ignoreInvisible = options.hasOwnProperty('ignoreInvisible') ? !!options.ignoreInvisible : false;
  
    this.animTimingFunction = options.animTimingFunction || Vivus.LINEAR;
    this.pathTimingFunction = options.pathTimingFunction || Vivus.LINEAR;
  
    if (this.delay >= this.duration) {
      throw new Error('Vivus [constructor]: delay must be shorter than duration');
    }
  };
  
  /**
   * Set up callback to the instance
   * The method will not return enything, but will throw an
   * error if the parameter is invalid
   *
   * @param  {Function} callback Callback for the animation end
   */
  Vivus.prototype.setCallback = function (callback) {
    // Basic check
    if (!!callback && callback.constructor !== Function) {
      throw new Error('Vivus [constructor]: "callback" parameter must be a function');
    }
    this.callback = callback || function () {};
  };
  
  
  /**
   * Core
   **************************************
   */
  
  /**
   * Map the svg, path by path.
   * The method return nothing, it just fill the
   * `map` array. Each item in this array represent
   * a path element from the SVG, with informations for
   * the animation.
   *
   * ```
   * [
   *   {
   *     el: <DOMobj> the path element
   *     length: <number> length of the path line
   *     startAt: <number> time start of the path animation (in frames)
   *     duration: <number> path animation duration (in frames)
   *   },
   *   ...
   * ]
   * ```
   *
   */
  Vivus.prototype.mapping = function () {
    var i, paths, path, pAttrs, pathObj, totalLength, lengthMeter, timePoint;
    timePoint = totalLength = lengthMeter = 0;
    paths = this.el.querySelectorAll('path');
  
    for (i = 0; i < paths.length; i++) {
      path = paths[i];
      if (this.isInvisible(path)) {
        continue;
      }
      pathObj = {
        el: path,
        length: Math.ceil(path.getTotalLength())
      };
      // Test if the path length is correct
      if (isNaN(pathObj.length)) {
        if (window.console && console.warn) {
          console.warn('Vivus [mapping]: cannot retrieve a path element length', path);
        }
        continue;
      }
      this.map.push(pathObj);
      path.style.strokeDasharray  = pathObj.length + ' ' + (pathObj.length + this.dashGap * 2);
      path.style.strokeDashoffset = pathObj.length + this.dashGap;
      pathObj.length += this.dashGap;
      totalLength += pathObj.length;
  
      this.renderPath(i);
    }
  
    totalLength = totalLength === 0 ? 1 : totalLength;
    this.delay = this.delay === null ? this.duration / 3 : this.delay;
    this.delayUnit = this.delay / (paths.length > 1 ? paths.length - 1 : 1);
  
    // Reverse stack if asked
    if (this.reverseStack) {
      this.map.reverse();
    }
  
    for (i = 0; i < this.map.length; i++) {
      pathObj = this.map[i];
  
      switch (this.type) {
      case 'delayed':
        pathObj.startAt = this.delayUnit * i;
        pathObj.duration = this.duration - this.delay;
        break;
  
      case 'oneByOne':
        pathObj.startAt = lengthMeter / totalLength * this.duration;
        pathObj.duration = pathObj.length / totalLength * this.duration;
        break;
  
      case 'sync':
      case 'async':
      case 'nsync':
        pathObj.startAt = 0;
        pathObj.duration = this.duration;
        break;
  
      case 'scenario-sync':
        path = pathObj.el;
        pAttrs = this.parseAttr(path);
        pathObj.startAt = timePoint + (parsePositiveInt(pAttrs['data-delay'], this.delayUnit) || 0);
        pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration);
        timePoint = pAttrs['data-async'] !== undefined ? pathObj.startAt : pathObj.startAt + pathObj.duration;
        this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration));
        break;
  
      case 'scenario':
        path = pathObj.el;
        pAttrs = this.parseAttr(path);
        pathObj.startAt = parsePositiveInt(pAttrs['data-start'], this.delayUnit) || 0;
        pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration);
        this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration));
        break;
      }
      lengthMeter += pathObj.length;
      this.frameLength = this.frameLength || this.duration;
    }
  };
  
  /**
   * Interval method to draw the SVG from current
   * position of the animation. It update the value of
   * `currentFrame` and re-trace the SVG.
   *
   * It use this.handle to store the requestAnimationFrame
   * and clear it one the animation is stopped. So this
   * attribute can be used to know if the animation is
   * playing.
   *
   * Once the animation at the end, this method will
   * trigger the Vivus callback.
   *
   */
  Vivus.prototype.drawer = function () {
    var self = this;
    this.currentFrame += this.speed;
  
    if (this.currentFrame <= 0) {
      this.stop();
      this.reset();
    } else if (this.currentFrame >= this.frameLength) {
      this.stop();
      this.currentFrame = this.frameLength;
      this.trace();
      if (this.selfDestroy) {
        this.destroy();
      }
    } else {
      this.trace();
      this.handle = requestAnimFrame(function () {
        self.drawer();
      });
      return;
    }
  
    this.callback(this);
    if (this.instanceCallback) {
      this.instanceCallback(this);
      this.instanceCallback = null;
    }
  };
  
  /**
   * Draw the SVG at the current instant from the
   * `currentFrame` value. Here is where most of the magic is.
   * The trick is to use the `strokeDashoffset` style property.
   *
   * For optimisation reasons, a new property called `progress`
   * is added in each item of `map`. This one contain the current
   * progress of the path element. Only if the new value is different
   * the new value will be applied to the DOM element. This
   * method save a lot of resources to re-render the SVG. And could
   * be improved if the animation couldn't be played forward.
   *
   */
  Vivus.prototype.trace = function () {
    var i, progress, path, currentFrame;
    currentFrame = this.animTimingFunction(this.currentFrame / this.frameLength) * this.frameLength;
    for (i = 0; i < this.map.length; i++) {
      path = this.map[i];
      progress = (currentFrame - path.startAt) / path.duration;
      progress = this.pathTimingFunction(Math.max(0, Math.min(1, progress)));
      if (path.progress !== progress) {
        path.progress = progress;
        path.el.style.strokeDashoffset = Math.floor(path.length * (1 - progress));
        this.renderPath(i);
      }
    }
  };
  
  /**
   * Method forcing the browser to re-render a path element
   * from it's index in the map. Depending on the `forceRender`
   * value.
   * The trick is to replace the path element by it's clone.
   * This practice is not recommended because it's asking more
   * ressources, too much DOM manupulation..
   * but it's the only way to let the magic happen on IE.
   * By default, this fallback is only applied on IE.
   *
   * @param  {Number} index Path index
   */
  Vivus.prototype.renderPath = function (index) {
    if (this.forceRender && this.map && this.map[index]) {
      var pathObj = this.map[index],
          newPath = pathObj.el.cloneNode(true);
      pathObj.el.parentNode.replaceChild(newPath, pathObj.el);
      pathObj.el = newPath;
    }
  };
  
  /**
   * When the SVG object is loaded and ready,
   * this method will continue the initialisation.
   *
   * This this mainly due to the case of passing an
   * object tag in the constructor. It will wait
   * the end of the loading to initialise.
   *
   */
  Vivus.prototype.init = function () {
    // Set object variables
    this.frameLength = 0;
    this.currentFrame = 0;
    this.map = [];
  
    // Start
    new Pathformer(this.el);
    this.mapping();
    this.starter();
  
    if (this.onReady) {
      this.onReady(this);
    }
  };
  
  /**
   * Trigger to start of the animation.
   * Depending on the `start` value, a different script
   * will be applied.
   *
   * If the `start` value is not valid, an error will be thrown.
   * Even if technically, this is impossible.
   *
   */
  Vivus.prototype.starter = function () {
    switch (this.start) {
    case 'manual':
      return;
  
    case 'autostart':
      this.play();
      break;
  
    case 'inViewport':
      var self = this,
      listener = function () {
        if (self.isInViewport(self.parentEl, 1)) {
          self.play();
          window.removeEventListener('scroll', listener);
        }
      };
      window.addEventListener('scroll', listener);
      listener();
      break;
    }
  };
  
  
  /**
   * Controls
   **************************************
   */
  
  /**
   * Get the current status of the animation between
   * three different states: 'start', 'progress', 'end'.
   * @return {string} Instance status
   */
  Vivus.prototype.getStatus = function () {
    return this.currentFrame === 0 ? 'start' : this.currentFrame === this.frameLength ? 'end' : 'progress';
  };
  
  /**
   * Reset the instance to the initial state : undraw
   * Be careful, it just reset the animation, if you're
   * playing the animation, this won't stop it. But just
   * make it start from start.
   *
   */
  Vivus.prototype.reset = function () {
    return this.setFrameProgress(0);
  };
  
  /**
   * Set the instance to the final state : drawn
   * Be careful, it just set the animation, if you're
   * playing the animation on rewind, this won't stop it.
   * But just make it start from the end.
   *
   */
  Vivus.prototype.finish = function () {
    return this.setFrameProgress(1);
  };
  
  /**
   * Set the level of progress of the drawing.
   *
   * @param {number} progress Level of progress to set
   */
  Vivus.prototype.setFrameProgress = function (progress) {
    progress = Math.min(1, Math.max(0, progress));
    this.currentFrame = Math.round(this.frameLength * progress);
    this.trace();
    return this;
  };
  
  /**
   * Play the animation at the desired speed.
   * Speed must be a valid number (no zero).
   * By default, the speed value is 1.
   * But a negative value is accepted to go forward.
   *
   * And works with float too.
   * But don't forget we are in JavaScript, se be nice
   * with him and give him a 1/2^x value.
   *
   * @param  {number} speed Animation speed [optional]
   */
  Vivus.prototype.play = function (speed, callback) {
    this.instanceCallback = null;
  
    if (speed && typeof speed === 'function') {
      this.instanceCallback = speed; // first parameter is actually the callback function
      speed = null;
    }
    else if (speed && typeof speed !== 'number') {
      throw new Error('Vivus [play]: invalid speed');
    }
    // if the first parameter wasn't the callback, check if the seconds was
    if (callback && typeof(callback) === 'function' && !this.instanceCallback) {
      this.instanceCallback = callback;
    }
  
  
    this.speed = speed || 1;
    if (!this.handle) {
      this.drawer();
    }
    return this;
  };
  
  /**
   * Stop the current animation, if on progress.
   * Should not trigger any error.
   *
   */
  Vivus.prototype.stop = function () {
    if (this.handle) {
      cancelAnimFrame(this.handle);
      this.handle = null;
    }
    return this;
  };
  
  /**
   * Destroy the instance.
   * Remove all bad styling attributes on all
   * path tags
   *
   */
  Vivus.prototype.destroy = function () {
    this.stop();
    var i, path;
    for (i = 0; i < this.map.length; i++) {
      path = this.map[i];
      path.el.style.strokeDashoffset = null;
      path.el.style.strokeDasharray = null;
      this.renderPath(i);
    }
  };
  
  
  /**
   * Utils methods
   * include methods from Codrops
   **************************************
   */
  
  /**
   * Method to best guess if a path should added into
   * the animation or not.
   *
   * 1. Use the `data-vivus-ignore` attribute if set
   * 2. Check if the instance must ignore invisible paths
   * 3. Check if the path is visible
   *
   * For now the visibility checking is unstable.
   * It will be used for a beta phase.
   *
   * Other improvments are planned. Like detecting
   * is the path got a stroke or a valid opacity.
   */
  Vivus.prototype.isInvisible = function (el) {
    var rect,
      ignoreAttr = el.getAttribute('data-ignore');
  
    if (ignoreAttr !== null) {
      return ignoreAttr !== 'false';
    }
  
    if (this.ignoreInvisible) {
      rect = el.getBoundingClientRect();
      return !rect.width && !rect.height;
    }
    else {
      return false;
    }
  };
  
  /**
   * Parse attributes of a DOM element to
   * get an object of {attributeName => attributeValue}
   *
   * @param  {object} element DOM element to parse
   * @return {object}         Object of attributes
   */
  Vivus.prototype.parseAttr = function (element) {
    var attr, output = {};
    if (element && element.attributes) {
      for (var i = 0; i < element.attributes.length; i++) {
        attr = element.attributes[i];
        output[attr.name] = attr.value;
      }
    }
    return output;
  };
  
  /**
   * Reply if an element is in the page viewport
   *
   * @param  {object} el Element to observe
   * @param  {number} h  Percentage of height
   * @return {boolean}
   */
  Vivus.prototype.isInViewport = function (el, h) {
    var scrolled   = this.scrollY(),
      viewed       = scrolled + this.getViewportH(),
      elBCR        = el.getBoundingClientRect(),
      elHeight     = elBCR.height,
      elTop        = scrolled + elBCR.top,
      elBottom     = elTop + elHeight;
  
    // if 0, the element is considered in the viewport as soon as it enters.
    // if 1, the element is considered in the viewport only when it's fully inside
    // value in percentage (1 >= h >= 0)
    h = h || 0;
  
    return (elTop + elHeight * h) <= viewed && (elBottom) >= scrolled;
  };
  
  
  /**
   * Get the viewport height in pixels
   *
   * @return {integer} Viewport height
   */
  Vivus.prototype.getViewportH = function () {
    var client = this.docElem.clientHeight,
      inner = window.innerHeight;
  
    if (client < inner) {
      return inner;
    }
    else {
      return client;
    }
  };
  
  /**
   * Get the page Y offset
   *
   * @return {integer} Page Y offset
   */
  Vivus.prototype.scrollY = function () {
    return window.pageYOffset || this.docElem.scrollTop;
  };
  
  setupEnv = function () {
  
    if (Vivus.prototype.docElem) {
      return;
    }
  
    /**
     * Alias for document element
     *
     * @type {DOMelement}
     */
    Vivus.prototype.docElem = window.document.documentElement;
  
    /**
     * Alias for `requestAnimationFrame` or
     * `setTimeout` function for deprecated browsers.
     *
     */
    requestAnimFrame = (function () {
      return (
        window.requestAnimationFrame       ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame    ||
        window.oRequestAnimationFrame      ||
        window.msRequestAnimationFrame     ||
        function(/* function */ callback){
          return window.setTimeout(callback, 1000 / 60);
        }
      );
    })();
  
    /**
     * Alias for `cancelAnimationFrame` or
     * `cancelTimeout` function for deprecated browsers.
     *
     */
    cancelAnimFrame = (function () {
      return (
        window.cancelAnimationFrame       ||
        window.webkitCancelAnimationFrame ||
        window.mozCancelAnimationFrame    ||
        window.oCancelAnimationFrame      ||
        window.msCancelAnimationFrame     ||
        function(id){
          return window.clearTimeout(id);
        }
      );
    })();
  };
  
  /**
   * Parse string to integer.
   * If the number is not positive or null
   * the method will return the default value
   * or 0 if undefined
   *
   * @param {string} value String to parse
   * @param {*} defaultValue Value to return if the result parsed is invalid
   * @return {number}
   *
   */
  parsePositiveInt = function (value, defaultValue) {
    var output = parseInt(value, 10);
    return (output >= 0) ? output : defaultValue;
  };
  
  
    if (typeof define === 'function' && define.amd) {
      // AMD. Register as an anonymous module.
      define([], function() {
        return Vivus;
      });
    } else if (typeof exports === 'object') {
      // Node. Does not work with strict CommonJS, but
      // only CommonJS-like environments that support module.exports,
      // like Node.
      module.exports = Vivus;
    } else {
      // Browser globals
      window.Vivus = Vivus;
    }
  
  }());
  