text: /*!
   * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
   * wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
   * v4.0.37 - 2020-08-06
   *
   *//*! Modernizr (Custom Build) | MIT & BSD */
  /* Modernizr (Custom Build) | MIT & BSD
   * Build: https://modernizr.com/download/#-elem_details-elem_progress_meter-mathml-cors-load-mq-css3-input-inputtypes-svg-cssclasses-csstransitions-fontface-backgroundsize-borderimage-teststyles-testprops-testallprops-hasevents-prefixes-domprefixes
   */
  ;window.Modernizr=function(e,t,n){function L(e){f.cssText=e}function A(e,t){return L(p.join(e+";")+(t||""))}function O(e,t){return typeof e===t}function M(e,t){return!!~(""+e).indexOf(t)}function _(e,t){for(var r in e){var i=e[r];if(!M(i,"-")&&f[i]!==n)return t=="pfx"?i:!0}return!1}function D(e,t,r){for(var i in e){var s=t[e[i]];if(s!==n)return r===!1?e[i]:O(s,"function")?s.bind(r||t):s}return!1}function P(e,t,n){var r=e.charAt(0).toUpperCase()+e.slice(1),i=(e+" "+v.join(r+" ")+r).split(" ");return O(t,"string")||O(t,"undefined")?_(i,t):(i=(e+" "+m.join(r+" ")+r).split(" "),D(i,t,n))}function H(){i.input=function(n){for(var r=0,i=n.length;r<i;r++)w[n[r]]=n[r]in l;return w.list&&(w.list=!!t.createElement("datalist")&&!!e.HTMLDataListElement),w}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),i.inputtypes=function(e){for(var r=0,i,s,u,a=e.length;r<a;r++)l.setAttribute("type",s=e[r]),i=l.type!=="text",i&&(l.value=c,l.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(s)&&l.style.WebkitAppearance!==n?(o.appendChild(l),u=t.defaultView,i=u.getComputedStyle&&u.getComputedStyle(l,null).WebkitAppearance!=="textfield"&&l.offsetHeight!==0,o.removeChild(l)):/^(search|tel)$/.test(s)||(/^(url|email)$/.test(s)?i=l.checkValidity&&l.checkValidity()===!1:i=l.value!=c)),b[e[r]]=!!i;return b}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var r="2.8.3",i={},s=!0,o=t.documentElement,u="modernizr",a=t.createElement(u),f=a.style,l=t.createElement("input"),c=":)",h={}.toString,p=" -webkit- -moz- -o- -ms- ".split(" "),d="Webkit Moz O ms",v=d.split(" "),m=d.toLowerCase().split(" "),g={svg:"http://www.w3.org/2000/svg"},y={},b={},w={},E=[],S=E.slice,x,T=function(e,n,r,i){var s,a,f,l,c=t.createElement("div"),h=t.body,p=h||t.createElement("body");if(parseInt(r,10))while(r--)f=t.createElement("div"),f.id=i?i[r]:u+(r+1),c.appendChild(f);return s=["&#173;",'<style id="s',u,'">',e,"</style>"].join(""),c.id=u,(h?c:p).innerHTML+=s,p.appendChild(c),h||(p.style.background="",p.style.overflow="hidden",l=o.style.overflow,o.style.overflow="hidden",o.appendChild(p)),a=n(c,e),h?c.parentNode.removeChild(c):(p.parentNode.removeChild(p),o.style.overflow=l),!!a},N=function(t){var n=e.matchMedia||e.msMatchMedia;if(n)return n(t)&&n(t).matches||!1;var r;return T("@media "+t+" { #"+u+" { position: absolute; } }",function(t){r=(e.getComputedStyle?getComputedStyle(t,null):t.currentStyle)["position"]=="absolute"}),r},C={}.hasOwnProperty,k;!O(C,"undefined")&&!O(C.call,"undefined")?k=function(e,t){return C.call(e,t)}:k=function(e,t){return t in e&&O(e.constructor.prototype[t],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError;var r=S.call(arguments,1),i=function(){if(this instanceof i){var e=function(){};e.prototype=n.prototype;var s=new e,o=n.apply(s,r.concat(S.call(arguments)));return Object(o)===o?o:s}return n.apply(t,r.concat(S.call(arguments)))};return i}),y.backgroundsize=function(){return P("backgroundSize")},y.borderimage=function(){return P("borderImage")},y.csstransitions=function(){return P("transition")},y.fontface=function(){var e;return T('@font-face {font-family:"font";src:url("https://")}',function(n,r){var i=t.getElementById("smodernizr"),s=i.sheet||i.styleSheet,o=s?s.cssRules&&s.cssRules[0]?s.cssRules[0].cssText:s.cssText||"":"";e=/src/i.test(o)&&o.indexOf(r.split(" ")[0])===0}),e},y.svg=function(){return!!t.createElementNS&&!!t.createElementNS(g.svg,"svg").createSVGRect};for(var B in y)k(y,B)&&(x=B.toLowerCase(),i[x]=y[B](),E.push((i[x]?"":"no-")+x));return i.input||H(),i.addTest=function(e,t){if(typeof e=="object")for(var r in e)k(e,r)&&i.addTest(r,e[r]);else{e=e.toLowerCase();if(i[e]!==n)return i;t=typeof t=="function"?t():t,typeof s!="undefined"&&s&&(o.className+=" "+(t?"":"no-")+e),i[e]=t}return i},L(""),a=l=null,i._version=r,i._prefixes=p,i._domPrefixes=m,i._cssomPrefixes=v,i.mq=N,i.testProp=function(e){return _([e])},i.testAllProps=P,i.testStyles=T,o.className=o.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(s?" js "+E.join(" "):""),i}(this,this.document),function(e,t,n){function r(e){return"[object Function]"==d.call(e)}function i(e){return"string"==typeof e}function s(){}function o(e){return!e||"loaded"==e||"complete"==e||"uninitialized"==e}function u(){var e=v.shift();m=1,e?e.t?h(function(){("c"==e.t?k.injectCss:k.injectJs)(e.s,0,e.a,e.x,e.e,1)},0):(e(),u()):m=0}function a(e,n,r,i,s,a,f){function l(t){if(!d&&o(c.readyState)&&(w.r=d=1,!m&&u(),c.onload=c.onreadystatechange=null,t)){"img"!=e&&h(function(){b.removeChild(c)},50);for(var r in T[n])T[n].hasOwnProperty(r)&&T[n][r].onload()}}var f=f||k.errorTimeout,c=t.createElement(e),d=0,g=0,w={t:r,s:n,e:s,a:a,x:f};1===T[n]&&(g=1,T[n]=[]),"object"==e?c.data=n:(c.src=n,c.type=e),c.width=c.height="0",c.onerror=c.onload=c.onreadystatechange=function(){l.call(this,g)},v.splice(i,0,w),"img"!=e&&(g||2===T[n]?(b.insertBefore(c,y?null:p),h(l,f)):T[n].push(c))}function f(e,t,n,r,s){return m=0,t=t||"j",i(e)?a("c"==t?E:w,e,t,this.i++,n,r,s):(v.splice(this.i++,0,e),1==v.length&&u()),this}function l(){var e=k;return e.loader={load:f,i:0},e}var c=t.documentElement,h=e.setTimeout,p=t.getElementsByTagName("script")[0],d={}.toString,v=[],m=0,g="MozAppearance"in c.style,y=g&&!!t.createRange().compareNode,b=y?c:p.parentNode,c=e.opera&&"[object Opera]"==d.call(e.opera),c=!!t.attachEvent&&!c,w=g?"object":c?"script":"img",E=c?"script":w,S=Array.isArray||function(e){return"[object Array]"==d.call(e)},x=[],T={},N={timeout:function(e,t){return t.length&&(e.timeout=t[0]),e}},C,k;k=function(e){function t(e){var e=e.split("!"),t=x.length,n=e.pop(),r=e.length,n={url:n,origUrl:n,prefixes:e},i,s,o;for(s=0;s<r;s++)o=e[s].split("="),(i=N[o.shift()])&&(n=i(n,o));for(s=0;s<t;s++)n=x[s](n);return n}function o(e,i,s,o,u){var a=t(e),f=a.autoCallback;a.url.split(".").pop().split("?").shift(),a.bypass||(i&&(i=r(i)?i:i[e]||i[o]||i[e.split("/").pop().split("?")[0]]),a.instead?a.instead(e,i,s,o,u):(T[a.url]?a.noexec=!0:T[a.url]=1,s.load(a.url,a.forceCSS||!a.forceJS&&"css"==a.url.split(".").pop().split("?").shift()?"c":n,a.noexec,a.attrs,a.timeout),(r(i)||r(f))&&s.load(function(){l(),i&&i(a.origUrl,u,o),f&&f(a.origUrl,u,o),T[a.url]=2})))}function u(e,t){function n(e,n){if(e){if(i(e))n||(f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}),o(e,f,t,0,u);else if(Object(e)===e)for(p in h=function(){var t=0,n;for(n in e)e.hasOwnProperty(n)&&t++;return t}(),e)e.hasOwnProperty(p)&&(!n&&!--h&&(r(f)?f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}:f[p]=function(e){return function(){var t=[].slice.call(arguments);e&&e.apply(this,t),c()}}(l[p])),o(e[p],f,t,p,u))}else!n&&c()}var u=!!e.test,a=e.load||e.both,f=e.callback||s,l=f,c=e.complete||s,h,p;n(u?e.yep:e.nope,!!a),a&&n(a)}var a,f,c=this.yepnope.loader;if(i(e))o(e,0,c,0);else if(S(e))for(a=0;a<e.length;a++)f=e[a],i(f)?o(f,0,c,0):S(f)?k(f):Object(f)===f&&u(f,c);else Object(e)===e&&u(e,c)},k.addPrefix=function(e,t){N[e]=t},k.addFilter=function(e){x.push(e)},k.errorTimeout=1e4,null==t.readyState&&t.addEventListener&&(t.readyState="loading",t.addEventListener("DOMContentLoaded",C=function(){t.removeEventListener("DOMContentLoaded",C,0),t.readyState="complete"},0)),e.yepnope=l(),e.yepnope.executeStack=u,e.yepnope.injectJs=function(e,n,r,i,a,f){var l=t.createElement("script"),c,d,i=i||k.errorTimeout;l.src=e;for(d in r)l.setAttribute(d,r[d]);n=f?u:n||s,l.onreadystatechange=l.onload=function(){!c&&o(l.readyState)&&(c=1,n(),l.onload=l.onreadystatechange=null)},h(function(){c||(c=1,n(1))},i),a?l.onload():p.parentNode.insertBefore(l,p)},e.yepnope.injectCss=function(e,n,r,i,o,a){var i=t.createElement("link"),f,n=a?u:n||s;i.href=e,i.rel="stylesheet",i.type="text/css";for(f in r)i.setAttribute(f,r[f]);o||(p.parentNode.insertBefore(i,p),h(n,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))},Modernizr.addTest("details",function(){var e=document,t=e.createElement("details"),n,r,i;return"open"in t?(r=e.body||function(){var t=e.documentElement;return n=!0,t.insertBefore(e.createElement("body"),t.firstElementChild||t.firstChild)}(),t.innerHTML="<summary>a</summary>b",t.style.display="block",r.appendChild(t),i=t.offsetHeight,t.open=!0,i=i!=t.offsetHeight,r.removeChild(t),n&&r.parentNode.removeChild(r),i):!1}),Modernizr.addTest("progressbar",function(){return document.createElement("progress").max!==undefined}),Modernizr.addTest("meter",function(){return document.createElement("meter").max!==undefined}),Modernizr.addTest("mathml",function(){var e=!1;if(document.createElementNS){var t="http://www.w3.org/1998/Math/MathML",n=document.createElement("div");n.style.position="absolute";var r=n.appendChild(document.createElementNS(t,"math")).appendChild(document.createElementNS(t,"mfrac"));r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("xx")),r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("yy")),document.body.appendChild(n),e=n.offsetHeight>n.offsetWidth}return e}),Modernizr.addTest("cors",!!(window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest));
  // Copyright 2006 Google Inc.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  
  // Known Issues:
  //
  // * Patterns only support repeat.
  // * Radial gradient are not implemented. The VML version of these look very
  //   different from the canvas one.
  // * Clipping paths are not implemented.
  // * Coordsize. The width and height attribute have higher priority than the
  //   width and height style values which isn't correct.
  // * Painting mode isn't implemented.
  // * Canvas width/height should is using content-box by default. IE in
  //   Quirks mode will draw the canvas using border-box. Either change your
  //   doctype to HTML5
  //   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
  //   or use Box Sizing Behavior from WebFX
  //   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
  // * Non uniform scaling does not correctly scale strokes.
  // * Optimize. There is always room for speed improvements.
  
  // Only add this code if we do not already have a canvas implementation
  if (!document.createElement('canvas').getContext) {
  
  (function() {
  
    // alias some functions to make (compiled) code shorter
    var m = Math;
    var mr = m.round;
    var ms = m.sin;
    var mc = m.cos;
    var abs = m.abs;
    var sqrt = m.sqrt;
  
    // this is used for sub pixel precision
    var Z = 10;
    var Z2 = Z / 2;
  
    /**
     * This funtion is assigned to the <canvas> elements as element.getContext().
     * @this {HTMLElement}
     * @return {CanvasRenderingContext2D_}
     */
    function getContext() {
      return this.context_ ||
          (this.context_ = new CanvasRenderingContext2D_(this));
    }
  
    var slice = Array.prototype.slice;
  
    /**
     * Binds a function to an object. The returned function will always use the
     * passed in {@code obj} as {@code this}.
     *
     * Example:
     *
     *   g = bind(f, obj, a, b)
     *   g(c, d) // will do f.call(obj, a, b, c, d)
     *
     * @param {Function} f The function to bind the object to
     * @param {Object} obj The object that should act as this when the function
     *     is called
     * @param {*} var_args Rest arguments that will be used as the initial
     *     arguments when the function is called
     * @return {Function} A new function that has bound this
     */
    function bind(f, obj, var_args) {
      var a = slice.call(arguments, 2);
      return function() {
        return f.apply(obj, a.concat(slice.call(arguments)));
      };
    }
  
    function encodeHtmlAttribute(s) {
      return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
    }
  
    function addNamespace(doc, prefix, urn) {
      if (!doc.namespaces[prefix]) {
        doc.namespaces.add(prefix, urn, '#default#VML');
      }
    }
  
    function addNamespacesAndStylesheet(doc) {
      addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
      addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
  
      // Setup default CSS.  Only add one style sheet per document
      if (!doc.styleSheets['ex_canvas_']) {
        var ss = doc.createStyleSheet();
        ss.owningElement.id = 'ex_canvas_';
        ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
            // default size is 300x150 in Gecko and Opera
            'text-align:left;width:300px;height:150px}';
      }
    }
  
    // Add namespaces and stylesheet at startup.
    addNamespacesAndStylesheet(document);
  
    var G_vmlCanvasManager_ = {
      init: function(opt_doc) {
        var doc = opt_doc || document;
        // Create a dummy element so that IE will allow canvas elements to be
        // recognized.
        doc.createElement('canvas');
        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
      },
  
      init_: function(doc) {
        // find all canvas elements
        var els = doc.getElementsByTagName('canvas');
        for (var i = 0; i < els.length; i++) {
          this.initElement(els[i]);
        }
      },
  
      /**
       * Public initializes a canvas element so that it can be used as canvas
       * element from now on. This is called automatically before the page is
       * loaded but if you are creating elements using createElement you need to
       * make sure this is called on the element.
       * @param {HTMLElement} el The canvas element to initialize.
       * @return {HTMLElement} the element that was created.
       */
      initElement: function(el) {
        if (!el.getContext) {
          el.getContext = getContext;
  
          // Add namespaces and stylesheet to document of the element.
          addNamespacesAndStylesheet(el.ownerDocument);
  
          // Remove fallback content. There is no way to hide text nodes so we
          // just remove all childNodes. We could hide all elements and remove
          // text nodes but who really cares about the fallback content.
          el.innerHTML = '';
  
          // do not use inline function because that will leak memory
          el.attachEvent('onpropertychange', onPropertyChange);
          el.attachEvent('onresize', onResize);
  
          var attrs = el.attributes;
          if (attrs.width && attrs.width.specified) {
            // TODO: use runtimeStyle and coordsize
            // el.getContext().setWidth_(attrs.width.nodeValue);
            el.style.width = attrs.width.nodeValue + 'px';
          } else {
            el.width = el.clientWidth;
          }
          if (attrs.height && attrs.height.specified) {
            // TODO: use runtimeStyle and coordsize
            // el.getContext().setHeight_(attrs.height.nodeValue);
            el.style.height = attrs.height.nodeValue + 'px';
          } else {
            el.height = el.clientHeight;
          }
          //el.getContext().setCoordsize_()
        }
        return el;
      }
    };
  
    function onPropertyChange(e) {
      var el = e.srcElement;
  
      switch (e.propertyName) {
        case 'width':
          el.getContext().clearRect();
          el.style.width = el.attributes.width.nodeValue + 'px';
          // In IE8 this does not trigger onresize.
          el.firstChild.style.width =  el.clientWidth + 'px';
          break;
        case 'height':
          el.getContext().clearRect();
          el.style.height = el.attributes.height.nodeValue + 'px';
          el.firstChild.style.height = el.clientHeight + 'px';
          break;
      }
    }
  
    function onResize(e) {
      var el = e.srcElement;
      if (el.firstChild) {
        el.firstChild.style.width =  el.clientWidth + 'px';
        el.firstChild.style.height = el.clientHeight + 'px';
      }
    }
  
    G_vmlCanvasManager_.init();
  
    // precompute "00" to "FF"
    var decToHex = [];
    for (var i = 0; i < 16; i++) {
      for (var j = 0; j < 16; j++) {
        decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
      }
    }
  
    function createMatrixIdentity() {
      return [
        [1, 0, 0],
        [0, 1, 0],
        [0, 0, 1]
      ];
    }
  
    function matrixMultiply(m1, m2) {
      var result = createMatrixIdentity();
  
      for (var x = 0; x < 3; x++) {
        for (var y = 0; y < 3; y++) {
          var sum = 0;
  
          for (var z = 0; z < 3; z++) {
            sum += m1[x][z] * m2[z][y];
          }
  
          result[x][y] = sum;
        }
      }
      return result;
    }
  
    function copyState(o1, o2) {
      o2.fillStyle     = o1.fillStyle;
      o2.lineCap       = o1.lineCap;
      o2.lineJoin      = o1.lineJoin;
      o2.lineWidth     = o1.lineWidth;
      o2.miterLimit    = o1.miterLimit;
      o2.shadowBlur    = o1.shadowBlur;
      o2.shadowColor   = o1.shadowColor;
      o2.shadowOffsetX = o1.shadowOffsetX;
      o2.shadowOffsetY = o1.shadowOffsetY;
      o2.strokeStyle   = o1.strokeStyle;
      o2.globalAlpha   = o1.globalAlpha;
      o2.font          = o1.font;
      o2.textAlign     = o1.textAlign;
      o2.textBaseline  = o1.textBaseline;
      o2.arcScaleX_    = o1.arcScaleX_;
      o2.arcScaleY_    = o1.arcScaleY_;
      o2.lineScale_    = o1.lineScale_;
    }
  
    var colorData = {
      aliceblue: '#F0F8FF',
      antiquewhite: '#FAEBD7',
      aquamarine: '#7FFFD4',
      azure: '#F0FFFF',
      beige: '#F5F5DC',
      bisque: '#FFE4C4',
      black: '#000000',
      blanchedalmond: '#FFEBCD',
      blueviolet: '#8A2BE2',
      brown: '#A52A2A',
      burlywood: '#DEB887',
      cadetblue: '#5F9EA0',
      chartreuse: '#7FFF00',
      chocolate: '#D2691E',
      coral: '#FF7F50',
      cornflowerblue: '#6495ED',
      cornsilk: '#FFF8DC',
      crimson: '#DC143C',
      cyan: '#00FFFF',
      darkblue: '#00008B',
      darkcyan: '#008B8B',
      darkgoldenrod: '#B8860B',
      darkgray: '#A9A9A9',
      darkgreen: '#006400',
      darkgrey: '#A9A9A9',
      darkkhaki: '#BDB76B',
      darkmagenta: '#8B008B',
      darkolivegreen: '#556B2F',
      darkorange: '#FF8C00',
      darkorchid: '#9932CC',
      darkred: '#8B0000',
      darksalmon: '#E9967A',
      darkseagreen: '#8FBC8F',
      darkslateblue: '#483D8B',
      darkslategray: '#2F4F4F',
      darkslategrey: '#2F4F4F',
      darkturquoise: '#00CED1',
      darkviolet: '#9400D3',
      deeppink: '#FF1493',
      deepskyblue: '#00BFFF',
      dimgray: '#696969',
      dimgrey: '#696969',
      dodgerblue: '#1E90FF',
      firebrick: '#B22222',
      floralwhite: '#FFFAF0',
      forestgreen: '#228B22',
      gainsboro: '#DCDCDC',
      ghostwhite: '#F8F8FF',
      gold: '#FFD700',
      goldenrod: '#DAA520',
      grey: '#808080',
      greenyellow: '#ADFF2F',
      honeydew: '#F0FFF0',
      hotpink: '#FF69B4',
      indianred: '#CD5C5C',
      indigo: '#4B0082',
      ivory: '#FFFFF0',
      khaki: '#F0E68C',
      lavender: '#E6E6FA',
      lavenderblush: '#FFF0F5',
      lawngreen: '#7CFC00',
      lemonchiffon: '#FFFACD',
      lightblue: '#ADD8E6',
      lightcoral: '#F08080',
      lightcyan: '#E0FFFF',
      lightgoldenrodyellow: '#FAFAD2',
      lightgreen: '#90EE90',
      lightgrey: '#D3D3D3',
      lightpink: '#FFB6C1',
      lightsalmon: '#FFA07A',
      lightseagreen: '#20B2AA',
      lightskyblue: '#87CEFA',
      lightslategray: '#778899',
      lightslategrey: '#778899',
      lightsteelblue: '#B0C4DE',
      lightyellow: '#FFFFE0',
      limegreen: '#32CD32',
      linen: '#FAF0E6',
      magenta: '#FF00FF',
      mediumaquamarine: '#66CDAA',
      mediumblue: '#0000CD',
      mediumorchid: '#BA55D3',
      mediumpurple: '#9370DB',
      mediumseagreen: '#3CB371',
      mediumslateblue: '#7B68EE',
      mediumspringgreen: '#00FA9A',
      mediumturquoise: '#48D1CC',
      mediumvioletred: '#C71585',
      midnightblue: '#191970',
      mintcream: '#F5FFFA',
      mistyrose: '#FFE4E1',
      moccasin: '#FFE4B5',
      navajowhite: '#FFDEAD',
      oldlace: '#FDF5E6',
      olivedrab: '#6B8E23',
      orange: '#FFA500',
      orangered: '#FF4500',
      orchid: '#DA70D6',
      palegoldenrod: '#EEE8AA',
      palegreen: '#98FB98',
      paleturquoise: '#AFEEEE',
      palevioletred: '#DB7093',
      papayawhip: '#FFEFD5',
      peachpuff: '#FFDAB9',
      peru: '#CD853F',
      pink: '#FFC0CB',
      plum: '#DDA0DD',
      powderblue: '#B0E0E6',
      rosybrown: '#BC8F8F',
      royalblue: '#4169E1',
      saddlebrown: '#8B4513',
      salmon: '#FA8072',
      sandybrown: '#F4A460',
      seagreen: '#2E8B57',
      seashell: '#FFF5EE',
      sienna: '#A0522D',
      skyblue: '#87CEEB',
      slateblue: '#6A5ACD',
      slategray: '#708090',
      slategrey: '#708090',
      snow: '#FFFAFA',
      springgreen: '#00FF7F',
      steelblue: '#4682B4',
      tan: '#D2B48C',
      thistle: '#D8BFD8',
      tomato: '#FF6347',
      turquoise: '#40E0D0',
      violet: '#EE82EE',
      wheat: '#F5DEB3',
      whitesmoke: '#F5F5F5',
      yellowgreen: '#9ACD32'
    };
  
  
    function getRgbHslContent(styleString) {
      var start = styleString.indexOf('(', 3);
      var end = styleString.indexOf(')', start + 1);
      var parts = styleString.substring(start + 1, end).split(',');
      // add alpha if needed
      if (parts.length != 4 || styleString.charAt(3) != 'a') {
        parts[3] = 1;
      }
      return parts;
    }
  
    function percent(s) {
      return parseFloat(s) / 100;
    }
  
    function clamp(v, min, max) {
      return Math.min(max, Math.max(min, v));
    }
  
    function hslToRgb(parts){
      var r, g, b, h, s, l;
      h = parseFloat(parts[0]) / 360 % 360;
      if (h < 0)
        h++;
      s = clamp(percent(parts[1]), 0, 1);
      l = clamp(percent(parts[2]), 0, 1);
      if (s == 0) {
        r = g = b = l; // achromatic
      } else {
        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hueToRgb(p, q, h + 1 / 3);
        g = hueToRgb(p, q, h);
        b = hueToRgb(p, q, h - 1 / 3);
      }
  
      return '#' + decToHex[Math.floor(r * 255)] +
          decToHex[Math.floor(g * 255)] +
          decToHex[Math.floor(b * 255)];
    }
  
    function hueToRgb(m1, m2, h) {
      if (h < 0)
        h++;
      if (h > 1)
        h--;
  
      if (6 * h < 1)
        return m1 + (m2 - m1) * 6 * h;
      else if (2 * h < 1)
        return m2;
      else if (3 * h < 2)
        return m1 + (m2 - m1) * (2 / 3 - h) * 6;
      else
        return m1;
    }
  
    var processStyleCache = {};
  
    function processStyle(styleString) {
      if (styleString in processStyleCache) {
        return processStyleCache[styleString];
      }
  
      var str, alpha = 1;
  
      styleString = String(styleString);
      if (styleString.charAt(0) == '#') {
        str = styleString;
      } else if (/^rgb/.test(styleString)) {
        var parts = getRgbHslContent(styleString);
        var str = '#', n;
        for (var i = 0; i < 3; i++) {
          if (parts[i].indexOf('%') != -1) {
            n = Math.floor(percent(parts[i]) * 255);
          } else {
            n = +parts[i];
          }
          str += decToHex[clamp(n, 0, 255)];
        }
        alpha = +parts[3];
      } else if (/^hsl/.test(styleString)) {
        var parts = getRgbHslContent(styleString);
        str = hslToRgb(parts);
        alpha = parts[3];
      } else {
        str = colorData[styleString] || styleString;
      }
      return processStyleCache[styleString] = {color: str, alpha: alpha};
    }
  
    var DEFAULT_STYLE = {
      style: 'normal',
      variant: 'normal',
      weight: 'normal',
      size: 10,
      family: 'sans-serif'
    };
  
    // Internal text style cache
    var fontStyleCache = {};
  
    function processFontStyle(styleString) {
      if (fontStyleCache[styleString]) {
        return fontStyleCache[styleString];
      }
  
      var el = document.createElement('div');
      var style = el.style;
      try {
        style.font = styleString;
      } catch (ex) {
        // Ignore failures to set to invalid font.
      }
  
      return fontStyleCache[styleString] = {
        style: style.fontStyle || DEFAULT_STYLE.style,
        variant: style.fontVariant || DEFAULT_STYLE.variant,
        weight: style.fontWeight || DEFAULT_STYLE.weight,
        size: style.fontSize || DEFAULT_STYLE.size,
        family: style.fontFamily || DEFAULT_STYLE.family
      };
    }
  
    function getComputedStyle(style, element) {
      var computedStyle = {};
  
      for (var p in style) {
        computedStyle[p] = style[p];
      }
  
      // Compute the size
      var canvasFontSize = parseFloat(element.currentStyle.fontSize),
          fontSize = parseFloat(style.size);
  
      if (typeof style.size == 'number') {
        computedStyle.size = style.size;
      } else if (style.size.indexOf('px') != -1) {
        computedStyle.size = fontSize;
      } else if (style.size.indexOf('em') != -1) {
        computedStyle.size = canvasFontSize * fontSize;
      } else if(style.size.indexOf('%') != -1) {
        computedStyle.size = (canvasFontSize / 100) * fontSize;
      } else if (style.size.indexOf('pt') != -1) {
        computedStyle.size = fontSize / .75;
      } else {
        computedStyle.size = canvasFontSize;
      }
  
      // Different scaling between normal text and VML text. This was found using
      // trial and error to get the same size as non VML text.
      computedStyle.size *= 0.981;
  
      return computedStyle;
    }
  
    function buildStyle(style) {
      return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
          style.size + 'px ' + style.family;
    }
  
    var lineCapMap = {
      'butt': 'flat',
      'round': 'round'
    };
  
    function processLineCap(lineCap) {
      return lineCapMap[lineCap] || 'square';
    }
  
    /**
     * This class implements CanvasRenderingContext2D interface as described by
     * the WHATWG.
     * @param {HTMLElement} canvasElement The element that the 2D context should
     * be associated with
     */
    function CanvasRenderingContext2D_(canvasElement) {
      this.m_ = createMatrixIdentity();
  
      this.mStack_ = [];
      this.aStack_ = [];
      this.currentPath_ = [];
  
      // Canvas context properties
      this.strokeStyle = '#000';
      this.fillStyle = '#000';
  
      this.lineWidth = 1;
      this.lineJoin = 'miter';
      this.lineCap = 'butt';
      this.miterLimit = Z * 1;
      this.globalAlpha = 1;
      this.font = '10px sans-serif';
      this.textAlign = 'left';
      this.textBaseline = 'alphabetic';
      this.canvas = canvasElement;
  
      var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
          canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
      var el = canvasElement.ownerDocument.createElement('div');
      el.style.cssText = cssText;
      canvasElement.appendChild(el);
  
      var overlayEl = el.cloneNode(false);
      // Use a non transparent background.
      overlayEl.style.backgroundColor = 'red';
      overlayEl.style.filter = 'alpha(opacity=0)';
      canvasElement.appendChild(overlayEl);
  
      this.element_ = el;
      this.arcScaleX_ = 1;
      this.arcScaleY_ = 1;
      this.lineScale_ = 1;
    }
  
    var contextPrototype = CanvasRenderingContext2D_.prototype;
    contextPrototype.clearRect = function() {
      if (this.textMeasureEl_) {
        this.textMeasureEl_.removeNode(true);
        this.textMeasureEl_ = null;
      }
      this.element_.innerHTML = '';
    };
  
    contextPrototype.beginPath = function() {
      // TODO: Branch current matrix so that save/restore has no effect
      //       as per safari docs.
      this.currentPath_ = [];
    };
  
    contextPrototype.moveTo = function(aX, aY) {
      var p = getCoords(this, aX, aY);
      this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
      this.currentX_ = p.x;
      this.currentY_ = p.y;
    };
  
    contextPrototype.lineTo = function(aX, aY) {
      var p = getCoords(this, aX, aY);
      this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
  
      this.currentX_ = p.x;
      this.currentY_ = p.y;
    };
  
    contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
                                              aCP2x, aCP2y,
                                              aX, aY) {
      var p = getCoords(this, aX, aY);
      var cp1 = getCoords(this, aCP1x, aCP1y);
      var cp2 = getCoords(this, aCP2x, aCP2y);
      bezierCurveTo(this, cp1, cp2, p);
    };
  
    // Helper function that takes the already fixed cordinates.
    function bezierCurveTo(self, cp1, cp2, p) {
      self.currentPath_.push({
        type: 'bezierCurveTo',
        cp1x: cp1.x,
        cp1y: cp1.y,
        cp2x: cp2.x,
        cp2y: cp2.y,
        x: p.x,
        y: p.y
      });
      self.currentX_ = p.x;
      self.currentY_ = p.y;
    }
  
    contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
      // the following is lifted almost directly from
      // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
  
      var cp = getCoords(this, aCPx, aCPy);
      var p = getCoords(this, aX, aY);
  
      var cp1 = {
        x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
        y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
      };
      var cp2 = {
        x: cp1.x + (p.x - this.currentX_) / 3.0,
        y: cp1.y + (p.y - this.currentY_) / 3.0
      };
  
      bezierCurveTo(this, cp1, cp2, p);
    };
  
    contextPrototype.arc = function(aX, aY, aRadius,
                                    aStartAngle, aEndAngle, aClockwise) {
      aRadius *= Z;
      var arcType = aClockwise ? 'at' : 'wa';
  
      var xStart = aX + mc(aStartAngle) * aRadius - Z2;
      var yStart = aY + ms(aStartAngle) * aRadius - Z2;
  
      var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
      var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
  
      // IE won't render arches drawn counter clockwise if xStart == xEnd.
      if (xStart == xEnd && !aClockwise) {
        xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
                         // that can be represented in binary
      }
  
      var p = getCoords(this, aX, aY);
      var pStart = getCoords(this, xStart, yStart);
      var pEnd = getCoords(this, xEnd, yEnd);
  
      this.currentPath_.push({type: arcType,
                             x: p.x,
                             y: p.y,
                             radius: aRadius,
                             xStart: pStart.x,
                             yStart: pStart.y,
                             xEnd: pEnd.x,
                             yEnd: pEnd.y});
  
    };
  
    contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
      this.moveTo(aX, aY);
      this.lineTo(aX + aWidth, aY);
      this.lineTo(aX + aWidth, aY + aHeight);
      this.lineTo(aX, aY + aHeight);
      this.closePath();
    };
  
    contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
      var oldPath = this.currentPath_;
      this.beginPath();
  
      this.moveTo(aX, aY);
      this.lineTo(aX + aWidth, aY);
      this.lineTo(aX + aWidth, aY + aHeight);
      this.lineTo(aX, aY + aHeight);
      this.closePath();
      this.stroke();
  
      this.currentPath_ = oldPath;
    };
  
    contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
      var oldPath = this.currentPath_;
      this.beginPath();
  
      this.moveTo(aX, aY);
      this.lineTo(aX + aWidth, aY);
      this.lineTo(aX + aWidth, aY + aHeight);
      this.lineTo(aX, aY + aHeight);
      this.closePath();
      this.fill();
  
      this.currentPath_ = oldPath;
    };
  
    contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
      var gradient = new CanvasGradient_('gradient');
      gradient.x0_ = aX0;
      gradient.y0_ = aY0;
      gradient.x1_ = aX1;
      gradient.y1_ = aY1;
      return gradient;
    };
  
    contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
                                                     aX1, aY1, aR1) {
      var gradient = new CanvasGradient_('gradientradial');
      gradient.x0_ = aX0;
      gradient.y0_ = aY0;
      gradient.r0_ = aR0;
      gradient.x1_ = aX1;
      gradient.y1_ = aY1;
      gradient.r1_ = aR1;
      return gradient;
    };
  
    contextPrototype.drawImage = function(image, var_args) {
      var dx, dy, dw, dh, sx, sy, sw, sh;
  
      // to find the original width we overide the width and height
      var oldRuntimeWidth = image.runtimeStyle.width;
      var oldRuntimeHeight = image.runtimeStyle.height;
      image.runtimeStyle.width = 'auto';
      image.runtimeStyle.height = 'auto';
  
      // get the original size
      var w = image.width;
      var h = image.height;
  
      // and remove overides
      image.runtimeStyle.width = oldRuntimeWidth;
      image.runtimeStyle.height = oldRuntimeHeight;
  
      if (arguments.length == 3) {
        dx = arguments[1];
        dy = arguments[2];
        sx = sy = 0;
        sw = dw = w;
        sh = dh = h;
      } else if (arguments.length == 5) {
        dx = arguments[1];
        dy = arguments[2];
        dw = arguments[3];
        dh = arguments[4];
        sx = sy = 0;
        sw = w;
        sh = h;
      } else if (arguments.length == 9) {
        sx = arguments[1];
        sy = arguments[2];
        sw = arguments[3];
        sh = arguments[4];
        dx = arguments[5];
        dy = arguments[6];
        dw = arguments[7];
        dh = arguments[8];
      } else {
        throw Error('Invalid number of arguments');
      }
  
      var d = getCoords(this, dx, dy);
  
      var w2 = sw / 2;
      var h2 = sh / 2;
  
      var vmlStr = [];
  
      var W = 10;
      var H = 10;
  
      // For some reason that I've now forgotten, using divs didn't work
      vmlStr.push(' <g_vml_:group',
                  ' coordsize="', Z * W, ',', Z * H, '"',
                  ' coordorigin="0,0"' ,
                  ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
  
      // If filters are necessary (rotation exists), create them
      // filters are bog-slow, so only create them if abbsolutely necessary
      // The following check doesn't account for skews (which don't exist
      // in the canvas spec (yet) anyway.
  
      if (this.m_[0][0] != 1 || this.m_[0][1] ||
          this.m_[1][1] != 1 || this.m_[1][0]) {
        var filter = [];
  
        // Note the 12/21 reversal
        filter.push('M11=', this.m_[0][0], ',',
                    'M12=', this.m_[1][0], ',',
                    'M21=', this.m_[0][1], ',',
                    'M22=', this.m_[1][1], ',',
                    'Dx=', mr(d.x / Z), ',',
                    'Dy=', mr(d.y / Z), '');
  
        // Bounding box calculation (need to minimize displayed area so that
        // filters don't waste time on unused pixels.
        var max = d;
        var c2 = getCoords(this, dx + dw, dy);
        var c3 = getCoords(this, dx, dy + dh);
        var c4 = getCoords(this, dx + dw, dy + dh);
  
        max.x = m.max(max.x, c2.x, c3.x, c4.x);
        max.y = m.max(max.y, c2.y, c3.y, c4.y);
  
        vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
                    'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
                    filter.join(''), ", sizingmethod='clip');");
  
      } else {
        vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
      }
  
      vmlStr.push(' ">' ,
                  '<g_vml_:image src="', image.src, '"',
                  ' style="width:', Z * dw, 'px;',
                  ' height:', Z * dh, 'px"',
                  ' cropleft="', sx / w, '"',
                  ' croptop="', sy / h, '"',
                  ' cropright="', (w - sx - sw) / w, '"',
                  ' cropbottom="', (h - sy - sh) / h, '"',
                  ' />',
                  '</g_vml_:group>');
  
      this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
    };
  
    contextPrototype.stroke = function(aFill) {
      var lineStr = [];
      var lineOpen = false;
  
      var W = 10;
      var H = 10;
  
      lineStr.push('<g_vml_:shape',
                   ' filled="', !!aFill, '"',
                   ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
                   ' coordorigin="0,0"',
                   ' coordsize="', Z * W, ',', Z * H, '"',
                   ' stroked="', !aFill, '"',
                   ' path="');
  
      var newSeq = false;
      var min = {x: null, y: null};
      var max = {x: null, y: null};
  
      for (var i = 0; i < this.currentPath_.length; i++) {
        var p = this.currentPath_[i];
        var c;
  
        switch (p.type) {
          case 'moveTo':
            c = p;
            lineStr.push(' m ', mr(p.x), ',', mr(p.y));
            break;
          case 'lineTo':
            lineStr.push(' l ', mr(p.x), ',', mr(p.y));
            break;
          case 'close':
            lineStr.push(' x ');
            p = null;
            break;
          case 'bezierCurveTo':
            lineStr.push(' c ',
                         mr(p.cp1x), ',', mr(p.cp1y), ',',
                         mr(p.cp2x), ',', mr(p.cp2y), ',',
                         mr(p.x), ',', mr(p.y));
            break;
          case 'at':
          case 'wa':
            lineStr.push(' ', p.type, ' ',
                         mr(p.x - this.arcScaleX_ * p.radius), ',',
                         mr(p.y - this.arcScaleY_ * p.radius), ' ',
                         mr(p.x + this.arcScaleX_ * p.radius), ',',
                         mr(p.y + this.arcScaleY_ * p.radius), ' ',
                         mr(p.xStart), ',', mr(p.yStart), ' ',
                         mr(p.xEnd), ',', mr(p.yEnd));
            break;
        }
  
  
        // TODO: Following is broken for curves due to
        //       move to proper paths.
  
        // Figure out dimensions so we can do gradient fills
        // properly
        if (p) {
          if (min.x == null || p.x < min.x) {
            min.x = p.x;
          }
          if (max.x == null || p.x > max.x) {
            max.x = p.x;
          }
          if (min.y == null || p.y < min.y) {
            min.y = p.y;
          }
          if (max.y == null || p.y > max.y) {
            max.y = p.y;
          }
        }
      }
      lineStr.push(' ">');
  
      if (!aFill) {
        appendStroke(this, lineStr);
      } else {
        appendFill(this, lineStr, min, max);
      }
  
      lineStr.push('</g_vml_:shape>');
  
      this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
    };
  
    function appendStroke(ctx, lineStr) {
      var a = processStyle(ctx.strokeStyle);
      var color = a.color;
      var opacity = a.alpha * ctx.globalAlpha;
      var lineWidth = ctx.lineScale_ * ctx.lineWidth;
  
      // VML cannot correctly render a line if the width is less than 1px.
      // In that case, we dilute the color to make the line look thinner.
      if (lineWidth < 1) {
        opacity *= lineWidth;
      }
  
      lineStr.push(
        '<g_vml_:stroke',
        ' opacity="', opacity, '"',
        ' joinstyle="', ctx.lineJoin, '"',
        ' miterlimit="', ctx.miterLimit, '"',
        ' endcap="', processLineCap(ctx.lineCap), '"',
        ' weight="', lineWidth, 'px"',
        ' color="', color, '" />'
      );
    }
  
    function appendFill(ctx, lineStr, min, max) {
      var fillStyle = ctx.fillStyle;
      var arcScaleX = ctx.arcScaleX_;
      var arcScaleY = ctx.arcScaleY_;
      var width = max.x - min.x;
      var height = max.y - min.y;
      if (fillStyle instanceof CanvasGradient_) {
        // TODO: Gradients transformed with the transformation matrix.
        var angle = 0;
        var focus = {x: 0, y: 0};
  
        // additional offset
        var shift = 0;
        // scale factor for offset
        var expansion = 1;
  
        if (fillStyle.type_ == 'gradient') {
          var x0 = fillStyle.x0_ / arcScaleX;
          var y0 = fillStyle.y0_ / arcScaleY;
          var x1 = fillStyle.x1_ / arcScaleX;
          var y1 = fillStyle.y1_ / arcScaleY;
          var p0 = getCoords(ctx, x0, y0);
          var p1 = getCoords(ctx, x1, y1);
          var dx = p1.x - p0.x;
          var dy = p1.y - p0.y;
          angle = Math.atan2(dx, dy) * 180 / Math.PI;
  
          // The angle should be a non-negative number.
          if (angle < 0) {
            angle += 360;
          }
  
          // Very small angles produce an unexpected result because they are
          // converted to a scientific notation string.
          if (angle < 1e-6) {
            angle = 0;
          }
        } else {
          var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
          focus = {
            x: (p0.x - min.x) / width,
            y: (p0.y - min.y) / height
          };
  
          width  /= arcScaleX * Z;
          height /= arcScaleY * Z;
          var dimension = m.max(width, height);
          shift = 2 * fillStyle.r0_ / dimension;
          expansion = 2 * fillStyle.r1_ / dimension - shift;
        }
  
        // We need to sort the color stops in ascending order by offset,
        // otherwise IE won't interpret it correctly.
        var stops = fillStyle.colors_;
        stops.sort(function(cs1, cs2) {
          return cs1.offset - cs2.offset;
        });
  
        var length = stops.length;
        var color1 = stops[0].color;
        var color2 = stops[length - 1].color;
        var opacity1 = stops[0].alpha * ctx.globalAlpha;
        var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
  
        var colors = [];
        for (var i = 0; i < length; i++) {
          var stop = stops[i];
          colors.push(stop.offset * expansion + shift + ' ' + stop.color);
        }
  
        // When colors attribute is used, the meanings of opacity and o:opacity2
        // are reversed.
        lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
                     ' method="none" focus="100%"',
                     ' color="', color1, '"',
                     ' color2="', color2, '"',
                     ' colors="', colors.join(','), '"',
                     ' opacity="', opacity2, '"',
                     ' g_o_:opacity2="', opacity1, '"',
                     ' angle="', angle, '"',
                     ' focusposition="', focus.x, ',', focus.y, '" />');
      } else if (fillStyle instanceof CanvasPattern_) {
        if (width && height) {
          var deltaLeft = -min.x;
          var deltaTop = -min.y;
          lineStr.push('<g_vml_:fill',
                       ' position="',
                       deltaLeft / width * arcScaleX * arcScaleX, ',',
                       deltaTop / height * arcScaleY * arcScaleY, '"',
                       ' type="tile"',
                       // TODO: Figure out the correct size to fit the scale.
                       //' size="', w, 'px ', h, 'px"',
                       ' src="', fillStyle.src_, '" />');
         }
      } else {
        var a = processStyle(ctx.fillStyle);
        var color = a.color;
        var opacity = a.alpha * ctx.globalAlpha;
        lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
                     '" />');
      }
    }
  
    contextPrototype.fill = function() {
      this.stroke(true);
    };
  
    contextPrototype.closePath = function() {
      this.currentPath_.push({type: 'close'});
    };
  
    function getCoords(ctx, aX, aY) {
      var m = ctx.m_;
      return {
        x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
        y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
      };
    };
  
    contextPrototype.save = function() {
      var o = {};
      copyState(this, o);
      this.aStack_.push(o);
      this.mStack_.push(this.m_);
      this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
    };
  
    contextPrototype.restore = function() {
      if (this.aStack_.length) {
        copyState(this.aStack_.pop(), this);
        this.m_ = this.mStack_.pop();
      }
    };
  
    function matrixIsFinite(m) {
      return isFinite(m[0][0]) && isFinite(m[0][1]) &&
          isFinite(m[1][0]) && isFinite(m[1][1]) &&
          isFinite(m[2][0]) && isFinite(m[2][1]);
    }
  
    function setM(ctx, m, updateLineScale) {
      if (!matrixIsFinite(m)) {
        return;
      }
      ctx.m_ = m;
  
      if (updateLineScale) {
        // Get the line scale.
        // Determinant of this.m_ means how much the area is enlarged by the
        // transformation. So its square root can be used as a scale factor
        // for width.
        var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
        ctx.lineScale_ = sqrt(abs(det));
      }
    }
  
    contextPrototype.translate = function(aX, aY) {
      var m1 = [
        [1,  0,  0],
        [0,  1,  0],
        [aX, aY, 1]
      ];
  
      setM(this, matrixMultiply(m1, this.m_), false);
    };
  
    contextPrototype.rotate = function(aRot) {
      var c = mc(aRot);
      var s = ms(aRot);
  
      var m1 = [
        [c,  s, 0],
        [-s, c, 0],
        [0,  0, 1]
      ];
  
      setM(this, matrixMultiply(m1, this.m_), false);
    };
  
    contextPrototype.scale = function(aX, aY) {
      this.arcScaleX_ *= aX;
      this.arcScaleY_ *= aY;
      var m1 = [
        [aX, 0,  0],
        [0,  aY, 0],
        [0,  0,  1]
      ];
  
      setM(this, matrixMultiply(m1, this.m_), true);
    };
  
    contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
      var m1 = [
        [m11, m12, 0],
        [m21, m22, 0],
        [dx,  dy,  1]
      ];
  
      setM(this, matrixMultiply(m1, this.m_), true);
    };
  
    contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
      var m = [
        [m11, m12, 0],
        [m21, m22, 0],
        [dx,  dy,  1]
      ];
  
      setM(this, m, true);
    };
  
    /**
     * The text drawing function.
     * The maxWidth argument isn't taken in account, since no browser supports
     * it yet.
     */
    contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
      var m = this.m_,
          delta = 1000,
          left = 0,
          right = delta,
          offset = {x: 0, y: 0},
          lineStr = [];
  
      var fontStyle = getComputedStyle(processFontStyle(this.font),
                                       this.element_);
  
      var fontStyleString = buildStyle(fontStyle);
  
      var elementStyle = this.element_.currentStyle;
      var textAlign = this.textAlign.toLowerCase();
      switch (textAlign) {
        case 'left':
        case 'center':
        case 'right':
          break;
        case 'end':
          textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
          break;
        case 'start':
          textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
          break;
        default:
          textAlign = 'left';
      }
  
      // 1.75 is an arbitrary number, as there is no info about the text baseline
      switch (this.textBaseline) {
        case 'hanging':
        case 'top':
          offset.y = fontStyle.size / 1.75;
          break;
        case 'middle':
          break;
        default:
        case null:
        case 'alphabetic':
        case 'ideographic':
        case 'bottom':
          offset.y = -fontStyle.size / 2.25;
          break;
      }
  
      switch(textAlign) {
        case 'right':
          left = delta;
          right = 0.05;
          break;
        case 'center':
          left = right = delta / 2;
          break;
      }
  
      var d = getCoords(this, x + offset.x, y + offset.y);
  
      lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
                   ' coordsize="100 100" coordorigin="0 0"',
                   ' filled="', !stroke, '" stroked="', !!stroke,
                   '" style="position:absolute;width:1px;height:1px;">');
  
      if (stroke) {
        appendStroke(this, lineStr);
      } else {
        // TODO: Fix the min and max params.
        appendFill(this, lineStr, {x: -left, y: 0},
                   {x: right, y: fontStyle.size});
      }
  
      var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
                  m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
  
      var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
  
      lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
                   ' offset="', skewOffset, '" origin="', left ,' 0" />',
                   '<g_vml_:path textpathok="true" />',
                   '<g_vml_:textpath on="true" string="',
                   encodeHtmlAttribute(text),
                   '" style="v-text-align:', textAlign,
                   ';font:', encodeHtmlAttribute(fontStyleString),
                   '" /></g_vml_:line>');
  
      this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
    };
  
    contextPrototype.fillText = function(text, x, y, maxWidth) {
      this.drawText_(text, x, y, maxWidth, false);
    };
  
    contextPrototype.strokeText = function(text, x, y, maxWidth) {
      this.drawText_(text, x, y, maxWidth, true);
    };
  
    contextPrototype.measureText = function(text) {
      if (!this.textMeasureEl_) {
        var s = '<span style="position:absolute;' +
            'top:-20000px;left:0;padding:0;margin:0;border:none;' +
            'white-space:pre;"></span>';
        this.element_.insertAdjacentHTML('beforeEnd', s);
        this.textMeasureEl_ = this.element_.lastChild;
      }
      var doc = this.element_.ownerDocument;
      this.textMeasureEl_.innerHTML = '';
      this.textMeasureEl_.style.font = this.font;
      // Don't use innerHTML or innerText because they allow markup/whitespace.
      this.textMeasureEl_.appendChild(doc.createTextNode(text));
      return {width: this.textMeasureEl_.offsetWidth};
    };
  
    /******** STUBS ********/
    contextPrototype.clip = function() {
      // TODO: Implement
    };
  
    contextPrototype.arcTo = function() {
      // TODO: Implement
    };
  
    contextPrototype.createPattern = function(image, repetition) {
      return new CanvasPattern_(image, repetition);
    };
  
    // Gradient / Pattern Stubs
    function CanvasGradient_(aType) {
      this.type_ = aType;
      this.x0_ = 0;
      this.y0_ = 0;
      this.r0_ = 0;
      this.x1_ = 0;
      this.y1_ = 0;
      this.r1_ = 0;
      this.colors_ = [];
    }
  
    CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
      aColor = processStyle(aColor);
      this.colors_.push({offset: aOffset,
                         color: aColor.color,
                         alpha: aColor.alpha});
    };
  
    function CanvasPattern_(image, repetition) {
      assertImageIsValid(image);
      switch (repetition) {
        case 'repeat':
        case null:
        case '':
          this.repetition_ = 'repeat';
          break
        case 'repeat-x':
        case 'repeat-y':
        case 'no-repeat':
          this.repetition_ = repetition;
          break;
        default:
          throwException('SYNTAX_ERR');
      }
  
      this.src_ = image.src;
      this.width_ = image.width;
      this.height_ = image.height;
    }
  
    function throwException(s) {
      throw new DOMException_(s);
    }
  
    function assertImageIsValid(img) {
      if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
        throwException('TYPE_MISMATCH_ERR');
      }
      if (img.readyState != 'complete') {
        throwException('INVALID_STATE_ERR');
      }
    }
  
    function DOMException_(s) {
      this.code = this[s];
      this.message = s +': DOM Exception ' + this.code;
    }
    var p = DOMException_.prototype = new Error;
    p.INDEX_SIZE_ERR = 1;
    p.DOMSTRING_SIZE_ERR = 2;
    p.HIERARCHY_REQUEST_ERR = 3;
    p.WRONG_DOCUMENT_ERR = 4;
    p.INVALID_CHARACTER_ERR = 5;
    p.NO_DATA_ALLOWED_ERR = 6;
    p.NO_MODIFICATION_ALLOWED_ERR = 7;
    p.NOT_FOUND_ERR = 8;
    p.NOT_SUPPORTED_ERR = 9;
    p.INUSE_ATTRIBUTE_ERR = 10;
    p.INVALID_STATE_ERR = 11;
    p.SYNTAX_ERR = 12;
    p.INVALID_MODIFICATION_ERR = 13;
    p.NAMESPACE_ERR = 14;
    p.INVALID_ACCESS_ERR = 15;
    p.VALIDATION_ERR = 16;
    p.TYPE_MISMATCH_ERR = 17;
  
    // set up externs
    G_vmlCanvasManager = G_vmlCanvasManager_;
    CanvasRenderingContext2D = CanvasRenderingContext2D_;
    CanvasGradient = CanvasGradient_;
    CanvasPattern = CanvasPattern_;
    DOMException = DOMException_;
  })();
  
  } // if
  
  /**
  * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
  */
  ;(function(window, document) {
  /*jshint evil:true */
    /** version */
    var version = '3.7.3';
  
    /** Preset options */
    var options = window.html5 || {};
  
    /** Used to skip problem elements */
    var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
  
    /** Not all elements can be cloned in IE **/
    var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
  
    /** Detect whether the browser supports default html5 styles */
    var supportsHtml5Styles;
  
    /** Name of the expando, to work with multiple documents or to re-shiv one document */
    var expando = '_html5shiv';
  
    /** The id for the the documents expando */
    var expanID = 0;
  
    /** Cached data for each document */
    var expandoData = {};
  
    /** Detect whether the browser supports unknown elements */
    var supportsUnknownElements;
  
    (function() {
      try {
          var a = document.createElement('a');
          a.innerHTML = '<xyz></xyz>';
          //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
          supportsHtml5Styles = ('hidden' in a);
  
          supportsUnknownElements = a.childNodes.length == 1 || (function() {
            // assign a false positive if unable to shiv
            (document.createElement)('a');
            var frag = document.createDocumentFragment();
            return (
              typeof frag.cloneNode == 'undefined' ||
              typeof frag.createDocumentFragment == 'undefined' ||
              typeof frag.createElement == 'undefined'
            );
          }());
      } catch(e) {
        // assign a false positive if detection fails => unable to shiv
        supportsHtml5Styles = true;
        supportsUnknownElements = true;
      }
  
    }());
  
    /*--------------------------------------------------------------------------*/
  
    /**
     * Creates a style sheet with the given CSS text and adds it to the document.
     * @private
     * @param {Document} ownerDocument The document.
     * @param {String} cssText The CSS text.
     * @returns {StyleSheet} The style element.
     */
    function addStyleSheet(ownerDocument, cssText) {
      var p = ownerDocument.createElement('p'),
          parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
  
      p.innerHTML = 'x<style>' + cssText + '</style>';
      return parent.insertBefore(p.lastChild, parent.firstChild);
    }
  
    /**
     * Returns the value of `html5.elements` as an array.
     * @private
     * @returns {Array} An array of shived element node names.
     */
    function getElements() {
      var elements = html5.elements;
      return typeof elements == 'string' ? elements.split(' ') : elements;
    }
  
    /**
     * Extends the built-in list of html5 elements
     * @memberOf html5
     * @param {String|Array} newElements whitespace separated list or array of new element names to shiv
     * @param {Document} ownerDocument The context document.
     */
    function addElements(newElements, ownerDocument) {
      var elements = html5.elements;
      if(typeof elements != 'string'){
        elements = elements.join(' ');
      }
      if(typeof newElements != 'string'){
        newElements = newElements.join(' ');
      }
      html5.elements = elements +' '+ newElements;
      shivDocument(ownerDocument);
    }
  
      /**
     * Returns the data associated to the given document
     * @private
     * @param {Document} ownerDocument The document.
     * @returns {Object} An object of data.
     */
    function getExpandoData(ownerDocument) {
      var data = expandoData[ownerDocument[expando]];
      if (!data) {
          data = {};
          expanID++;
          ownerDocument[expando] = expanID;
          expandoData[expanID] = data;
      }
      return data;
    }
  
    /**
     * returns a shived element for the given nodeName and document
     * @memberOf html5
     * @param {String} nodeName name of the element
     * @param {Document} ownerDocument The context document.
     * @returns {Object} The shived element.
     */
    function createElement(nodeName, ownerDocument, data){
      if (!ownerDocument) {
          ownerDocument = document;
      }
      if(supportsUnknownElements){
          return ownerDocument.createElement(nodeName);
      }
      if (!data) {
          data = getExpandoData(ownerDocument);
      }
      var node;
  
      if (data.cache[nodeName]) {
          node = data.cache[nodeName].cloneNode();
      } else if (saveClones.test(nodeName)) {
          node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
      } else {
          node = data.createElem(nodeName);
      }
  
      // Avoid adding some elements to fragments in IE < 9 because
      // * Attributes like `name` or `type` cannot be set/changed once an element
      //   is inserted into a document/fragment
      // * Link elements with `src` attributes that are inaccessible, as with
      //   a 403 response, will cause the tab/window to crash
      // * Script elements appended to fragments will execute when their `src`
      //   or `text` property is set
      return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
    }
  
    /**
     * returns a shived DocumentFragment for the given document
     * @memberOf html5
     * @param {Document} ownerDocument The context document.
     * @returns {Object} The shived DocumentFragment.
     */
    function createDocumentFragment(ownerDocument, data){
      if (!ownerDocument) {
          ownerDocument = document;
      }
      if(supportsUnknownElements){
          return ownerDocument.createDocumentFragment();
      }
      data = data || getExpandoData(ownerDocument);
      var clone = data.frag.cloneNode(),
          i = 0,
          elems = getElements(),
          l = elems.length;
      for(;i<l;i++){
          clone.createElement(elems[i]);
      }
      return clone;
    }
  
    /**
     * Shivs the `createElement` and `createDocumentFragment` methods of the document.
     * @private
     * @param {Document|DocumentFragment} ownerDocument The document.
     * @param {Object} data of the document.
     */
    function shivMethods(ownerDocument, data) {
      if (!data.cache) {
          data.cache = {};
          data.createElem = ownerDocument.createElement;
          data.createFrag = ownerDocument.createDocumentFragment;
          data.frag = data.createFrag();
      }
  
  
      ownerDocument.createElement = function(nodeName) {
        //abort shiv
        if (!html5.shivMethods) {
            return data.createElem(nodeName);
        }
        return createElement(nodeName, ownerDocument, data);
      };
  
      ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
        'var n=f.cloneNode(),c=n.createElement;' +
        'h.shivMethods&&(' +
          // unroll the `createElement` calls
          getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
            data.createElem(nodeName);
            data.frag.createElement(nodeName);
            return 'c("' + nodeName + '")';
          }) +
        ');return n}'
      )(html5, data.frag);
    }
  
    /*--------------------------------------------------------------------------*/
  
    /**
     * Shivs the given document.
     * @memberOf html5
     * @param {Document} ownerDocument The document to shiv.
     * @returns {Document} The shived document.
     */
    function shivDocument(ownerDocument) {
      if (!ownerDocument) {
          ownerDocument = document;
      }
      var data = getExpandoData(ownerDocument);
  
      if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
        data.hasCSS = !!addStyleSheet(ownerDocument,
          // corrects block display not defined in IE6/7/8/9
          'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
          // adds styling not present in IE6/7/8/9
          'mark{background:#FF0;color:#000}' +
          // hides non-rendered elements
          'template{display:none}'
        );
      }
      if (!supportsUnknownElements) {
        shivMethods(ownerDocument, data);
      }
      return ownerDocument;
    }
  
    /*--------------------------------------------------------------------------*/
  
    /**
     * The `html5` object is exposed so that more elements can be shived and
     * existing shiving can be detected on iframes.
     * @type Object
     * @example
     *
     * // options can be changed before the script is included
     * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
     */
    var html5 = {
  
      /**
       * An array or space separated string of node names of the elements to shiv.
       * @memberOf html5
       * @type Array|String
       */
      'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
  
      /**
       * current version of html5shiv
       */
      'version': version,
  
      /**
       * A flag to indicate that the HTML5 style sheet should be inserted.
       * @memberOf html5
       * @type Boolean
       */
      'shivCSS': (options.shivCSS !== false),
  
      /**
       * Is equal to true if a browser supports creating unknown/HTML5 elements
       * @memberOf html5
       * @type boolean
       */
      'supportsUnknownElements': supportsUnknownElements,
  
      /**
       * A flag to indicate that the document's `createElement` and `createDocumentFragment`
       * methods should be overwritten.
       * @memberOf html5
       * @type Boolean
       */
      'shivMethods': (options.shivMethods !== false),
  
      /**
       * A string to describe the type of `html5` object ("default" or "default print").
       * @memberOf html5
       * @type String
       */
      'type': 'default',
  
      // shivs the document according to the specified `html5` object options
      'shivDocument': shivDocument,
  
      //creates a shived element
      createElement: createElement,
  
      //creates a shived documentFragment
      createDocumentFragment: createDocumentFragment,
  
      //extends list of elements
      addElements: addElements
    };
  
    /*--------------------------------------------------------------------------*/
  
    // expose html5
    window.html5 = html5;
  
    // shiv the document
    shivDocument(document);
  
    /*------------------------------- Print Shiv -------------------------------*/
  
    /** Used to filter media types */
    var reMedia = /^$|\b(?:all|print)\b/;
  
    /** Used to namespace printable elements */
    var shivNamespace = 'html5shiv';
  
    /** Detect whether the browser supports shivable style sheets */
    var supportsShivableSheets = !supportsUnknownElements && (function() {
      // assign a false negative if unable to shiv
      var docEl = document.documentElement;
      return !(
        typeof document.namespaces == 'undefined' ||
        typeof document.parentWindow == 'undefined' ||
        typeof docEl.applyElement == 'undefined' ||
        typeof docEl.removeNode == 'undefined' ||
        typeof window.attachEvent == 'undefined'
      );
    }());
  
    /*--------------------------------------------------------------------------*/
  
    /**
     * Wraps all HTML5 elements in the given document with printable elements.
     * (eg. the "header" element is wrapped with the "html5shiv:header" element)
     * @private
     * @param {Document} ownerDocument The document.
     * @returns {Array} An array wrappers added.
     */
    function addWrappers(ownerDocument) {
      var node,
          nodes = ownerDocument.getElementsByTagName('*'),
          index = nodes.length,
          reElements = RegExp('^(?:' + getElements().join('|') + ')$', 'i'),
          result = [];
  
      while (index--) {
        node = nodes[index];
        if (reElements.test(node.nodeName)) {
          result.push(node.applyElement(createWrapper(node)));
        }
      }
      return result;
    }
  
    /**
     * Creates a printable wrapper for the given element.
     * @private
     * @param {Element} element The element.
     * @returns {Element} The wrapper.
     */
    function createWrapper(element) {
      var node,
          nodes = element.attributes,
          index = nodes.length,
          wrapper = element.ownerDocument.createElement(shivNamespace + ':' + element.nodeName);
  
      // copy element attributes to the wrapper
      while (index--) {
        node = nodes[index];
        node.specified && wrapper.setAttribute(node.nodeName, node.nodeValue);
      }
      // copy element styles to the wrapper
      wrapper.style.cssText = element.style.cssText;
      return wrapper;
    }
  
    /**
     * Shivs the given CSS text.
     * (eg. header{} becomes html5shiv\:header{})
     * @private
     * @param {String} cssText The CSS text to shiv.
     * @returns {String} The shived CSS text.
     */
    function shivCssText(cssText) {
      var pair,
          parts = cssText.split('{'),
          index = parts.length,
          reElements = RegExp('(^|[\\s,>+~])(' + getElements().join('|') + ')(?=[[\\s,>+~#.:]|$)', 'gi'),
          replacement = '$1' + shivNamespace + '\\:$2';
  
      while (index--) {
        pair = parts[index] = parts[index].split('}');
        pair[pair.length - 1] = pair[pair.length - 1].replace(reElements, replacement);
        parts[index] = pair.join('}');
      }
      return parts.join('{');
    }
  
    /**
     * Removes the given wrappers, leaving the original elements.
     * @private
     * @params {Array} wrappers An array of printable wrappers.
     */
    function removeWrappers(wrappers) {
      var index = wrappers.length;
      while (index--) {
        wrappers[index].removeNode();
      }
    }
  
    /*--------------------------------------------------------------------------*/
  
    /**
     * Shivs the given document for print.
     * @memberOf html5
     * @param {Document} ownerDocument The document to shiv.
     * @returns {Document} The shived document.
     */
    function shivPrint(ownerDocument) {
      var shivedSheet,
          wrappers,
          data = getExpandoData(ownerDocument),
          namespaces = ownerDocument.namespaces,
          ownerWindow = ownerDocument.parentWindow;
  
      if (!supportsShivableSheets || ownerDocument.printShived) {
        return ownerDocument;
      }
      if (typeof namespaces[shivNamespace] == 'undefined') {
        namespaces.add(shivNamespace);
      }
  
      function removeSheet() {
        clearTimeout(data._removeSheetTimer);
        if (shivedSheet) {
            shivedSheet.removeNode(true);
        }
        shivedSheet= null;
      }
  
      ownerWindow.attachEvent('onbeforeprint', function() {
  
        removeSheet();
  
        var imports,
            length,
            sheet,
            collection = ownerDocument.styleSheets,
            cssText = [],
            index = collection.length,
            sheets = Array(index);
  
        // convert styleSheets collection to an array
        while (index--) {
          sheets[index] = collection[index];
        }
        // concat all style sheet CSS text
        while ((sheet = sheets.pop())) {
          // IE does not enforce a same origin policy for external style sheets...
          // but has trouble with some dynamically created stylesheets
          if (!sheet.disabled && reMedia.test(sheet.media)) {
  
            try {
              imports = sheet.imports;
              length = imports.length;
            } catch(er){
              length = 0;
            }
  
            for (index = 0; index < length; index++) {
              sheets.push(imports[index]);
            }
  
            try {
              cssText.push(sheet.cssText);
            } catch(er){}
          }
        }
  
        // wrap all HTML5 elements with printable elements and add the shived style sheet
        cssText = shivCssText(cssText.reverse().join(''));
        wrappers = addWrappers(ownerDocument);
        shivedSheet = addStyleSheet(ownerDocument, cssText);
  
      });
  
      ownerWindow.attachEvent('onafterprint', function() {
        // remove wrappers, leaving the original elements, and remove the shived style sheet
        removeWrappers(wrappers);
        clearTimeout(data._removeSheetTimer);
        data._removeSheetTimer = setTimeout(removeSheet, 500);
      });
  
      ownerDocument.printShived = true;
      return ownerDocument;
    }
  
    /*--------------------------------------------------------------------------*/
  
    // expose API
    html5.type += ' print';
    html5.shivPrint = shivPrint;
  
    // shiv for print
    shivPrint(document);
  
    if(typeof module == 'object' && module.exports){
      module.exports = html5;
    }
  
  }(typeof window !== "undefined" ? window : this, document));
  
  // Copyright 2009-2012 by contributors, MIT License
  // vim: ts=4 sts=4 sw=4 expandtab
  
  //Add semicolon to prevent IIFE from being passed as argument to concated code.
  ;
  // Module systems magic dance
  (function (definition) {
      // RequireJS
      if (typeof define == "function") {
          define(definition);
      // YUI3
      } else if (typeof YUI == "function") {
          YUI.add("es5", definition);
      // CommonJS and <script>
      } else {
          definition();
      }
  })(function () {
  
  /**
   * Brings an environment as close to ECMAScript 5 compliance
   * as is possible with the facilities of erstwhile engines.
   *
   * Annotated ES5: http://es5.github.com/ (specific links below)
   * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
   * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/
   */
  
  // ES-5 15.1.2.2
  if (parseInt('08') !== 8) {
      parseInt = (function (origParseInt) {
          var hexRegex = /^0[xX]/;
          return function parseIntES5(str, radix) {
              str = String(str).trim();
              if (!+radix) {
                  radix = hexRegex.test(str) ? 16 : 10;
              }
              return origParseInt(str, radix);
          };
      }(parseInt));
  }
  
  //
  // Function
  // ========
  //
  
  // ES-5 15.3.4.5
  // http://es5.github.com/#x15.3.4.5
  
  function Empty() {}
  
  if (!Function.prototype.bind) {
      Function.prototype.bind = function bind(that) { // .length is 1
          // 1. Let Target be the this value.
          var target = this;
          // 2. If IsCallable(Target) is false, throw a TypeError exception.
          if (typeof target != "function") {
              throw new TypeError("Function.prototype.bind called on incompatible " + target);
          }
          // 3. Let A be a new (possibly empty) internal list of all of the
          //   argument values provided after thisArg (arg1, arg2 etc), in order.
          // XXX slicedArgs will stand in for "A" if used
          var args = _Array_slice_.call(arguments, 1); // for normal call
          // 4. Let F be a new native ECMAScript object.
          // 11. Set the [[Prototype]] internal property of F to the standard
          //   built-in Function prototype object as specified in 15.3.3.1.
          // 12. Set the [[Call]] internal property of F as described in
          //   15.3.4.5.1.
          // 13. Set the [[Construct]] internal property of F as described in
          //   15.3.4.5.2.
          // 14. Set the [[HasInstance]] internal property of F as described in
          //   15.3.4.5.3.
          var binder = function () {
  
              if (this instanceof bound) {
                  // 15.3.4.5.2 [[Construct]]
                  // When the [[Construct]] internal method of a function object,
                  // F that was created using the bind function is called with a
                  // list of arguments ExtraArgs, the following steps are taken:
                  // 1. Let target be the value of F's [[TargetFunction]]
                  //   internal property.
                  // 2. If target has no [[Construct]] internal method, a
                  //   TypeError exception is thrown.
                  // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
                  //   property.
                  // 4. Let args be a new list containing the same values as the
                  //   list boundArgs in the same order followed by the same
                  //   values as the list ExtraArgs in the same order.
                  // 5. Return the result of calling the [[Construct]] internal
                  //   method of target providing args as the arguments.
  
                  var result = target.apply(
                      this,
                      args.concat(_Array_slice_.call(arguments))
                  );
                  if (Object(result) === result) {
                      return result;
                  }
                  return this;
  
              } else {
                  // 15.3.4.5.1 [[Call]]
                  // When the [[Call]] internal method of a function object, F,
                  // which was created using the bind function is called with a
                  // this value and a list of arguments ExtraArgs, the following
                  // steps are taken:
                  // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
                  //   property.
                  // 2. Let boundThis be the value of F's [[BoundThis]] internal
                  //   property.
                  // 3. Let target be the value of F's [[TargetFunction]] internal
                  //   property.
                  // 4. Let args be a new list containing the same values as the
                  //   list boundArgs in the same order followed by the same
                  //   values as the list ExtraArgs in the same order.
                  // 5. Return the result of calling the [[Call]] internal method
                  //   of target providing boundThis as the this value and
                  //   providing args as the arguments.
  
                  // equiv: target.call(this, ...boundArgs, ...args)
                  return target.apply(
                      that,
                      args.concat(_Array_slice_.call(arguments))
                  );
  
              }
  
          };
  
          // 15. If the [[Class]] internal property of Target is "Function", then
          //     a. Let L be the length property of Target minus the length of A.
          //     b. Set the length own property of F to either 0 or L, whichever is
          //       larger.
          // 16. Else set the length own property of F to 0.
  
          var boundLength = Math.max(0, target.length - args.length);
  
          // 17. Set the attributes of the length own property of F to the values
          //   specified in 15.3.5.1.
          var boundArgs = [];
          for (var i = 0; i < boundLength; i++) {
              boundArgs.push("$" + i);
          }
  
          // XXX Build a dynamic function with desired amount of arguments is the only 
          // way to set the length property of a function. 
          // In environments where Content Security Policies enabled (Chrome extensions, 
          // for ex.) all use of eval or Function costructor throws an exception. 
          // However in all of these environments Function.prototype.bind exists 
          // and so this code will never be executed.
          var bound = Function("binder", "return function(" + boundArgs.join(",") + "){return binder.apply(this,arguments)}")(binder);
  
          if (target.prototype) {
              Empty.prototype = target.prototype;
              bound.prototype = new Empty();
              // Clean up dangling references.
              Empty.prototype = null;
          }
  
          // TODO
          // 18. Set the [[Extensible]] internal property of F to true.
  
          // TODO
          // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
          // 20. Call the [[DefineOwnProperty]] internal method of F with
          //   arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
          //   thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
          //   false.
          // 21. Call the [[DefineOwnProperty]] internal method of F with
          //   arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
          //   [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
          //   and false.
  
          // TODO
          // NOTE Function objects created using Function.prototype.bind do not
          // have a prototype property or the [[Code]], [[FormalParameters]], and
          // [[Scope]] internal properties.
          // XXX can't delete prototype in pure-js.
  
          // 22. Return F.
          return bound;
      };
  }
  
  // Shortcut to an often accessed properties, in order to avoid multiple
  // dereference that costs universally.
  // _Please note: Shortcuts are defined after `Function.prototype.bind` as we
  // us it in defining shortcuts.
  var call = Function.prototype.call;
  var prototypeOfArray = Array.prototype;
  var prototypeOfObject = Object.prototype;
  var _Array_slice_ = prototypeOfArray.slice;
  // Having a toString local variable name breaks in Opera so use _toString.
  var _toString = call.bind(prototypeOfObject.toString);
  var owns = call.bind(prototypeOfObject.hasOwnProperty);
  
  // If JS engine supports accessors creating shortcuts.
  var defineGetter;
  var defineSetter;
  var lookupGetter;
  var lookupSetter;
  var supportsAccessors;
  if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) {
      defineGetter = call.bind(prototypeOfObject.__defineGetter__);
      defineSetter = call.bind(prototypeOfObject.__defineSetter__);
      lookupGetter = call.bind(prototypeOfObject.__lookupGetter__);
      lookupSetter = call.bind(prototypeOfObject.__lookupSetter__);
  }
  
  //
  // Array
  // =====
  //
  
  // ES5 15.4.4.12
  // http://es5.github.com/#x15.4.4.12
  // Default value for second param
  // [bugfix, ielt9, old browsers]
  // IE < 9 bug: [1,2].splice(0).join("") == "" but should be "12"
  if ([1,2].splice(0).length != 2) {
      var array_splice = Array.prototype.splice;
      var array_push = Array.prototype.push;
      var array_unshift = Array.prototype.unshift;
  
      if (function() { // test IE < 9 to splice bug - see issue #138
          function makeArray(l) {
              var a = [];
              while (l--) {
                  a.unshift(l)
              }
              return a
          }
  
          var array = []
              , lengthBefore
          ;
  
          array.splice.bind(array, 0, 0).apply(null, makeArray(20));
          array.splice.bind(array, 0, 0).apply(null, makeArray(26));
  
          lengthBefore = array.length; //20
          array.splice(5, 0, "XXX"); // add one element
  
          if (lengthBefore + 1 == array.length) {
              return true;// has right splice implementation without bugs
          }
          // else {
          //    IE8 bug
          // }
      }()) {//IE 6/7
          Array.prototype.splice = function(start, deleteCount) {
              if (!arguments.length) {
                  return [];
              } else {
                  return array_splice.apply(this, [
                      start === void 0 ? 0 : start,
                      deleteCount === void 0 ? (this.length - start) : deleteCount
                  ].concat(_Array_slice_.call(arguments, 2)))
              }
          };
      }
      else {//IE8
          Array.prototype.splice = function(start, deleteCount) {
              var result
                  , args = _Array_slice_.call(arguments, 2)
                  , addElementsCount = args.length
              ;
  
              if (!arguments.length) {
                  return [];
              }
  
              if (start === void 0) { // default
                  start = 0;
              }
              if (deleteCount === void 0) { // default
                  deleteCount = this.length - start;
              }
  
              if (addElementsCount > 0) {
                  if (deleteCount <= 0) {
                      if (start == this.length) { // tiny optimisation #1
                          array_push.apply(this, args);
                          return [];
                      }
  
                      if (start == 0) { // tiny optimisation #2
                          array_unshift.apply(this, args);
                          return [];
                      }
                  }
  
                  // Array.prototype.splice implementation
                  result = _Array_slice_.call(this, start, start + deleteCount);// delete part
                  args.push.apply(args, _Array_slice_.call(this, start + deleteCount, this.length));// right part
                  args.unshift.apply(args, _Array_slice_.call(this, 0, start));// left part
  
                  // delete all items from this array and replace it to 'left part' + _Array_slice_.call(arguments, 2) + 'right part'
                  args.unshift(0, this.length);
  
                  array_splice.apply(this, args);
  
                  return result;
              }
  
              return array_splice.call(this, start, deleteCount);
          }
  
      }
  }
  
  // ES5 15.4.4.12
  // http://es5.github.com/#x15.4.4.13
  // Return len+argCount.
  // [bugfix, ielt8]
  // IE < 8 bug: [].unshift(0) == undefined but should be "1"
  if ([].unshift(0) != 1) {
      var array_unshift = Array.prototype.unshift;
      Array.prototype.unshift = function() {
          array_unshift.apply(this, arguments);
          return this.length;
      };
  }
  
  // ES5 15.4.3.2
  // http://es5.github.com/#x15.4.3.2
  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
  if (!Array.isArray) {
      Array.isArray = function isArray(obj) {
          return _toString(obj) == "[object Array]";
      };
  }
  
  // The IsCallable() check in the Array functions
  // has been replaced with a strict check on the
  // internal class of the object to trap cases where
  // the provided function was actually a regular
  // expression literal, which in V8 and
  // JavaScriptCore is a typeof "function".  Only in
  // V8 are regular expression literals permitted as
  // reduce parameters, so it is desirable in the
  // general case for the shim to match the more
  // strict and common behavior of rejecting regular
  // expressions.
  
  // ES5 15.4.4.18
  // http://es5.github.com/#x15.4.4.18
  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach
  
  // Check failure of by-index access of string characters (IE < 9)
  // and failure of `0 in boxedString` (Rhino)
  var boxedString = Object("a"),
      splitString = boxedString[0] != "a" || !(0 in boxedString);
  // Check node 0.6.21 bug where third parameter is not boxed
  var boxedForEach = true;
  if (Array.prototype.forEach) {
      Array.prototype.forEach.call("foo", function(item, i, obj) {
          if (typeof obj !== 'object') boxedForEach = false;
      });
  }
  
  if (!Array.prototype.forEach || !boxedForEach) {
      Array.prototype.forEach = function forEach(fun /*, thisp*/) {
          var object = toObject(this),
              self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  object,
              thisp = arguments[1],
              i = -1,
              length = self.length >>> 0;
  
          // If no callback function or if callback is not a callable function
          if (_toString(fun) != "[object Function]") {
              throw new TypeError(); // TODO message
          }
  
          while (++i < length) {
              if (i in self) {
                  // Invoke the callback function with call, passing arguments:
                  // context, property value, property key, thisArg object
                  // context
                  fun.call(thisp, self[i], i, object);
              }
          }
      };
  }
  
  // ES5 15.4.4.19
  // http://es5.github.com/#x15.4.4.19
  // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
  if (!Array.prototype.map) {
      Array.prototype.map = function map(fun /*, thisp*/) {
          var object = toObject(this),
              self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  object,
              length = self.length >>> 0,
              result = Array(length),
              thisp = arguments[1];
  
          // If no callback function or if callback is not a callable function
          if (_toString(fun) != "[object Function]") {
              throw new TypeError(fun + " is not a function");
          }
  
          for (var i = 0; i < length; i++) {
              if (i in self)
                  result[i] = fun.call(thisp, self[i], i, object);
          }
          return result;
      };
  }
  
  // ES5 15.4.4.20
  // http://es5.github.com/#x15.4.4.20
  // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
  if (!Array.prototype.filter) {
      Array.prototype.filter = function filter(fun /*, thisp */) {
          var object = toObject(this),
              self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                      object,
              length = self.length >>> 0,
              result = [],
              value,
              thisp = arguments[1];
  
          // If no callback function or if callback is not a callable function
          if (_toString(fun) != "[object Function]") {
              throw new TypeError(fun + " is not a function");
          }
  
          for (var i = 0; i < length; i++) {
              if (i in self) {
                  value = self[i];
                  if (fun.call(thisp, value, i, object)) {
                      result.push(value);
                  }
              }
          }
          return result;
      };
  }
  
  // ES5 15.4.4.16
  // http://es5.github.com/#x15.4.4.16
  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
  if (!Array.prototype.every) {
      Array.prototype.every = function every(fun /*, thisp */) {
          var object = toObject(this),
              self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  object,
              length = self.length >>> 0,
              thisp = arguments[1];
  
          // If no callback function or if callback is not a callable function
          if (_toString(fun) != "[object Function]") {
              throw new TypeError(fun + " is not a function");
          }
  
          for (var i = 0; i < length; i++) {
              if (i in self && !fun.call(thisp, self[i], i, object)) {
                  return false;
              }
          }
          return true;
      };
  }
  
  // ES5 15.4.4.17
  // http://es5.github.com/#x15.4.4.17
  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
  if (!Array.prototype.some) {
      Array.prototype.some = function some(fun /*, thisp */) {
          var object = toObject(this),
              self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  object,
              length = self.length >>> 0,
              thisp = arguments[1];
  
          // If no callback function or if callback is not a callable function
          if (_toString(fun) != "[object Function]") {
              throw new TypeError(fun + " is not a function");
          }
  
          for (var i = 0; i < length; i++) {
              if (i in self && fun.call(thisp, self[i], i, object)) {
                  return true;
              }
          }
          return false;
      };
  }
  
  // ES5 15.4.4.21
  // http://es5.github.com/#x15.4.4.21
  // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
  if (!Array.prototype.reduce) {
      Array.prototype.reduce = function reduce(fun /*, initial*/) {
          var object = toObject(this),
              self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  object,
              length = self.length >>> 0;
  
          // If no callback function or if callback is not a callable function
          if (_toString(fun) != "[object Function]") {
              throw new TypeError(fun + " is not a function");
          }
  
          // no value to return if no initial value and an empty array
          if (!length && arguments.length == 1) {
              throw new TypeError("reduce of empty array with no initial value");
          }
  
          var i = 0;
          var result;
          if (arguments.length >= 2) {
              result = arguments[1];
          } else {
              do {
                  if (i in self) {
                      result = self[i++];
                      break;
                  }
  
                  // if array contains no values, no initial value to return
                  if (++i >= length) {
                      throw new TypeError("reduce of empty array with no initial value");
                  }
              } while (true);
          }
  
          for (; i < length; i++) {
              if (i in self) {
                  result = fun.call(void 0, result, self[i], i, object);
              }
          }
  
          return result;
      };
  }
  
  // ES5 15.4.4.22
  // http://es5.github.com/#x15.4.4.22
  // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight
  if (!Array.prototype.reduceRight) {
      Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
          var object = toObject(this),
              self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  object,
              length = self.length >>> 0;
  
          // If no callback function or if callback is not a callable function
          if (_toString(fun) != "[object Function]") {
              throw new TypeError(fun + " is not a function");
          }
  
          // no value to return if no initial value, empty array
          if (!length && arguments.length == 1) {
              throw new TypeError("reduceRight of empty array with no initial value");
          }
  
          var result, i = length - 1;
          if (arguments.length >= 2) {
              result = arguments[1];
          } else {
              do {
                  if (i in self) {
                      result = self[i--];
                      break;
                  }
  
                  // if array contains no values, no initial value to return
                  if (--i < 0) {
                      throw new TypeError("reduceRight of empty array with no initial value");
                  }
              } while (true);
          }
  
          if (i < 0) {
              return result;
          }
  
          do {
              if (i in this) {
                  result = fun.call(void 0, result, self[i], i, object);
              }
          } while (i--);
  
          return result;
      };
  }
  
  // ES5 15.4.4.14
  // http://es5.github.com/#x15.4.4.14
  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
  if (!Array.prototype.indexOf || ([0, 1].indexOf(1, 2) != -1)) {
      Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) {
          var self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  toObject(this),
              length = self.length >>> 0;
  
          if (!length) {
              return -1;
          }
  
          var i = 0;
          if (arguments.length > 1) {
              i = toInteger(arguments[1]);
          }
  
          // handle negative indices
          i = i >= 0 ? i : Math.max(0, length + i);
          for (; i < length; i++) {
              if (i in self && self[i] === sought) {
                  return i;
              }
          }
          return -1;
      };
  }
  
  // ES5 15.4.4.15
  // http://es5.github.com/#x15.4.4.15
  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
  if (!Array.prototype.lastIndexOf || ([0, 1].lastIndexOf(0, -3) != -1)) {
      Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) {
          var self = splitString && _toString(this) == "[object String]" ?
                  this.split("") :
                  toObject(this),
              length = self.length >>> 0;
  
          if (!length) {
              return -1;
          }
          var i = length - 1;
          if (arguments.length > 1) {
              i = Math.min(i, toInteger(arguments[1]));
          }
          // handle negative indices
          i = i >= 0 ? i : length - Math.abs(i);
          for (; i >= 0; i--) {
              if (i in self && sought === self[i]) {
                  return i;
              }
          }
          return -1;
      };
  }
  
  //
  // Object
  // ======
  //
  
  // ES5 15.2.3.14
  // http://es5.github.com/#x15.2.3.14
  if (!Object.keys) {
      // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
      var hasDontEnumBug = true,
          dontEnums = [
              "toString",
              "toLocaleString",
              "valueOf",
              "hasOwnProperty",
              "isPrototypeOf",
              "propertyIsEnumerable",
              "constructor"
          ],
          dontEnumsLength = dontEnums.length;
  
      for (var key in {"toString": null}) {
          hasDontEnumBug = false;
      }
  
      Object.keys = function keys(object) {
  
          if (
              (typeof object != "object" && typeof object != "function") ||
              object === null
          ) {
              throw new TypeError("Object.keys called on a non-object");
          }
  
          var keys = [];
          for (var name in object) {
              if (owns(object, name)) {
                  keys.push(name);
              }
          }
  
          if (hasDontEnumBug) {
              for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
                  var dontEnum = dontEnums[i];
                  if (owns(object, dontEnum)) {
                      keys.push(dontEnum);
                  }
              }
          }
          return keys;
      };
  
  }
  
  //
  // Date
  // ====
  //
  
  // ES5 15.9.5.43
  // http://es5.github.com/#x15.9.5.43
  // This function returns a String value represent the instance in time
  // represented by this Date object. The format of the String is the Date Time
  // string format defined in 15.9.1.15. All fields are present in the String.
  // The time zone is always UTC, denoted by the suffix Z. If the time value of
  // this object is not a finite Number a RangeError exception is thrown.
  var negativeDate = -62198755200000,
      negativeYearString = "-000001";
  if (
      !Date.prototype.toISOString ||
      (new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1)
  ) {
      Date.prototype.toISOString = function toISOString() {
          var result, length, value, year, month;
          if (!isFinite(this)) {
              throw new RangeError("Date.prototype.toISOString called on non-finite value.");
          }
  
          year = this.getUTCFullYear();
  
          month = this.getUTCMonth();
          // see https://github.com/es-shims/es5-shim/issues/111
          year += Math.floor(month / 12);
          month = (month % 12 + 12) % 12;
  
          // the date time string format is specified in 15.9.1.15.
          result = [month + 1, this.getUTCDate(),
              this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()];
          year = (
              (year < 0 ? "-" : (year > 9999 ? "+" : "")) +
              ("00000" + Math.abs(year))
              .slice(0 <= year && year <= 9999 ? -4 : -6)
          );
  
          length = result.length;
          while (length--) {
              value = result[length];
              // pad months, days, hours, minutes, and seconds to have two
              // digits.
              if (value < 10) {
                  result[length] = "0" + value;
              }
          }
          // pad milliseconds to have three digits.
          return (
              year + "-" + result.slice(0, 2).join("-") +
              "T" + result.slice(2).join(":") + "." +
              ("000" + this.getUTCMilliseconds()).slice(-3) + "Z"
          );
      };
  }
  
  
  // ES5 15.9.5.44
  // http://es5.github.com/#x15.9.5.44
  // This function provides a String representation of a Date object for use by
  // JSON.stringify (15.12.3).
  var dateToJSONIsSupported = false;
  try {
      dateToJSONIsSupported = (
          Date.prototype.toJSON &&
          new Date(NaN).toJSON() === null &&
          new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 &&
          Date.prototype.toJSON.call({ // generic
              toISOString: function () {
                  return true;
              }
          })
      );
  } catch (e) {
  }
  if (!dateToJSONIsSupported) {
      Date.prototype.toJSON = function toJSON(key) {
          // When the toJSON method is called with argument key, the following
          // steps are taken:
  
          // 1.  Let O be the result of calling ToObject, giving it the this
          // value as its argument.
          // 2. Let tv be toPrimitive(O, hint Number).
          var o = Object(this),
              tv = toPrimitive(o),
              toISO;
          // 3. If tv is a Number and is not finite, return null.
          if (typeof tv === "number" && !isFinite(tv)) {
              return null;
          }
          // 4. Let toISO be the result of calling the [[Get]] internal method of
          // O with argument "toISOString".
          toISO = o.toISOString;
          // 5. If IsCallable(toISO) is false, throw a TypeError exception.
          if (typeof toISO != "function") {
              throw new TypeError("toISOString property is not callable");
          }
          // 6. Return the result of calling the [[Call]] internal method of
          //  toISO with O as the this value and an empty argument list.
          return toISO.call(o);
  
          // NOTE 1 The argument is ignored.
  
          // NOTE 2 The toJSON function is intentionally generic; it does not
          // require that its this value be a Date object. Therefore, it can be
          // transferred to other kinds of objects for use as a method. However,
          // it does require that any such object have a toISOString method. An
          // object is free to use the argument key to filter its
          // stringification.
      };
  }
  
  // ES5 15.9.4.2
  // http://es5.github.com/#x15.9.4.2
  // based on work shared by Daniel Friesen (dantman)
  // http://gist.github.com/303249
  if (!Date.parse || "Date.parse is buggy") {
      // XXX global assignment won't work in embeddings that use
      // an alternate object for the context.
      Date = (function(NativeDate) {
  
          // Date.length === 7
          function Date(Y, M, D, h, m, s, ms) {
              var length = arguments.length;
              if (this instanceof NativeDate) {
                  var date = length == 1 && String(Y) === Y ? // isString(Y)
                      // We explicitly pass it through parse:
                      new NativeDate(Date.parse(Y)) :
                      // We have to manually make calls depending on argument
                      // length here
                      length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) :
                      length >= 6 ? new NativeDate(Y, M, D, h, m, s) :
                      length >= 5 ? new NativeDate(Y, M, D, h, m) :
                      length >= 4 ? new NativeDate(Y, M, D, h) :
                      length >= 3 ? new NativeDate(Y, M, D) :
                      length >= 2 ? new NativeDate(Y, M) :
                      length >= 1 ? new NativeDate(Y) :
                                    new NativeDate();
                  // Prevent mixups with unfixed Date object
                  date.constructor = Date;
                  return date;
              }
              return NativeDate.apply(this, arguments);
          };
  
          // 15.9.1.15 Date Time String Format.
          var isoDateExpression = new RegExp("^" +
              "(\\d{4}|[\+\-]\\d{6})" + // four-digit year capture or sign +
                                        // 6-digit extended year
              "(?:-(\\d{2})" + // optional month capture
              "(?:-(\\d{2})" + // optional day capture
              "(?:" + // capture hours:minutes:seconds.milliseconds
                  "T(\\d{2})" + // hours capture
                  ":(\\d{2})" + // minutes capture
                  "(?:" + // optional :seconds.milliseconds
                      ":(\\d{2})" + // seconds capture
                      "(?:(\\.\\d{1,}))?" + // milliseconds capture
                  ")?" +
              "(" + // capture UTC offset component
                  "Z|" + // UTC capture
                  "(?:" + // offset specifier +/-hours:minutes
                      "([-+])" + // sign capture
                      "(\\d{2})" + // hours offset capture
                      ":(\\d{2})" + // minutes offset capture
                  ")" +
              ")?)?)?)?" +
          "$");
  
          var months = [
              0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
          ];
  
          function dayFromMonth(year, month) {
              var t = month > 1 ? 1 : 0;
              return (
                  months[month] +
                  Math.floor((year - 1969 + t) / 4) -
                  Math.floor((year - 1901 + t) / 100) +
                  Math.floor((year - 1601 + t) / 400) +
                  365 * (year - 1970)
              );
          }
  
          function toUTC(t) {
              return Number(new NativeDate(1970, 0, 1, 0, 0, 0, t));
          }
  
          // Copy any custom methods a 3rd party library may have added
          for (var key in NativeDate) {
              Date[key] = NativeDate[key];
          }
  
          // Copy "native" methods explicitly; they may be non-enumerable
          Date.now = NativeDate.now;
          Date.UTC = NativeDate.UTC;
          Date.prototype = NativeDate.prototype;
          Date.prototype.constructor = Date;
  
          // Upgrade Date.parse to handle simplified ISO 8601 strings
          Date.parse = function parse(string) {
              var match = isoDateExpression.exec(string);
              if (match) {
                  // parse months, days, hours, minutes, seconds, and milliseconds
                  // provide default values if necessary
                  // parse the UTC offset component
                  var year = Number(match[1]),
                      month = Number(match[2] || 1) - 1,
                      day = Number(match[3] || 1) - 1,
                      hour = Number(match[4] || 0),
                      minute = Number(match[5] || 0),
                      second = Number(match[6] || 0),
                      millisecond = Math.floor(Number(match[7] || 0) * 1000),
                      // When time zone is missed, local offset should be used
                      // (ES 5.1 bug)
                      // see https://bugs.ecmascript.org/show_bug.cgi?id=112
                      isLocalTime = Boolean(match[4] && !match[8]),
                      signOffset = match[9] === "-" ? 1 : -1,
                      hourOffset = Number(match[10] || 0),
                      minuteOffset = Number(match[11] || 0),
                      result;
                  if (
                      hour < (
                          minute > 0 || second > 0 || millisecond > 0 ?
                          24 : 25
                      ) &&
                      minute < 60 && second < 60 && millisecond < 1000 &&
                      month > -1 && month < 12 && hourOffset < 24 &&
                      minuteOffset < 60 && // detect invalid offsets
                      day > -1 &&
                      day < (
                          dayFromMonth(year, month + 1) -
                          dayFromMonth(year, month)
                      )
                  ) {
                      result = (
                          (dayFromMonth(year, month) + day) * 24 +
                          hour +
                          hourOffset * signOffset
                      ) * 60;
                      result = (
                          (result + minute + minuteOffset * signOffset) * 60 +
                          second
                      ) * 1000 + millisecond;
                      if (isLocalTime) {
                          result = toUTC(result);
                      }
                      if (-8.64e15 <= result && result <= 8.64e15) {
                          return result;
                      }
                  }
                  return NaN;
              }
              return NativeDate.parse.apply(this, arguments);
          };
  
          return Date;
      })(Date);
  }
  
  // ES5 15.9.4.4
  // http://es5.github.com/#x15.9.4.4
  if (!Date.now) {
      Date.now = function now() {
          return new Date().getTime();
      };
  }
  
  
  //
  // Number
  // ======
  //
  
  // ES5.1 15.7.4.5
  // http://es5.github.com/#x15.7.4.5
  if (!Number.prototype.toFixed || (0.00008).toFixed(3) !== '0.000' || (0.9).toFixed(0) === '0' || (1.255).toFixed(2) !== '1.25' || (1000000000000000128).toFixed(0) !== "1000000000000000128") {
      // Hide these variables and functions
      (function () {
          var base, size, data, i;
  
          base = 1e7;
          size = 6;
          data = [0, 0, 0, 0, 0, 0];
  
          function multiply(n, c) {
              var i = -1;
              while (++i < size) {
                  c += n * data[i];
                  data[i] = c % base;
                  c = Math.floor(c / base);
              }
          }
  
          function divide(n) {
              var i = size, c = 0;
              while (--i >= 0) {
                  c += data[i];
                  data[i] = Math.floor(c / n);
                  c = (c % n) * base;
              }
          }
  
          function toString() {
              var i = size;
              var s = '';
              while (--i >= 0) {
                  if (s !== '' || i === 0 || data[i] !== 0) {
                      var t = String(data[i]);
                      if (s === '') {
                          s = t;
                      } else {
                          s += '0000000'.slice(0, 7 - t.length) + t;
                      }
                  }
              }
              return s;
          }
  
          function pow(x, n, acc) {
              return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc)));
          }
  
          function log(x) {
              var n = 0;
              while (x >= 4096) {
                  n += 12;
                  x /= 4096;
              }
              while (x >= 2) {
                  n += 1;
                  x /= 2;
              }
              return n;
          }
  
          Number.prototype.toFixed = function (fractionDigits) {
              var f, x, s, m, e, z, j, k;
  
              // Test for NaN and round fractionDigits down
              f = Number(fractionDigits);
              f = f !== f ? 0 : Math.floor(f);
  
              if (f < 0 || f > 20) {
                  throw new RangeError("Number.toFixed called with invalid number of decimals");
              }
  
              x = Number(this);
  
              // Test for NaN
              if (x !== x) {
                  return "NaN";
              }
  
              // If it is too big or small, return the string value of the number
              if (x <= -1e21 || x >= 1e21) {
                  return String(x);
              }
  
              s = "";
  
              if (x < 0) {
                  s = "-";
                  x = -x;
              }
  
              m = "0";
  
              if (x > 1e-21) {
                  // 1e-21 < x < 1e21
                  // -70 < log2(x) < 70
                  e = log(x * pow(2, 69, 1)) - 69;
                  z = (e < 0 ? x * pow(2, -e, 1) : x / pow(2, e, 1));
                  z *= 0x10000000000000; // Math.pow(2, 52);
                  e = 52 - e;
  
                  // -18 < e < 122
                  // x = z / 2 ^ e
                  if (e > 0) {
                      multiply(0, z);
                      j = f;
  
                      while (j >= 7) {
                          multiply(1e7, 0);
                          j -= 7;
                      }
  
                      multiply(pow(10, j, 1), 0);
                      j = e - 1;
  
                      while (j >= 23) {
                          divide(1 << 23);
                          j -= 23;
                      }
  
                      divide(1 << j);
                      multiply(1, 1);
                      divide(2);
                      m = toString();
                  } else {
                      multiply(0, z);
                      multiply(1 << (-e), 0);
                      m = toString() + '0.00000000000000000000'.slice(2, 2 + f);
                  }
              }
  
              if (f > 0) {
                  k = m.length;
  
                  if (k <= f) {
                      m = s + '0.0000000000000000000'.slice(0, f - k + 2) + m;
                  } else {
                      m = s + m.slice(0, k - f) + '.' + m.slice(k - f);
                  }
              } else {
                  m = s + m;
              }
  
              return m;
          }
      }());
  }
  
  
  //
  // String
  // ======
  //
  
  
  // ES5 15.5.4.14
  // http://es5.github.com/#x15.5.4.14
  
  // [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]
  // Many browsers do not split properly with regular expressions or they
  // do not perform the split correctly under obscure conditions.
  // See http://blog.stevenlevithan.com/archives/cross-browser-split
  // I've tested in many browsers and this seems to cover the deviant ones:
  //    'ab'.split(/(?:ab)*/) should be ["", ""], not [""]
  //    '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""]
  //    'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not
  //       [undefined, "t", undefined, "e", ...]
  //    ''.split(/.?/) should be [], not [""]
  //    '.'.split(/()()/) should be ["."], not ["", "", "."]
  
  var string_split = String.prototype.split;
  if (
      'ab'.split(/(?:ab)*/).length !== 2 ||
      '.'.split(/(.?)(.?)/).length !== 4 ||
      'tesst'.split(/(s)*/)[1] === "t" ||
      ''.split(/.?/).length ||
      '.'.split(/()()/).length > 1
  ) {
      (function () {
          var compliantExecNpcg = /()??/.exec("")[1] === void 0; // NPCG: nonparticipating capturing group
  
          String.prototype.split = function (separator, limit) {
              var string = this;
              if (separator === void 0 && limit === 0)
                  return [];
  
              // If `separator` is not a regex, use native split
              if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
                  return string_split.apply(this, arguments);
              }
  
              var output = [],
                  flags = (separator.ignoreCase ? "i" : "") +
                          (separator.multiline  ? "m" : "") +
                          (separator.extended   ? "x" : "") + // Proposed for ES6
                          (separator.sticky     ? "y" : ""), // Firefox 3+
                  lastLastIndex = 0,
                  // Make `global` and avoid `lastIndex` issues by working with a copy
                  separator = new RegExp(separator.source, flags + "g"),
                  separator2, match, lastIndex, lastLength;
              string += ""; // Type-convert
              if (!compliantExecNpcg) {
                  // Doesn't need flags gy, but they don't hurt
                  separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
              }
              /* Values for `limit`, per the spec:
               * If undefined: 4294967295 // Math.pow(2, 32) - 1
               * If 0, Infinity, or NaN: 0
               * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
               * If negative number: 4294967296 - Math.floor(Math.abs(limit))
               * If other: Type-convert, then use the above rules
               */
              limit = limit === void 0 ?
                  -1 >>> 0 : // Math.pow(2, 32) - 1
                  limit >>> 0; // ToUint32(limit)
              while (match = separator.exec(string)) {
                  // `separator.lastIndex` is not reliable cross-browser
                  lastIndex = match.index + match[0].length;
                  if (lastIndex > lastLastIndex) {
                      output.push(string.slice(lastLastIndex, match.index));
                      // Fix browsers whose `exec` methods don't consistently return `undefined` for
                      // nonparticipating capturing groups
                      if (!compliantExecNpcg && match.length > 1) {
                          match[0].replace(separator2, function () {
                              for (var i = 1; i < arguments.length - 2; i++) {
                                  if (arguments[i] === void 0) {
                                      match[i] = void 0;
                                  }
                              }
                          });
                      }
                      if (match.length > 1 && match.index < string.length) {
                          Array.prototype.push.apply(output, match.slice(1));
                      }
                      lastLength = match[0].length;
                      lastLastIndex = lastIndex;
                      if (output.length >= limit) {
                          break;
                      }
                  }
                  if (separator.lastIndex === match.index) {
                      separator.lastIndex++; // Avoid an infinite loop
                  }
              }
              if (lastLastIndex === string.length) {
                  if (lastLength || !separator.test("")) {
                      output.push("");
                  }
              } else {
                  output.push(string.slice(lastLastIndex));
              }
              return output.length > limit ? output.slice(0, limit) : output;
          };
      }());
  
  // [bugfix, chrome]
  // If separator is undefined, then the result array contains just one String,
  // which is the this value (converted to a String). If limit is not undefined,
  // then the output array is truncated so that it contains no more than limit
  // elements.
  // "0".split(undefined, 0) -> []
  } else if ("0".split(void 0, 0).length) {
      String.prototype.split = function(separator, limit) {
          if (separator === void 0 && limit === 0) return [];
          return string_split.apply(this, arguments);
      }
  }
  
  
  // ECMA-262, 3rd B.2.3
  // Note an ECMAScript standart, although ECMAScript 3rd Edition has a
  // non-normative section suggesting uniform semantics and it should be
  // normalized across all browsers
  // [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE
  if ("".substr && "0b".substr(-1) !== "b") {
      var string_substr = String.prototype.substr;
      /**
       *  Get the substring of a string
       *  @param  {integer}  start   where to start the substring
       *  @param  {integer}  length  how many characters to return
       *  @return {string}
       */
      String.prototype.substr = function(start, length) {
          return string_substr.call(
              this,
              start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start,
              length
          );
      }
  }
  
  // ES5 15.5.4.20
  // http://es5.github.com/#x15.5.4.20
  var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
      "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
      "\u2029\uFEFF";
  if (!String.prototype.trim || ws.trim()) {
      // http://blog.stevenlevithan.com/archives/faster-trim-javascript
      // http://perfectionkills.com/whitespace-deviations/
      ws = "[" + ws + "]";
      var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
          trimEndRegexp = new RegExp(ws + ws + "*$");
      String.prototype.trim = function trim() {
          if (this === void 0 || this === null) {
              throw new TypeError("can't convert "+this+" to object");
          }
          return String(this)
              .replace(trimBeginRegexp, "")
              .replace(trimEndRegexp, "");
      };
  }
  
  //
  // Util
  // ======
  //
  
  // ES5 9.4
  // http://es5.github.com/#x9.4
  // http://jsperf.com/to-integer
  
  function toInteger(n) {
      n = +n;
      if (n !== n) { // isNaN
          n = 0;
      } else if (n !== 0 && n !== (1/0) && n !== -(1/0)) {
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
      }
      return n;
  }
  
  function isPrimitive(input) {
      var type = typeof input;
      return (
          input === null ||
          type === "undefined" ||
          type === "boolean" ||
          type === "number" ||
          type === "string"
      );
  }
  
  function toPrimitive(input) {
      var val, valueOf, toString;
      if (isPrimitive(input)) {
          return input;
      }
      valueOf = input.valueOf;
      if (typeof valueOf === "function") {
          val = valueOf.call(input);
          if (isPrimitive(val)) {
              return val;
          }
      }
      toString = input.toString;
      if (typeof toString === "function") {
          val = toString.call(input);
          if (isPrimitive(val)) {
              return val;
          }
      }
      throw new TypeError();
  }
  
  // ES5 9.9
  // http://es5.github.com/#x9.9
  var toObject = function (o) {
      if (o == null) { // this matches both null and undefined
          throw new TypeError("can't convert "+o+" to object");
      }
      return Object(o);
  };
  
  });
  
  /**
   * @title WET-BOEW Vapour loader
   * @overview Helper methods for WET
   * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
   * @author WET Community
   */
  /*
   * Vapour Object that will store tombstone data for plugins to leverage
   */
  ( function( $, window, document, undef ) {
  "use strict";
  
  /**
   * @method getUrlParts
   * @param {String} url of URL to examine
   * @return {Object} of the parts of passed URL
   */
  var getUrlParts = function( url ) {
  		var a = document.createElement( "a" );
  		a.href = url;
  		return {
  			href: a.href,
  			absolute: a.href,
  			host: a.host,
  			hostname: a.hostname,
  			port: a.port,
  			pathname: a.pathname.replace( /^([^\/])/, "/$1" ), // Prefix pathname with a slash in browsers that don't natively do it (i.e. all versions of IE and possibly early versions of Edge). See pull request #8110.
  			protocol: a.protocol,
  			hash: a.hash,
  			search: a.search,
  
  			// A collection of the parameters of the query string part of the URL.
  			params: ( function() {
  				var results = {},
  					queryString = encodeURI( decodeURI( a.search.replace( /^\?/, "" ) ) ).replace( /'/g, "%27" ).split( "&" ),
  					len = queryString.length,
  					key, strings, i;
  
  				for ( i = 0; i !== len; i += 1 ) {
  					if ( ( key = queryString[ i ] ) !== null ) {
  						strings = key.split( "=" );
  						results[ strings[ 0 ] ] = strings[ 1 ];
  					}
  				}
  				return results;
  			}() )
  		};
  	},
  
  	/**
  	 * @variable seed
  	 * @return a unique number for auto-generating ids
  	 */
  	seed = 0,
  
  	/**
  	 * @variable $src
  	 * @return {jQuery Element} of wb script element
  	 */
  	$src = $( "script[src*='wet-boew.js'],script[src*='wet-boew.min.js'],script[data-wb-core]" )
  		.last(),
  
  	/**
  	 * @variable i18n
  	 * @return {string} of HTML document language
  	 */
  	lang = document.documentElement.lang,
  
  	paths = ( function( ele ) {
  		var paths = {};
  
  		paths.home = ele.prop( "src" )
  				.split( "?" )[ 0 ].split( "/" )
  				.slice( 0, -1 )
  				.join( "/" );
  		paths.asset = paths.home + "/../assets";
  		paths.template = paths.home + "/assets/templates";
  		paths.dep = paths.home + "/deps";
  		paths.js = paths.home;
  		paths.css = paths.home.substring( 0, paths.home.length - 2 ) + "css";
  		paths.mode = ele.prop( "src" ).indexOf( ".min" ) < 0 ? "" : ".min";
  
  		if ( ele[ 0 ].hasAttribute( "data-wb-core" ) ) {
  			$.extend( paths, {
  				home: ele.attr( "data-home" ),
  				asset: ele.attr( "data-asset" ),
  				template: ele.attr( "data-template" ),
  				dep: ele.attr( "data-dep" ),
  				js: ele.attr( "data-js" ),
  				css: ele.attr( "data-css" ),
  				mode: ele.attr( "data-mode" )
  			} );
  		}
  
  		return paths;
  	}( $src ) ),
  
  	/**
  	 * @variable oldie
  	 * @return {integer} of IE version
  	 */
  	oldie = ( function() {
  		var undef,
  			v = 3,
  			div = document.createElement( "div" ),
  			all = div.getElementsByTagName( "i" );
  
  		while ( (
  			div.innerHTML = "<!--[if gt IE " + ( v += 1 ) + "]><i></i><![endif]-->",
  			all[ 0 ]
  		) ) {};
  
  		return v > 4 ? v : undef;
  	}() ),
  
  	/**
  	 * @variable currentpage
  	 * @return {Object} of parts of the current page URL
  	 */
  	currentpage = getUrlParts( window.location.href ),
  
  	/**
  	 * @variable disabled
  	 * @return {boolean} of state of disabled flag
  	 */
  	disabled = ( function() {
  		var disabledSaved = "false",
  			disabled;
  
  		try {
  			disabledSaved = localStorage.getItem( "wbdisable" ) || disabledSaved;
  		} catch ( e ) {}
  
  		disabled = currentpage.params.wbdisable || disabledSaved;
  		return ( typeof disabled === "string" ) ? ( disabled.toLowerCase() === "true" ) : Boolean( disabled );
  	}() ),
  
  	/*-----------------------------
  	 * Core Library Object
  	 *-----------------------------
  	 */
  	wb = {
  		"/": paths.home,
  		"/assets": paths.asset,
  		"/templates": paths.template,
  		"/deps": paths.dep,
  		lang: lang,
  		mode: paths.mode,
  		doc: $( document ),
  		win: $( window ),
  		html: $( "html" ),
  		pageUrlParts: currentpage,
  		getUrlParts: getUrlParts,
  		isDisabled: disabled,
  		isStarted: false,
  		isReady: false,
  		ignoreHashChange: false,
  		initQueue: 0,
  
  		getPath: function( property ) {
  			return this.hasOwnProperty( property ) ? this[ property ] : undef;
  		},
  
  		getMode: function() {
  			return this.mode;
  		},
  
  		getId: function() {
  			return "wb-auto-" + ( seed += 1 );
  		},
  
  		init: function( event, componentName, selector, noAutoId ) {
  			var	eventTarget = event.target,
  				isEvent = !!eventTarget,
  				node = isEvent ? eventTarget : event,
  				initedClass = componentName + "-inited",
  				isDocumentNode = node === document;
  
  			// Filter out any events triggered by descendants and only initializes
  			// the element once (if is an event and document node is not the target)
  			if ( !isEvent || isDocumentNode || ( event.currentTarget === node &&
  				node.className.indexOf( initedClass ) === -1 ) ) {
  
  				this.initQueue += 1;
  				this.remove( selector );
  				if ( !isDocumentNode ) {
  					node.className += " " + initedClass;
  
  					if ( !noAutoId && !node.id ) {
  						node.id = wb.getId();
  					}
  				}
  
  				return node;
  			}
  
  			return undef;
  		},
  
  		ready: function( $elm, componentName, context ) {
  			if ( $elm ) {
  
  				// Trigger any nested elements (excluding nested within nested)
  				$elm
  					.find( wb.allSelectors )
  						.addClass( "wb-init" )
  						.filter( ":not(#" + $elm.attr( "id" ) + " .wb-init .wb-init)" )
  							.trigger( "timerpoke.wb" );
  
  				// Identify that the component is ready
  				$elm.trigger( "wb-ready." + componentName, context );
  				this.initQueue -= 1;
  			} else {
  				this.doc.trigger( "wb-ready." + componentName, context );
  			}
  
  			// Identify that global initialization is complete
  			if ( !this.isReady && this.isStarted && this.initQueue < 1 ) {
  				this.isReady = true;
  				this.doc.trigger( "wb-ready.wb" );
  			}
  		},
  
  		// Lets load some variables into wb for IE detection
  		other: !oldie,
  		desktop: ( window.orientation === undefined ),
  		ie: !!oldie,
  		ie6: ( oldie === 6 ),
  		ie7: ( oldie === 7 ),
  		ie8: ( oldie === 8 ),
  		ie9: ( oldie === 9 ),
  		ielt7: ( oldie < 7 ),
  		ielt8: ( oldie < 8 ),
  		ielt9: ( oldie < 9 ),
  		ielt10: ( oldie < 10 ),
  		ie11: ( !!navigator.userAgent.match( /Trident\/7\./ ) ),
  
  		selectors: [],
  
  		resizeEvents: "xxsmallview.wb xsmallview.wb smallview.wb mediumview.wb largeview.wb xlargeview.wb",
  
  		// For Charts and Geomap
  		drawColours: [
  			"#8d201c",
  			"#EE8310",
  			"#2a7da6",
  			"#5a306b",
  			"#285228",
  			"#154055",
  			"#555555",
  			"#f6d200",
  			"#d73d38",
  			"#418541",
  			"#87aec9",
  			"#23447e",
  			"#999999"
  		],
  
  		// Get and generate a unique session id
  		sessionGUID: function() {
  			var sessionId = sessionStorage.getItem( "wb-session-GUID" );
  			if ( !sessionId ) {
  				sessionId = wb.guid();
  				sessionStorage.setItem( "wb-session-GUID", sessionId );
  			}
  			return sessionId;
  		},
  
  		// Add a selector to be targeted by timerpoke
  		add: function( selector ) {
  			var exists = false,
  				len = wb.selectors.length,
  				i;
  
  			// Lets ensure we are not running if things are disabled
  			if ( wb.isDisabled && selector !== "#wb-tphp" ) {
  				return 0;
  			}
  
  			// Check to see if the selector is already targeted
  			for ( i = 0; i !== len; i += 1 ) {
  				if ( wb.selectors[ i ] === selector ) {
  					exists = true;
  					break;
  				}
  			}
  
  			// Add the selector if it isn't already targeted
  			if ( !exists ) {
  				wb.selectors.push( selector );
  			}
  		},
  
  		// Remove a selector targeted by timerpoke
  		remove: function( selector ) {
  			var len = this.selectors.length,
  				i;
  
  			for ( i = 0; i !== len; i += 1 ) {
  				if ( this.selectors[ i ] === selector ) {
  					this.selectors.splice( i, 1 );
  					break;
  				}
  			}
  		},
  
  		// Handles triggering of timerpoke events
  		timerpoke: function( initial ) {
  			var selectorsLocal = wb.selectors.slice( 0 ),
  				len = selectorsLocal.length,
  				selector, $elms, $foundElms, i;
  
  			if ( initial ) {
  				$foundElms = $();
  				for ( i = 0; i !== len; i += 1 ) {
  					selector = selectorsLocal[ i ];
  					$elms = $( selector );
  					if ( $elms.length !== 0 ) {
  						$foundElms = $foundElms.add( $elms );
  
  					// If the selector returns no elements, remove the selector
  					} else {
  						wb.remove( selector );
  					}
  				}
  
  				// Keep only the non-nested plugin/polyfill elements
  				$elms = $foundElms.filter( ":not(.wb-init .wb-init)" ).addClass( "wb-init" );
  			} else {
  				$elms = $( selectorsLocal.join( ", " ) );
  			}
  			$elms.trigger( "timerpoke.wb" );
  		},
  
  		start: function() {
  
  			// Save a copy of all the possible selectors
  			wb.allSelectors = wb.selectors.join( ", " );
  
  			// Initiate timerpoke events right way
  			wb.timerpoke( true );
  			this.isStarted = true;
  			this.ready();
  
  			// Initiate timerpoke events again every half second
  			setInterval( wb.timerpoke, 500 );
  		},
  
  		i18nDict: {},
  		i18n: function( key, state, mixin ) {
  			var dictionary = wb.i18nDict,
  
  				// eg. 000 or 001 ie. 0 or 1
  				truthiness = ( typeof key === "string" && key !== "" ) |
  
  					// eg. 000 or 010 ie. 0 or 2
  					( typeof state === "string" && state !== "" ) << 1 |
  
  					// eg. 000 or 100 ie. 0 or 4
  					( typeof mixin === "string" && mixin !== "" ) << 2;
  
  			switch ( truthiness ) {
  			case 1:
  
  				// only key was provided
  				return dictionary[ key ];
  
  			case 3:
  
  				// key and state were provided
  				return dictionary[ key ][ state ];
  
  			case 7:
  
  				// key, state, and mixin were provided
  				return dictionary[ key ][ state ].replace( "[MIXIN]", mixin );
  			default:
  				return "";
  			}
  		},
  
  		hashString: function( str ) {
  
  			// Sources:
  			//	https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
  			//	https://jsperf.com/hashing-strings
  			var hash = 0,
  				chr, i;
  
  			if ( str.length === 0 ) {
  				return hash;
  			}
  
  			for ( i = 0; i < str.length; i++ ) {
  				chr = str.charCodeAt( i );
  				hash = ( ( hash << 5 ) - hash ) + chr;
  
  				// Convert to 32bit integer
  				hash = hash & hash;
  			}
  
  			return hash;
  		},
  
  		stripWhitespace: function( str ) {
  			return str.replace( /\s+/g, "" );
  		},
  
  		// Core function to deal with the dependency racing issue
  		whenLibReady: function( testCallback, readyCallback ) {
  			if ( testCallback() ) {
  				readyCallback();
  			} else {
  				setTimeout( function() {
  					wb.whenLibReady( testCallback, readyCallback );
  				}, 50 );
  			}
  
  		}
  	};
  
  window.wb = wb;
  
  /*-----------------------------
   * Yepnope Prefixes
   *-----------------------------*/
  /*
   * Establish the base path to be more flexible in terms of WCMS where JS can
   * reside in theme folders and not in the root of sites
   * @TODO: For modularity the prefixes where written independently as we are
   * flushing out some use cases on better grouping and optimization of polyfills.
   * Once this more hashed out, we could optimize the prefixes down to one or two
   * prefixes "site" and "disabled" to thin out the codeblock a bit more
   * increase performance due to redundant chaining of the prefixes.
   */
  
  /**
   * @prefix: site! - adds the root js directory of yepnope resources
   */
  yepnope.addPrefix( "site", function( resourceObj ) {
  	resourceObj.url = paths.js + "/" + resourceObj.url;
  	return resourceObj;
  } );
  
  /**
   * @prefix: plyfll! - builds the path for the polyfill resource
   */
  yepnope.addPrefix( "plyfll", function( resourceObj ) {
  	var path,
  		url = resourceObj.url;
  
  	if ( disabled && url.indexOf( "svg" ) === -1 ) {
  		resourceObj.bypass = true;
  	} else if ( !paths.mode ) {
  		url = url.replace( ".min", "" );
  	}
  
  	if ( url.indexOf( ".css" ) !== -1 ) {
  		resourceObj.forceCSS = true;
  		path = paths.css;
  	} else {
  		path = paths.js;
  	}
  	resourceObj.url = path + "/polyfills/" + url;
  
  	return resourceObj;
  } );
  
  /**
   * @prefix: i18n! - adds the correct document language for our i18n library
   */
  yepnope.addPrefix( "i18n", function( resourceObj ) {
  	resourceObj.url = paths.js + "/" + resourceObj.url + lang + paths.mode + ".js";
  	return resourceObj;
  } );
  
  /**
   * @prefix: mthjx! - adds the root directory of MathJax resources
   */
  yepnope.addPrefix( "mthjx", function( resourceObj ) {
  	resourceObj.url = paths.js + "/MathJax/" + resourceObj.url;
  	return resourceObj;
  } );
  
  /*-----------------------------
   * Deps loading, call "complete" callback when the deps is ready if a testReady is defined
   *-----------------------------*/
  wb.modernizrLoad = Modernizr.load;
  Modernizr.load = function( options ) {
  	var i, i_len, i_cache,
  		testReady, complete;
  	if ( !$.isArray( options ) ) {
  		options = [ options ];
  	}
  	i_len = options.length;
  	for ( i = 0; i !== i_len; i += 1 ) {
  		i_cache = options[ i ];
  		testReady = i_cache.testReady;
  		complete = i_cache.complete;
  		if ( testReady && complete ) {
  			i_cache.complete = wb.whenLibReady( testReady, complete );
  		}
  	}
  	wb.modernizrLoad( options );
  };
  
  /*-----------------------------
   * Modernizr Polyfill Loading
   *-----------------------------*/
  Modernizr.load( [
  	{
  		test: Modernizr.details,
  		nope: [
  			"plyfll!details.min.js",
  			"plyfll!details.min.css"
  		]
  	}, {
  		test: Modernizr.input.list,
  		nope: [
  			"plyfll!datalist.min.js",
  			"plyfll!datalist.min.css"
  		]
  	}, {
  		test: Modernizr.inputtypes.date,
  		nope: [
  			"plyfll!datepicker.min.js",
  			"plyfll!datepicker.min.css"
  		]
  	}, {
  		test: Modernizr.inputtypes.range,
  		nope: [
  			"plyfll!slider.min.js",
  			"plyfll!slider_wrapper.min.js",
  			"plyfll!slider.min.css"
  		],
  		callback: function( url ) {
  			if ( url === "slider.min.js" ) {
  				window.fdSlider.onDomReady();
  			}
  		}
  	}, {
  		test: Modernizr.progressbar,
  		nope: [
  			"plyfll!progress.min.js",
  			"plyfll!progress.min.css"
  		]
  	}, {
  		test: Modernizr.mathml,
  
  		// Cleanup Modernizr test and add selector to global timer
  		complete: function() {
  			var	componentName = "wb-math",
  				selector = "math",
  				math = document.getElementsByTagName( selector ),
  				$document = wb.doc;
  
  			// Cleanup elements that Modernizr.mathml test leaves behind.
  			if ( math.length ) {
  				document.body.removeChild( math[ math.length - 1 ].parentNode );
  			}
  
  			// Defer loading the polyfill till an element is detected due to the size
  			if ( !Modernizr.mathml ) {
  
  				// Bind the init event of the plugin
  				$document.one( "timerpoke.wb wb-init." + componentName, selector, function() {
  
  					// Start initialization
  					wb.init( document, componentName, selector );
  
  					// Load the MathML dependency. Since the polyfill is only loaded
  					// when !Modernizr.mathml, we can skip the test here.
  					Modernizr.load( [ {
  						load: "timeout=500!https://cdn.jsdelivr.net/npm/mathjax@2.7.1/MathJax.js?config=Accessible",
  						complete: function() {
  							Modernizr.load( [ {
  								test: window.MathJax === undefined,
  								yep: "mthjx!MathJax.js?config=Accessible"
  							} ] );
  
  							// Identify that initialization has completed
  							wb.ready( $document, componentName );
  						}
  					} ] );
  				} );
  
  				wb.add( selector );
  			}
  		}
  	}, {
  		test: Modernizr.meter,
  		nope: [
  			"plyfll!meter.min.js",
  			"plyfll!meter.min.css"
  		]
  	}, {
  		test: Modernizr.touch,
  		yep: "plyfll!mobile.min.js"
  	}, {
  		test: Modernizr.svg,
  		nope: "plyfll!svg.min.js"
  	}, {
  		load: "i18n!i18n/",
  		testReady: function() {
  			return wb.i18nDict.tphp;
  		},
  		complete: function() {
  			wb.start();
  		}
  	}
  ] );
  
  } )( jQuery, window, document );
  